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