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