9 "golang.org/x/mod/modfile"
10 "golang.org/x/tools/internal/lsp/protocol"
11 "golang.org/x/tools/internal/lsp/source"
12 "golang.org/x/tools/internal/span"
15 // LensFuncs returns the supported lensFuncs for go.mod files.
16 func LensFuncs() map[string]source.LensFunc {
17 return map[string]source.LensFunc{
18 source.CommandUpgradeDependency.Name: upgradeLens,
19 source.CommandTidy.Name: tidyLens,
20 source.CommandVendor.Name: vendorLens,
24 func upgradeLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
25 pm, err := snapshot.ParseMod(ctx, fh)
29 module := pm.File.Module
30 if module == nil || module.Syntax == nil {
33 upgrades, err := snapshot.ModUpgrade(ctx, fh)
38 codelenses []protocol.CodeLens
41 for _, req := range pm.File.Require {
43 latest, ok := upgrades[dep]
47 if req.Syntax == nil {
50 // Get the range of the require directive.
51 rng, err := positionsToRange(fh.URI(), pm.Mapper, req.Syntax.Start, req.Syntax.End)
55 upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, []string{dep})
59 codelenses = append(codelenses, protocol.CodeLens{
61 Command: protocol.Command{
62 Title: fmt.Sprintf("Upgrade dependency to %s", latest),
63 Command: source.CommandUpgradeDependency.ID(),
64 Arguments: upgradeDepArgs,
67 allUpgrades = append(allUpgrades, dep)
69 // If there is at least 1 upgrade, add "Upgrade all dependencies" to
70 // the module statement.
71 if len(allUpgrades) > 0 {
72 upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, append([]string{"-u"}, allUpgrades...))
76 // Get the range of the module directive.
77 moduleRng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, module.Syntax.Start, module.Syntax.End)
81 codelenses = append(codelenses, protocol.CodeLens{
83 Command: protocol.Command{
84 Title: "Upgrade all dependencies",
85 Command: source.CommandUpgradeDependency.ID(),
86 Arguments: upgradeDepArgs,
90 return codelenses, err
93 func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
94 goModArgs, err := source.MarshalArgs(fh.URI())
98 tidied, err := snapshot.ModTidy(ctx, fh)
102 if len(tidied.Errors) == 0 {
105 pm, err := snapshot.ParseMod(ctx, fh)
109 if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil {
110 return nil, fmt.Errorf("no parsed go.mod for %s", fh.URI())
112 rng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End)
116 return []protocol.CodeLens{{
118 Command: protocol.Command{
119 Title: source.CommandTidy.Title,
120 Command: source.CommandTidy.ID(),
121 Arguments: goModArgs,
126 func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) {
127 goModArgs, err := source.MarshalArgs(fh.URI())
131 pm, err := snapshot.ParseMod(ctx, fh)
135 if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil {
136 return nil, fmt.Errorf("no parsed go.mod for %s", fh.URI())
138 rng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End)
142 // Change the message depending on whether or not the module already has a
144 title := "Create vendor directory"
145 vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor")
146 if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() {
147 title = "Sync vendor directory"
149 return []protocol.CodeLens{{
151 Command: protocol.Command{
153 Command: source.CommandVendor.ID(),
154 Arguments: goModArgs,
159 func positionsToRange(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) {
160 line, col, err := m.Converter.ToPosition(s.Byte)
162 return protocol.Range{}, err
164 start := span.NewPoint(line, col, s.Byte)
165 line, col, err = m.Converter.ToPosition(e.Byte)
167 return protocol.Range{}, err
169 end := span.NewPoint(line, col, e.Byte)
170 rng, err := m.Range(span.New(uri, start, end))
172 return protocol.Range{}, err