github.com/v2fly/tools@v0.100.0/internal/lsp/mod/code_lens.go (about) 1 // Copyright 2020 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 5 package mod 6 7 import ( 8 "context" 9 "fmt" 10 "os" 11 "path/filepath" 12 13 "github.com/v2fly/tools/internal/lsp/command" 14 "github.com/v2fly/tools/internal/lsp/protocol" 15 "github.com/v2fly/tools/internal/lsp/source" 16 "github.com/v2fly/tools/internal/span" 17 "golang.org/x/mod/modfile" 18 ) 19 20 // LensFuncs returns the supported lensFuncs for go.mod files. 21 func LensFuncs() map[command.Command]source.LensFunc { 22 return map[command.Command]source.LensFunc{ 23 command.UpgradeDependency: upgradeLenses, 24 command.Tidy: tidyLens, 25 command.Vendor: vendorLens, 26 } 27 } 28 29 func upgradeLenses(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { 30 pm, err := snapshot.ParseMod(ctx, fh) 31 if err != nil || pm.File == nil { 32 return nil, err 33 } 34 if len(pm.File.Require) == 0 { 35 // Nothing to upgrade. 36 return nil, nil 37 } 38 var requires []string 39 for _, req := range pm.File.Require { 40 requires = append(requires, req.Mod.Path) 41 } 42 uri := protocol.URIFromSpanURI(fh.URI()) 43 checkUpgrade, err := command.NewCheckUpgradesCommand("Check for upgrades", command.CheckUpgradesArgs{ 44 URI: uri, 45 Modules: requires, 46 }) 47 if err != nil { 48 return nil, err 49 } 50 upgradeTransitive, err := command.NewUpgradeDependencyCommand("Upgrade transitive dependencies", command.DependencyArgs{ 51 URI: uri, 52 AddRequire: false, 53 GoCmdArgs: []string{"-d", "-u", "-t", "./..."}, 54 }) 55 if err != nil { 56 return nil, err 57 } 58 upgradeDirect, err := command.NewUpgradeDependencyCommand("Upgrade direct dependencies", command.DependencyArgs{ 59 URI: uri, 60 AddRequire: false, 61 GoCmdArgs: append([]string{"-d"}, requires...), 62 }) 63 if err != nil { 64 return nil, err 65 } 66 // Put the upgrade code lenses above the first require block or statement. 67 rng, err := firstRequireRange(fh, pm) 68 if err != nil { 69 return nil, err 70 } 71 72 return []protocol.CodeLens{ 73 {Range: rng, Command: checkUpgrade}, 74 {Range: rng, Command: upgradeTransitive}, 75 {Range: rng, Command: upgradeDirect}, 76 }, nil 77 } 78 79 func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { 80 pm, err := snapshot.ParseMod(ctx, fh) 81 if err != nil || pm.File == nil { 82 return nil, err 83 } 84 if len(pm.File.Require) == 0 { 85 // Nothing to vendor. 86 return nil, nil 87 } 88 uri := protocol.URIFromSpanURI(fh.URI()) 89 cmd, err := command.NewTidyCommand("Run go mod tidy", command.URIArgs{URIs: []protocol.DocumentURI{uri}}) 90 if err != nil { 91 return nil, err 92 } 93 rng, err := moduleStmtRange(fh, pm) 94 if err != nil { 95 return nil, err 96 } 97 return []protocol.CodeLens{{ 98 Range: rng, 99 Command: cmd, 100 }}, nil 101 } 102 103 func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { 104 pm, err := snapshot.ParseMod(ctx, fh) 105 if err != nil || pm.File == nil { 106 return nil, err 107 } 108 rng, err := moduleStmtRange(fh, pm) 109 if err != nil { 110 return nil, err 111 } 112 title := "Create vendor directory" 113 uri := protocol.URIFromSpanURI(fh.URI()) 114 cmd, err := command.NewVendorCommand(title, command.URIArg{URI: uri}) 115 if err != nil { 116 return nil, err 117 } 118 // Change the message depending on whether or not the module already has a 119 // vendor directory. 120 vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor") 121 if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() { 122 title = "Sync vendor directory" 123 } 124 return []protocol.CodeLens{{Range: rng, Command: cmd}}, nil 125 } 126 127 func moduleStmtRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) { 128 if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil { 129 return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI()) 130 } 131 syntax := pm.File.Module.Syntax 132 return lineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End) 133 } 134 135 // firstRequireRange returns the range for the first "require" in the given 136 // go.mod file. This is either a require block or an individual require line. 137 func firstRequireRange(fh source.FileHandle, pm *source.ParsedModule) (protocol.Range, error) { 138 if len(pm.File.Require) == 0 { 139 return protocol.Range{}, fmt.Errorf("no requires in the file %s", fh.URI()) 140 } 141 var start, end modfile.Position 142 for _, stmt := range pm.File.Syntax.Stmt { 143 if b, ok := stmt.(*modfile.LineBlock); ok && len(b.Token) == 1 && b.Token[0] == "require" { 144 start, end = b.Span() 145 break 146 } 147 } 148 149 firstRequire := pm.File.Require[0].Syntax 150 if start.Byte == 0 || firstRequire.Start.Byte < start.Byte { 151 start, end = firstRequire.Start, firstRequire.End 152 } 153 return lineToRange(pm.Mapper, fh.URI(), start, end) 154 } 155 156 func lineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) { 157 line, col, err := m.Converter.ToPosition(start.Byte) 158 if err != nil { 159 return protocol.Range{}, err 160 } 161 s := span.NewPoint(line, col, start.Byte) 162 line, col, err = m.Converter.ToPosition(end.Byte) 163 if err != nil { 164 return protocol.Range{}, err 165 } 166 e := span.NewPoint(line, col, end.Byte) 167 return m.Range(span.New(uri, s, e)) 168 }