golang.org/x/tools/gopls@v0.15.3/internal/cmd/codelens.go (about)

     1  // Copyright 2019 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 cmd
     6  
     7  import (
     8  	"context"
     9  	"flag"
    10  	"fmt"
    11  
    12  	"golang.org/x/tools/gopls/internal/protocol"
    13  	"golang.org/x/tools/gopls/internal/settings"
    14  	"golang.org/x/tools/internal/tool"
    15  )
    16  
    17  // codelens implements the codelens verb for gopls.
    18  type codelens struct {
    19  	EditFlags
    20  	app *Application
    21  
    22  	Exec bool `flag:"exec" help:"execute the first matching code lens"`
    23  }
    24  
    25  func (r *codelens) Name() string      { return "codelens" }
    26  func (r *codelens) Parent() string    { return r.app.Name() }
    27  func (r *codelens) Usage() string     { return "[codelens-flags] file[:line[:col]] [title]" }
    28  func (r *codelens) ShortHelp() string { return "List or execute code lenses for a file" }
    29  func (r *codelens) DetailedHelp(f *flag.FlagSet) {
    30  	fmt.Fprint(f.Output(), `
    31  The codelens command lists or executes code lenses for the specified
    32  file, or line within a file. A code lens is a command associated with
    33  a position in the code.
    34  
    35  With an optional title argment, only code lenses matching that
    36  title are considered.
    37  
    38  By default, the codelens command lists the available lenses for the
    39  specified file or line within a file, including the title and
    40  title of the command. With the -exec flag, the first matching command
    41  is executed, and its output is printed to stdout.
    42  
    43  Example:
    44  
    45  	$ gopls codelens a_test.go                    # list code lenses in a file
    46  	$ gopls codelens a_test.go:10                 # list code lenses on line 10
    47  	$ gopls codelens a_test.go gopls.test         # list gopls.test commands
    48  	$ gopls codelens -run a_test.go:10 gopls.test # run a specific test
    49  
    50  codelens-flags:
    51  `)
    52  	printFlagDefaults(f)
    53  }
    54  
    55  func (r *codelens) Run(ctx context.Context, args ...string) error {
    56  	var filename, title string
    57  	switch len(args) {
    58  	case 0:
    59  		return tool.CommandLineErrorf("codelens requires a file name")
    60  	case 2:
    61  		title = args[1]
    62  		fallthrough
    63  	case 1:
    64  		filename = args[0]
    65  	default:
    66  		return tool.CommandLineErrorf("codelens expects at most two arguments")
    67  	}
    68  
    69  	r.app.editFlags = &r.EditFlags // in case a codelens perform an edit
    70  
    71  	// Override the default setting for codelenses[Test], which is
    72  	// off by default because VS Code has a superior client-side
    73  	// implementation. But this client is not VS Code.
    74  	// See golang.LensFuncs().
    75  	origOptions := r.app.options
    76  	r.app.options = func(opts *settings.Options) {
    77  		origOptions(opts)
    78  		if opts.Codelenses == nil {
    79  			opts.Codelenses = make(map[string]bool)
    80  		}
    81  		opts.Codelenses["test"] = true
    82  	}
    83  
    84  	// TODO(adonovan): cleanup: factor progress with stats subcommand.
    85  	cmdDone, onProgress := commandProgress()
    86  
    87  	conn, err := r.app.connect(ctx, onProgress)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer conn.terminate(ctx)
    92  
    93  	filespan := parseSpan(filename)
    94  	file, err := conn.openFile(ctx, filespan.URI())
    95  	if err != nil {
    96  		return err
    97  	}
    98  	loc, err := file.spanLocation(filespan)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	p := protocol.CodeLensParams{
   104  		TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
   105  	}
   106  	lenses, err := conn.CodeLens(ctx, &p)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	for _, lens := range lenses {
   112  		sp, err := file.rangeSpan(lens.Range)
   113  		if err != nil {
   114  			return nil
   115  		}
   116  
   117  		if title != "" && lens.Command.Title != title {
   118  			continue // title was specified but does not match
   119  		}
   120  		if filespan.HasPosition() && !protocol.Intersect(loc.Range, lens.Range) {
   121  			continue // position was specified but does not match
   122  		}
   123  
   124  		// -exec: run the first matching code lens.
   125  		if r.Exec {
   126  			_, err := conn.executeCommand(ctx, cmdDone, lens.Command)
   127  			return err
   128  		}
   129  
   130  		// No -exec: list matching code lenses.
   131  		fmt.Printf("%v: %q [%s]\n", sp, lens.Command.Title, lens.Command.Command)
   132  	}
   133  
   134  	if r.Exec {
   135  		return fmt.Errorf("no code lens at %s with title %q", filespan, title)
   136  	}
   137  	return nil
   138  }