cuelang.org/go@v0.13.0/internal/golangorgx/gopls/test/integration/fake/editor.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 fake
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"regexp"
    17  	"slices"
    18  	"strings"
    19  	"sync"
    20  
    21  	"cuelang.org/go/internal/golangorgx/gopls/protocol"
    22  	"cuelang.org/go/internal/golangorgx/gopls/protocol/command"
    23  	"cuelang.org/go/internal/golangorgx/gopls/test/integration/fake/glob"
    24  	"cuelang.org/go/internal/golangorgx/gopls/util/pathutil"
    25  	"cuelang.org/go/internal/golangorgx/tools/jsonrpc2"
    26  	"cuelang.org/go/internal/golangorgx/tools/jsonrpc2/servertest"
    27  	"cuelang.org/go/internal/golangorgx/tools/xcontext"
    28  )
    29  
    30  // Editor is a fake client editor.  It keeps track of client state and can be
    31  // used for writing LSP tests.
    32  type Editor struct {
    33  
    34  	// Server, client, and sandbox are concurrency safe and written only
    35  	// at construction time, so do not require synchronization.
    36  	Server     protocol.Server
    37  	cancelConn func()
    38  	serverConn jsonrpc2.Conn
    39  	client     *Client
    40  	sandbox    *Sandbox
    41  
    42  	// TODO(rfindley): buffers should be keyed by protocol.DocumentURI.
    43  	mu                 sync.Mutex
    44  	config             EditorConfig                // editor configuration
    45  	buffers            map[string]buffer           // open buffers (relative path -> buffer content)
    46  	serverCapabilities protocol.ServerCapabilities // capabilities / options
    47  	semTokOpts         protocol.SemanticTokensOptions
    48  	watchPatterns      []*glob.Glob // glob patterns to watch
    49  
    50  	// Call metrics for the purpose of expectations. This is done in an ad-hoc
    51  	// manner for now. Perhaps in the future we should do something more
    52  	// systematic. Guarded with a separate mutex as calls may need to be accessed
    53  	// asynchronously via callbacks into the Editor.
    54  	callsMu sync.Mutex
    55  	calls   CallCounts
    56  }
    57  
    58  // CallCounts tracks the number of protocol notifications of different types.
    59  type CallCounts struct {
    60  	DidOpen, DidChange, DidSave, DidChangeWatchedFiles, DidClose, DidChangeConfiguration uint64
    61  }
    62  
    63  // buffer holds information about an open buffer in the editor.
    64  type buffer struct {
    65  	version int              // monotonic version; incremented on edits
    66  	path    string           // relative path in the workspace
    67  	mapper  *protocol.Mapper // buffer content
    68  	dirty   bool             // if true, content is unsaved (TODO(rfindley): rename this field)
    69  }
    70  
    71  func (b buffer) text() string {
    72  	return string(b.mapper.Content)
    73  }
    74  
    75  // EditorConfig configures the editor's LSP session. This is similar to
    76  // golang.UserOptions, but we use a separate type here so that we expose only
    77  // that configuration which we support.
    78  //
    79  // The zero value for EditorConfig is the default configuration.
    80  type EditorConfig struct {
    81  	// ClientName sets the clientInfo.name for the LSP session (in the initialize request).
    82  	//
    83  	// Since this can only be set during initialization, changing this field via
    84  	// Editor.ChangeConfiguration has no effect.
    85  	ClientName string
    86  
    87  	// Env holds environment variables to apply on top of the default editor
    88  	// environment. When applying these variables, the special string
    89  	// $SANDBOX_WORKDIR is replaced by the absolute path to the sandbox working
    90  	// directory.
    91  	Env map[string]string
    92  
    93  	// WorkspaceFolders is the workspace folders to configure on the LSP server,
    94  	// relative to the sandbox workdir.
    95  	//
    96  	// As a special case, if WorkspaceFolders is nil the editor defaults to
    97  	// configuring a single workspace folder corresponding to the workdir root.
    98  	// To explicitly send no workspace folders, use an empty (non-nil) slice.
    99  	WorkspaceFolders []string
   100  
   101  	// RootURIAsDefaultFolder configures the RootURI initialization
   102  	// parameter to be set to the default workdir root.
   103  	RootURIAsDefaultFolder bool
   104  
   105  	// Whether to edit files with windows line endings.
   106  	WindowsLineEndings bool
   107  
   108  	// Map of language ID -> regexp to match, used to set the file type of new
   109  	// buffers. Applied as an overlay on top of the following defaults:
   110  	//  "go" -> ".*\.go"
   111  	//  "go.mod" -> "go\.mod"
   112  	//  "go.sum" -> "go\.sum"
   113  	//  "gotmpl" -> ".*tmpl"
   114  	FileAssociations map[string]string
   115  
   116  	// Settings holds user-provided configuration for the LSP server.
   117  	Settings map[string]any
   118  
   119  	// FolderSettings holds user-provided per-folder configuration, if any.
   120  	//
   121  	// It maps each folder (as a relative path to the sandbox workdir) to its
   122  	// configuration mapping (like Settings).
   123  	FolderSettings map[string]map[string]any
   124  
   125  	// CapabilitiesJSON holds JSON client capabilities to overlay over the
   126  	// editor's default client capabilities.
   127  	//
   128  	// Specifically, this JSON string will be unmarshalled into the editor's
   129  	// client capabilities struct, before sending to the server.
   130  	CapabilitiesJSON []byte
   131  
   132  	// If non-nil, MessageResponder is used to respond to ShowMessageRequest
   133  	// messages.
   134  	MessageResponder func(params *protocol.ShowMessageRequestParams) (*protocol.MessageActionItem, error)
   135  }
   136  
   137  // NewEditor creates a new Editor.
   138  func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor {
   139  	return &Editor{
   140  		buffers: make(map[string]buffer),
   141  		sandbox: sandbox,
   142  		config:  config,
   143  	}
   144  }
   145  
   146  // Connect configures the editor to communicate with an LSP server on conn. It
   147  // is not concurrency safe, and should be called at most once, before using the
   148  // editor.
   149  //
   150  // It returns the editor, so that it may be called as follows:
   151  //
   152  //	editor, err := NewEditor(s).Connect(ctx, conn, hooks)
   153  func (e *Editor) Connect(ctx context.Context, connector servertest.Connector, hooks ClientHooks, skipApplyEdits bool) (*Editor, error) {
   154  	bgCtx, cancelConn := context.WithCancel(xcontext.Detach(ctx))
   155  	conn := connector.Connect(bgCtx)
   156  	e.cancelConn = cancelConn
   157  
   158  	e.serverConn = conn
   159  	e.Server = protocol.ServerDispatcher(conn)
   160  	e.client = &Client{editor: e, hooks: hooks, skipApplyEdits: skipApplyEdits}
   161  	conn.Go(bgCtx,
   162  		protocol.Handlers(
   163  			protocol.ClientHandler(e.client,
   164  				jsonrpc2.MethodNotFound)))
   165  
   166  	if err := e.initialize(ctx); err != nil {
   167  		return nil, err
   168  	}
   169  	e.sandbox.Workdir.AddWatcher(e.onFileChanges)
   170  	return e, nil
   171  }
   172  
   173  func (e *Editor) Stats() CallCounts {
   174  	e.callsMu.Lock()
   175  	defer e.callsMu.Unlock()
   176  	return e.calls
   177  }
   178  
   179  // Shutdown issues the 'shutdown' LSP notification.
   180  func (e *Editor) Shutdown(ctx context.Context) error {
   181  	if e.Server != nil {
   182  		if err := e.Server.Shutdown(ctx); err != nil {
   183  			return fmt.Errorf("Shutdown: %w", err)
   184  		}
   185  	}
   186  	return nil
   187  }
   188  
   189  // Exit issues the 'exit' LSP notification.
   190  func (e *Editor) Exit(ctx context.Context) error {
   191  	if e.Server != nil {
   192  		// Not all LSP clients issue the exit RPC, but we do so here to ensure that
   193  		// we gracefully handle it on multi-session servers.
   194  		if err := e.Server.Exit(ctx); err != nil {
   195  			return fmt.Errorf("Exit: %w", err)
   196  		}
   197  	}
   198  	return nil
   199  }
   200  
   201  // Close issues the shutdown and exit sequence an editor should.
   202  func (e *Editor) Close(ctx context.Context) error {
   203  	if err := e.Shutdown(ctx); err != nil {
   204  		return err
   205  	}
   206  	if err := e.Exit(ctx); err != nil {
   207  		return err
   208  	}
   209  	defer func() {
   210  		e.cancelConn()
   211  	}()
   212  
   213  	// called close on the editor should result in the connection closing
   214  	select {
   215  	case <-e.serverConn.Done():
   216  		// connection closed itself
   217  		return nil
   218  	case <-ctx.Done():
   219  		return fmt.Errorf("connection not closed: %w", ctx.Err())
   220  	}
   221  }
   222  
   223  // Client returns the LSP client for this editor.
   224  func (e *Editor) Client() *Client {
   225  	return e.client
   226  }
   227  
   228  // makeSettings builds the settings map for use in LSP settings RPCs.
   229  func makeSettings(sandbox *Sandbox, config EditorConfig, scopeURI *protocol.URI) map[string]any {
   230  	env := make(map[string]string)
   231  	for k, v := range sandbox.GoEnv() {
   232  		env[k] = v
   233  	}
   234  	for k, v := range config.Env {
   235  		env[k] = v
   236  	}
   237  	for k, v := range env {
   238  		v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", sandbox.Workdir.RootURI().Path())
   239  		env[k] = v
   240  	}
   241  
   242  	settings := map[string]any{
   243  		"env": env,
   244  
   245  		// Use verbose progress reporting so that integration tests can assert on
   246  		// asynchronous operations being completed (such as diagnosing a snapshot).
   247  		"verboseWorkDoneProgress": true,
   248  
   249  		// Set an unlimited completion budget, so that tests don't flake because
   250  		// completions are too slow.
   251  		"completionBudget": "0s",
   252  	}
   253  
   254  	for k, v := range config.Settings {
   255  		if k == "env" {
   256  			panic("must not provide env via the EditorConfig.Settings field: use the EditorConfig.Env field instead")
   257  		}
   258  		settings[k] = v
   259  	}
   260  
   261  	// If the server is requesting configuration for a specific scope, apply
   262  	// settings for the nearest folder that has customized settings, if any.
   263  	if scopeURI != nil {
   264  		var (
   265  			scopePath       = protocol.DocumentURI(*scopeURI).Path()
   266  			closestDir      string         // longest dir with settings containing the scope, if any
   267  			closestSettings map[string]any // settings for that dir, if any
   268  		)
   269  		for relPath, settings := range config.FolderSettings {
   270  			dir := sandbox.Workdir.AbsPath(relPath)
   271  			if strings.HasPrefix(scopePath+string(filepath.Separator), dir+string(filepath.Separator)) && len(dir) > len(closestDir) {
   272  				closestDir = dir
   273  				closestSettings = settings
   274  			}
   275  		}
   276  		if closestSettings != nil {
   277  			for k, v := range closestSettings {
   278  				settings[k] = v
   279  			}
   280  		}
   281  	}
   282  
   283  	return settings
   284  }
   285  
   286  func (e *Editor) initialize(ctx context.Context) error {
   287  	config := e.Config()
   288  
   289  	params := &protocol.ParamInitialize{}
   290  	if e.config.ClientName != "" {
   291  		params.ClientInfo = &protocol.ClientInfo{
   292  			Name:    e.config.ClientName,
   293  			Version: "v1.0.0",
   294  		}
   295  	}
   296  	params.InitializationOptions = makeSettings(e.sandbox, config, nil)
   297  	params.WorkspaceFolders = makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders)
   298  	if config.RootURIAsDefaultFolder {
   299  		params.RootURI = e.sandbox.Workdir.URI(string(e.sandbox.Workdir.RelativeTo))
   300  	}
   301  
   302  	capabilities, err := clientCapabilities(config)
   303  	if err != nil {
   304  		return fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err)
   305  	}
   306  	params.Capabilities = capabilities
   307  
   308  	trace := protocol.TraceValues("messages")
   309  	params.Trace = &trace
   310  	// TODO: support workspace folders.
   311  	if e.Server != nil {
   312  		resp, err := e.Server.Initialize(ctx, params)
   313  		if err != nil {
   314  			return fmt.Errorf("initialize: %w", err)
   315  		}
   316  		semTokOpts, err := marshalUnmarshal[protocol.SemanticTokensOptions](resp.Capabilities.SemanticTokensProvider)
   317  		if err != nil {
   318  			return fmt.Errorf("unmarshalling semantic tokens options: %v", err)
   319  		}
   320  		e.mu.Lock()
   321  		e.serverCapabilities = resp.Capabilities
   322  		e.semTokOpts = semTokOpts
   323  		e.mu.Unlock()
   324  
   325  		if err := e.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
   326  			return fmt.Errorf("initialized: %w", err)
   327  		}
   328  	}
   329  	// TODO: await initial configuration here, or expect gopls to manage that?
   330  	return nil
   331  }
   332  
   333  func clientCapabilities(cfg EditorConfig) (protocol.ClientCapabilities, error) {
   334  	var capabilities protocol.ClientCapabilities
   335  	// Set various client capabilities that are sought by gopls.
   336  	capabilities.Workspace.Configuration = true // support workspace/configuration
   337  	capabilities.TextDocument.Completion.CompletionItem.TagSupport = &protocol.CompletionItemTagOptions{}
   338  	capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated}
   339  	capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true
   340  	capabilities.TextDocument.SemanticTokens.Requests.Full = &protocol.Or_ClientSemanticTokensRequestOptions_full{Value: true}
   341  	capabilities.Window.WorkDoneProgress = true // support window/workDoneProgress
   342  	capabilities.TextDocument.SemanticTokens.TokenTypes = []string{
   343  		"namespace", "type", "class", "enum", "interface",
   344  		"struct", "typeParameter", "parameter", "variable", "property", "enumMember",
   345  		"event", "function", "method", "macro", "keyword", "modifier", "comment",
   346  		"string", "number", "regexp", "operator",
   347  	}
   348  	capabilities.TextDocument.SemanticTokens.TokenModifiers = []string{
   349  		"declaration", "definition", "readonly", "static",
   350  		"deprecated", "abstract", "async", "modification", "documentation", "defaultLibrary",
   351  	}
   352  	// The LSP tests have historically enabled this flag,
   353  	// but really we should test both ways for older editors.
   354  	capabilities.TextDocument.DocumentSymbol.HierarchicalDocumentSymbolSupport = true
   355  	// Glob pattern watching is enabled.
   356  	capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true
   357  	// "rename" operations are used for package renaming.
   358  	//
   359  	// TODO(rfindley): add support for other resource operations (create, delete, ...)
   360  	capabilities.Workspace.WorkspaceEdit = &protocol.WorkspaceEditClientCapabilities{
   361  		ResourceOperations: []protocol.ResourceOperationKind{
   362  			"rename",
   363  		},
   364  	}
   365  
   366  	// Apply capabilities overlay.
   367  	if cfg.CapabilitiesJSON != nil {
   368  		if err := json.Unmarshal(cfg.CapabilitiesJSON, &capabilities); err != nil {
   369  			return protocol.ClientCapabilities{}, fmt.Errorf("unmarshalling EditorConfig.CapabilitiesJSON: %v", err)
   370  		}
   371  	}
   372  	return capabilities, nil
   373  }
   374  
   375  // marshalUnmarshal is a helper to json Marshal and then Unmarshal as a
   376  // different type. Used to work around cases where our protocol types are not
   377  // specific.
   378  func marshalUnmarshal[T any](v any) (T, error) {
   379  	var t T
   380  	data, err := json.Marshal(v)
   381  	if err != nil {
   382  		return t, err
   383  	}
   384  	err = json.Unmarshal(data, &t)
   385  	return t, err
   386  }
   387  
   388  // HasCommand reports whether the connected server supports the command with the given ID.
   389  func (e *Editor) HasCommand(id string) bool {
   390  	for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands {
   391  		if command == id {
   392  			return true
   393  		}
   394  	}
   395  	return false
   396  }
   397  
   398  // makeWorkspaceFolders creates a slice of workspace folders to use for
   399  // this editing session, based on the editor configuration.
   400  func makeWorkspaceFolders(sandbox *Sandbox, paths []string) (folders []protocol.WorkspaceFolder) {
   401  	if paths == nil {
   402  		paths = []string{string(sandbox.Workdir.RelativeTo)}
   403  	}
   404  
   405  	for _, path := range paths {
   406  		uri := string(sandbox.Workdir.URI(path))
   407  		folders = append(folders, protocol.WorkspaceFolder{
   408  			URI:  uri,
   409  			Name: filepath.Base(uri),
   410  		})
   411  	}
   412  
   413  	return folders
   414  }
   415  
   416  // onFileChanges is registered to be called by the Workdir on any writes that
   417  // go through the Workdir API. It is called synchronously by the Workdir.
   418  func (e *Editor) onFileChanges(ctx context.Context, evts []protocol.FileEvent) {
   419  	if e.Server == nil {
   420  		return
   421  	}
   422  
   423  	// e may be locked when onFileChanges is called, but it is important that we
   424  	// synchronously increment this counter so that we can subsequently assert on
   425  	// the number of expected DidChangeWatchedFiles calls.
   426  	e.callsMu.Lock()
   427  	e.calls.DidChangeWatchedFiles++
   428  	e.callsMu.Unlock()
   429  
   430  	// Since e may be locked, we must run this mutation asynchronously.
   431  	go func() {
   432  		e.mu.Lock()
   433  		defer e.mu.Unlock()
   434  		for _, evt := range evts {
   435  			// Always send an on-disk change, even for events that seem useless
   436  			// because they're shadowed by an open buffer.
   437  			path := e.sandbox.Workdir.URIToPath(evt.URI)
   438  			if buf, ok := e.buffers[path]; ok {
   439  				// Following VS Code, don't honor deletions or changes to dirty buffers.
   440  				if buf.dirty || evt.Type == protocol.Deleted {
   441  					continue
   442  				}
   443  
   444  				content, err := e.sandbox.Workdir.ReadFile(path)
   445  				if err != nil {
   446  					continue // A race with some other operation.
   447  				}
   448  				// No need to update if the buffer content hasn't changed.
   449  				if string(content) == buf.text() {
   450  					continue
   451  				}
   452  				// During shutdown, this call will fail. Ignore the error.
   453  				_ = e.setBufferContentLocked(ctx, path, false, content, nil)
   454  			}
   455  		}
   456  		var matchedEvts []protocol.FileEvent
   457  		for _, evt := range evts {
   458  			filename := filepath.ToSlash(evt.URI.Path())
   459  			for _, g := range e.watchPatterns {
   460  				if g.Match(filename) {
   461  					matchedEvts = append(matchedEvts, evt)
   462  					break
   463  				}
   464  			}
   465  		}
   466  
   467  		// TODO(rfindley): don't send notifications while locked.
   468  		e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
   469  			Changes: matchedEvts,
   470  		})
   471  	}()
   472  }
   473  
   474  // OpenFile creates a buffer for the given workdir-relative file.
   475  //
   476  // If the file is already open, it is a no-op.
   477  func (e *Editor) OpenFile(ctx context.Context, path string) error {
   478  	if e.HasBuffer(path) {
   479  		return nil
   480  	}
   481  	content, err := e.sandbox.Workdir.ReadFile(path)
   482  	if err != nil {
   483  		return err
   484  	}
   485  	if e.Config().WindowsLineEndings {
   486  		content = toWindowsLineEndings(content)
   487  	}
   488  	return e.createBuffer(ctx, path, false, content)
   489  }
   490  
   491  // toWindowsLineEndings checks whether content has windows line endings.
   492  //
   493  // If so, it returns content unmodified. If not, it returns a new byte slice modified to use CRLF line endings.
   494  func toWindowsLineEndings(content []byte) []byte {
   495  	abnormal := false
   496  	for i, b := range content {
   497  		if b == '\n' && (i == 0 || content[i-1] != '\r') {
   498  			abnormal = true
   499  			break
   500  		}
   501  	}
   502  	if !abnormal {
   503  		return content
   504  	}
   505  	var buf bytes.Buffer
   506  	for i, b := range content {
   507  		if b == '\n' && (i == 0 || content[i-1] != '\r') {
   508  			buf.WriteByte('\r')
   509  		}
   510  		buf.WriteByte(b)
   511  	}
   512  	return buf.Bytes()
   513  }
   514  
   515  // CreateBuffer creates a new unsaved buffer corresponding to the workdir path,
   516  // containing the given textual content.
   517  func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
   518  	return e.createBuffer(ctx, path, true, []byte(content))
   519  }
   520  
   521  func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content []byte) error {
   522  	e.mu.Lock()
   523  
   524  	if _, ok := e.buffers[path]; ok {
   525  		e.mu.Unlock()
   526  		return fmt.Errorf("buffer %q already exists", path)
   527  	}
   528  
   529  	uri := e.sandbox.Workdir.URI(path)
   530  	buf := buffer{
   531  		version: 1,
   532  		path:    path,
   533  		mapper:  protocol.NewMapper(uri, content),
   534  		dirty:   dirty,
   535  	}
   536  	e.buffers[path] = buf
   537  
   538  	item := e.textDocumentItem(buf)
   539  	e.mu.Unlock()
   540  
   541  	return e.sendDidOpen(ctx, item)
   542  }
   543  
   544  // textDocumentItem builds a protocol.TextDocumentItem for the given buffer.
   545  //
   546  // Precondition: e.mu must be held.
   547  func (e *Editor) textDocumentItem(buf buffer) protocol.TextDocumentItem {
   548  	return protocol.TextDocumentItem{
   549  		URI:        e.sandbox.Workdir.URI(buf.path),
   550  		LanguageID: languageID(buf.path, e.config.FileAssociations),
   551  		Version:    int32(buf.version),
   552  		Text:       buf.text(),
   553  	}
   554  }
   555  
   556  func (e *Editor) sendDidOpen(ctx context.Context, item protocol.TextDocumentItem) error {
   557  	if e.Server != nil {
   558  		if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
   559  			TextDocument: item,
   560  		}); err != nil {
   561  			return fmt.Errorf("DidOpen: %w", err)
   562  		}
   563  		e.callsMu.Lock()
   564  		e.calls.DidOpen++
   565  		e.callsMu.Unlock()
   566  	}
   567  	return nil
   568  }
   569  
   570  var defaultFileAssociations = map[string]*regexp.Regexp{
   571  	"go":      regexp.MustCompile(`^.*\.go$`), // '$' is important: don't match .gotmpl!
   572  	"go.mod":  regexp.MustCompile(`^go\.mod$`),
   573  	"go.sum":  regexp.MustCompile(`^go(\.work)?\.sum$`),
   574  	"go.work": regexp.MustCompile(`^go\.work$`),
   575  	"gotmpl":  regexp.MustCompile(`^.*tmpl$`),
   576  }
   577  
   578  // languageID returns the language identifier for the path p given the user
   579  // configured fileAssociations.
   580  func languageID(p string, fileAssociations map[string]string) string {
   581  	base := path.Base(p)
   582  	for lang, re := range fileAssociations {
   583  		re := regexp.MustCompile(re)
   584  		if re.MatchString(base) {
   585  			return lang
   586  		}
   587  	}
   588  	for lang, re := range defaultFileAssociations {
   589  		if re.MatchString(base) {
   590  			return lang
   591  		}
   592  	}
   593  	return ""
   594  }
   595  
   596  // CloseBuffer removes the current buffer (regardless of whether it is saved).
   597  func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
   598  	e.mu.Lock()
   599  	_, ok := e.buffers[path]
   600  	if !ok {
   601  		e.mu.Unlock()
   602  		return ErrUnknownBuffer
   603  	}
   604  	delete(e.buffers, path)
   605  	e.mu.Unlock()
   606  
   607  	return e.sendDidClose(ctx, e.TextDocumentIdentifier(path))
   608  }
   609  
   610  func (e *Editor) sendDidClose(ctx context.Context, doc protocol.TextDocumentIdentifier) error {
   611  	if e.Server != nil {
   612  		if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
   613  			TextDocument: doc,
   614  		}); err != nil {
   615  			return fmt.Errorf("DidClose: %w", err)
   616  		}
   617  		e.callsMu.Lock()
   618  		e.calls.DidClose++
   619  		e.callsMu.Unlock()
   620  	}
   621  	return nil
   622  }
   623  
   624  func (e *Editor) TextDocumentIdentifier(path string) protocol.TextDocumentIdentifier {
   625  	return protocol.TextDocumentIdentifier{
   626  		URI: e.sandbox.Workdir.URI(path),
   627  	}
   628  }
   629  
   630  // SaveBuffer writes the content of the buffer specified by the given path to
   631  // the filesystem.
   632  func (e *Editor) SaveBuffer(ctx context.Context, path string) error {
   633  	if err := e.OrganizeImports(ctx, path); err != nil {
   634  		return fmt.Errorf("organizing imports before save: %w", err)
   635  	}
   636  	if err := e.FormatBuffer(ctx, path); err != nil {
   637  		return fmt.Errorf("formatting before save: %w", err)
   638  	}
   639  	return e.SaveBufferWithoutActions(ctx, path)
   640  }
   641  
   642  func (e *Editor) SaveBufferWithoutActions(ctx context.Context, path string) error {
   643  	e.mu.Lock()
   644  	defer e.mu.Unlock()
   645  	buf, ok := e.buffers[path]
   646  	if !ok {
   647  		return fmt.Errorf("unknown buffer: %q", path)
   648  	}
   649  	content := buf.text()
   650  	includeText := false
   651  	syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions)
   652  	if ok {
   653  		includeText = syncOptions.Save.IncludeText
   654  	}
   655  
   656  	docID := e.TextDocumentIdentifier(buf.path)
   657  	if e.Server != nil {
   658  		if err := e.Server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{
   659  			TextDocument: docID,
   660  			Reason:       protocol.Manual,
   661  		}); err != nil {
   662  			return fmt.Errorf("WillSave: %w", err)
   663  		}
   664  	}
   665  	if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil {
   666  		return fmt.Errorf("writing %q: %w", path, err)
   667  	}
   668  
   669  	buf.dirty = false
   670  	e.buffers[path] = buf
   671  
   672  	if e.Server != nil {
   673  		params := &protocol.DidSaveTextDocumentParams{
   674  			TextDocument: docID,
   675  		}
   676  		if includeText {
   677  			params.Text = &content
   678  		}
   679  		if err := e.Server.DidSave(ctx, params); err != nil {
   680  			return fmt.Errorf("DidSave: %w", err)
   681  		}
   682  		e.callsMu.Lock()
   683  		e.calls.DidSave++
   684  		e.callsMu.Unlock()
   685  	}
   686  	return nil
   687  }
   688  
   689  // ErrNoMatch is returned if a regexp search fails.
   690  var (
   691  	ErrNoMatch       = errors.New("no match")
   692  	ErrUnknownBuffer = errors.New("unknown buffer")
   693  )
   694  
   695  // regexpLocation returns the location of the first occurrence of either re
   696  // or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match.
   697  func regexpLocation(mapper *protocol.Mapper, re string) (protocol.Location, error) {
   698  	var start, end int
   699  	rec, err := regexp.Compile(re)
   700  	if err != nil {
   701  		return protocol.Location{}, err
   702  	}
   703  	indexes := rec.FindSubmatchIndex(mapper.Content)
   704  	if indexes == nil {
   705  		return protocol.Location{}, ErrNoMatch
   706  	}
   707  	switch len(indexes) {
   708  	case 2:
   709  		// no subgroups: return the range of the regexp expression
   710  		start, end = indexes[0], indexes[1]
   711  	case 4:
   712  		// one subgroup: return its range
   713  		start, end = indexes[2], indexes[3]
   714  	default:
   715  		return protocol.Location{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1)
   716  	}
   717  	return mapper.OffsetLocation(start, end)
   718  }
   719  
   720  // RegexpSearch returns the Location of the first match for re in the buffer
   721  // bufName. For convenience, RegexpSearch supports the following two modes:
   722  //  1. If re has no subgroups, return the position of the match for re itself.
   723  //  2. If re has one subgroup, return the position of the first subgroup.
   724  //
   725  // It returns an error re is invalid, has more than one subgroup, or doesn't
   726  // match the buffer.
   727  func (e *Editor) RegexpSearch(bufName, re string) (protocol.Location, error) {
   728  	e.mu.Lock()
   729  	buf, ok := e.buffers[bufName]
   730  	e.mu.Unlock()
   731  	if !ok {
   732  		return protocol.Location{}, ErrUnknownBuffer
   733  	}
   734  	return regexpLocation(buf.mapper, re)
   735  }
   736  
   737  // RegexpReplace edits the buffer corresponding to path by replacing the first
   738  // instance of re, or its first subgroup, with the replace text. See
   739  // RegexpSearch for more explanation of these two modes.
   740  // It returns an error if re is invalid, has more than one subgroup, or doesn't
   741  // match the buffer.
   742  func (e *Editor) RegexpReplace(ctx context.Context, path, re, replace string) error {
   743  	e.mu.Lock()
   744  	defer e.mu.Unlock()
   745  	buf, ok := e.buffers[path]
   746  	if !ok {
   747  		return ErrUnknownBuffer
   748  	}
   749  	loc, err := regexpLocation(buf.mapper, re)
   750  	if err != nil {
   751  		return err
   752  	}
   753  	edits := []protocol.TextEdit{{
   754  		Range:   loc.Range,
   755  		NewText: replace,
   756  	}}
   757  	patched, err := applyEdits(buf.mapper, edits, e.config.WindowsLineEndings)
   758  	if err != nil {
   759  		return fmt.Errorf("editing %q: %v", path, err)
   760  	}
   761  	return e.setBufferContentLocked(ctx, path, true, patched, edits)
   762  }
   763  
   764  // EditBuffer applies the given test edits to the buffer identified by path.
   765  func (e *Editor) EditBuffer(ctx context.Context, path string, edits []protocol.TextEdit) error {
   766  	e.mu.Lock()
   767  	defer e.mu.Unlock()
   768  	return e.editBufferLocked(ctx, path, edits)
   769  }
   770  
   771  func (e *Editor) SetBufferContent(ctx context.Context, path, content string) error {
   772  	e.mu.Lock()
   773  	defer e.mu.Unlock()
   774  	return e.setBufferContentLocked(ctx, path, true, []byte(content), nil)
   775  }
   776  
   777  // HasBuffer reports whether the file name is open in the editor.
   778  func (e *Editor) HasBuffer(name string) bool {
   779  	e.mu.Lock()
   780  	defer e.mu.Unlock()
   781  	_, ok := e.buffers[name]
   782  	return ok
   783  }
   784  
   785  // BufferText returns the content of the buffer with the given name, or "" if
   786  // the file at that path is not open. The second return value reports whether
   787  // the file is open.
   788  func (e *Editor) BufferText(name string) (string, bool) {
   789  	e.mu.Lock()
   790  	defer e.mu.Unlock()
   791  	buf, ok := e.buffers[name]
   792  	if !ok {
   793  		return "", false
   794  	}
   795  	return buf.text(), true
   796  }
   797  
   798  // Mapper returns the protocol.Mapper for the given buffer name, if it is open.
   799  func (e *Editor) Mapper(name string) (*protocol.Mapper, error) {
   800  	e.mu.Lock()
   801  	defer e.mu.Unlock()
   802  	buf, ok := e.buffers[name]
   803  	if !ok {
   804  		return nil, fmt.Errorf("no mapper for %q", name)
   805  	}
   806  	return buf.mapper, nil
   807  }
   808  
   809  // BufferVersion returns the current version of the buffer corresponding to
   810  // name (or 0 if it is not being edited).
   811  func (e *Editor) BufferVersion(name string) int {
   812  	e.mu.Lock()
   813  	defer e.mu.Unlock()
   814  	return e.buffers[name].version
   815  }
   816  
   817  func (e *Editor) editBufferLocked(ctx context.Context, path string, edits []protocol.TextEdit) error {
   818  	buf, ok := e.buffers[path]
   819  	if !ok {
   820  		return fmt.Errorf("unknown buffer %q", path)
   821  	}
   822  	content, err := applyEdits(buf.mapper, edits, e.config.WindowsLineEndings)
   823  	if err != nil {
   824  		return fmt.Errorf("editing %q: %v; edits:\n%v", path, err, edits)
   825  	}
   826  	return e.setBufferContentLocked(ctx, path, true, content, edits)
   827  }
   828  
   829  func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty bool, content []byte, fromEdits []protocol.TextEdit) error {
   830  	buf, ok := e.buffers[path]
   831  	if !ok {
   832  		return fmt.Errorf("unknown buffer %q", path)
   833  	}
   834  	buf.mapper = protocol.NewMapper(buf.mapper.URI, content)
   835  	buf.version++
   836  	buf.dirty = dirty
   837  	e.buffers[path] = buf
   838  
   839  	// A simple heuristic: if there is only one edit, send it incrementally.
   840  	// Otherwise, send the entire content.
   841  	var evt protocol.TextDocumentContentChangeEvent
   842  	if len(fromEdits) == 1 {
   843  		evt.Range = &fromEdits[0].Range
   844  		evt.Text = fromEdits[0].NewText
   845  	} else {
   846  		evt.Text = buf.text()
   847  	}
   848  	params := &protocol.DidChangeTextDocumentParams{
   849  		TextDocument: protocol.VersionedTextDocumentIdentifier{
   850  			Version:                int32(buf.version),
   851  			TextDocumentIdentifier: e.TextDocumentIdentifier(buf.path),
   852  		},
   853  		ContentChanges: []protocol.TextDocumentContentChangeEvent{evt},
   854  	}
   855  	if e.Server != nil {
   856  		if err := e.Server.DidChange(ctx, params); err != nil {
   857  			return fmt.Errorf("DidChange: %w", err)
   858  		}
   859  		e.callsMu.Lock()
   860  		e.calls.DidChange++
   861  		e.callsMu.Unlock()
   862  	}
   863  	return nil
   864  }
   865  
   866  // GoToDefinition jumps to the definition of the symbol at the given position
   867  // in an open buffer. It returns the location of the resulting jump.
   868  func (e *Editor) Definition(ctx context.Context, loc protocol.Location) (protocol.Location, error) {
   869  	if err := e.checkBufferLocation(loc); err != nil {
   870  		return protocol.Location{}, err
   871  	}
   872  	params := &protocol.DefinitionParams{}
   873  	params.TextDocument.URI = loc.URI
   874  	params.Position = loc.Range.Start
   875  
   876  	resp, err := e.Server.Definition(ctx, params)
   877  	if err != nil {
   878  		return protocol.Location{}, fmt.Errorf("definition: %w", err)
   879  	}
   880  	return e.extractFirstLocation(ctx, resp)
   881  }
   882  
   883  // TypeDefinition jumps to the type definition of the symbol at the given
   884  // location in an open buffer.
   885  func (e *Editor) TypeDefinition(ctx context.Context, loc protocol.Location) (protocol.Location, error) {
   886  	if err := e.checkBufferLocation(loc); err != nil {
   887  		return protocol.Location{}, err
   888  	}
   889  	params := &protocol.TypeDefinitionParams{}
   890  	params.TextDocument.URI = loc.URI
   891  	params.Position = loc.Range.Start
   892  
   893  	resp, err := e.Server.TypeDefinition(ctx, params)
   894  	if err != nil {
   895  		return protocol.Location{}, fmt.Errorf("type definition: %w", err)
   896  	}
   897  	return e.extractFirstLocation(ctx, resp)
   898  }
   899  
   900  // extractFirstLocation returns the first location.
   901  // It opens the file if needed.
   902  func (e *Editor) extractFirstLocation(ctx context.Context, locs []protocol.Location) (protocol.Location, error) {
   903  	if len(locs) == 0 {
   904  		return protocol.Location{}, nil
   905  	}
   906  
   907  	newPath := e.sandbox.Workdir.URIToPath(locs[0].URI)
   908  	if !e.HasBuffer(newPath) {
   909  		if err := e.OpenFile(ctx, newPath); err != nil {
   910  			return protocol.Location{}, fmt.Errorf("OpenFile: %w", err)
   911  		}
   912  	}
   913  	return locs[0], nil
   914  }
   915  
   916  // Symbol performs a workspace symbol search using query
   917  func (e *Editor) Symbol(ctx context.Context, query string) ([]protocol.SymbolInformation, error) {
   918  	params := &protocol.WorkspaceSymbolParams{Query: query}
   919  	return e.Server.Symbol(ctx, params)
   920  }
   921  
   922  // OrganizeImports requests and performs the source.organizeImports codeAction.
   923  func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
   924  	loc := protocol.Location{URI: e.sandbox.Workdir.URI(path)} // zero Range => whole file
   925  	_, err := e.applyCodeActions(ctx, loc, nil, protocol.SourceOrganizeImports)
   926  	return err
   927  }
   928  
   929  // RefactorRewrite requests and performs the source.refactorRewrite codeAction.
   930  func (e *Editor) RefactorRewrite(ctx context.Context, loc protocol.Location) error {
   931  	applied, err := e.applyCodeActions(ctx, loc, nil, protocol.RefactorRewrite)
   932  	if err != nil {
   933  		return err
   934  	}
   935  	if applied == 0 {
   936  		return fmt.Errorf("no refactorings were applied")
   937  	}
   938  	return nil
   939  }
   940  
   941  // ApplyQuickFixes requests and performs the quickfix codeAction.
   942  func (e *Editor) ApplyQuickFixes(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) error {
   943  	applied, err := e.applyCodeActions(ctx, loc, diagnostics, protocol.SourceFixAll, protocol.QuickFix)
   944  	if applied == 0 {
   945  		return fmt.Errorf("no quick fixes were applied")
   946  	}
   947  	return err
   948  }
   949  
   950  // ApplyCodeAction applies the given code action.
   951  func (e *Editor) ApplyCodeAction(ctx context.Context, action protocol.CodeAction) error {
   952  	// Resolve the code actions if necessary and supported.
   953  	if action.Edit == nil {
   954  		editSupport, err := e.EditResolveSupport()
   955  		if err != nil {
   956  			return err
   957  		}
   958  		if editSupport {
   959  			ca, err := e.Server.ResolveCodeAction(ctx, &action)
   960  			if err != nil {
   961  				return err
   962  			}
   963  			action.Edit = ca.Edit
   964  		}
   965  	}
   966  
   967  	if action.Edit != nil {
   968  		for _, change := range action.Edit.DocumentChanges {
   969  			if change.TextDocumentEdit != nil {
   970  				path := e.sandbox.Workdir.URIToPath(change.TextDocumentEdit.TextDocument.URI)
   971  				if int32(e.buffers[path].version) != change.TextDocumentEdit.TextDocument.Version {
   972  					// Skip edits for old versions.
   973  					continue
   974  				}
   975  				if err := e.EditBuffer(ctx, path, protocol.AsTextEdits(change.TextDocumentEdit.Edits)); err != nil {
   976  					return fmt.Errorf("editing buffer %q: %w", path, err)
   977  				}
   978  			}
   979  		}
   980  	}
   981  	// Execute any commands. The specification says that commands are
   982  	// executed after edits are applied.
   983  	if action.Command != nil {
   984  		if _, err := e.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{
   985  			Command:   action.Command.Command,
   986  			Arguments: action.Command.Arguments,
   987  		}); err != nil {
   988  			return err
   989  		}
   990  	}
   991  	// Some commands may edit files on disk.
   992  	return e.sandbox.Workdir.CheckForFileChanges(ctx)
   993  }
   994  
   995  // GetQuickFixes returns the available quick fix code actions.
   996  func (e *Editor) GetQuickFixes(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
   997  	return e.CodeActions(ctx, loc, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
   998  }
   999  
  1000  func (e *Editor) applyCodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) {
  1001  	actions, err := e.CodeActions(ctx, loc, diagnostics, only...)
  1002  	if err != nil {
  1003  		return 0, err
  1004  	}
  1005  	applied := 0
  1006  	for _, action := range actions {
  1007  		if action.Title == "" {
  1008  			return 0, fmt.Errorf("empty title for code action")
  1009  		}
  1010  		var match bool
  1011  		for _, o := range only {
  1012  			if action.Kind == o {
  1013  				match = true
  1014  				break
  1015  			}
  1016  		}
  1017  		if !match {
  1018  			continue
  1019  		}
  1020  		applied++
  1021  		if err := e.ApplyCodeAction(ctx, action); err != nil {
  1022  			return 0, err
  1023  		}
  1024  	}
  1025  	return applied, nil
  1026  }
  1027  
  1028  func (e *Editor) CodeActions(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) {
  1029  	if e.Server == nil {
  1030  		return nil, nil
  1031  	}
  1032  	params := &protocol.CodeActionParams{}
  1033  	params.TextDocument.URI = loc.URI
  1034  	params.Context.Only = only
  1035  	params.Range = loc.Range // may be zero => whole file
  1036  	if diagnostics != nil {
  1037  		params.Context.Diagnostics = diagnostics
  1038  	}
  1039  	return e.Server.CodeAction(ctx, params)
  1040  }
  1041  
  1042  func (e *Editor) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
  1043  	if e.Server == nil {
  1044  		return nil, nil
  1045  	}
  1046  	var match bool
  1047  	if e.serverCapabilities.ExecuteCommandProvider != nil {
  1048  		// Ensure that this command was actually listed as a supported command.
  1049  		for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands {
  1050  			if command == params.Command {
  1051  				match = true
  1052  				break
  1053  			}
  1054  		}
  1055  	}
  1056  	if !match {
  1057  		return nil, fmt.Errorf("unsupported command %q", params.Command)
  1058  	}
  1059  	result, err := e.Server.ExecuteCommand(ctx, params)
  1060  	if err != nil {
  1061  		return nil, err
  1062  	}
  1063  	// Some commands use the go command, which writes directly to disk.
  1064  	// For convenience, check for those changes.
  1065  	if err := e.sandbox.Workdir.CheckForFileChanges(ctx); err != nil {
  1066  		return nil, fmt.Errorf("checking for file changes: %v", err)
  1067  	}
  1068  	return result, nil
  1069  }
  1070  
  1071  // FormatBuffer gofmts a Go file.
  1072  func (e *Editor) FormatBuffer(ctx context.Context, path string) error {
  1073  	if e.Server == nil {
  1074  		return nil
  1075  	}
  1076  	e.mu.Lock()
  1077  	version := e.buffers[path].version
  1078  	e.mu.Unlock()
  1079  	params := &protocol.DocumentFormattingParams{}
  1080  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
  1081  	edits, err := e.Server.Formatting(ctx, params)
  1082  	if err != nil {
  1083  		return fmt.Errorf("textDocument/formatting: %w", err)
  1084  	}
  1085  	e.mu.Lock()
  1086  	defer e.mu.Unlock()
  1087  	if versionAfter := e.buffers[path].version; versionAfter != version {
  1088  		return fmt.Errorf("before receipt of formatting edits, buffer version changed from %d to %d", version, versionAfter)
  1089  	}
  1090  	if len(edits) == 0 {
  1091  		return nil
  1092  	}
  1093  	return e.editBufferLocked(ctx, path, edits)
  1094  }
  1095  
  1096  func (e *Editor) checkBufferLocation(loc protocol.Location) error {
  1097  	e.mu.Lock()
  1098  	defer e.mu.Unlock()
  1099  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1100  	buf, ok := e.buffers[path]
  1101  	if !ok {
  1102  		return fmt.Errorf("buffer %q is not open", path)
  1103  	}
  1104  
  1105  	_, _, err := buf.mapper.RangeOffsets(loc.Range)
  1106  	return err
  1107  }
  1108  
  1109  // RunGenerate runs `go generate` non-recursively in the workdir-relative dir
  1110  // path. It does not report any resulting file changes as a watched file
  1111  // change, so must be followed by a call to Workdir.CheckForFileChanges once
  1112  // the generate command has completed.
  1113  // TODO(rFindley): this shouldn't be necessary anymore. Delete it.
  1114  func (e *Editor) RunGenerate(ctx context.Context, dir string) error {
  1115  	if e.Server == nil {
  1116  		return nil
  1117  	}
  1118  	absDir := e.sandbox.Workdir.AbsPath(dir)
  1119  	cmd, err := command.NewGenerateCommand("", command.GenerateArgs{
  1120  		Dir:       protocol.URIFromPath(absDir),
  1121  		Recursive: false,
  1122  	})
  1123  	if err != nil {
  1124  		return err
  1125  	}
  1126  	params := &protocol.ExecuteCommandParams{
  1127  		Command:   cmd.Command,
  1128  		Arguments: cmd.Arguments,
  1129  	}
  1130  	if _, err := e.ExecuteCommand(ctx, params); err != nil {
  1131  		return fmt.Errorf("running generate: %v", err)
  1132  	}
  1133  	// Unfortunately we can't simply poll the workdir for file changes here,
  1134  	// because server-side command may not have completed. In integration tests, we can
  1135  	// Await this state change, but here we must delegate that responsibility to
  1136  	// the caller.
  1137  	return nil
  1138  }
  1139  
  1140  // CodeLens executes a codelens request on the server.
  1141  func (e *Editor) CodeLens(ctx context.Context, path string) ([]protocol.CodeLens, error) {
  1142  	if e.Server == nil {
  1143  		return nil, nil
  1144  	}
  1145  	e.mu.Lock()
  1146  	_, ok := e.buffers[path]
  1147  	e.mu.Unlock()
  1148  	if !ok {
  1149  		return nil, fmt.Errorf("buffer %q is not open", path)
  1150  	}
  1151  	params := &protocol.CodeLensParams{
  1152  		TextDocument: e.TextDocumentIdentifier(path),
  1153  	}
  1154  	lens, err := e.Server.CodeLens(ctx, params)
  1155  	if err != nil {
  1156  		return nil, err
  1157  	}
  1158  	return lens, nil
  1159  }
  1160  
  1161  // Completion executes a completion request on the server.
  1162  func (e *Editor) Completion(ctx context.Context, loc protocol.Location) (*protocol.CompletionList, error) {
  1163  	if e.Server == nil {
  1164  		return nil, nil
  1165  	}
  1166  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1167  	e.mu.Lock()
  1168  	_, ok := e.buffers[path]
  1169  	e.mu.Unlock()
  1170  	if !ok {
  1171  		return nil, fmt.Errorf("buffer %q is not open", path)
  1172  	}
  1173  	params := &protocol.CompletionParams{
  1174  		TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc),
  1175  	}
  1176  	completions, err := e.Server.Completion(ctx, params)
  1177  	if err != nil {
  1178  		return nil, err
  1179  	}
  1180  	return completions, nil
  1181  }
  1182  
  1183  // AcceptCompletion accepts a completion for the given item at the given
  1184  // position.
  1185  func (e *Editor) AcceptCompletion(ctx context.Context, loc protocol.Location, item protocol.CompletionItem) error {
  1186  	if e.Server == nil {
  1187  		return nil
  1188  	}
  1189  	e.mu.Lock()
  1190  	defer e.mu.Unlock()
  1191  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1192  	_, ok := e.buffers[path]
  1193  	if !ok {
  1194  		return fmt.Errorf("buffer %q is not open", path)
  1195  	}
  1196  	return e.editBufferLocked(ctx, path, append([]protocol.TextEdit{
  1197  		*item.TextEdit,
  1198  	}, item.AdditionalTextEdits...))
  1199  }
  1200  
  1201  // Symbols executes a workspace/symbols request on the server.
  1202  func (e *Editor) Symbols(ctx context.Context, sym string) ([]protocol.SymbolInformation, error) {
  1203  	if e.Server == nil {
  1204  		return nil, nil
  1205  	}
  1206  	params := &protocol.WorkspaceSymbolParams{Query: sym}
  1207  	ans, err := e.Server.Symbol(ctx, params)
  1208  	return ans, err
  1209  }
  1210  
  1211  // CodeLens executes a codelens request on the server.
  1212  func (e *Editor) InlayHint(ctx context.Context, path string) ([]protocol.InlayHint, error) {
  1213  	if e.Server == nil {
  1214  		return nil, nil
  1215  	}
  1216  	e.mu.Lock()
  1217  	_, ok := e.buffers[path]
  1218  	e.mu.Unlock()
  1219  	if !ok {
  1220  		return nil, fmt.Errorf("buffer %q is not open", path)
  1221  	}
  1222  	params := &protocol.InlayHintParams{
  1223  		TextDocument: e.TextDocumentIdentifier(path),
  1224  	}
  1225  	hints, err := e.Server.InlayHint(ctx, params)
  1226  	if err != nil {
  1227  		return nil, err
  1228  	}
  1229  	return hints, nil
  1230  }
  1231  
  1232  // References returns references to the object at loc, as returned by
  1233  // the connected LSP server. If no server is connected, it returns (nil, nil).
  1234  func (e *Editor) References(ctx context.Context, loc protocol.Location) ([]protocol.Location, error) {
  1235  	if e.Server == nil {
  1236  		return nil, nil
  1237  	}
  1238  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1239  	e.mu.Lock()
  1240  	_, ok := e.buffers[path]
  1241  	e.mu.Unlock()
  1242  	if !ok {
  1243  		return nil, fmt.Errorf("buffer %q is not open", path)
  1244  	}
  1245  	params := &protocol.ReferenceParams{
  1246  		TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc),
  1247  		Context: protocol.ReferenceContext{
  1248  			IncludeDeclaration: true,
  1249  		},
  1250  	}
  1251  	locations, err := e.Server.References(ctx, params)
  1252  	if err != nil {
  1253  		return nil, err
  1254  	}
  1255  	return locations, nil
  1256  }
  1257  
  1258  // Rename performs a rename of the object at loc to newName, using the
  1259  // connected LSP server. If no server is connected, it returns nil.
  1260  func (e *Editor) Rename(ctx context.Context, loc protocol.Location, newName string) error {
  1261  	if e.Server == nil {
  1262  		return nil
  1263  	}
  1264  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1265  
  1266  	// Verify that PrepareRename succeeds.
  1267  	prepareParams := &protocol.PrepareRenameParams{}
  1268  	prepareParams.TextDocument = e.TextDocumentIdentifier(path)
  1269  	prepareParams.Position = loc.Range.Start
  1270  	if _, err := e.Server.PrepareRename(ctx, prepareParams); err != nil {
  1271  		return fmt.Errorf("preparing rename: %v", err)
  1272  	}
  1273  
  1274  	params := &protocol.RenameParams{
  1275  		TextDocument: e.TextDocumentIdentifier(path),
  1276  		Position:     loc.Range.Start,
  1277  		NewName:      newName,
  1278  	}
  1279  	wsEdits, err := e.Server.Rename(ctx, params)
  1280  	if err != nil {
  1281  		return err
  1282  	}
  1283  	for _, change := range wsEdits.DocumentChanges {
  1284  		if err := e.applyDocumentChange(ctx, change); err != nil {
  1285  			return err
  1286  		}
  1287  	}
  1288  	return nil
  1289  }
  1290  
  1291  // Implementations returns implementations for the object at loc, as
  1292  // returned by the connected LSP server. If no server is connected, it returns
  1293  // (nil, nil).
  1294  func (e *Editor) Implementations(ctx context.Context, loc protocol.Location) ([]protocol.Location, error) {
  1295  	if e.Server == nil {
  1296  		return nil, nil
  1297  	}
  1298  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1299  	e.mu.Lock()
  1300  	_, ok := e.buffers[path]
  1301  	e.mu.Unlock()
  1302  	if !ok {
  1303  		return nil, fmt.Errorf("buffer %q is not open", path)
  1304  	}
  1305  	params := &protocol.ImplementationParams{
  1306  		TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc),
  1307  	}
  1308  	return e.Server.Implementation(ctx, params)
  1309  }
  1310  
  1311  func (e *Editor) SignatureHelp(ctx context.Context, loc protocol.Location) (*protocol.SignatureHelp, error) {
  1312  	if e.Server == nil {
  1313  		return nil, nil
  1314  	}
  1315  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1316  	e.mu.Lock()
  1317  	_, ok := e.buffers[path]
  1318  	e.mu.Unlock()
  1319  	if !ok {
  1320  		return nil, fmt.Errorf("buffer %q is not open", path)
  1321  	}
  1322  	params := &protocol.SignatureHelpParams{
  1323  		TextDocumentPositionParams: protocol.LocationTextDocumentPositionParams(loc),
  1324  	}
  1325  	return e.Server.SignatureHelp(ctx, params)
  1326  }
  1327  
  1328  func (e *Editor) RenameFile(ctx context.Context, oldPath, newPath string) error {
  1329  	closed, opened, err := e.renameBuffers(oldPath, newPath)
  1330  	if err != nil {
  1331  		return err
  1332  	}
  1333  
  1334  	for _, c := range closed {
  1335  		if err := e.sendDidClose(ctx, c); err != nil {
  1336  			return err
  1337  		}
  1338  	}
  1339  	for _, o := range opened {
  1340  		if err := e.sendDidOpen(ctx, o); err != nil {
  1341  			return err
  1342  		}
  1343  	}
  1344  
  1345  	// Finally, perform the renaming on disk.
  1346  	if err := e.sandbox.Workdir.RenameFile(ctx, oldPath, newPath); err != nil {
  1347  		return fmt.Errorf("renaming sandbox file: %w", err)
  1348  	}
  1349  	return nil
  1350  }
  1351  
  1352  // renameBuffers renames in-memory buffers affected by the renaming of
  1353  // oldPath->newPath, returning the resulting text documents that must be closed
  1354  // and opened over the LSP.
  1355  func (e *Editor) renameBuffers(oldPath, newPath string) (closed []protocol.TextDocumentIdentifier, opened []protocol.TextDocumentItem, _ error) {
  1356  	e.mu.Lock()
  1357  	defer e.mu.Unlock()
  1358  
  1359  	// In case either oldPath or newPath is absolute, convert to absolute paths
  1360  	// before checking for containment.
  1361  	oldAbs := e.sandbox.Workdir.AbsPath(oldPath)
  1362  	newAbs := e.sandbox.Workdir.AbsPath(newPath)
  1363  
  1364  	// Collect buffers that are affected by the given file or directory renaming.
  1365  	buffersToRename := make(map[string]string) // old path -> new path
  1366  
  1367  	for path := range e.buffers {
  1368  		abs := e.sandbox.Workdir.AbsPath(path)
  1369  		if oldAbs == abs || pathutil.InDir(oldAbs, abs) {
  1370  			rel, err := filepath.Rel(oldAbs, abs)
  1371  			if err != nil {
  1372  				return nil, nil, fmt.Errorf("filepath.Rel(%q, %q): %v", oldAbs, abs, err)
  1373  			}
  1374  			nabs := filepath.Join(newAbs, rel)
  1375  			newPath := e.sandbox.Workdir.RelPath(nabs)
  1376  			buffersToRename[path] = newPath
  1377  		}
  1378  	}
  1379  
  1380  	// Update buffers, and build protocol changes.
  1381  	for old, new := range buffersToRename {
  1382  		buf := e.buffers[old]
  1383  		delete(e.buffers, old)
  1384  		buf.version = 1
  1385  		buf.path = new
  1386  		e.buffers[new] = buf
  1387  
  1388  		closed = append(closed, e.TextDocumentIdentifier(old))
  1389  		opened = append(opened, e.textDocumentItem(buf))
  1390  	}
  1391  
  1392  	return closed, opened, nil
  1393  }
  1394  
  1395  func (e *Editor) applyDocumentChange(ctx context.Context, change protocol.DocumentChanges) error {
  1396  	if change.RenameFile != nil {
  1397  		oldPath := e.sandbox.Workdir.URIToPath(change.RenameFile.OldURI)
  1398  		newPath := e.sandbox.Workdir.URIToPath(change.RenameFile.NewURI)
  1399  
  1400  		return e.RenameFile(ctx, oldPath, newPath)
  1401  	}
  1402  	if change.TextDocumentEdit != nil {
  1403  		return e.applyTextDocumentEdit(ctx, *change.TextDocumentEdit)
  1404  	}
  1405  	panic("Internal error: one of RenameFile or TextDocumentEdit must be set")
  1406  }
  1407  
  1408  func (e *Editor) applyTextDocumentEdit(ctx context.Context, change protocol.TextDocumentEdit) error {
  1409  	path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI)
  1410  	if ver := int32(e.BufferVersion(path)); ver != change.TextDocument.Version {
  1411  		return fmt.Errorf("buffer versions for %q do not match: have %d, editing %d", path, ver, change.TextDocument.Version)
  1412  	}
  1413  	if !e.HasBuffer(path) {
  1414  		err := e.OpenFile(ctx, path)
  1415  		if os.IsNotExist(err) {
  1416  			// TODO: it's unclear if this is correct. Here we create the buffer (with
  1417  			// version 1), then apply edits. Perhaps we should apply the edits before
  1418  			// sending the didOpen notification.
  1419  			e.CreateBuffer(ctx, path, "")
  1420  			err = nil
  1421  		}
  1422  		if err != nil {
  1423  			return err
  1424  		}
  1425  	}
  1426  	return e.EditBuffer(ctx, path, protocol.AsTextEdits(change.Edits))
  1427  }
  1428  
  1429  // Config returns the current editor configuration.
  1430  func (e *Editor) Config() EditorConfig {
  1431  	e.mu.Lock()
  1432  	defer e.mu.Unlock()
  1433  	return e.config
  1434  }
  1435  
  1436  func (e *Editor) SetConfig(cfg EditorConfig) {
  1437  	e.mu.Lock()
  1438  	e.config = cfg
  1439  	e.mu.Unlock()
  1440  }
  1441  
  1442  // ChangeConfiguration sets the new editor configuration, and if applicable
  1443  // sends a didChangeConfiguration notification.
  1444  //
  1445  // An error is returned if the change notification failed to send.
  1446  func (e *Editor) ChangeConfiguration(ctx context.Context, newConfig EditorConfig) error {
  1447  	e.SetConfig(newConfig)
  1448  	if e.Server != nil {
  1449  		var params protocol.DidChangeConfigurationParams // empty: gopls ignores the Settings field
  1450  		if err := e.Server.DidChangeConfiguration(ctx, &params); err != nil {
  1451  			return err
  1452  		}
  1453  		e.callsMu.Lock()
  1454  		e.calls.DidChangeConfiguration++
  1455  		e.callsMu.Unlock()
  1456  	}
  1457  	return nil
  1458  }
  1459  
  1460  // ChangeWorkspaceFolders sets the new workspace folders, and sends a
  1461  // didChangeWorkspaceFolders notification to the server.
  1462  //
  1463  // The given folders must all be unique.
  1464  func (e *Editor) ChangeWorkspaceFolders(ctx context.Context, folders []string) error {
  1465  	config := e.Config()
  1466  
  1467  	// capture existing folders so that we can compute the change.
  1468  	oldFolders := makeWorkspaceFolders(e.sandbox, config.WorkspaceFolders)
  1469  	newFolders := makeWorkspaceFolders(e.sandbox, folders)
  1470  	config.WorkspaceFolders = folders
  1471  	e.SetConfig(config)
  1472  
  1473  	if e.Server == nil {
  1474  		return nil
  1475  	}
  1476  
  1477  	var params protocol.DidChangeWorkspaceFoldersParams
  1478  
  1479  	// Keep track of old workspace folders that must be removed.
  1480  	toRemove := make(map[protocol.URI]protocol.WorkspaceFolder)
  1481  	for _, folder := range oldFolders {
  1482  		toRemove[folder.URI] = folder
  1483  	}
  1484  
  1485  	// Sanity check: if we see a folder twice the algorithm below doesn't work,
  1486  	// so track seen folders to ensure that we panic in that case.
  1487  	seen := make(map[protocol.URI]protocol.WorkspaceFolder)
  1488  	for _, folder := range newFolders {
  1489  		if _, ok := seen[folder.URI]; ok {
  1490  			panic(fmt.Sprintf("folder %s seen twice", folder.URI))
  1491  		}
  1492  
  1493  		// If this folder already exists, we don't want to remove it.
  1494  		// Otherwise, we need to add it.
  1495  		if _, ok := toRemove[folder.URI]; ok {
  1496  			delete(toRemove, folder.URI)
  1497  		} else {
  1498  			params.Event.Added = append(params.Event.Added, folder)
  1499  		}
  1500  	}
  1501  
  1502  	for _, v := range toRemove {
  1503  		params.Event.Removed = append(params.Event.Removed, v)
  1504  	}
  1505  
  1506  	return e.Server.DidChangeWorkspaceFolders(ctx, &params)
  1507  }
  1508  
  1509  // CodeAction executes a codeAction request on the server.
  1510  // If loc.Range is zero, the whole file is implied.
  1511  func (e *Editor) CodeAction(ctx context.Context, loc protocol.Location, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
  1512  	if e.Server == nil {
  1513  		return nil, nil
  1514  	}
  1515  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1516  	e.mu.Lock()
  1517  	_, ok := e.buffers[path]
  1518  	e.mu.Unlock()
  1519  	if !ok {
  1520  		return nil, fmt.Errorf("buffer %q is not open", path)
  1521  	}
  1522  	params := &protocol.CodeActionParams{
  1523  		TextDocument: e.TextDocumentIdentifier(path),
  1524  		Context: protocol.CodeActionContext{
  1525  			Diagnostics: diagnostics,
  1526  		},
  1527  		Range: loc.Range, // may be zero
  1528  	}
  1529  	lens, err := e.Server.CodeAction(ctx, params)
  1530  	if err != nil {
  1531  		return nil, err
  1532  	}
  1533  	return lens, nil
  1534  }
  1535  
  1536  func (e *Editor) EditResolveSupport() (bool, error) {
  1537  	capabilities, err := clientCapabilities(e.Config())
  1538  	if err != nil {
  1539  		return false, err
  1540  	}
  1541  	return capabilities.TextDocument.CodeAction.ResolveSupport != nil && slices.Contains(capabilities.TextDocument.CodeAction.ResolveSupport.Properties, "edit"), nil
  1542  }
  1543  
  1544  // Hover triggers a hover at the given position in an open buffer.
  1545  func (e *Editor) Hover(ctx context.Context, loc protocol.Location) (*protocol.MarkupContent, protocol.Location, error) {
  1546  	if err := e.checkBufferLocation(loc); err != nil {
  1547  		return nil, protocol.Location{}, err
  1548  	}
  1549  	params := &protocol.HoverParams{}
  1550  	params.TextDocument.URI = loc.URI
  1551  	params.Position = loc.Range.Start
  1552  
  1553  	resp, err := e.Server.Hover(ctx, params)
  1554  	if err != nil {
  1555  		return nil, protocol.Location{}, fmt.Errorf("hover: %w", err)
  1556  	}
  1557  	if resp == nil {
  1558  		return nil, protocol.Location{}, nil
  1559  	}
  1560  	return &resp.Contents, protocol.Location{URI: loc.URI, Range: resp.Range}, nil
  1561  }
  1562  
  1563  func (e *Editor) DocumentLink(ctx context.Context, path string) ([]protocol.DocumentLink, error) {
  1564  	if e.Server == nil {
  1565  		return nil, nil
  1566  	}
  1567  	params := &protocol.DocumentLinkParams{}
  1568  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
  1569  	return e.Server.DocumentLink(ctx, params)
  1570  }
  1571  
  1572  func (e *Editor) DocumentHighlight(ctx context.Context, loc protocol.Location) ([]protocol.DocumentHighlight, error) {
  1573  	if e.Server == nil {
  1574  		return nil, nil
  1575  	}
  1576  	if err := e.checkBufferLocation(loc); err != nil {
  1577  		return nil, err
  1578  	}
  1579  	params := &protocol.DocumentHighlightParams{}
  1580  	params.TextDocument.URI = loc.URI
  1581  	params.Position = loc.Range.Start
  1582  
  1583  	return e.Server.DocumentHighlight(ctx, params)
  1584  }
  1585  
  1586  // SemanticTokensFull invokes textDocument/semanticTokens/full, and interprets
  1587  // its result.
  1588  func (e *Editor) SemanticTokensFull(ctx context.Context, path string) ([]SemanticToken, error) {
  1589  	p := &protocol.SemanticTokensParams{
  1590  		TextDocument: protocol.TextDocumentIdentifier{
  1591  			URI: e.sandbox.Workdir.URI(path),
  1592  		},
  1593  	}
  1594  	resp, err := e.Server.SemanticTokensFull(ctx, p)
  1595  	if err != nil {
  1596  		return nil, err
  1597  	}
  1598  	content, ok := e.BufferText(path)
  1599  	if !ok {
  1600  		return nil, fmt.Errorf("buffer %s is not open", path)
  1601  	}
  1602  	return e.interpretTokens(resp.Data, content), nil
  1603  }
  1604  
  1605  // SemanticTokensRange invokes textDocument/semanticTokens/range, and
  1606  // interprets its result.
  1607  func (e *Editor) SemanticTokensRange(ctx context.Context, loc protocol.Location) ([]SemanticToken, error) {
  1608  	p := &protocol.SemanticTokensRangeParams{
  1609  		TextDocument: protocol.TextDocumentIdentifier{URI: loc.URI},
  1610  		Range:        loc.Range,
  1611  	}
  1612  	resp, err := e.Server.SemanticTokensRange(ctx, p)
  1613  	if err != nil {
  1614  		return nil, err
  1615  	}
  1616  	path := e.sandbox.Workdir.URIToPath(loc.URI)
  1617  	// As noted above: buffers should be keyed by protocol.DocumentURI.
  1618  	content, ok := e.BufferText(path)
  1619  	if !ok {
  1620  		return nil, fmt.Errorf("buffer %s is not open", path)
  1621  	}
  1622  	return e.interpretTokens(resp.Data, content), nil
  1623  }
  1624  
  1625  // A SemanticToken is an interpreted semantic token value.
  1626  type SemanticToken struct {
  1627  	Token     string
  1628  	TokenType string
  1629  	Mod       string
  1630  }
  1631  
  1632  // Note: previously this function elided comment, string, and number tokens.
  1633  // Instead, filtering of token types should be done by the caller.
  1634  func (e *Editor) interpretTokens(x []uint32, contents string) []SemanticToken {
  1635  	e.mu.Lock()
  1636  	legend := e.semTokOpts.Legend
  1637  	e.mu.Unlock()
  1638  	lines := strings.Split(contents, "\n")
  1639  	ans := []SemanticToken{}
  1640  	line, col := 1, 1
  1641  	for i := 0; i < len(x); i += 5 {
  1642  		line += int(x[i])
  1643  		col += int(x[i+1])
  1644  		if x[i] != 0 { // new line
  1645  			col = int(x[i+1]) + 1 // 1-based column numbers
  1646  		}
  1647  		sz := x[i+2]
  1648  		t := legend.TokenTypes[x[i+3]]
  1649  		l := x[i+4]
  1650  		var mods []string
  1651  		for i, mod := range legend.TokenModifiers {
  1652  			if l&(1<<i) != 0 {
  1653  				mods = append(mods, mod)
  1654  			}
  1655  		}
  1656  		// Preexisting note: "col is a utf-8 offset"
  1657  		// TODO(rfindley): is that true? Or is it UTF-16, like other columns in the LSP?
  1658  		tok := lines[line-1][col-1 : col-1+int(sz)]
  1659  		ans = append(ans, SemanticToken{tok, t, strings.Join(mods, " ")})
  1660  	}
  1661  	return ans
  1662  }