diff --git a/langserver/handler.go b/langserver/handler.go index d6ffff0b..c9a24ee1 100644 --- a/langserver/handler.go +++ b/langserver/handler.go @@ -446,6 +446,15 @@ func (h *LangHandler) Handle(ctx context.Context, conn jsonrpc2.JSONRPC2, req *j } return h.handleWorkspaceReferences(ctx, conn, req, params) + case "textDocument/rename": + if req.Params == nil { + return nil, &jsonrpc2.Error{Code: jsonrpc2.CodeInvalidParams} + } + var params lsp.RenameParams + if err := json.Unmarshal(*req.Params, ¶ms); err != nil { + return nil, err + } + return h.handleRename(ctx, conn, req, params) default: if isFileSystemRequest(req.Method) { uri, fileChanged, err := h.handleFileSystemRequest(ctx, req) diff --git a/langserver/langserver_test.go b/langserver/langserver_test.go index b27e7b46..8b0d2e2b 100644 --- a/langserver/langserver_test.go +++ b/langserver/langserver_test.go @@ -1181,6 +1181,35 @@ func main() { }, }, }, + "renaming": { + rootURI: "file:///src/test/pkg", + fs: map[string]string{ + "a.go": `package p +import "fmt" + +func main() { + str := A() + fmt.Println(str) +} + +func A() string { + return "test" +} +`, + }, + cases: lspTestCases{ + wantRenames: map[string]map[string]string{ + "a.go:5:5": map[string]string{ + "4:4-4:7": "/src/test/pkg/a.go", + "5:16-5:19": "/src/test/pkg/a.go", + }, + "a.go:9:6": map[string]string{ + "4:11-4:12": "/src/test/pkg/a.go", + "8:5-8:6": "/src/test/pkg/a.go", + }, + }, + }, + }, } func TestServer(t *testing.T) { @@ -1314,6 +1343,7 @@ type lspTestCases struct { wantSignatures map[string]string wantWorkspaceReferences map[*lspext.WorkspaceReferencesParams][]string wantFormatting map[string]map[string]string + wantRenames map[string]map[string]string } func copyFileToOS(ctx context.Context, fs *AtomicFS, targetFile, srcFile string) error { @@ -1495,6 +1525,12 @@ func lspTests(t testing.TB, ctx context.Context, h *LangHandler, c *jsonrpc2.Con formattingTest(t, ctx, c, rootURI, file, want) }) } + + for pos, want := range cases.wantRenames { + tbRun(t, fmt.Sprintf("renaming-%s", strings.Replace(pos, "/", "-", -1)), func(t testing.TB) { + renamingTest(t, ctx, c, rootURI, pos, want) + }) + } } // tbRun calls (testing.T).Run or (testing.B).Run. @@ -1716,6 +1752,29 @@ func formattingTest(t testing.TB, ctx context.Context, c *jsonrpc2.Conn, rootURI } } +func renamingTest(t testing.TB, ctx context.Context, c *jsonrpc2.Conn, rootURI lsp.DocumentURI, pos string, want map[string]string) { + file, line, char, err := parsePos(pos) + if err != nil { + t.Fatal(err) + } + + workspaceEdit, err := callRenaming(ctx, c, uriJoin(rootURI, file), line, char, "") + if err != nil { + t.Fatal(err) + } + + got := map[string]string{} + for file, edits := range workspaceEdit.Changes { + for _, edit := range edits { + got[edit.Range.String()] = util.UriToPath(lsp.DocumentURI(file)) + } + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want: %v", got, want) + } +} + func parsePos(s string) (file string, line, char int, err error) { parts := strings.Split(s, ":") if len(parts) != 3 { @@ -1970,6 +2029,16 @@ func callFormatting(ctx context.Context, c *jsonrpc2.Conn, uri lsp.DocumentURI) return edits, err } +func callRenaming(ctx context.Context, c *jsonrpc2.Conn, uri lsp.DocumentURI, line, char int, newName string) (lsp.WorkspaceEdit, error) { + var edit lsp.WorkspaceEdit + err := c.Call(ctx, "textDocument/rename", lsp.RenameParams{ + TextDocument: lsp.TextDocumentIdentifier{URI: uri}, + Position: lsp.Position{Line: line, Character: char}, + NewName: newName, + }, &edit) + return edit, err +} + type markedStrings []lsp.MarkedString func (v *markedStrings) UnmarshalJSON(data []byte) error { diff --git a/langserver/rename.go b/langserver/rename.go new file mode 100644 index 00000000..4c3dddd0 --- /dev/null +++ b/langserver/rename.go @@ -0,0 +1,45 @@ +package langserver + +import ( + "context" + + lsp "github.com/sourcegraph/go-lsp" + "github.com/sourcegraph/jsonrpc2" +) + +func (h *LangHandler) handleRename(ctx context.Context, conn jsonrpc2.JSONRPC2, + req *jsonrpc2.Request, params lsp.RenameParams) (lsp.WorkspaceEdit, error) { + rp := lsp.ReferenceParams{ + TextDocumentPositionParams: lsp.TextDocumentPositionParams{ + TextDocument: params.TextDocument, + Position: params.Position, + }, + Context: lsp.ReferenceContext{ + IncludeDeclaration: true, + XLimit: 0, + }, + } + + references, err := h.handleTextDocumentReferences(ctx, conn, req, rp) + if err != nil { + return lsp.WorkspaceEdit{}, err + } + + result := lsp.WorkspaceEdit{} + if result.Changes == nil { + result.Changes = make(map[string][]lsp.TextEdit) + } + for _, ref := range references { + edit := lsp.TextEdit{ + Range: ref.Range, + NewText: params.NewName, + } + edits := result.Changes[string(ref.URI)] + if edits == nil { + edits = []lsp.TextEdit{} + } + edits = append(edits, edit) + result.Changes[string(ref.URI)] = edits + } + return result, nil +}