golang.org/x/tools/gopls@v0.15.3/internal/mod/inlayhint.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 package mod 5 6 import ( 7 "context" 8 "fmt" 9 10 "golang.org/x/mod/modfile" 11 "golang.org/x/tools/gopls/internal/cache" 12 "golang.org/x/tools/gopls/internal/file" 13 "golang.org/x/tools/gopls/internal/protocol" 14 ) 15 16 func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, _ protocol.Range) ([]protocol.InlayHint, error) { 17 // Inlay hints are enabled if the client supports them. 18 pm, err := snapshot.ParseMod(ctx, fh) 19 if err != nil { 20 return nil, err 21 } 22 23 // Compare the version of the module used in the snapshot's 24 // metadata (i.e. the solution to the MVS constraints computed 25 // by go list) with the version requested by the module, in 26 // both cases, taking replaces into account. Produce an 27 // InlayHint when the version of the module is not the one 28 // used. 29 30 replaces := make(map[string]*modfile.Replace) 31 for _, x := range pm.File.Replace { 32 replaces[x.Old.Path] = x 33 } 34 35 requires := make(map[string]*modfile.Require) 36 for _, x := range pm.File.Require { 37 requires[x.Mod.Path] = x 38 } 39 40 am, err := snapshot.AllMetadata(ctx) 41 if err != nil { 42 return nil, err 43 } 44 45 var ans []protocol.InlayHint 46 seen := make(map[string]bool) 47 for _, meta := range am { 48 if meta.Module == nil || seen[meta.Module.Path] { 49 continue 50 } 51 seen[meta.Module.Path] = true 52 metaVersion := meta.Module.Version 53 if meta.Module.Replace != nil { 54 metaVersion = meta.Module.Replace.Version 55 } 56 // These versions can be blank, as in gopls/go.mod's local replace 57 if oldrepl, ok := replaces[meta.Module.Path]; ok && oldrepl.New.Version != metaVersion { 58 ih := genHint(oldrepl.Syntax, oldrepl.New.Version, metaVersion, pm.Mapper) 59 if ih != nil { 60 ans = append(ans, *ih) 61 } 62 } else if oldreq, ok := requires[meta.Module.Path]; ok && oldreq.Mod.Version != metaVersion { 63 // maybe it was replaced: 64 if _, ok := replaces[meta.Module.Path]; ok { 65 continue 66 } 67 ih := genHint(oldreq.Syntax, oldreq.Mod.Version, metaVersion, pm.Mapper) 68 if ih != nil { 69 ans = append(ans, *ih) 70 } 71 } 72 } 73 return ans, nil 74 } 75 76 func genHint(mline *modfile.Line, oldVersion, newVersion string, m *protocol.Mapper) *protocol.InlayHint { 77 x := mline.End.Byte // the parser has removed trailing whitespace and comments (see modfile_test.go) 78 x -= len(mline.Token[len(mline.Token)-1]) 79 line, err := m.OffsetPosition(x) 80 if err != nil { 81 return nil 82 } 83 part := protocol.InlayHintLabelPart{ 84 Value: newVersion, 85 Tooltip: &protocol.OrPTooltipPLabel{ 86 Value: fmt.Sprintf("The build selects version %s rather than go.mod's version %s.", newVersion, oldVersion), 87 }, 88 } 89 rng, err := m.OffsetRange(x, mline.End.Byte) 90 if err != nil { 91 return nil 92 } 93 te := protocol.TextEdit{ 94 Range: rng, 95 NewText: newVersion, 96 } 97 return &protocol.InlayHint{ 98 Position: line, 99 Label: []protocol.InlayHintLabelPart{part}, 100 Kind: protocol.Parameter, 101 PaddingRight: true, 102 TextEdits: []protocol.TextEdit{te}, 103 } 104 }