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

     1  // Copyright 2018 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 handles the gopls command line.
     6  // It contains a handler for each of the modes, along with all the flag handling
     7  // and the command line output format.
     8  package cmd
     9  
    10  import (
    11  	"context"
    12  	"flag"
    13  	"fmt"
    14  	"log"
    15  	"os"
    16  	"path/filepath"
    17  	"reflect"
    18  	"sort"
    19  	"strings"
    20  	"sync"
    21  	"text/tabwriter"
    22  	"time"
    23  
    24  	"golang.org/x/tools/gopls/internal/cache"
    25  	"golang.org/x/tools/gopls/internal/debug"
    26  	"golang.org/x/tools/gopls/internal/filecache"
    27  	"golang.org/x/tools/gopls/internal/lsprpc"
    28  	"golang.org/x/tools/gopls/internal/protocol"
    29  	"golang.org/x/tools/gopls/internal/protocol/command"
    30  	"golang.org/x/tools/gopls/internal/server"
    31  	"golang.org/x/tools/gopls/internal/settings"
    32  	"golang.org/x/tools/gopls/internal/util/browser"
    33  	bugpkg "golang.org/x/tools/gopls/internal/util/bug"
    34  	"golang.org/x/tools/gopls/internal/util/constraints"
    35  	"golang.org/x/tools/internal/diff"
    36  	"golang.org/x/tools/internal/jsonrpc2"
    37  	"golang.org/x/tools/internal/tool"
    38  )
    39  
    40  // Application is the main application as passed to tool.Main
    41  // It handles the main command line parsing and dispatch to the sub commands.
    42  type Application struct {
    43  	// Core application flags
    44  
    45  	// Embed the basic profiling flags supported by the tool package
    46  	tool.Profile
    47  
    48  	// We include the server configuration directly for now, so the flags work
    49  	// even without the verb.
    50  	// TODO: Remove this when we stop allowing the serve verb by default.
    51  	Serve Serve
    52  
    53  	// the options configuring function to invoke when building a server
    54  	options func(*settings.Options)
    55  
    56  	// Support for remote LSP server.
    57  	Remote string `flag:"remote" help:"forward all commands to a remote lsp specified by this flag. With no special prefix, this is assumed to be a TCP address. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. If 'auto', or prefixed by 'auto;', the remote address is automatically resolved based on the executing environment."`
    58  
    59  	// Verbose enables verbose logging.
    60  	Verbose bool `flag:"v,verbose" help:"verbose output"`
    61  
    62  	// VeryVerbose enables a higher level of verbosity in logging output.
    63  	VeryVerbose bool `flag:"vv,veryverbose" help:"very verbose output"`
    64  
    65  	// Control ocagent export of telemetry
    66  	OCAgent string `flag:"ocagent" help:"the address of the ocagent (e.g. http://localhost:55678), or off"`
    67  
    68  	// PrepareOptions is called to update the options when a new view is built.
    69  	// It is primarily to allow the behavior of gopls to be modified by hooks.
    70  	PrepareOptions func(*settings.Options)
    71  
    72  	// editFlags holds flags that control how file edit operations
    73  	// are applied, in particular when the server makes an ApplyEdits
    74  	// downcall to the client. Present only for commands that apply edits.
    75  	editFlags *EditFlags
    76  }
    77  
    78  // EditFlags defines flags common to {fix,format,imports,rename}
    79  // that control how edits are applied to the client's files.
    80  //
    81  // The type is exported for flag reflection.
    82  //
    83  // The -write, -diff, and -list flags are orthogonal but any
    84  // of them suppresses the default behavior, which is to print
    85  // the edited file contents.
    86  type EditFlags struct {
    87  	Write    bool `flag:"w,write" help:"write edited content to source files"`
    88  	Preserve bool `flag:"preserve" help:"with -write, make copies of original files"`
    89  	Diff     bool `flag:"d,diff" help:"display diffs instead of edited file content"`
    90  	List     bool `flag:"l,list" help:"display names of edited files"`
    91  }
    92  
    93  func (app *Application) verbose() bool {
    94  	return app.Verbose || app.VeryVerbose
    95  }
    96  
    97  // New returns a new Application ready to run.
    98  func New(options func(*settings.Options)) *Application {
    99  	app := &Application{
   100  		options: options,
   101  		OCAgent: "off", //TODO: Remove this line to default the exporter to on
   102  
   103  		Serve: Serve{
   104  			RemoteListenTimeout: 1 * time.Minute,
   105  		},
   106  	}
   107  	app.Serve.app = app
   108  	return app
   109  }
   110  
   111  // Name implements tool.Application returning the binary name.
   112  func (app *Application) Name() string { return "gopls" }
   113  
   114  // Usage implements tool.Application returning empty extra argument usage.
   115  func (app *Application) Usage() string { return "" }
   116  
   117  // ShortHelp implements tool.Application returning the main binary help.
   118  func (app *Application) ShortHelp() string {
   119  	return ""
   120  }
   121  
   122  // DetailedHelp implements tool.Application returning the main binary help.
   123  // This includes the short help for all the sub commands.
   124  func (app *Application) DetailedHelp(f *flag.FlagSet) {
   125  	w := tabwriter.NewWriter(f.Output(), 0, 0, 2, ' ', 0)
   126  	defer w.Flush()
   127  
   128  	fmt.Fprint(w, `
   129  gopls is a Go language server.
   130  
   131  It is typically used with an editor to provide language features. When no
   132  command is specified, gopls will default to the 'serve' command. The language
   133  features can also be accessed via the gopls command-line interface.
   134  
   135  Usage:
   136    gopls help [<subject>]
   137  
   138  Command:
   139  `)
   140  	fmt.Fprint(w, "\nMain\t\n")
   141  	for _, c := range app.mainCommands() {
   142  		fmt.Fprintf(w, "  %s\t%s\n", c.Name(), c.ShortHelp())
   143  	}
   144  	fmt.Fprint(w, "\t\nFeatures\t\n")
   145  	for _, c := range app.featureCommands() {
   146  		fmt.Fprintf(w, "  %s\t%s\n", c.Name(), c.ShortHelp())
   147  	}
   148  	if app.verbose() {
   149  		fmt.Fprint(w, "\t\nInternal Use Only\t\n")
   150  		for _, c := range app.internalCommands() {
   151  			fmt.Fprintf(w, "  %s\t%s\n", c.Name(), c.ShortHelp())
   152  		}
   153  	}
   154  	fmt.Fprint(w, "\nflags:\n")
   155  	printFlagDefaults(f)
   156  }
   157  
   158  // this is a slightly modified version of flag.PrintDefaults to give us control
   159  func printFlagDefaults(s *flag.FlagSet) {
   160  	var flags [][]*flag.Flag
   161  	seen := map[flag.Value]int{}
   162  	s.VisitAll(func(f *flag.Flag) {
   163  		if i, ok := seen[f.Value]; !ok {
   164  			seen[f.Value] = len(flags)
   165  			flags = append(flags, []*flag.Flag{f})
   166  		} else {
   167  			flags[i] = append(flags[i], f)
   168  		}
   169  	})
   170  	for _, entry := range flags {
   171  		sort.SliceStable(entry, func(i, j int) bool {
   172  			return len(entry[i].Name) < len(entry[j].Name)
   173  		})
   174  		var b strings.Builder
   175  		for i, f := range entry {
   176  			switch i {
   177  			case 0:
   178  				b.WriteString("  -")
   179  			default:
   180  				b.WriteString(",-")
   181  			}
   182  			b.WriteString(f.Name)
   183  		}
   184  
   185  		f := entry[0]
   186  		name, usage := flag.UnquoteUsage(f)
   187  		if len(name) > 0 {
   188  			b.WriteString("=")
   189  			b.WriteString(name)
   190  		}
   191  		// Boolean flags of one ASCII letter are so common we
   192  		// treat them specially, putting their usage on the same line.
   193  		if b.Len() <= 4 { // space, space, '-', 'x'.
   194  			b.WriteString("\t")
   195  		} else {
   196  			// Four spaces before the tab triggers good alignment
   197  			// for both 4- and 8-space tab stops.
   198  			b.WriteString("\n    \t")
   199  		}
   200  		b.WriteString(strings.ReplaceAll(usage, "\n", "\n    \t"))
   201  		if !isZeroValue(f, f.DefValue) {
   202  			if reflect.TypeOf(f.Value).Elem().Name() == "stringValue" {
   203  				fmt.Fprintf(&b, " (default %q)", f.DefValue)
   204  			} else {
   205  				fmt.Fprintf(&b, " (default %v)", f.DefValue)
   206  			}
   207  		}
   208  		fmt.Fprint(s.Output(), b.String(), "\n")
   209  	}
   210  }
   211  
   212  // isZeroValue is copied from the flags package
   213  func isZeroValue(f *flag.Flag, value string) bool {
   214  	// Build a zero value of the flag's Value type, and see if the
   215  	// result of calling its String method equals the value passed in.
   216  	// This works unless the Value type is itself an interface type.
   217  	typ := reflect.TypeOf(f.Value)
   218  	var z reflect.Value
   219  	if typ.Kind() == reflect.Ptr {
   220  		z = reflect.New(typ.Elem())
   221  	} else {
   222  		z = reflect.Zero(typ)
   223  	}
   224  	return value == z.Interface().(flag.Value).String()
   225  }
   226  
   227  // Run takes the args after top level flag processing, and invokes the correct
   228  // sub command as specified by the first argument.
   229  // If no arguments are passed it will invoke the server sub command, as a
   230  // temporary measure for compatibility.
   231  func (app *Application) Run(ctx context.Context, args ...string) error {
   232  	// In the category of "things we can do while waiting for the Go command":
   233  	// Pre-initialize the filecache, which takes ~50ms to hash the gopls
   234  	// executable, and immediately runs a gc.
   235  	filecache.Start()
   236  
   237  	ctx = debug.WithInstance(ctx, app.OCAgent)
   238  	if len(args) == 0 {
   239  		s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
   240  		return tool.Run(ctx, s, &app.Serve, args)
   241  	}
   242  	command, args := args[0], args[1:]
   243  	for _, c := range app.Commands() {
   244  		if c.Name() == command {
   245  			s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
   246  			return tool.Run(ctx, s, c, args)
   247  		}
   248  	}
   249  	return tool.CommandLineErrorf("Unknown command %v", command)
   250  }
   251  
   252  // Commands returns the set of commands supported by the gopls tool on the
   253  // command line.
   254  // The command is specified by the first non flag argument.
   255  func (app *Application) Commands() []tool.Application {
   256  	var commands []tool.Application
   257  	commands = append(commands, app.mainCommands()...)
   258  	commands = append(commands, app.featureCommands()...)
   259  	commands = append(commands, app.internalCommands()...)
   260  	return commands
   261  }
   262  
   263  func (app *Application) mainCommands() []tool.Application {
   264  	return []tool.Application{
   265  		&app.Serve,
   266  		&version{app: app},
   267  		&bug{app: app},
   268  		&help{app: app},
   269  		&apiJSON{app: app},
   270  		&licenses{app: app},
   271  	}
   272  }
   273  
   274  func (app *Application) internalCommands() []tool.Application {
   275  	return []tool.Application{
   276  		&vulncheck{app: app},
   277  	}
   278  }
   279  
   280  func (app *Application) featureCommands() []tool.Application {
   281  	return []tool.Application{
   282  		&callHierarchy{app: app},
   283  		&check{app: app},
   284  		&codelens{app: app},
   285  		&definition{app: app},
   286  		&execute{app: app},
   287  		&foldingRanges{app: app},
   288  		&format{app: app},
   289  		&highlight{app: app},
   290  		&implementation{app: app},
   291  		&imports{app: app},
   292  		newRemote(app, ""),
   293  		newRemote(app, "inspect"),
   294  		&links{app: app},
   295  		&prepareRename{app: app},
   296  		&references{app: app},
   297  		&rename{app: app},
   298  		&semtok{app: app},
   299  		&signature{app: app},
   300  		&stats{app: app},
   301  		&suggestedFix{app: app},
   302  		&symbols{app: app},
   303  
   304  		&workspaceSymbol{app: app},
   305  	}
   306  }
   307  
   308  var (
   309  	internalMu          sync.Mutex
   310  	internalConnections = make(map[string]*connection)
   311  )
   312  
   313  // connect creates and initializes a new in-process gopls session.
   314  //
   315  // If onProgress is set, it is called for each new progress notification.
   316  func (app *Application) connect(ctx context.Context, onProgress func(*protocol.ProgressParams)) (*connection, error) {
   317  	switch {
   318  	case app.Remote == "":
   319  		client := newClient(app, onProgress)
   320  		options := settings.DefaultOptions(app.options)
   321  		server := server.New(cache.NewSession(ctx, cache.New(nil)), client, options)
   322  		conn := newConnection(server, client)
   323  		if err := conn.initialize(protocol.WithClient(ctx, client), app.options); err != nil {
   324  			return nil, err
   325  		}
   326  		return conn, nil
   327  
   328  	default:
   329  		return app.connectRemote(ctx, app.Remote)
   330  	}
   331  }
   332  
   333  func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) {
   334  	conn, err := lsprpc.ConnectToRemote(ctx, remote)
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  	stream := jsonrpc2.NewHeaderStream(conn)
   339  	cc := jsonrpc2.NewConn(stream)
   340  	server := protocol.ServerDispatcher(cc)
   341  	client := newClient(app, nil)
   342  	connection := newConnection(server, client)
   343  	ctx = protocol.WithClient(ctx, connection.client)
   344  	cc.Go(ctx,
   345  		protocol.Handlers(
   346  			protocol.ClientHandler(client, jsonrpc2.MethodNotFound)))
   347  	return connection, connection.initialize(ctx, app.options)
   348  }
   349  
   350  func (c *connection) initialize(ctx context.Context, options func(*settings.Options)) error {
   351  	wd, err := os.Getwd()
   352  	if err != nil {
   353  		return fmt.Errorf("finding workdir: %v", err)
   354  	}
   355  	params := &protocol.ParamInitialize{}
   356  	params.RootURI = protocol.URIFromPath(wd)
   357  	params.Capabilities.Workspace.Configuration = true
   358  
   359  	// Make sure to respect configured options when sending initialize request.
   360  	opts := settings.DefaultOptions(options)
   361  	// If you add an additional option here, you must update the map key in connect.
   362  	params.Capabilities.TextDocument.Hover = &protocol.HoverClientCapabilities{
   363  		ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
   364  	}
   365  	params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport
   366  	params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{}
   367  	params.Capabilities.TextDocument.SemanticTokens.Formats = []protocol.TokenFormat{"relative"}
   368  	params.Capabilities.TextDocument.SemanticTokens.Requests.Range = &protocol.Or_ClientSemanticTokensRequestOptions_range{Value: true}
   369  	//params.Capabilities.TextDocument.SemanticTokens.Requests.Range.Value = true
   370  	params.Capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true}
   371  	params.Capabilities.TextDocument.SemanticTokens.TokenTypes = protocol.SemanticTypes()
   372  	params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = protocol.SemanticModifiers()
   373  
   374  	// If the subcommand has registered a progress handler, report the progress
   375  	// capability.
   376  	if c.client.onProgress != nil {
   377  		params.Capabilities.Window.WorkDoneProgress = true
   378  	}
   379  
   380  	params.InitializationOptions = map[string]interface{}{
   381  		"symbolMatcher": string(opts.SymbolMatcher),
   382  	}
   383  	if _, err := c.Server.Initialize(ctx, params); err != nil {
   384  		return err
   385  	}
   386  	if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
   387  		return err
   388  	}
   389  	return nil
   390  }
   391  
   392  type connection struct {
   393  	protocol.Server
   394  	client *cmdClient
   395  }
   396  
   397  // cmdClient defines the protocol.Client interface behavior of the gopls CLI tool.
   398  type cmdClient struct {
   399  	app        *Application
   400  	onProgress func(*protocol.ProgressParams)
   401  
   402  	filesMu sync.Mutex // guards files map and each cmdFile.diagnostics
   403  	files   map[protocol.DocumentURI]*cmdFile
   404  }
   405  
   406  type cmdFile struct {
   407  	uri         protocol.DocumentURI
   408  	mapper      *protocol.Mapper
   409  	err         error
   410  	diagnostics []protocol.Diagnostic
   411  }
   412  
   413  func newClient(app *Application, onProgress func(*protocol.ProgressParams)) *cmdClient {
   414  	return &cmdClient{
   415  		app:        app,
   416  		onProgress: onProgress,
   417  		files:      make(map[protocol.DocumentURI]*cmdFile),
   418  	}
   419  }
   420  
   421  func newConnection(server protocol.Server, client *cmdClient) *connection {
   422  	return &connection{
   423  		Server: server,
   424  		client: client,
   425  	}
   426  }
   427  
   428  func (c *cmdClient) CodeLensRefresh(context.Context) error { return nil }
   429  
   430  func (c *cmdClient) FoldingRangeRefresh(context.Context) error { return nil }
   431  
   432  func (c *cmdClient) LogTrace(context.Context, *protocol.LogTraceParams) error { return nil }
   433  
   434  func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error {
   435  	fmt.Fprintf(os.Stderr, "%s: %s\n", p.Type, p.Message)
   436  	return nil
   437  }
   438  
   439  func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
   440  	return nil, nil
   441  }
   442  
   443  func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
   444  	// This logic causes server logging to be double-prefixed with a timestamp.
   445  	//     2023/11/08 10:50:21 Error:2023/11/08 10:50:21 <actual message>
   446  	// TODO(adonovan): print just p.Message, plus a newline if needed?
   447  	switch p.Type {
   448  	case protocol.Error:
   449  		log.Print("Error:", p.Message)
   450  	case protocol.Warning:
   451  		log.Print("Warning:", p.Message)
   452  	case protocol.Info:
   453  		if c.app.verbose() {
   454  			log.Print("Info:", p.Message)
   455  		}
   456  	case protocol.Log:
   457  		if c.app.verbose() {
   458  			log.Print("Log:", p.Message)
   459  		}
   460  	default:
   461  		if c.app.verbose() {
   462  			log.Print(p.Message)
   463  		}
   464  	}
   465  	return nil
   466  }
   467  
   468  func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
   469  
   470  func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
   471  	return nil
   472  }
   473  
   474  func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
   475  	return nil
   476  }
   477  
   478  func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
   479  	return nil, nil
   480  }
   481  
   482  func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) {
   483  	results := make([]interface{}, len(p.Items))
   484  	for i, item := range p.Items {
   485  		if item.Section != "gopls" {
   486  			continue
   487  		}
   488  		m := map[string]interface{}{
   489  			"analyses": map[string]any{
   490  				"fillreturns":    true,
   491  				"nonewvars":      true,
   492  				"noresultvalues": true,
   493  				"undeclaredname": true,
   494  			},
   495  		}
   496  		if c.app.VeryVerbose {
   497  			m["verboseOutput"] = true
   498  		}
   499  		results[i] = m
   500  	}
   501  	return results, nil
   502  }
   503  
   504  func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) {
   505  	if err := c.applyWorkspaceEdit(&p.Edit); err != nil {
   506  		return &protocol.ApplyWorkspaceEditResult{FailureReason: err.Error()}, nil
   507  	}
   508  	return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil
   509  }
   510  
   511  // applyWorkspaceEdit applies a complete WorkspaceEdit to the client's
   512  // files, honoring the preferred edit mode specified by cli.app.editMode.
   513  // (Used by rename and by ApplyEdit downcalls.)
   514  func (cli *cmdClient) applyWorkspaceEdit(edit *protocol.WorkspaceEdit) error {
   515  	var orderedURIs []protocol.DocumentURI
   516  	edits := map[protocol.DocumentURI][]protocol.TextEdit{}
   517  	for _, c := range edit.DocumentChanges {
   518  		if c.TextDocumentEdit != nil {
   519  			uri := c.TextDocumentEdit.TextDocument.URI
   520  			edits[uri] = append(edits[uri], protocol.AsTextEdits(c.TextDocumentEdit.Edits)...)
   521  			orderedURIs = append(orderedURIs, uri)
   522  		}
   523  		if c.RenameFile != nil {
   524  			return fmt.Errorf("client does not support file renaming (%s -> %s)",
   525  				c.RenameFile.OldURI,
   526  				c.RenameFile.NewURI)
   527  		}
   528  	}
   529  	sortSlice(orderedURIs)
   530  	for _, uri := range orderedURIs {
   531  		f := cli.openFile(uri)
   532  		if f.err != nil {
   533  			return f.err
   534  		}
   535  		if err := applyTextEdits(f.mapper, edits[uri], cli.app.editFlags); err != nil {
   536  			return err
   537  		}
   538  	}
   539  	return nil
   540  }
   541  
   542  func sortSlice[T constraints.Ordered](slice []T) {
   543  	sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
   544  }
   545  
   546  // applyTextEdits applies a list of edits to the mapper file content,
   547  // using the preferred edit mode. It is a no-op if there are no edits.
   548  func applyTextEdits(mapper *protocol.Mapper, edits []protocol.TextEdit, flags *EditFlags) error {
   549  	if len(edits) == 0 {
   550  		return nil
   551  	}
   552  	newContent, renameEdits, err := protocol.ApplyEdits(mapper, edits)
   553  	if err != nil {
   554  		return err
   555  	}
   556  
   557  	filename := mapper.URI.Path()
   558  
   559  	if flags.List {
   560  		fmt.Println(filename)
   561  	}
   562  
   563  	if flags.Write {
   564  		if flags.Preserve {
   565  			if err := os.Rename(filename, filename+".orig"); err != nil {
   566  				return err
   567  			}
   568  		}
   569  		if err := os.WriteFile(filename, newContent, 0644); err != nil {
   570  			return err
   571  		}
   572  	}
   573  
   574  	if flags.Diff {
   575  		unified, err := diff.ToUnified(filename+".orig", filename, string(mapper.Content), renameEdits, diff.DefaultContextLines)
   576  		if err != nil {
   577  			return err
   578  		}
   579  		fmt.Print(unified)
   580  	}
   581  
   582  	// No flags: just print edited file content.
   583  	// TODO(adonovan): how is this ever useful with multiple files?
   584  	if !(flags.List || flags.Write || flags.Diff) {
   585  		os.Stdout.Write(newContent)
   586  	}
   587  
   588  	return nil
   589  }
   590  
   591  func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
   592  	// Don't worry about diagnostics without versions.
   593  	if p.Version == 0 {
   594  		return nil
   595  	}
   596  
   597  	c.filesMu.Lock()
   598  	defer c.filesMu.Unlock()
   599  
   600  	file := c.getFile(p.URI)
   601  	file.diagnostics = append(file.diagnostics, p.Diagnostics...)
   602  
   603  	// Perform a crude in-place deduplication.
   604  	// TODO(golang/go#60122): replace the gopls.diagnose_files
   605  	// command with support for textDocument/diagnostic,
   606  	// so that we don't need to do this de-duplication.
   607  	type key [6]interface{}
   608  	seen := make(map[key]bool)
   609  	out := file.diagnostics[:0]
   610  	for _, d := range file.diagnostics {
   611  		var codeHref string
   612  		if desc := d.CodeDescription; desc != nil {
   613  			codeHref = desc.Href
   614  		}
   615  		k := key{d.Range, d.Severity, d.Code, codeHref, d.Source, d.Message}
   616  		if !seen[k] {
   617  			seen[k] = true
   618  			out = append(out, d)
   619  		}
   620  	}
   621  	file.diagnostics = out
   622  
   623  	return nil
   624  }
   625  
   626  func (c *cmdClient) Progress(_ context.Context, params *protocol.ProgressParams) error {
   627  	if c.onProgress != nil {
   628  		c.onProgress(params)
   629  	}
   630  	return nil
   631  }
   632  
   633  func (c *cmdClient) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) {
   634  	var success bool
   635  	if params.External {
   636  		// Open URI in external browser.
   637  		success = browser.Open(params.URI)
   638  	} else {
   639  		// Open file in editor, optionally taking focus and selecting a range.
   640  		// (cmdClient has no editor. Should it fork+exec $EDITOR?)
   641  		log.Printf("Server requested that client editor open %q (takeFocus=%t, selection=%+v)",
   642  			params.URI, params.TakeFocus, params.Selection)
   643  		success = true
   644  	}
   645  	return &protocol.ShowDocumentResult{Success: success}, nil
   646  }
   647  
   648  func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
   649  	return nil
   650  }
   651  
   652  func (c *cmdClient) DiagnosticRefresh(context.Context) error {
   653  	return nil
   654  }
   655  
   656  func (c *cmdClient) InlayHintRefresh(context.Context) error {
   657  	return nil
   658  }
   659  
   660  func (c *cmdClient) SemanticTokensRefresh(context.Context) error {
   661  	return nil
   662  }
   663  
   664  func (c *cmdClient) InlineValueRefresh(context.Context) error {
   665  	return nil
   666  }
   667  
   668  func (c *cmdClient) getFile(uri protocol.DocumentURI) *cmdFile {
   669  	file, found := c.files[uri]
   670  	if !found || file.err != nil {
   671  		file = &cmdFile{
   672  			uri: uri,
   673  		}
   674  		c.files[uri] = file
   675  	}
   676  	if file.mapper == nil {
   677  		content, err := os.ReadFile(uri.Path())
   678  		if err != nil {
   679  			file.err = fmt.Errorf("getFile: %v: %v", uri, err)
   680  			return file
   681  		}
   682  		file.mapper = protocol.NewMapper(uri, content)
   683  	}
   684  	return file
   685  }
   686  
   687  func (c *cmdClient) openFile(uri protocol.DocumentURI) *cmdFile {
   688  	c.filesMu.Lock()
   689  	defer c.filesMu.Unlock()
   690  	return c.getFile(uri)
   691  }
   692  
   693  // TODO(adonovan): provide convenience helpers to:
   694  // - map a (URI, protocol.Range) to a MappedRange;
   695  // - parse a command-line argument to a MappedRange.
   696  func (c *connection) openFile(ctx context.Context, uri protocol.DocumentURI) (*cmdFile, error) {
   697  	file := c.client.openFile(uri)
   698  	if file.err != nil {
   699  		return nil, file.err
   700  	}
   701  
   702  	p := &protocol.DidOpenTextDocumentParams{
   703  		TextDocument: protocol.TextDocumentItem{
   704  			URI:        uri,
   705  			LanguageID: "go",
   706  			Version:    1,
   707  			Text:       string(file.mapper.Content),
   708  		},
   709  	}
   710  	if err := c.Server.DidOpen(ctx, p); err != nil {
   711  		// TODO(adonovan): is this assignment concurrency safe?
   712  		file.err = fmt.Errorf("%v: %v", uri, err)
   713  		return nil, file.err
   714  	}
   715  	return file, nil
   716  }
   717  
   718  func (c *connection) semanticTokens(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
   719  	// use range to avoid limits on full
   720  	resp, err := c.Server.SemanticTokensRange(ctx, p)
   721  	if err != nil {
   722  		return nil, err
   723  	}
   724  	return resp, nil
   725  }
   726  
   727  func (c *connection) diagnoseFiles(ctx context.Context, files []protocol.DocumentURI) error {
   728  	cmd, err := command.NewDiagnoseFilesCommand("Diagnose files", command.DiagnoseFilesArgs{
   729  		Files: files,
   730  	})
   731  	if err != nil {
   732  		return err
   733  	}
   734  	_, err = c.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{
   735  		Command:   cmd.Command,
   736  		Arguments: cmd.Arguments,
   737  	})
   738  	return err
   739  }
   740  
   741  func (c *connection) terminate(ctx context.Context) {
   742  	//TODO: do we need to handle errors on these calls?
   743  	c.Shutdown(ctx)
   744  	//TODO: right now calling exit terminates the process, we should rethink that
   745  	//server.Exit(ctx)
   746  }
   747  
   748  // Implement io.Closer.
   749  func (c *cmdClient) Close() error {
   750  	return nil
   751  }
   752  
   753  // -- conversions to span (UTF-8) domain --
   754  
   755  // locationSpan converts a protocol (UTF-16) Location to a (UTF-8) span.
   756  // Precondition: the URIs of Location and Mapper match.
   757  func (f *cmdFile) locationSpan(loc protocol.Location) (span, error) {
   758  	// TODO(adonovan): check that l.URI matches m.URI.
   759  	return f.rangeSpan(loc.Range)
   760  }
   761  
   762  // rangeSpan converts a protocol (UTF-16) range to a (UTF-8) span.
   763  // The resulting span has valid Positions and Offsets.
   764  func (f *cmdFile) rangeSpan(r protocol.Range) (span, error) {
   765  	start, end, err := f.mapper.RangeOffsets(r)
   766  	if err != nil {
   767  		return span{}, err
   768  	}
   769  	return f.offsetSpan(start, end)
   770  }
   771  
   772  // offsetSpan converts a byte-offset interval to a (UTF-8) span.
   773  // The resulting span contains line, column, and offset information.
   774  func (f *cmdFile) offsetSpan(start, end int) (span, error) {
   775  	if start > end {
   776  		return span{}, fmt.Errorf("start offset (%d) > end (%d)", start, end)
   777  	}
   778  	startPoint, err := offsetPoint(f.mapper, start)
   779  	if err != nil {
   780  		return span{}, fmt.Errorf("start: %v", err)
   781  	}
   782  	endPoint, err := offsetPoint(f.mapper, end)
   783  	if err != nil {
   784  		return span{}, fmt.Errorf("end: %v", err)
   785  	}
   786  	return newSpan(f.mapper.URI, startPoint, endPoint), nil
   787  }
   788  
   789  // offsetPoint converts a byte offset to a span (UTF-8) point.
   790  // The resulting point contains line, column, and offset information.
   791  func offsetPoint(m *protocol.Mapper, offset int) (point, error) {
   792  	if !(0 <= offset && offset <= len(m.Content)) {
   793  		return point{}, fmt.Errorf("invalid offset %d (want 0-%d)", offset, len(m.Content))
   794  	}
   795  	line, col8 := m.OffsetLineCol8(offset)
   796  	return newPoint(line, col8, offset), nil
   797  }
   798  
   799  // -- conversions from span (UTF-8) domain --
   800  
   801  // spanLocation converts a (UTF-8) span to a protocol (UTF-16) range.
   802  // Precondition: the URIs of spanLocation and Mapper match.
   803  func (f *cmdFile) spanLocation(s span) (protocol.Location, error) {
   804  	rng, err := f.spanRange(s)
   805  	if err != nil {
   806  		return protocol.Location{}, err
   807  	}
   808  	return f.mapper.RangeLocation(rng), nil
   809  }
   810  
   811  // spanRange converts a (UTF-8) span to a protocol (UTF-16) range.
   812  // Precondition: the URIs of span and Mapper match.
   813  func (f *cmdFile) spanRange(s span) (protocol.Range, error) {
   814  	// Assert that we aren't using the wrong mapper.
   815  	// We check only the base name, and case insensitively,
   816  	// because we can't assume clean paths, no symbolic links,
   817  	// case-sensitive directories. The authoritative answer
   818  	// requires querying the file system, and we don't want
   819  	// to do that.
   820  	if !strings.EqualFold(filepath.Base(string(f.mapper.URI)), filepath.Base(string(s.URI()))) {
   821  		return protocol.Range{}, bugpkg.Errorf("mapper is for file %q instead of %q", f.mapper.URI, s.URI())
   822  	}
   823  	start, err := pointPosition(f.mapper, s.Start())
   824  	if err != nil {
   825  		return protocol.Range{}, fmt.Errorf("start: %w", err)
   826  	}
   827  	end, err := pointPosition(f.mapper, s.End())
   828  	if err != nil {
   829  		return protocol.Range{}, fmt.Errorf("end: %w", err)
   830  	}
   831  	return protocol.Range{Start: start, End: end}, nil
   832  }
   833  
   834  // pointPosition converts a valid span (UTF-8) point to a protocol (UTF-16) position.
   835  func pointPosition(m *protocol.Mapper, p point) (protocol.Position, error) {
   836  	if p.HasPosition() {
   837  		return m.LineCol8Position(p.Line(), p.Column())
   838  	}
   839  	if p.HasOffset() {
   840  		return m.OffsetPosition(p.Offset())
   841  	}
   842  	return protocol.Position{}, fmt.Errorf("point has neither offset nor line/column")
   843  }