diff --git a/gopls/internal/regtest/misc/rename_test.go b/gopls/internal/regtest/misc/rename_test.go index 121b70725..65a9ecb52 100644 --- a/gopls/internal/regtest/misc/rename_test.go +++ b/gopls/internal/regtest/misc/rename_test.go @@ -8,9 +8,49 @@ import ( "strings" "testing" + "golang.org/x/tools/internal/lsp/protocol" . "golang.org/x/tools/internal/lsp/regtest" ) +func TestPrepareRenamePackage(t *testing.T) { + const files = ` +-- go.mod -- +module mod.com + +go 1.18 +-- main.go -- +package main + +import ( + "fmt" +) + +func main() { + fmt.Println(1) +} +` + const wantErr = "can't rename packages: LSP client does not support file renaming" + Run(t, files, func(t *testing.T, env *Env) { + env.OpenFile("main.go") + pos := env.RegexpSearch("main.go", `main`) + tdpp := protocol.TextDocumentPositionParams{ + TextDocument: env.Editor.TextDocumentIdentifier("main.go"), + Position: pos.ToProtocolPosition(), + } + params := &protocol.PrepareRenameParams{ + TextDocumentPositionParams: tdpp, + } + _, err := env.Editor.Server.PrepareRename(env.Ctx, params) + if err == nil { + t.Errorf("missing can't rename package error from PrepareRename") + } + + if err.Error() != wantErr { + t.Errorf("got %v, want %v", err.Error(), wantErr) + } + }) +} + // Test for golang/go#47564. func TestRenameInTestVariant(t *testing.T) { const files = ` diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go index 240e35cff..a07e9e7c6 100644 --- a/internal/lsp/fake/editor.go +++ b/internal/lsp/fake/editor.go @@ -428,7 +428,7 @@ func (e *Editor) CloseBuffer(ctx context.Context, path string) error { if e.Server != nil { if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{ - TextDocument: e.textDocumentIdentifier(path), + TextDocument: e.TextDocumentIdentifier(path), }); err != nil { return fmt.Errorf("DidClose: %w", err) } @@ -439,7 +439,7 @@ func (e *Editor) CloseBuffer(ctx context.Context, path string) error { return nil } -func (e *Editor) textDocumentIdentifier(path string) protocol.TextDocumentIdentifier { +func (e *Editor) TextDocumentIdentifier(path string) protocol.TextDocumentIdentifier { return protocol.TextDocumentIdentifier{ URI: e.sandbox.Workdir.URI(path), } @@ -471,7 +471,7 @@ func (e *Editor) SaveBufferWithoutActions(ctx context.Context, path string) erro includeText = syncOptions.Save.IncludeText } - docID := e.textDocumentIdentifier(buf.path) + docID := e.TextDocumentIdentifier(buf.path) if e.Server != nil { if err := e.Server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{ TextDocument: docID, @@ -693,7 +693,7 @@ func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty params := &protocol.DidChangeTextDocumentParams{ TextDocument: protocol.VersionedTextDocumentIdentifier{ Version: int32(buf.version), - TextDocumentIdentifier: e.textDocumentIdentifier(buf.path), + TextDocumentIdentifier: e.TextDocumentIdentifier(buf.path), }, ContentChanges: evts, } @@ -1008,7 +1008,7 @@ func (e *Editor) CodeLens(ctx context.Context, path string) ([]protocol.CodeLens return nil, fmt.Errorf("buffer %q is not open", path) } params := &protocol.CodeLensParams{ - TextDocument: e.textDocumentIdentifier(path), + TextDocument: e.TextDocumentIdentifier(path), } lens, err := e.Server.CodeLens(ctx, params) if err != nil { @@ -1030,7 +1030,7 @@ func (e *Editor) Completion(ctx context.Context, path string, pos Pos) (*protoco } params := &protocol.CompletionParams{ TextDocumentPositionParams: protocol.TextDocumentPositionParams{ - TextDocument: e.textDocumentIdentifier(path), + TextDocument: e.TextDocumentIdentifier(path), Position: pos.ToProtocolPosition(), }, } @@ -1080,7 +1080,7 @@ func (e *Editor) InlayHint(ctx context.Context, path string) ([]protocol.InlayHi return nil, fmt.Errorf("buffer %q is not open", path) } params := &protocol.InlayHintParams{ - TextDocument: e.textDocumentIdentifier(path), + TextDocument: e.TextDocumentIdentifier(path), } hints, err := e.Server.InlayHint(ctx, params) if err != nil { @@ -1102,7 +1102,7 @@ func (e *Editor) References(ctx context.Context, path string, pos Pos) ([]protoc } params := &protocol.ReferenceParams{ TextDocumentPositionParams: protocol.TextDocumentPositionParams{ - TextDocument: e.textDocumentIdentifier(path), + TextDocument: e.TextDocumentIdentifier(path), Position: pos.ToProtocolPosition(), }, Context: protocol.ReferenceContext{ @@ -1121,7 +1121,7 @@ func (e *Editor) Rename(ctx context.Context, path string, pos Pos, newName strin return nil } params := &protocol.RenameParams{ - TextDocument: e.textDocumentIdentifier(path), + TextDocument: e.TextDocumentIdentifier(path), Position: pos.ToProtocolPosition(), NewName: newName, } @@ -1195,7 +1195,7 @@ func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Rang return nil, fmt.Errorf("buffer %q is not open", path) } params := &protocol.CodeActionParams{ - TextDocument: e.textDocumentIdentifier(path), + TextDocument: e.TextDocumentIdentifier(path), Context: protocol.CodeActionContext{ Diagnostics: diagnostics, }, diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index 1bb135dd5..11ba15766 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -204,6 +204,7 @@ type ClientOptions struct { RelatedInformationSupported bool CompletionTags bool CompletionDeprecated bool + SupportedResourceOperations []protocol.ResourceOperationKind } // ServerOptions holds LSP-specific configuration that is provided by the @@ -701,6 +702,9 @@ func SetOptions(options *Options, opts interface{}) OptionResults { func (o *Options) ForClientCapabilities(caps protocol.ClientCapabilities) { // Check if the client supports snippets in completion items. + if caps.Workspace.WorkspaceEdit != nil { + o.SupportedResourceOperations = caps.Workspace.WorkspaceEdit.ResourceOperations + } if c := caps.TextDocument.Completion; c.CompletionItem.SnippetSupport { o.InsertTextFormat = protocol.SnippetTextFormat } diff --git a/internal/lsp/source/rename.go b/internal/lsp/source/rename.go index 503422aa9..b6f0e6531 100644 --- a/internal/lsp/source/rename.go +++ b/internal/lsp/source/rename.go @@ -49,6 +49,29 @@ type PrepareItem struct { // the prepare fails. Probably we could eliminate the redundancy in returning // two errors, but for now this is done defensively. func PrepareRename(ctx context.Context, snapshot Snapshot, f FileHandle, pp protocol.Position) (_ *PrepareItem, usererr, err error) { + fileRenameSupported := false + for _, op := range snapshot.View().Options().SupportedResourceOperations { + if op == protocol.Rename { + fileRenameSupported = true + break + } + } + + // Find position of the package name declaration + pgf, err := snapshot.ParseGo(ctx, f, ParseFull) + if err != nil { + return nil, err, err + } + inPackageName, err := isInPackageName(ctx, snapshot, f, pgf, pp) + if err != nil { + return nil, err, err + } + + if inPackageName && !fileRenameSupported { + err := errors.New("can't rename packages: LSP client does not support file renaming") + return nil, err, err + } + ctx, done := event.Start(ctx, "source.PrepareRename") defer done()