diff options
| author | adamdottv <[email protected]> | 2025-05-29 15:10:44 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-05-29 15:10:44 -0500 |
| commit | 37c0c1f358cadbc918319500cd2b1b3fcbe41a9e (patch) | |
| tree | f012aad2481ea47b062c630420ecc008287a65dc /internal/lsp/util | |
| parent | 2a132f86d687be767df4a7657e9c4441b8a4058d (diff) | |
| download | opencode-37c0c1f358cadbc918319500cd2b1b3fcbe41a9e.tar.gz opencode-37c0c1f358cadbc918319500cd2b1b3fcbe41a9e.zip | |
wip: refactoring tui
Diffstat (limited to 'internal/lsp/util')
| -rw-r--r-- | internal/lsp/util/edit.go | 239 |
1 files changed, 0 insertions, 239 deletions
diff --git a/internal/lsp/util/edit.go b/internal/lsp/util/edit.go deleted file mode 100644 index ef7a231e3..000000000 --- a/internal/lsp/util/edit.go +++ /dev/null @@ -1,239 +0,0 @@ -package util - -import ( - "bytes" - "fmt" - "os" - "sort" - "strings" - - "github.com/sst/opencode/internal/lsp/protocol" -) - -func applyTextEdits(uri protocol.DocumentUri, edits []protocol.TextEdit) error { - path := strings.TrimPrefix(string(uri), "file://") - - // Read the file content - content, err := os.ReadFile(path) - if err != nil { - return fmt.Errorf("failed to read file: %w", err) - } - - // Detect line ending style - var lineEnding string - if bytes.Contains(content, []byte("\r\n")) { - lineEnding = "\r\n" - } else { - lineEnding = "\n" - } - - // Track if file ends with a newline - endsWithNewline := len(content) > 0 && bytes.HasSuffix(content, []byte(lineEnding)) - - // Split into lines without the endings - lines := strings.Split(string(content), lineEnding) - - // Check for overlapping edits - for i, edit1 := range edits { - for j := i + 1; j < len(edits); j++ { - if rangesOverlap(edit1.Range, edits[j].Range) { - return fmt.Errorf("overlapping edits detected between edit %d and %d", i, j) - } - } - } - - // Sort edits in reverse order - sortedEdits := make([]protocol.TextEdit, len(edits)) - copy(sortedEdits, edits) - sort.Slice(sortedEdits, func(i, j int) bool { - if sortedEdits[i].Range.Start.Line != sortedEdits[j].Range.Start.Line { - return sortedEdits[i].Range.Start.Line > sortedEdits[j].Range.Start.Line - } - return sortedEdits[i].Range.Start.Character > sortedEdits[j].Range.Start.Character - }) - - // Apply each edit - for _, edit := range sortedEdits { - newLines, err := applyTextEdit(lines, edit) - if err != nil { - return fmt.Errorf("failed to apply edit: %w", err) - } - lines = newLines - } - - // Join lines with proper line endings - var newContent strings.Builder - for i, line := range lines { - if i > 0 { - newContent.WriteString(lineEnding) - } - newContent.WriteString(line) - } - - // Only add a newline if the original file had one and we haven't already added it - if endsWithNewline && !strings.HasSuffix(newContent.String(), lineEnding) { - newContent.WriteString(lineEnding) - } - - if err := os.WriteFile(path, []byte(newContent.String()), 0o644); err != nil { - return fmt.Errorf("failed to write file: %w", err) - } - - return nil -} - -func applyTextEdit(lines []string, edit protocol.TextEdit) ([]string, error) { - startLine := int(edit.Range.Start.Line) - endLine := int(edit.Range.End.Line) - startChar := int(edit.Range.Start.Character) - endChar := int(edit.Range.End.Character) - - // Validate positions - if startLine < 0 || startLine >= len(lines) { - return nil, fmt.Errorf("invalid start line: %d", startLine) - } - if endLine < 0 || endLine >= len(lines) { - endLine = len(lines) - 1 - } - - // Create result slice with initial capacity - result := make([]string, 0, len(lines)) - - // Copy lines before edit - result = append(result, lines[:startLine]...) - - // Get the prefix of the start line - startLineContent := lines[startLine] - if startChar < 0 || startChar > len(startLineContent) { - startChar = len(startLineContent) - } - prefix := startLineContent[:startChar] - - // Get the suffix of the end line - endLineContent := lines[endLine] - if endChar < 0 || endChar > len(endLineContent) { - endChar = len(endLineContent) - } - suffix := endLineContent[endChar:] - - // Handle the edit - if edit.NewText == "" { - if prefix+suffix != "" { - result = append(result, prefix+suffix) - } - } else { - // Split new text into lines, being careful not to add extra newlines - // newLines := strings.Split(strings.TrimRight(edit.NewText, "\n"), "\n") - newLines := strings.Split(edit.NewText, "\n") - - if len(newLines) == 1 { - // Single line change - result = append(result, prefix+newLines[0]+suffix) - } else { - // Multi-line change - result = append(result, prefix+newLines[0]) - result = append(result, newLines[1:len(newLines)-1]...) - result = append(result, newLines[len(newLines)-1]+suffix) - } - } - - // Add remaining lines - if endLine+1 < len(lines) { - result = append(result, lines[endLine+1:]...) - } - - return result, nil -} - -// applyDocumentChange applies a DocumentChange (create/rename/delete operations) -func applyDocumentChange(change protocol.DocumentChange) error { - if change.CreateFile != nil { - path := strings.TrimPrefix(string(change.CreateFile.URI), "file://") - if change.CreateFile.Options != nil { - if change.CreateFile.Options.Overwrite { - // Proceed with overwrite - } else if change.CreateFile.Options.IgnoreIfExists { - if _, err := os.Stat(path); err == nil { - return nil // File exists and we're ignoring it - } - } - } - if err := os.WriteFile(path, []byte(""), 0o644); err != nil { - return fmt.Errorf("failed to create file: %w", err) - } - } - - if change.DeleteFile != nil { - path := strings.TrimPrefix(string(change.DeleteFile.URI), "file://") - if change.DeleteFile.Options != nil && change.DeleteFile.Options.Recursive { - if err := os.RemoveAll(path); err != nil { - return fmt.Errorf("failed to delete directory recursively: %w", err) - } - } else { - if err := os.Remove(path); err != nil { - return fmt.Errorf("failed to delete file: %w", err) - } - } - } - - if change.RenameFile != nil { - oldPath := strings.TrimPrefix(string(change.RenameFile.OldURI), "file://") - newPath := strings.TrimPrefix(string(change.RenameFile.NewURI), "file://") - if change.RenameFile.Options != nil { - if !change.RenameFile.Options.Overwrite { - if _, err := os.Stat(newPath); err == nil { - return fmt.Errorf("target file already exists and overwrite is not allowed: %s", newPath) - } - } - } - if err := os.Rename(oldPath, newPath); err != nil { - return fmt.Errorf("failed to rename file: %w", err) - } - } - - if change.TextDocumentEdit != nil { - textEdits := make([]protocol.TextEdit, len(change.TextDocumentEdit.Edits)) - for i, edit := range change.TextDocumentEdit.Edits { - var err error - textEdits[i], err = edit.AsTextEdit() - if err != nil { - return fmt.Errorf("invalid edit type: %w", err) - } - } - return applyTextEdits(change.TextDocumentEdit.TextDocument.URI, textEdits) - } - - return nil -} - -// ApplyWorkspaceEdit applies the given WorkspaceEdit to the filesystem -func ApplyWorkspaceEdit(edit protocol.WorkspaceEdit) error { - // Handle Changes field - for uri, textEdits := range edit.Changes { - if err := applyTextEdits(uri, textEdits); err != nil { - return fmt.Errorf("failed to apply text edits: %w", err) - } - } - - // Handle DocumentChanges field - for _, change := range edit.DocumentChanges { - if err := applyDocumentChange(change); err != nil { - return fmt.Errorf("failed to apply document change: %w", err) - } - } - - return nil -} - -func rangesOverlap(r1, r2 protocol.Range) bool { - if r1.Start.Line > r2.End.Line || r2.Start.Line > r1.End.Line { - return false - } - if r1.Start.Line == r2.End.Line && r1.Start.Character > r2.End.Character { - return false - } - if r2.Start.Line == r1.End.Line && r2.Start.Character > r1.End.Character { - return false - } - return true -} |
