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  }