From 871637b6476ec258626a649fd4c4e5bc871f9535 Mon Sep 17 00:00:00 2001 From: Suzy Mueller Date: Mon, 13 Jun 2022 19:25:46 -0400 Subject: [PATCH] internal/lsp: add settings for inlay hints and enable This change adds user settings for enabling inlay hints, modeled roughly after analyzers. This will allow users to turn on specific inlay hints that they like and leave others off. With all of the inlay hints turned off by default, we can now enable inlay hints. Change-Id: Ie5dfcbbab1e0b7312eafcc4aa08cb4fe8a83fc31 Reviewed-on: https://go-review.googlesource.com/c/tools/+/411906 Run-TryBot: Suzy Mueller Reviewed-by: Jamal Carvalho gopls-CI: kokoro Reviewed-by: Robert Findley --- gopls/doc/generate.go | 44 +++++++ gopls/doc/inlayHints.md | 73 +++++++++++ gopls/doc/settings.md | 13 ++ internal/lsp/general.go | 1 + internal/lsp/lsp_test.go | 3 + internal/lsp/source/api_json.go | 75 +++++++++++ internal/lsp/source/inlay_hint.go | 206 +++++++++++++++++++++++------- internal/lsp/source/options.go | 35 ++++- internal/lsp/tests/util.go | 9 ++ 9 files changed, 410 insertions(+), 49 deletions(-) create mode 100644 gopls/doc/inlayHints.md diff --git a/gopls/doc/generate.go b/gopls/doc/generate.go index e63653de6b..c7e0e0ffcc 100644 --- a/gopls/doc/generate.go +++ b/gopls/doc/generate.go @@ -63,6 +63,9 @@ func doMain(baseDir string, write bool) (bool, error) { if ok, err := rewriteFile(filepath.Join(baseDir, "gopls/doc/analyzers.md"), api, write, rewriteAnalyzers); !ok || err != nil { return ok, err } + if ok, err := rewriteFile(filepath.Join(baseDir, "gopls/doc/inlayHints.md"), api, write, rewriteInlayHints); !ok || err != nil { + return ok, err + } return true, nil } @@ -102,6 +105,7 @@ func loadAPI() (*source.APIJSON, error) { } { api.Analyzers = append(api.Analyzers, loadAnalyzers(m)...) } + api.Hints = loadHints(source.AllInlayHints) for _, category := range []reflect.Value{ reflect.ValueOf(defaults.UserOptions), } { @@ -146,6 +150,14 @@ func loadAPI() (*source.APIJSON, error) { Default: def, }) } + case "hints": + for _, a := range api.Hints { + opt.EnumKeys.Keys = append(opt.EnumKeys.Keys, source.EnumKey{ + Name: fmt.Sprintf("%q", a.Name), + Doc: a.Doc, + Default: strconv.FormatBool(a.Default), + }) + } } } } @@ -488,6 +500,23 @@ func loadAnalyzers(m map[string]*source.Analyzer) []*source.AnalyzerJSON { return json } +func loadHints(m map[string]*source.Hint) []*source.HintJSON { + var sorted []string + for _, h := range m { + sorted = append(sorted, h.Name) + } + sort.Strings(sorted) + var json []*source.HintJSON + for _, name := range sorted { + h := m[name] + json = append(json, &source.HintJSON{ + Name: h.Name, + Doc: h.Doc, + }) + } + return json +} + func lowerFirst(x string) string { if x == "" { return x @@ -699,6 +728,21 @@ func rewriteAnalyzers(doc []byte, api *source.APIJSON) ([]byte, error) { return replaceSection(doc, "Analyzers", section.Bytes()) } +func rewriteInlayHints(doc []byte, api *source.APIJSON) ([]byte, error) { + section := bytes.NewBuffer(nil) + for _, hint := range api.Hints { + fmt.Fprintf(section, "## **%v**\n\n", hint.Name) + fmt.Fprintf(section, "%s\n\n", hint.Doc) + switch hint.Default { + case true: + fmt.Fprintf(section, "**Enabled by default.**\n\n") + case false: + fmt.Fprintf(section, "**Disabled by default. Enable it by setting `\"hints\": {\"%s\": true}`.**\n\n", hint.Name) + } + } + return replaceSection(doc, "Hints", section.Bytes()) +} + func replaceSection(doc []byte, sectionName string, replacement []byte) ([]byte, error) { re := regexp.MustCompile(fmt.Sprintf(`(?s)\n(.*?)`, sectionName, sectionName)) idx := re.FindSubmatchIndex(doc) diff --git a/gopls/doc/inlayHints.md b/gopls/doc/inlayHints.md new file mode 100644 index 0000000000..a4fd3e5155 --- /dev/null +++ b/gopls/doc/inlayHints.md @@ -0,0 +1,73 @@ +# Hints + +This document describes the inlay hints that `gopls` uses inside the editor. + + +## **assign_variable_types** + +Enable/disable inlay hints for variable types in assign statements: + + i/* int/*, j/* int/* := 0, len(r)-1 + +**Disabled by default. Enable it by setting `"hints": {"assign_variable_types": true}`.** + +## **composite_literal_fields** + +Enable/disable inlay hints for composite literal field names: + + {in: "Hello, world", want: "dlrow ,olleH"} + +**Disabled by default. Enable it by setting `"hints": {"composite_literal_fields": true}`.** + +## **composite_literal_types** + +Enable/disable inlay hints for composite literal types: + + for _, c := range []struct { + in, want string + }{ + /*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"}, + } + +**Disabled by default. Enable it by setting `"hints": {"composite_literal_types": true}`.** + +## **constant_values** + +Enable/disable inlay hints for constant values: + + const ( + KindNone Kind = iota/* = 0*/ + KindPrint/* = 1*/ + KindPrintf/* = 2*/ + KindErrorf/* = 3*/ + ) + +**Disabled by default. Enable it by setting `"hints": {"constant_values": true}`.** + +## **function_type_parameters** + +Enable/disable inlay hints for implicit type parameters on generic functions: + + myFoo/*[int, string]*/(1, "hello") + +**Disabled by default. Enable it by setting `"hints": {"function_type_parameters": true}`.** + +## **parameter_names** + +Enable/disable inlay hints for parameter names: + + parseInt(/* str: */ "123", /* radix: */ 8) + +**Disabled by default. Enable it by setting `"hints": {"parameter_names": true}`.** + +## **range_variable_types** + +Enable/disable inlay hints for variable types in range statements: + + for k/* int*/, v/* string/* := range []string{} { + fmt.Println(k, v) + } + +**Disabled by default. Enable it by setting `"hints": {"range_variable_types": true}`.** + + diff --git a/gopls/doc/settings.md b/gopls/doc/settings.md index 092a3c7cfa..0ed0e19bb0 100644 --- a/gopls/doc/settings.md +++ b/gopls/doc/settings.md @@ -35,6 +35,7 @@ still be able to independently override specific experimental features. * [Completion](#completion) * [Diagnostic](#diagnostic) * [Documentation](#documentation) + * [Inlayhint](#inlayhint) * [Navigation](#navigation) ### Build @@ -370,6 +371,18 @@ linksInHover toggles the presence of links to documentation in hover. Default: `true`. +#### Inlayhint + +##### **hints** *map[string]bool* + +**This setting is experimental and may be deleted.** + +hints specify inlay hints that users want to see. +A full list of hints that gopls uses can be found +[here](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md). + +Default: `{}`. + #### Navigation ##### **importShortcut** *enum* diff --git a/internal/lsp/general.go b/internal/lsp/general.go index 478152bdf9..385a04a25f 100644 --- a/internal/lsp/general.go +++ b/internal/lsp/general.go @@ -153,6 +153,7 @@ See https://github.com/golang/go/issues/45732 for more information.`, HoverProvider: true, DocumentHighlightProvider: true, DocumentLinkProvider: protocol.DocumentLinkOptions{}, + InlayHintProvider: protocol.InlayHintOptions{}, ReferencesProvider: true, RenameProvider: renameOpts, SignatureHelpProvider: protocol.SignatureHelpOptions{ diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 56356e9b5a..2ec833b860 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -67,6 +67,9 @@ func testLSP(t *testing.T, datum *tests.Data) { tests.EnableAllAnalyzers(view, options) view.SetOptions(ctx, options) + // Enable all inlay hints for tests. + tests.EnableAllInlayHints(view, options) + // Only run the -modfile specific tests in module mode with Go 1.14 or above. datum.ModfileFlagAvailable = len(snapshot.ModFiles()) > 0 && testenv.Go1Point() >= 14 release() diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go index 0695efc2fa..4188d9d06f 100755 --- a/internal/lsp/source/api_json.go +++ b/internal/lsp/source/api_json.go @@ -505,6 +505,51 @@ var GeneratedAPIJSON = &APIJSON{ Status: "experimental", Hierarchy: "ui.diagnostic", }, + { + Name: "hints", + Type: "map[string]bool", + Doc: "hints specify inlay hints that users want to see.\nA full list of hints that gopls uses can be found\n[here](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md).\n", + EnumKeys: EnumKeys{Keys: []EnumKey{ + { + Name: "\"assign_variable_types\"", + Doc: "Enable/disable inlay hints for variable types in assign statements:\n\n\ti/* int/*, j/* int/* := 0, len(r)-1", + Default: "false", + }, + { + Name: "\"composite_literal_fields\"", + Doc: "Enable/disable inlay hints for composite literal field names:\n\n\t{in: \"Hello, world\", want: \"dlrow ,olleH\"}", + Default: "false", + }, + { + Name: "\"composite_literal_types\"", + Doc: "Enable/disable inlay hints for composite literal types:\n\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}", + Default: "false", + }, + { + Name: "\"constant_values\"", + Doc: "Enable/disable inlay hints for constant values:\n\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)", + Default: "false", + }, + { + Name: "\"function_type_parameters\"", + Doc: "Enable/disable inlay hints for implicit type parameters on generic functions:\n\n\tmyFoo/*[int, string]*/(1, \"hello\")", + Default: "false", + }, + { + Name: "\"parameter_names\"", + Doc: "Enable/disable inlay hints for parameter names:\n\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)", + Default: "false", + }, + { + Name: "\"range_variable_types\"", + Doc: "Enable/disable inlay hints for variable types in range statements:\n\n\tfor k/* int*/, v/* string/* := range []string{} {\n\t\tfmt.Println(k, v)\n\t}", + Default: "false", + }, + }}, + Default: "{}", + Status: "experimental", + Hierarchy: "ui.inlayhint", + }, { Name: "codelenses", Type: "map[string]bool", @@ -979,4 +1024,34 @@ var GeneratedAPIJSON = &APIJSON{ Default: true, }, }, + Hints: []*HintJSON{ + { + Name: "assign_variable_types", + Doc: "Enable/disable inlay hints for variable types in assign statements:\n\n\ti/* int/*, j/* int/* := 0, len(r)-1", + }, + { + Name: "composite_literal_fields", + Doc: "Enable/disable inlay hints for composite literal field names:\n\n\t{in: \"Hello, world\", want: \"dlrow ,olleH\"}", + }, + { + Name: "composite_literal_types", + Doc: "Enable/disable inlay hints for composite literal types:\n\n\tfor _, c := range []struct {\n\t\tin, want string\n\t}{\n\t\t/*struct{ in string; want string }*/{\"Hello, world\", \"dlrow ,olleH\"},\n\t}", + }, + { + Name: "constant_values", + Doc: "Enable/disable inlay hints for constant values:\n\n\tconst (\n\t\tKindNone Kind = iota/* = 0*/\n\t\tKindPrint/* = 1*/\n\t\tKindPrintf/* = 2*/\n\t\tKindErrorf/* = 3*/\n\t)", + }, + { + Name: "function_type_parameters", + Doc: "Enable/disable inlay hints for implicit type parameters on generic functions:\n\n\tmyFoo/*[int, string]*/(1, \"hello\")", + }, + { + Name: "parameter_names", + Doc: "Enable/disable inlay hints for parameter names:\n\n\tparseInt(/* str: */ \"123\", /* radix: */ 8)", + }, + { + Name: "range_variable_types", + Doc: "Enable/disable inlay hints for variable types in range statements:\n\n\tfor k/* int*/, v/* string/* := range []string{} {\n\t\tfmt.Println(k, v)\n\t}", + }, + }, } diff --git a/internal/lsp/source/inlay_hint.go b/internal/lsp/source/inlay_hint.go index 8369681003..99e1ad09d8 100644 --- a/internal/lsp/source/inlay_hint.go +++ b/internal/lsp/source/inlay_hint.go @@ -23,6 +23,87 @@ const ( maxLabelLength = 28 ) +type InlayHintFunc func(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint + +type Hint struct { + Name string + Doc string + Run InlayHintFunc +} + +const ( + ParameterNames = "parameter_names" + AssignVariableTypes = "assign_variable_types" + ConstantValues = "constant_values" + RangeVariableTypes = "range_variable_types" + CompositeLiteralTypes = "composite_literal_types" + CompositeLiteralFieldNames = "composite_literal_fields" + FunctionTypeParameters = "function_type_parameters" +) + +var AllInlayHints = map[string]*Hint{ + AssignVariableTypes: { + Name: AssignVariableTypes, + Doc: `Enable/disable inlay hints for variable types in assign statements: + + i/* int/*, j/* int/* := 0, len(r)-1`, + Run: assignVariableTypes, + }, + ParameterNames: { + Name: ParameterNames, + Doc: `Enable/disable inlay hints for parameter names: + + parseInt(/* str: */ "123", /* radix: */ 8)`, + Run: parameterNames, + }, + ConstantValues: { + Name: ConstantValues, + Doc: `Enable/disable inlay hints for constant values: + + const ( + KindNone Kind = iota/* = 0*/ + KindPrint/* = 1*/ + KindPrintf/* = 2*/ + KindErrorf/* = 3*/ + )`, + Run: constantValues, + }, + RangeVariableTypes: { + Name: RangeVariableTypes, + Doc: `Enable/disable inlay hints for variable types in range statements: + + for k/* int*/, v/* string/* := range []string{} { + fmt.Println(k, v) + }`, + Run: rangeVariableTypes, + }, + CompositeLiteralTypes: { + Name: CompositeLiteralTypes, + Doc: `Enable/disable inlay hints for composite literal types: + + for _, c := range []struct { + in, want string + }{ + /*struct{ in string; want string }*/{"Hello, world", "dlrow ,olleH"}, + }`, + Run: compositeLiteralTypes, + }, + CompositeLiteralFieldNames: { + Name: CompositeLiteralFieldNames, + Doc: `Enable/disable inlay hints for composite literal field names: + + {in: "Hello, world", want: "dlrow ,olleH"}`, + Run: compositeLiteralFields, + }, + FunctionTypeParameters: { + Name: FunctionTypeParameters, + Doc: `Enable/disable inlay hints for implicit type parameters on generic functions: + + myFoo/*[int, string]*/(1, "hello")`, + Run: funcTypeParams, + }, +} + func InlayHint(ctx context.Context, snapshot Snapshot, fh FileHandle, _ protocol.Range) ([]protocol.InlayHint, error) { ctx, done := event.Start(ctx, "source.InlayHint") defer done() @@ -32,38 +113,47 @@ func InlayHint(ctx context.Context, snapshot Snapshot, fh FileHandle, _ protocol return nil, fmt.Errorf("getting file for InlayHint: %w", err) } + // Collect a list of the inlay hints that are enabled. + inlayHintOptions := snapshot.View().Options().InlayHintOptions + var enabledHints []InlayHintFunc + for hint, enabled := range inlayHintOptions.Hints { + if !enabled { + continue + } + if h, ok := AllInlayHints[hint]; ok { + enabledHints = append(enabledHints, h.Run) + } + } + if len(enabledHints) == 0 { + return nil, nil + } + tmap := lsppos.NewTokenMapper(pgf.Src, pgf.Tok) info := pkg.GetTypesInfo() q := Qualifier(pgf.File, pkg.GetTypes(), info) 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)...) - hints = append(hints, funcTypeParams(n, tmap, info)...) - case *ast.AssignStmt: - hints = append(hints, assignVariableTypes(n, tmap, info, &q)...) - case *ast.RangeStmt: - hints = append(hints, rangeVariableTypes(n, tmap, info, &q)...) - case *ast.GenDecl: - hints = append(hints, constantValues(n, tmap, info)...) - case *ast.CompositeLit: - hints = append(hints, compositeLiterals(n, tmap, info, &q)...) + for _, fn := range enabledHints { + hints = append(hints, fn(node, tmap, info, &q)...) } 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) +func parameterNames(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, _ *types.Qualifier) []protocol.InlayHint { + callExpr, ok := node.(*ast.CallExpr) + if !ok { + return nil + } + signature, ok := info.TypeOf(callExpr.Fun).(*types.Signature) if !ok { return nil } var hints []protocol.InlayHint - for i, v := range node.Args { + for i, v := range callExpr.Args { start, ok := tmap.Position(v.Pos()) if !ok { continue @@ -92,8 +182,12 @@ func parameterNames(node *ast.CallExpr, tmap *lsppos.TokenMapper, info *types.In return hints } -func funcTypeParams(node *ast.CallExpr, tmap *lsppos.TokenMapper, info *types.Info) []protocol.InlayHint { - id, ok := node.Fun.(*ast.Ident) +func funcTypeParams(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, _ *types.Qualifier) []protocol.InlayHint { + ce, ok := node.(*ast.CallExpr) + if !ok { + return nil + } + id, ok := ce.Fun.(*ast.Ident) if !ok { return nil } @@ -119,12 +213,14 @@ func funcTypeParams(node *ast.CallExpr, tmap *lsppos.TokenMapper, info *types.In }} } -func assignVariableTypes(node *ast.AssignStmt, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint { - if node.Tok != token.DEFINE { +func assignVariableTypes(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint { + stmt, ok := node.(*ast.AssignStmt) + if !ok || stmt.Tok != token.DEFINE { return nil } + var hints []protocol.InlayHint - for _, v := range node.Lhs { + for _, v := range stmt.Lhs { if h := variableType(v, tmap, info, q); h != nil { hints = append(hints, *h) } @@ -132,12 +228,16 @@ func assignVariableTypes(node *ast.AssignStmt, tmap *lsppos.TokenMapper, info *t return hints } -func rangeVariableTypes(node *ast.RangeStmt, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint { +func rangeVariableTypes(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint { + rStmt, ok := node.(*ast.RangeStmt) + if !ok { + return nil + } var hints []protocol.InlayHint - if h := variableType(node.Key, tmap, info, q); h != nil { + if h := variableType(rStmt.Key, tmap, info, q); h != nil { hints = append(hints, *h) } - if h := variableType(node.Value, tmap, info, q); h != nil { + if h := variableType(rStmt.Value, tmap, info, q); h != nil { hints = append(hints, *h) } return hints @@ -160,13 +260,14 @@ func variableType(e ast.Expr, tmap *lsppos.TokenMapper, info *types.Info, q *typ } } -func constantValues(node *ast.GenDecl, tmap *lsppos.TokenMapper, info *types.Info) []protocol.InlayHint { - if node.Tok != token.CONST { +func constantValues(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, _ *types.Qualifier) []protocol.InlayHint { + genDecl, ok := node.(*ast.GenDecl) + if !ok || genDecl.Tok != token.CONST { return nil } var hints []protocol.InlayHint - for _, v := range node.Specs { + for _, v := range genDecl.Specs { spec, ok := v.(*ast.ValueSpec) if !ok { continue @@ -210,36 +311,26 @@ func constantValues(node *ast.GenDecl, tmap *lsppos.TokenMapper, info *types.Inf return hints } -func compositeLiterals(node *ast.CompositeLit, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint { - typ := info.TypeOf(node) +func compositeLiteralFields(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint { + compLit, ok := node.(*ast.CompositeLit) + if !ok { + return nil + } + typ := info.TypeOf(compLit) if typ == nil { return nil } - - prefix := "" if t, ok := typ.(*types.Pointer); ok { typ = t.Elem() - prefix = "&" } - strct, ok := typ.Underlying().(*types.Struct) if !ok { return nil } var hints []protocol.InlayHint - if node.Type == nil { - // The type for this struct is implicit, add an inlay hint. - if start, ok := tmap.Position(node.Lbrace); ok { - hints = append(hints, protocol.InlayHint{ - Position: &start, - Label: buildLabel(fmt.Sprintf("%s%s", prefix, types.TypeString(typ, *q))), - Kind: protocol.Type, - }) - } - } - for i, v := range node.Elts { + for i, v := range compLit.Elts { if _, ok := v.(*ast.KeyValueExpr); !ok { start, ok := tmap.Position(v.Pos()) if !ok { @@ -259,6 +350,35 @@ func compositeLiterals(node *ast.CompositeLit, tmap *lsppos.TokenMapper, info *t return hints } +func compositeLiteralTypes(node ast.Node, tmap *lsppos.TokenMapper, info *types.Info, q *types.Qualifier) []protocol.InlayHint { + compLit, ok := node.(*ast.CompositeLit) + if !ok { + return nil + } + typ := info.TypeOf(compLit) + if typ == nil { + return nil + } + if compLit.Type != nil { + return nil + } + prefix := "" + if t, ok := typ.(*types.Pointer); ok { + typ = t.Elem() + prefix = "&" + } + // The type for this composite literal is implicit, add an inlay hint. + start, ok := tmap.Position(compLit.Lbrace) + if !ok { + return nil + } + return []protocol.InlayHint{{ + Position: &start, + Label: buildLabel(fmt.Sprintf("%s%s", prefix, types.TypeString(typ, *q))), + Kind: protocol.Type, + }} +} + func buildLabel(s string) []protocol.InlayHintLabelPart { label := protocol.InlayHintLabelPart{ Value: s, diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go index d1d34efe78..5da14ebfe9 100644 --- a/internal/lsp/source/options.go +++ b/internal/lsp/source/options.go @@ -130,6 +130,7 @@ func DefaultOptions() *Options { Nil: true, }, }, + InlayHintOptions: InlayHintOptions{}, DocumentationOptions: DocumentationOptions{ HoverKind: FullDocumentation, LinkTarget: "pkg.go.dev", @@ -289,6 +290,7 @@ type UIOptions struct { CompletionOptions NavigationOptions DiagnosticOptions + InlayHintOptions // Codelenses overrides the enabled/disabled state of code lenses. See the // "Code Lenses" section of the @@ -407,6 +409,13 @@ type DiagnosticOptions struct { ExperimentalWatchedFileDelay time.Duration `status:"experimental"` } +type InlayHintOptions struct { + // Hints specify inlay hints that users want to see. + // A full list of hints that gopls uses can be found + // [here](https://github.com/golang/tools/blob/master/gopls/doc/inlayHints.md). + Hints map[string]bool `status:"experimental"` +} + type NavigationOptions struct { // ImportShortcut specifies whether import statements should link to // documentation or go to definitions. @@ -915,6 +924,9 @@ func (o *Options) set(name string, value interface{}, seen map[string]struct{}) case "analyses": result.setBoolMap(&o.Analyses) + case "hints": + result.setBoolMap(&o.Hints) + case "annotations": result.setAnnotationMap(&o.Annotations) @@ -1351,6 +1363,7 @@ type APIJSON struct { Commands []*CommandJSON Lenses []*LensJSON Analyzers []*AnalyzerJSON + Hints []*HintJSON } type OptionJSON struct { @@ -1416,12 +1429,8 @@ func collectEnums(opt *OptionJSON) string { } func shouldShowEnumKeysInSettings(name string) bool { - // Both of these fields have too many possible options to print. - return !hardcodedEnumKeys(name) -} - -func hardcodedEnumKeys(name string) bool { - return name == "analyses" || name == "codelenses" + // These fields have too many possible options to print. + return !(name == "analyses" || name == "codelenses" || name == "hints") } type EnumKeys struct { @@ -1489,3 +1498,17 @@ func (a *AnalyzerJSON) String() string { func (a *AnalyzerJSON) Write(w io.Writer) { fmt.Fprintf(w, "%s (%s): %v", a.Name, a.Doc, a.Default) } + +type HintJSON struct { + Name string + Doc string + Default bool +} + +func (h *HintJSON) String() string { + return h.Name +} + +func (h *HintJSON) Write(w io.Writer) { + fmt.Fprintf(w, "%s (%s): %v", h.Name, h.Doc, h.Default) +} diff --git a/internal/lsp/tests/util.go b/internal/lsp/tests/util.go index 11dda1f8ed..98562d6365 100644 --- a/internal/lsp/tests/util.go +++ b/internal/lsp/tests/util.go @@ -512,6 +512,15 @@ func EnableAllAnalyzers(view source.View, opts *source.Options) { } } +func EnableAllInlayHints(view source.View, opts *source.Options) { + if opts.Hints == nil { + opts.Hints = make(map[string]bool) + } + for name := range source.AllInlayHints { + opts.Hints[name] = true + } +} + func WorkspaceSymbolsString(ctx context.Context, data *Data, queryURI span.URI, symbols []protocol.SymbolInformation) (string, error) { queryDir := filepath.Dir(queryURI.Filename()) var filtered []string