internal/lsp: Check if user's editor support rename operation

Change-Id: Iadda768e93eda1d53fa00a5ff8a28013a575ef57
Reviewed-on: https://go-review.googlesource.com/c/tools/+/419774
Run-TryBot: Dylan Le <dungtuanle@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/internal/lsp/fake/editor.go b/internal/lsp/fake/editor.go
index 240e35c..a07e9e7 100644
--- a/internal/lsp/fake/editor.go
+++ b/internal/lsp/fake/editor.go
@@ -428,7 +428,7 @@
 
 	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 @@
 	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 @@
 		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 @@
 	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 @@
 		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 @@
 	}
 	params := &protocol.CompletionParams{
 		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
-			TextDocument: e.textDocumentIdentifier(path),
+			TextDocument: e.TextDocumentIdentifier(path),
 			Position:     pos.ToProtocolPosition(),
 		},
 	}
@@ -1080,7 +1080,7 @@
 		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 @@
 	}
 	params := &protocol.ReferenceParams{
 		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
-			TextDocument: e.textDocumentIdentifier(path),
+			TextDocument: e.TextDocumentIdentifier(path),
 			Position:     pos.ToProtocolPosition(),
 		},
 		Context: protocol.ReferenceContext{
@@ -1121,7 +1121,7 @@
 		return nil
 	}
 	params := &protocol.RenameParams{
-		TextDocument: e.textDocumentIdentifier(path),
+		TextDocument: e.TextDocumentIdentifier(path),
 		Position:     pos.ToProtocolPosition(),
 		NewName:      newName,
 	}
@@ -1195,7 +1195,7 @@
 		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 1bb135d..11ba157 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -204,6 +204,7 @@
 	RelatedInformationSupported                bool
 	CompletionTags                             bool
 	CompletionDeprecated                       bool
+	SupportedResourceOperations                []protocol.ResourceOperationKind
 }
 
 // ServerOptions holds LSP-specific configuration that is provided by the
@@ -701,6 +702,9 @@
 
 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 503422a..b6f0e65 100644
--- a/internal/lsp/source/rename.go
+++ b/internal/lsp/source/rename.go
@@ -49,6 +49,29 @@
 // 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()