diff --git a/internal/lsp/inlay_hint.go b/internal/lsp/inlay_hint.go new file mode 100644 index 0000000000..b2fd028d72 --- /dev/null +++ b/internal/lsp/inlay_hint.go @@ -0,0 +1,21 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lsp + +import ( + "context" + + "golang.org/x/tools/internal/lsp/protocol" + "golang.org/x/tools/internal/lsp/source" +) + +func (s *Server) inlayHint(ctx context.Context, params *protocol.InlayHintParams) ([]protocol.InlayHint, error) { + snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.Go) + defer release() + if !ok { + return nil, err + } + return source.InlayHint(ctx, snapshot, fh, params.ViewPort) +} diff --git a/internal/lsp/server_gen.go b/internal/lsp/server_gen.go index 93b2f9913b..4e9db0efa1 100644 --- a/internal/lsp/server_gen.go +++ b/internal/lsp/server_gen.go @@ -160,8 +160,8 @@ func (s *Server) Initialized(ctx context.Context, params *protocol.InitializedPa return s.initialized(ctx, params) } -func (s *Server) InlayHint(context.Context, *protocol.InlayHintParams) ([]protocol.InlayHint, error) { - return nil, notImplemented("InlayHint") +func (s *Server) InlayHint(ctx context.Context, params *protocol.InlayHintParams) ([]protocol.InlayHint, error) { + return s.inlayHint(ctx, params) } func (s *Server) InlayHintRefresh(context.Context) error { diff --git a/internal/lsp/source/inlay_hint.go b/internal/lsp/source/inlay_hint.go new file mode 100644 index 0000000000..94dc1372d0 --- /dev/null +++ b/internal/lsp/source/inlay_hint.go @@ -0,0 +1,90 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package source + +import ( + "context" + "fmt" + "go/ast" + "go/types" + + "golang.org/x/tools/internal/event" + "golang.org/x/tools/internal/lsp/lsppos" + "golang.org/x/tools/internal/lsp/protocol" +) + +const ( + maxLabelLength = 28 +) + +func InlayHint(ctx context.Context, snapshot Snapshot, fh FileHandle, _ protocol.Range) ([]protocol.InlayHint, error) { + ctx, done := event.Start(ctx, "source.InlayHint") + defer done() + + pkg, pgf, err := GetParsedFile(ctx, snapshot, fh, NarrowestPackage) + if err != nil { + return nil, fmt.Errorf("getting file for InlayHint: %w", err) + } + + tmap := lsppos.NewTokenMapper(pgf.Src, pgf.Tok) + info := pkg.GetTypesInfo() + + var hints []protocol.InlayHint + ast.Inspect(pgf.File, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.CallExpr: + hints = append(hints, parameterNames(n, tmap, info)...) + } + return true + }) + return hints, nil +} + +func parameterNames(node *ast.CallExpr, tmap *lsppos.TokenMapper, info *types.Info) []protocol.InlayHint { + signature, ok := info.TypeOf(node.Fun).(*types.Signature) + if !ok { + return nil + } + + var hints []protocol.InlayHint + for i, v := range node.Args { + start, ok := tmap.Position(v.Pos()) + if !ok { + continue + } + params := signature.Params() + // When a function has variadic params, we skip args after + // params.Len(). + if i > params.Len()-1 { + break + } + value := params.At(i).Name() + // param.Name is empty for built-ins like append + if value == "" { + continue + } + if signature.Variadic() && i == params.Len()-1 { + value = value + "..." + } + hints = append(hints, protocol.InlayHint{ + Position: &start, + Label: buildLabel(value + ":"), + Kind: protocol.Parameter, + PaddingRight: true, + }) + } + return hints +} + +func buildLabel(s string) []protocol.InlayHintLabelPart { + label := protocol.InlayHintLabelPart{ + Value: s, + } + if len(s) > maxLabelLength { + label.Value = s[:maxLabelLength] + "..." + label.Tooltip = s + } + return []protocol.InlayHintLabelPart{label} +} diff --git a/internal/lsp/testdata/inlay_hint/parameter_names.go b/internal/lsp/testdata/inlay_hint/parameter_names.go new file mode 100644 index 0000000000..6fba23530a --- /dev/null +++ b/internal/lsp/testdata/inlay_hint/parameter_names.go @@ -0,0 +1,45 @@ +package inlayHint //@inlayHint("package") + +import "fmt" + +func hello(name string) string { + return "Hello " + name +} + +func helloWorld() string { + return hello("World") +} + +type foo struct{} + +func (*foo) bar(baz string, qux int) int { + if baz != "" { + return qux + 1 + } + return qux +} + +func kase(foo int, bar bool, baz ...string) { + fmt.Println(foo, bar, baz) +} + +func kipp(foo string, bar, baz string) { + fmt.Println(foo, bar, baz) +} + +func plex(foo, bar string, baz string) { + fmt.Println(foo, bar, baz) +} + +func tars(foo string, bar, baz string) { + fmt.Println(foo, bar, baz) +} + +func foobar() { + var x foo + x.bar("", 1) + kase(0, true, "c", "d", "e") + kipp("a", "b", "c") + plex("a", "b", "c") + tars("a", "b", "c") +} diff --git a/internal/lsp/testdata/inlay_hint/parameter_names.go.golden b/internal/lsp/testdata/inlay_hint/parameter_names.go.golden new file mode 100644 index 0000000000..66351e4830 --- /dev/null +++ b/internal/lsp/testdata/inlay_hint/parameter_names.go.golden @@ -0,0 +1,47 @@ +-- inlayHint -- +package inlayHint //@inlayHint("package") + +import "fmt" + +func hello(name string) string { + return "Hello " + name +} + +func helloWorld() string { + return hello("World") +} + +type foo struct{} + +func (*foo) bar(baz string, qux int) int { + if baz != "" { + return qux + 1 + } + return qux +} + +func kase(foo int, bar bool, baz ...string) { + fmt.Println(foo, bar, baz) +} + +func kipp(foo string, bar, baz string) { + fmt.Println(foo, bar, baz) +} + +func plex(foo, bar string, baz string) { + fmt.Println(foo, bar, baz) +} + +func tars(foo string, bar, baz string) { + fmt.Println(foo, bar, baz) +} + +func foobar() { + var x foo + x.bar("", 1) + kase(0, true, "c", "d", "e") + kipp("a", "b", "c") + plex("a", "b", "c") + tars("a", "b", "c") +} +