package mod import ( "context" "fmt" "golang.org/x/mod/modfile" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/lsp/debug/tag" "golang.org/x/tools/internal/lsp/protocol" "golang.org/x/tools/internal/lsp/source" "golang.org/x/tools/internal/span" ) // CodeLens computes code lens for a go.mod file. func CodeLens(ctx context.Context, snapshot source.Snapshot, uri span.URI) ([]protocol.CodeLens, error) { if !snapshot.View().Options().EnabledCodeLens[source.CommandUpgradeDependency.Name] { return nil, nil } ctx, done := event.Start(ctx, "mod.CodeLens", tag.URI.Of(uri)) defer done() // Only show go.mod code lenses in module mode, for the view's go.mod. if modURI := snapshot.View().ModFile(); modURI == "" || modURI != uri { return nil, nil } fh, err := snapshot.GetFile(ctx, uri) if err != nil { return nil, err } pm, err := snapshot.ParseMod(ctx, fh) if err != nil { return nil, err } upgrades, err := snapshot.ModUpgrade(ctx, fh) if err != nil { return nil, err } var ( codelens []protocol.CodeLens allUpgrades []string ) for _, req := range pm.File.Require { dep := req.Mod.Path latest, ok := upgrades[dep] if !ok { continue } // Get the range of the require directive. rng, err := positionsToRange(uri, pm.Mapper, req.Syntax.Start, req.Syntax.End) if err != nil { return nil, err } upgradeDepArgs, err := source.MarshalArgs(uri, []string{dep}) if err != nil { return nil, err } codelens = append(codelens, protocol.CodeLens{ Range: rng, Command: protocol.Command{ Title: fmt.Sprintf("Upgrade dependency to %s", latest), Command: source.CommandUpgradeDependency.Name, Arguments: upgradeDepArgs, }, }) allUpgrades = append(allUpgrades, dep) } module := pm.File.Module if module == nil || module.Syntax == nil { return codelens, nil } // Get the range of the module directive. rng, err := positionsToRange(uri, pm.Mapper, module.Syntax.Start, module.Syntax.End) if err != nil { return nil, err } // Add go mod vendor and go mod tidy lenses goModArgs, err := source.MarshalArgs(uri) if err != nil { return nil, err } codelens = append(codelens, protocol.CodeLens{ Range: rng, Command: protocol.Command{ Title: "Sync vendor directory", Command: source.CommandVendor.Name, Arguments: goModArgs, }, }) tidied, err := snapshot.ModTidy(ctx, fh) if err != nil && err != source.ErrTmpModfileUnsupported { return nil, err } if tidied != nil && len(tidied.Errors) > 0 { codelens = append(codelens, protocol.CodeLens{ Range: rng, Command: protocol.Command{ Title: "Remove unused dependencies", Command: source.CommandTidy.Name, Arguments: goModArgs, }, }) } // If there is at least 1 upgrade, add an "Upgrade all dependencies" to the module statement. if len(allUpgrades) > 0 { upgradeDepArgs, err := source.MarshalArgs(uri, append([]string{"-u"}, allUpgrades...)) if err != nil { return nil, err } codelens = append(codelens, protocol.CodeLens{ Range: rng, Command: protocol.Command{ Title: "Upgrade all dependencies", Command: source.CommandUpgradeDependency.Name, Arguments: upgradeDepArgs, }, }) } return codelens, 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 }