package mod import ( "context" "fmt" "os" "path/filepath" "golang.org/x/mod/modfile" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" ) // LensFuncs returns the supported lensFuncs for go.mod files. func LensFuncs() map[string]source.LensFunc { return map[string]source.LensFunc{ source.CommandUpgradeDependency.Name: upgradeLens, source.CommandTidy.Name: tidyLens, source.CommandVendor.Name: vendorLens, } } func upgradeLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } module := pm.File.Module if module == nil || module.Syntax == nil { return nil, nil } upgrades, err := snapshot.ModUpgrade(ctx, fh) if err != nil { return nil, err } var ( codelenses []protocol.CodeLens allUpgrades []string ) for _, req := range pm.File.Require { dep := req.Mod.Path latest, ok := upgrades[dep] if !ok { continue } if req.Syntax == nil { continue } // Get the range of the require directive. rng, err := positionsToRange(fh.URI(), pm.Mapper, req.Syntax.Start, req.Syntax.End) if err != nil { return nil, err } upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, []string{dep}) if err != nil { return nil, err } codelenses = append(codelenses, protocol.CodeLens{ Range: rng, Command: protocol.Command{ Title: fmt.Sprintf("Upgrade dependency to %s", latest), Command: source.CommandUpgradeDependency.ID(), Arguments: upgradeDepArgs, }, }) allUpgrades = append(allUpgrades, dep) } // If there is at least 1 upgrade, add "Upgrade all dependencies" to // the module statement. if len(allUpgrades) > 0 { upgradeDepArgs, err := source.MarshalArgs(fh.URI(), false, append([]string{"-u"}, allUpgrades...)) if err != nil { return nil, err } // Get the range of the module directive. moduleRng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, module.Syntax.Start, module.Syntax.End) if err != nil { return nil, err } codelenses = append(codelenses, protocol.CodeLens{ Range: moduleRng, Command: protocol.Command{ Title: "Upgrade all dependencies", Command: source.CommandUpgradeDependency.ID(), Arguments: upgradeDepArgs, }, }) } return codelenses, err } func tidyLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { goModArgs, err := source.MarshalArgs(fh.URI()) if err != nil { return nil, err } tidied, err := snapshot.ModTidy(ctx, fh) if err != nil { return nil, err } if len(tidied.Errors) == 0 { return nil, nil } pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil { return nil, fmt.Errorf("no parsed go.mod for %s", fh.URI()) } rng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End) if err != nil { return nil, err } return []protocol.CodeLens{{ Range: rng, Command: protocol.Command{ Title: source.CommandTidy.Title, Command: source.CommandTidy.ID(), Arguments: goModArgs, }, }}, err } func vendorLens(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.CodeLens, error) { goModArgs, err := source.MarshalArgs(fh.URI()) if err != nil { return nil, err } pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } if pm.File == nil || pm.File.Module == nil || pm.File.Module.Syntax == nil { return nil, fmt.Errorf("no parsed go.mod for %s", fh.URI()) } rng, err := positionsToRange(pm.Mapper.URI, pm.Mapper, pm.File.Module.Syntax.Start, pm.File.Module.Syntax.End) if err != nil { return nil, err } // Change the message depending on whether or not the module already has a // vendor directory. title := "Create vendor directory" vendorDir := filepath.Join(filepath.Dir(fh.URI().Filename()), "vendor") if info, _ := os.Stat(vendorDir); info != nil && info.IsDir() { title = "Sync vendor directory" } return []protocol.CodeLens{{ Range: rng, Command: protocol.Command{ Title: title, Command: source.CommandVendor.ID(), Arguments: goModArgs, }, }}, nil } func positionsToRange(uri span.URI, m *protocol.ColumnMapper, s, e modfile.Position) (protocol.Range, error) { line, col, err := m.Converter.ToPosition(s.Byte) if err != nil { return protocol.Range{}, err } start := span.NewPoint(line, col, s.Byte) line, col, err = m.Converter.ToPosition(e.Byte) if err != nil { return protocol.Range{}, err } end := span.NewPoint(line, col, e.Byte) rng, err := m.Range(span.New(uri, start, end)) if err != nil { return protocol.Range{}, err } return rng, err }