ollie/tools/lsp/cmd/lsp_rename/main.go

147 lines
3.4 KiB
Go

// lsp_rename — Rename symbol across workspace. Applies edits directly.
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"ollie/tools/lsp/internal/lsp"
"ollie/tools/lsp/internal/cmdutil"
)
func main() {
cmdutil.RunCustom(func() {
var args struct {
Path string `json:"path"`
Line string `json:"line"`
Col string `json:"col"`
NewName string `json:"new_name"`
}
if err := json.NewDecoder(os.Stdin).Decode(&args); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
file, _ := filepath.Abs(args.Path)
line, _ := strconv.Atoi(args.Line)
col, _ := strconv.Atoi(args.Col)
result, err := lsp.ClientRequest(&lsp.BridgeRequest{
Method: "rename",
File: file,
Line: line - 1,
Col: col - 1,
NewName: args.NewName,
})
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if result == nil || string(result) == "null" {
fmt.Println("(rename produced no changes)")
return
}
summary := applyWorkspaceEdit(result)
if summary == "" {
fmt.Println("(rename produced no changes)")
} else {
fmt.Print(summary)
}
})
}
func applyWorkspaceEdit(raw json.RawMessage) string {
var edit struct {
Changes map[string][]textEdit `json:"changes"`
DocumentChanges []struct {
TextDocument struct {
URI string `json:"uri"`
} `json:"textDocument"`
Edits []textEdit `json:"edits"`
} `json:"documentChanges"`
}
if err := json.Unmarshal(raw, &edit); err != nil {
return ""
}
changes := make(map[string][]textEdit)
if len(edit.DocumentChanges) > 0 {
for _, dc := range edit.DocumentChanges {
path := lsp.URIToPath(dc.TextDocument.URI)
changes[path] = append(changes[path], dc.Edits...)
}
} else {
for uri, edits := range edit.Changes {
path := lsp.URIToPath(uri)
changes[path] = edits
}
}
var sb strings.Builder
totalEdits := 0
for path, edits := range changes {
data, err := os.ReadFile(path)
if err != nil {
continue
}
lines := strings.SplitAfter(string(data), "\n")
sort.Slice(edits, func(i, j int) bool {
if edits[i].Range.Start.Line != edits[j].Range.Start.Line {
return edits[i].Range.Start.Line > edits[j].Range.Start.Line
}
return edits[i].Range.Start.Character > edits[j].Range.Start.Character
})
for _, e := range edits {
sl, sc := e.Range.Start.Line, e.Range.Start.Character
el, ec := e.Range.End.Line, e.Range.End.Character
if sl >= len(lines) {
continue
}
before := strings.Join(lines[:sl], "") + lines[sl][:min(sc, len(lines[sl]))]
var after string
if el < len(lines) {
after = lines[el][min(ec, len(lines[el])):] + strings.Join(lines[el+1:], "")
}
content := before + e.NewText + after
lines = strings.SplitAfter(content, "\n")
}
os.WriteFile(path, []byte(strings.Join(lines, "")), 0644)
totalEdits += len(edits)
fmt.Fprintf(&sb, "%s: %d edit(s)\n", path, len(edits))
}
fmt.Fprintf(&sb, "total: %d file(s), %d edit(s)\n", len(changes), totalEdits)
return sb.String()
}
type textEdit struct {
Range struct {
Start struct {
Line int `json:"line"`
Character int `json:"character"`
} `json:"start"`
End struct {
Line int `json:"line"`
Character int `json:"character"`
} `json:"end"`
} `json:"range"`
NewText string `json:"newText"`
}
func min(a, b int) int {
if a < b {
return a
}
return b
}