internal/lsp: add an importShortcut configuration
This configuration deals with the fact that VS Code has the same
shortcut for go-to-definition and clicking a link. Many users don't like
the behavior of this shortcut triggering both requests, so we allow
users to choose between each behavior.
Fixes golang/go#39065
Change-Id: I760c99c1fade4d157515d3b0fd647daf0c8314eb
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242457
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/link.go b/internal/lsp/link.go
index 7218b3c..135d457 100644
--- a/internal/lsp/link.go
+++ b/internal/lsp/link.go
@@ -130,28 +130,31 @@
return true
})
var links []protocol.DocumentLink
- for _, imp := range imports {
- // For import specs, provide a link to a documentation website, like https://pkg.go.dev.
- target, err := strconv.Unquote(imp.Path.Value)
- if err != nil {
- continue
+ // For import specs, provide a link to a documentation website, like
+ // https://pkg.go.dev.
+ if view.Options().ImportShortcut.ShowLinks() {
+ for _, imp := range imports {
+ target, err := strconv.Unquote(imp.Path.Value)
+ if err != nil {
+ continue
+ }
+ // See golang/go#36998: don't link to modules matching GOPRIVATE.
+ if view.IsGoPrivatePath(target) {
+ continue
+ }
+ if mod, version, ok := moduleAtVersion(ctx, target, ph); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
+ target = strings.Replace(target, mod, mod+"@"+version, 1)
+ }
+ // Account for the quotation marks in the positions.
+ start := imp.Path.Pos() + 1
+ end := imp.Path.End() - 1
+ target = fmt.Sprintf("https://%s/%s", view.Options().LinkTarget, target)
+ l, err := toProtocolLink(view, m, target, start, end, source.Go)
+ if err != nil {
+ return nil, err
+ }
+ links = append(links, l)
}
- // See golang/go#36998: don't link to modules matching GOPRIVATE.
- if view.IsGoPrivatePath(target) {
- continue
- }
- if mod, version, ok := moduleAtVersion(ctx, target, ph); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
- target = strings.Replace(target, mod, mod+"@"+version, 1)
- }
- // Account for the quotation marks in the positions.
- start := imp.Path.Pos() + 1
- end := imp.Path.End() - 1
- target = fmt.Sprintf("https://%s/%s", view.Options().LinkTarget, target)
- l, err := toProtocolLink(view, m, target, start, end, source.Go)
- if err != nil {
- return nil, err
- }
- links = append(links, l)
}
for _, s := range str {
l, err := findLinksInString(ctx, view, s.Value, s.Pos(), m, source.Go)
diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go
index 08e1050..198dd5e 100644
--- a/internal/lsp/source/identifier.go
+++ b/internal/lsp/source/identifier.go
@@ -73,9 +73,12 @@
var ErrNoIdentFound = errors.New("no identifier found")
func findIdentifier(ctx context.Context, s Snapshot, pkg Package, file *ast.File, pos token.Pos) (*IdentifierInfo, error) {
- // Handle import specs separately, as there is no formal position for a package declaration.
- if result, err := importSpec(s, pkg, file, pos); result != nil || err != nil {
- return result, err
+ // Handle import specs separately, as there is no formal position for a
+ // package declaration.
+ if s.View().Options().ImportShortcut.ShowDefinition() {
+ if result, err := importSpec(s, pkg, file, pos); result != nil || err != nil {
+ return result, err
+ }
}
path := pathEnclosingObjNode(file, pos)
if path == nil {
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 88b840e..5d378cf 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -206,9 +206,14 @@
// StaticCheck enables additional analyses from staticcheck.io.
StaticCheck bool
- // LinkTarget is the website used for documentation.
+ // LinkTarget is the website used for documentation. If empty, no link is
+ // provided.
LinkTarget string
+ // ImportShortcut specifies whether import statements should link to
+ // documentation or go to definitions. The default is both.
+ ImportShortcut ImportShortcut
+
// LocalPrefix is used to specify goimports's -local behavior.
LocalPrefix string
@@ -241,6 +246,22 @@
Gofumpt bool
}
+type ImportShortcut int
+
+const (
+ Both ImportShortcut = iota
+ Link
+ Definition
+)
+
+func (s ImportShortcut) ShowLinks() bool {
+ return s == Both || s == Link
+}
+
+func (s ImportShortcut) ShowDefinition() bool {
+ return s == Both || s == Definition
+}
+
type completionOptions struct {
deepCompletion bool
unimported bool
@@ -505,6 +526,18 @@
case "linkTarget":
result.setString(&o.LinkTarget)
+ case "importShortcut":
+ var s string
+ result.setString(&s)
+ switch s {
+ case "both":
+ o.ImportShortcut = Both
+ case "link":
+ o.ImportShortcut = Link
+ case "definition":
+ o.ImportShortcut = Definition
+ }
+
case "analyses":
result.setBoolMap(&o.UserEnabledAnalyses)