cuelang.org/go@v0.10.1/internal/golangorgx/gopls/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  	"cuelang.org/go/internal/golangorgx/gopls/cache"
    25  	"cuelang.org/go/internal/golangorgx/gopls/debug"
    26  	"cuelang.org/go/internal/golangorgx/gopls/filecache"
    27  	"cuelang.org/go/internal/golangorgx/gopls/lsprpc"
    28  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    29  	"cuelang.org/go/internal/golangorgx/gopls/protocol/command"
    30  	"cuelang.org/go/internal/golangorgx/gopls/server"
    31  	"cuelang.org/go/internal/golangorgx/gopls/settings"
    32  	"cuelang.org/go/internal/golangorgx/gopls/util/browser"
    33  	bugpkg "cuelang.org/go/internal/golangorgx/gopls/util/bug"
    34  	"cuelang.org/go/internal/golangorgx/gopls/util/constraints"
    35  	"cuelang.org/go/internal/golangorgx/tools/diff"
    36  	"cuelang.org/go/internal/golangorgx/tools/jsonrpc2"
    37  	"cuelang.org/go/internal/golangorgx/tools/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  }
   277  
   278  func (app *Application) featureCommands() []tool.Application {
   279  	return []tool.Application{
   280  		&format{app: app},
   281  		newRemote(app, ""),
   282  		newRemote(app, "inspect"),
   283  	}
   284  }
   285  
   286  var (
   287  	internalMu          sync.Mutex
   288  	internalConnections = make(map[string]*connection)
   289  )
   290  
   291  // connect creates and initializes a new in-process gopls session.
   292  //
   293  // If onProgress is set, it is called for each new progress notification.
   294  func (app *Application) connect(ctx context.Context, onProgress func(*protocol.ProgressParams)) (*connection, error) {
   295  	switch {
   296  	case app.Remote == "":
   297  		client := newClient(app, onProgress)
   298  		options := settings.DefaultOptions(app.options)
   299  		server := server.New(cache.NewSession(ctx, cache.New(nil)), client, options)
   300  		conn := newConnection(server, client)
   301  		if err := conn.initialize(protocol.WithClient(ctx, client), app.options); err != nil {
   302  			return nil, err
   303  		}
   304  		return conn, nil
   305  
   306  	default:
   307  		return app.connectRemote(ctx, app.Remote)
   308  	}
   309  }
   310  
   311  func (app *Application) connectRemote(ctx context.Context, remote string) (*connection, error) {
   312  	conn, err := lsprpc.ConnectToRemote(ctx, remote)
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  	stream := jsonrpc2.NewHeaderStream(conn)
   317  	cc := jsonrpc2.NewConn(stream)
   318  	server := protocol.ServerDispatcher(cc)
   319  	client := newClient(app, nil)
   320  	connection := newConnection(server, client)
   321  	ctx = protocol.WithClient(ctx, connection.client)
   322  	cc.Go(ctx,
   323  		protocol.Handlers(
   324  			protocol.ClientHandler(client, jsonrpc2.MethodNotFound)))
   325  	return connection, connection.initialize(ctx, app.options)
   326  }
   327  
   328  func (c *connection) initialize(ctx context.Context, options func(*settings.Options)) error {
   329  	wd, err := os.Getwd()
   330  	if err != nil {
   331  		return fmt.Errorf("finding workdir: %v", err)
   332  	}
   333  	params := &protocol.ParamInitialize{}
   334  	params.RootURI = protocol.URIFromPath(wd)
   335  	params.Capabilities.Workspace.Configuration = true
   336  
   337  	// Make sure to respect configured options when sending initialize request.
   338  	opts := settings.DefaultOptions(options)
   339  	// If you add an additional option here, you must update the map key in connect.
   340  	params.Capabilities.TextDocument.Hover = &protocol.HoverClientCapabilities{
   341  		ContentFormat: []protocol.MarkupKind{opts.PreferredContentFormat},
   342  	}
   343  	params.Capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = opts.HierarchicalDocumentSymbolSupport
   344  	params.Capabilities.TextDocument.SemanticTokens = protocol.SemanticTokensClientCapabilities{}
   345  	params.Capabilities.TextDocument.SemanticTokens.Formats = []protocol.TokenFormat{"relative"}
   346  	params.Capabilities.TextDocument.SemanticTokens.Requests.Range = &protocol.Or_ClientSemanticTokensRequestOptions_range{Value: true}
   347  	//params.Capabilities.TextDocument.SemanticTokens.Requests.Range.Value = true
   348  	params.Capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true}
   349  	params.Capabilities.TextDocument.SemanticTokens.TokenTypes = protocol.SemanticTypes()
   350  	params.Capabilities.TextDocument.SemanticTokens.TokenModifiers = protocol.SemanticModifiers()
   351  
   352  	// If the subcommand has registered a progress handler, report the progress
   353  	// capability.
   354  	if c.client.onProgress != nil {
   355  		params.Capabilities.Window.WorkDoneProgress = true
   356  	}
   357  
   358  	params.InitializationOptions = map[string]interface{}{
   359  		"symbolMatcher": string(opts.SymbolMatcher),
   360  	}
   361  	if _, err := c.Server.Initialize(ctx, params); err != nil {
   362  		return err
   363  	}
   364  	if err := c.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
   365  		return err
   366  	}
   367  	return nil
   368  }
   369  
   370  type connection struct {
   371  	protocol.Server
   372  	client *cmdClient
   373  }
   374  
   375  // cmdClient defines the protocol.Client interface behavior of the gopls CLI tool.
   376  type cmdClient struct {
   377  	app        *Application
   378  	onProgress func(*protocol.ProgressParams)
   379  
   380  	filesMu sync.Mutex // guards files map and each cmdFile.diagnostics
   381  	files   map[protocol.DocumentURI]*cmdFile
   382  }
   383  
   384  type cmdFile struct {
   385  	uri         protocol.DocumentURI
   386  	mapper      *protocol.Mapper
   387  	err         error
   388  	diagnostics []protocol.Diagnostic
   389  }
   390  
   391  func newClient(app *Application, onProgress func(*protocol.ProgressParams)) *cmdClient {
   392  	return &cmdClient{
   393  		app:        app,
   394  		onProgress: onProgress,
   395  		files:      make(map[protocol.DocumentURI]*cmdFile),
   396  	}
   397  }
   398  
   399  func newConnection(server protocol.Server, client *cmdClient) *connection {
   400  	return &connection{
   401  		Server: server,
   402  		client: client,
   403  	}
   404  }
   405  
   406  func (c *cmdClient) CodeLensRefresh(context.Context) error { return nil }
   407  
   408  func (c *cmdClient) FoldingRangeRefresh(context.Context) error { return nil }
   409  
   410  func (c *cmdClient) LogTrace(context.Context, *protocol.LogTraceParams) error { return nil }
   411  
   412  func (c *cmdClient) ShowMessage(ctx context.Context, p *protocol.ShowMessageParams) error {
   413  	fmt.Fprintf(os.Stderr, "%s: %s\n", p.Type, p.Message)
   414  	return nil
   415  }
   416  
   417  func (c *cmdClient) ShowMessageRequest(ctx context.Context, p *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error) {
   418  	return nil, nil
   419  }
   420  
   421  func (c *cmdClient) LogMessage(ctx context.Context, p *protocol.LogMessageParams) error {
   422  	// This logic causes server logging to be double-prefixed with a timestamp.
   423  	//     2023/11/08 10:50:21 Error:2023/11/08 10:50:21 <actual message>
   424  	// TODO(adonovan): print just p.Message, plus a newline if needed?
   425  	switch p.Type {
   426  	case protocol.Error:
   427  		log.Print("Error:", p.Message)
   428  	case protocol.Warning:
   429  		log.Print("Warning:", p.Message)
   430  	case protocol.Info:
   431  		if c.app.verbose() {
   432  			log.Print("Info:", p.Message)
   433  		}
   434  	case protocol.Log:
   435  		if c.app.verbose() {
   436  			log.Print("Log:", p.Message)
   437  		}
   438  	default:
   439  		if c.app.verbose() {
   440  			log.Print(p.Message)
   441  		}
   442  	}
   443  	return nil
   444  }
   445  
   446  func (c *cmdClient) Event(ctx context.Context, t *interface{}) error { return nil }
   447  
   448  func (c *cmdClient) RegisterCapability(ctx context.Context, p *protocol.RegistrationParams) error {
   449  	return nil
   450  }
   451  
   452  func (c *cmdClient) UnregisterCapability(ctx context.Context, p *protocol.UnregistrationParams) error {
   453  	return nil
   454  }
   455  
   456  func (c *cmdClient) WorkspaceFolders(ctx context.Context) ([]protocol.WorkspaceFolder, error) {
   457  	return nil, nil
   458  }
   459  
   460  func (c *cmdClient) Configuration(ctx context.Context, p *protocol.ParamConfiguration) ([]interface{}, error) {
   461  	results := make([]interface{}, len(p.Items))
   462  	for i, item := range p.Items {
   463  		if item.Section != "gopls" {
   464  			continue
   465  		}
   466  		m := map[string]interface{}{
   467  			"analyses": map[string]any{
   468  				"fillreturns":    true,
   469  				"nonewvars":      true,
   470  				"noresultvalues": true,
   471  				"undeclaredname": true,
   472  			},
   473  		}
   474  		if c.app.VeryVerbose {
   475  			m["verboseOutput"] = true
   476  		}
   477  		results[i] = m
   478  	}
   479  	return results, nil
   480  }
   481  
   482  func (c *cmdClient) ApplyEdit(ctx context.Context, p *protocol.ApplyWorkspaceEditParams) (*protocol.ApplyWorkspaceEditResult, error) {
   483  	if err := c.applyWorkspaceEdit(&p.Edit); err != nil {
   484  		return &protocol.ApplyWorkspaceEditResult{FailureReason: err.Error()}, nil
   485  	}
   486  	return &protocol.ApplyWorkspaceEditResult{Applied: true}, nil
   487  }
   488  
   489  // applyWorkspaceEdit applies a complete WorkspaceEdit to the client's
   490  // files, honoring the preferred edit mode specified by cli.app.editMode.
   491  // (Used by rename and by ApplyEdit downcalls.)
   492  func (cli *cmdClient) applyWorkspaceEdit(edit *protocol.WorkspaceEdit) error {
   493  	var orderedURIs []protocol.DocumentURI
   494  	edits := map[protocol.DocumentURI][]protocol.TextEdit{}
   495  	for _, c := range edit.DocumentChanges {
   496  		if c.TextDocumentEdit != nil {
   497  			uri := c.TextDocumentEdit.TextDocument.URI
   498  			edits[uri] = append(edits[uri], protocol.AsTextEdits(c.TextDocumentEdit.Edits)...)
   499  			orderedURIs = append(orderedURIs, uri)
   500  		}
   501  		if c.RenameFile != nil {
   502  			return fmt.Errorf("client does not support file renaming (%s -> %s)",
   503  				c.RenameFile.OldURI,
   504  				c.RenameFile.NewURI)
   505  		}
   506  	}
   507  	sortSlice(orderedURIs)
   508  	for _, uri := range orderedURIs {
   509  		f := cli.openFile(uri)
   510  		if f.err != nil {
   511  			return f.err
   512  		}
   513  		if err := applyTextEdits(f.mapper, edits[uri], cli.app.editFlags); err != nil {
   514  			return err
   515  		}
   516  	}
   517  	return nil
   518  }
   519  
   520  func sortSlice[T constraints.Ordered](slice []T) {
   521  	sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
   522  }
   523  
   524  // applyTextEdits applies a list of edits to the mapper file content,
   525  // using the preferred edit mode. It is a no-op if there are no edits.
   526  func applyTextEdits(mapper *protocol.Mapper, edits []protocol.TextEdit, flags *EditFlags) error {
   527  	if len(edits) == 0 {
   528  		return nil
   529  	}
   530  	newContent, renameEdits, err := protocol.ApplyEdits(mapper, edits)
   531  	if err != nil {
   532  		return err
   533  	}
   534  
   535  	filename := mapper.URI.Path()
   536  
   537  	if flags.List {
   538  		fmt.Println(filename)
   539  	}
   540  
   541  	if flags.Write {
   542  		if flags.Preserve {
   543  			if err := os.Rename(filename, filename+".orig"); err != nil {
   544  				return err
   545  			}
   546  		}
   547  		if err := os.WriteFile(filename, newContent, 0644); err != nil {
   548  			return err
   549  		}
   550  	}
   551  
   552  	if flags.Diff {
   553  		unified, err := diff.ToUnified(filename+".orig", filename, string(mapper.Content), renameEdits, diff.DefaultContextLines)
   554  		if err != nil {
   555  			return err
   556  		}
   557  		fmt.Print(unified)
   558  	}
   559  
   560  	// No flags: just print edited file content.
   561  	// TODO(adonovan): how is this ever useful with multiple files?
   562  	if !(flags.List || flags.Write || flags.Diff) {
   563  		os.Stdout.Write(newContent)
   564  	}
   565  
   566  	return nil
   567  }
   568  
   569  func (c *cmdClient) PublishDiagnostics(ctx context.Context, p *protocol.PublishDiagnosticsParams) error {
   570  	// Don't worry about diagnostics without versions.
   571  	if p.Version == 0 {
   572  		return nil
   573  	}
   574  
   575  	c.filesMu.Lock()
   576  	defer c.filesMu.Unlock()
   577  
   578  	file := c.getFile(p.URI)
   579  	file.diagnostics = append(file.diagnostics, p.Diagnostics...)
   580  
   581  	// Perform a crude in-place deduplication.
   582  	// TODO(golang/go#60122): replace the gopls.diagnose_files
   583  	// command with support for textDocument/diagnostic,
   584  	// so that we don't need to do this de-duplication.
   585  	type key [6]interface{}
   586  	seen := make(map[key]bool)
   587  	out := file.diagnostics[:0]
   588  	for _, d := range file.diagnostics {
   589  		var codeHref string
   590  		if desc := d.CodeDescription; desc != nil {
   591  			codeHref = desc.Href
   592  		}
   593  		k := key{d.Range, d.Severity, d.Code, codeHref, d.Source, d.Message}
   594  		if !seen[k] {
   595  			seen[k] = true
   596  			out = append(out, d)
   597  		}
   598  	}
   599  	file.diagnostics = out
   600  
   601  	return nil
   602  }
   603  
   604  func (c *cmdClient) Progress(_ context.Context, params *protocol.ProgressParams) error {
   605  	if c.onProgress != nil {
   606  		c.onProgress(params)
   607  	}
   608  	return nil
   609  }
   610  
   611  func (c *cmdClient) ShowDocument(ctx context.Context, params *protocol.ShowDocumentParams) (*protocol.ShowDocumentResult, error) {
   612  	var success bool
   613  	if params.External {
   614  		// Open URI in external browser.
   615  		success = browser.Open(params.URI)
   616  	} else {
   617  		// Open file in editor, optionally taking focus and selecting a range.
   618  		// (cmdClient has no editor. Should it fork+exec $EDITOR?)
   619  		log.Printf("Server requested that client editor open %q (takeFocus=%t, selection=%+v)",
   620  			params.URI, params.TakeFocus, params.Selection)
   621  		success = true
   622  	}
   623  	return &protocol.ShowDocumentResult{Success: success}, nil
   624  }
   625  
   626  func (c *cmdClient) WorkDoneProgressCreate(context.Context, *protocol.WorkDoneProgressCreateParams) error {
   627  	return nil
   628  }
   629  
   630  func (c *cmdClient) DiagnosticRefresh(context.Context) error {
   631  	return nil
   632  }
   633  
   634  func (c *cmdClient) InlayHintRefresh(context.Context) error {
   635  	return nil
   636  }
   637  
   638  func (c *cmdClient) SemanticTokensRefresh(context.Context) error {
   639  	return nil
   640  }
   641  
   642  func (c *cmdClient) InlineValueRefresh(context.Context) error {
   643  	return nil
   644  }
   645  
   646  func (c *cmdClient) getFile(uri protocol.DocumentURI) *cmdFile {
   647  	file, found := c.files[uri]
   648  	if !found || file.err != nil {
   649  		file = &cmdFile{
   650  			uri: uri,
   651  		}
   652  		c.files[uri] = file
   653  	}
   654  	if file.mapper == nil {
   655  		content, err := os.ReadFile(uri.Path())
   656  		if err != nil {
   657  			file.err = fmt.Errorf("getFile: %v: %v", uri, err)
   658  			return file
   659  		}
   660  		file.mapper = protocol.NewMapper(uri, content)
   661  	}
   662  	return file
   663  }
   664  
   665  func (c *cmdClient) openFile(uri protocol.DocumentURI) *cmdFile {
   666  	c.filesMu.Lock()
   667  	defer c.filesMu.Unlock()
   668  	return c.getFile(uri)
   669  }
   670  
   671  // TODO(adonovan): provide convenience helpers to:
   672  // - map a (URI, protocol.Range) to a MappedRange;
   673  // - parse a command-line argument to a MappedRange.
   674  func (c *connection) openFile(ctx context.Context, uri protocol.DocumentURI) (*cmdFile, error) {
   675  	file := c.client.openFile(uri)
   676  	if file.err != nil {
   677  		return nil, file.err
   678  	}
   679  
   680  	p := &protocol.DidOpenTextDocumentParams{
   681  		TextDocument: protocol.TextDocumentItem{
   682  			URI:        uri,
   683  			LanguageID: "go",
   684  			Version:    1,
   685  			Text:       string(file.mapper.Content),
   686  		},
   687  	}
   688  	if err := c.Server.DidOpen(ctx, p); err != nil {
   689  		// TODO(adonovan): is this assignment concurrency safe?
   690  		file.err = fmt.Errorf("%v: %v", uri, err)
   691  		return nil, file.err
   692  	}
   693  	return file, nil
   694  }
   695  
   696  func (c *connection) semanticTokens(ctx context.Context, p *protocol.SemanticTokensRangeParams) (*protocol.SemanticTokens, error) {
   697  	// use range to avoid limits on full
   698  	resp, err := c.Server.SemanticTokensRange(ctx, p)
   699  	if err != nil {
   700  		return nil, err
   701  	}
   702  	return resp, nil
   703  }
   704  
   705  func (c *connection) diagnoseFiles(ctx context.Context, files []protocol.DocumentURI) error {
   706  	cmd, err := command.NewDiagnoseFilesCommand("Diagnose files", command.DiagnoseFilesArgs{
   707  		Files: files,
   708  	})
   709  	if err != nil {
   710  		return err
   711  	}
   712  	_, err = c.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{
   713  		Command:   cmd.Command,
   714  		Arguments: cmd.Arguments,
   715  	})
   716  	return err
   717  }
   718  
   719  func (c *connection) terminate(ctx context.Context) {
   720  	//TODO: do we need to handle errors on these calls?
   721  	c.Shutdown(ctx)
   722  	//TODO: right now calling exit terminates the process, we should rethink that
   723  	//server.Exit(ctx)
   724  }
   725  
   726  // Implement io.Closer.
   727  func (c *cmdClient) Close() error {
   728  	return nil
   729  }
   730  
   731  // -- conversions to span (UTF-8) domain --
   732  
   733  // locationSpan converts a protocol (UTF-16) Location to a (UTF-8) span.
   734  // Precondition: the URIs of Location and Mapper match.
   735  func (f *cmdFile) locationSpan(loc protocol.Location) (span, error) {
   736  	// TODO(adonovan): check that l.URI matches m.URI.
   737  	return f.rangeSpan(loc.Range)
   738  }
   739  
   740  // rangeSpan converts a protocol (UTF-16) range to a (UTF-8) span.
   741  // The resulting span has valid Positions and Offsets.
   742  func (f *cmdFile) rangeSpan(r protocol.Range) (span, error) {
   743  	start, end, err := f.mapper.RangeOffsets(r)
   744  	if err != nil {
   745  		return span{}, err
   746  	}
   747  	return f.offsetSpan(start, end)
   748  }
   749  
   750  // offsetSpan converts a byte-offset interval to a (UTF-8) span.
   751  // The resulting span contains line, column, and offset information.
   752  func (f *cmdFile) offsetSpan(start, end int) (span, error) {
   753  	if start > end {
   754  		return span{}, fmt.Errorf("start offset (%d) > end (%d)", start, end)
   755  	}
   756  	startPoint, err := offsetPoint(f.mapper, start)
   757  	if err != nil {
   758  		return span{}, fmt.Errorf("start: %v", err)
   759  	}
   760  	endPoint, err := offsetPoint(f.mapper, end)
   761  	if err != nil {
   762  		return span{}, fmt.Errorf("end: %v", err)
   763  	}
   764  	return newSpan(f.mapper.URI, startPoint, endPoint), nil
   765  }
   766  
   767  // offsetPoint converts a byte offset to a span (UTF-8) point.
   768  // The resulting point contains line, column, and offset information.
   769  func offsetPoint(m *protocol.Mapper, offset int) (point, error) {
   770  	if !(0 <= offset && offset <= len(m.Content)) {
   771  		return point{}, fmt.Errorf("invalid offset %d (want 0-%d)", offset, len(m.Content))
   772  	}
   773  	line, col8 := m.OffsetLineCol8(offset)
   774  	return newPoint(line, col8, offset), nil
   775  }
   776  
   777  // -- conversions from span (UTF-8) domain --
   778  
   779  // spanLocation converts a (UTF-8) span to a protocol (UTF-16) range.
   780  // Precondition: the URIs of spanLocation and Mapper match.
   781  func (f *cmdFile) spanLocation(s span) (protocol.Location, error) {
   782  	rng, err := f.spanRange(s)
   783  	if err != nil {
   784  		return protocol.Location{}, err
   785  	}
   786  	return f.mapper.RangeLocation(rng), nil
   787  }
   788  
   789  // spanRange converts a (UTF-8) span to a protocol (UTF-16) range.
   790  // Precondition: the URIs of span and Mapper match.
   791  func (f *cmdFile) spanRange(s span) (protocol.Range, error) {
   792  	// Assert that we aren't using the wrong mapper.
   793  	// We check only the base name, and case insensitively,
   794  	// because we can't assume clean paths, no symbolic links,
   795  	// case-sensitive directories. The authoritative answer
   796  	// requires querying the file system, and we don't want
   797  	// to do that.
   798  	if !strings.EqualFold(filepath.Base(string(f.mapper.URI)), filepath.Base(string(s.URI()))) {
   799  		return protocol.Range{}, bugpkg.Errorf("mapper is for file %q instead of %q", f.mapper.URI, s.URI())
   800  	}
   801  	start, err := pointPosition(f.mapper, s.Start())
   802  	if err != nil {
   803  		return protocol.Range{}, fmt.Errorf("start: %w", err)
   804  	}
   805  	end, err := pointPosition(f.mapper, s.End())
   806  	if err != nil {
   807  		return protocol.Range{}, fmt.Errorf("end: %w", err)
   808  	}
   809  	return protocol.Range{Start: start, End: end}, nil
   810  }
   811  
   812  // pointPosition converts a valid span (UTF-8) point to a protocol (UTF-16) position.
   813  func pointPosition(m *protocol.Mapper, p point) (protocol.Position, error) {
   814  	if p.HasPosition() {
   815  		return m.LineCol8Position(p.Line(), p.Column())
   816  	}
   817  	if p.HasOffset() {
   818  		return m.OffsetPosition(p.Offset())
   819  	}
   820  	return protocol.Position{}, fmt.Errorf("point has neither offset nor line/column")
   821  }