github.com/jhump/golang-x-tools@v0.0.0-20220218190644-4958d6d39439/internal/lsp/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  	"bufio"
     9  	"context"
    10  	"fmt"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strings"
    16  	"sync"
    17  
    18  	"github.com/jhump/golang-x-tools/internal/jsonrpc2"
    19  	"github.com/jhump/golang-x-tools/internal/lsp/command"
    20  	"github.com/jhump/golang-x-tools/internal/lsp/protocol"
    21  	"github.com/jhump/golang-x-tools/internal/span"
    22  	errors "golang.org/x/xerrors"
    23  )
    24  
    25  // Editor is a fake editor client.  It keeps track of client state and can be
    26  // used for writing LSP tests.
    27  type Editor struct {
    28  	Config EditorConfig
    29  
    30  	// Server, client, and sandbox are concurrency safe and written only
    31  	// at construction time, so do not require synchronization.
    32  	Server     protocol.Server
    33  	serverConn jsonrpc2.Conn
    34  	client     *Client
    35  	sandbox    *Sandbox
    36  	defaultEnv map[string]string
    37  
    38  	// Since this editor is intended just for testing, we use very coarse
    39  	// locking.
    40  	mu sync.Mutex
    41  	// Editor state.
    42  	buffers map[string]buffer
    43  	// Capabilities / Options
    44  	serverCapabilities protocol.ServerCapabilities
    45  
    46  	// Call metrics for the purpose of expectations. This is done in an ad-hoc
    47  	// manner for now. Perhaps in the future we should do something more
    48  	// systematic. Guarded with a separate mutex as calls may need to be accessed
    49  	// asynchronously via callbacks into the Editor.
    50  	callsMu sync.Mutex
    51  	calls   CallCounts
    52  }
    53  
    54  type CallCounts struct {
    55  	DidOpen, DidChange, DidSave, DidChangeWatchedFiles, DidClose uint64
    56  }
    57  
    58  type buffer struct {
    59  	windowsLineEndings bool
    60  	version            int
    61  	path               string
    62  	lines              []string
    63  	dirty              bool
    64  }
    65  
    66  func (b buffer) text() string {
    67  	eol := "\n"
    68  	if b.windowsLineEndings {
    69  		eol = "\r\n"
    70  	}
    71  	return strings.Join(b.lines, eol)
    72  }
    73  
    74  // EditorConfig configures the editor's LSP session. This is similar to
    75  // source.UserOptions, but we use a separate type here so that we expose only
    76  // that configuration which we support.
    77  //
    78  // The zero value for EditorConfig should correspond to its defaults.
    79  type EditorConfig struct {
    80  	Env        map[string]string
    81  	BuildFlags []string
    82  
    83  	// CodeLenses is a map defining whether codelens are enabled, keyed by the
    84  	// codeLens command. CodeLenses which are not present in this map are left in
    85  	// their default state.
    86  	CodeLenses map[string]bool
    87  
    88  	// SymbolMatcher is the config associated with the "symbolMatcher" gopls
    89  	// config option.
    90  	SymbolMatcher, SymbolStyle *string
    91  
    92  	// LimitWorkspaceScope is true if the user does not want to expand their
    93  	// workspace scope to the entire module.
    94  	LimitWorkspaceScope bool
    95  
    96  	// WorkspaceFolders is the workspace folders to configure on the LSP server,
    97  	// relative to the sandbox workdir.
    98  	//
    99  	// As a special case, if WorkspaceFolders is nil the editor defaults to
   100  	// configuring a single workspace folder corresponding to the workdir root.
   101  	// To explicitly send no workspace folders, use an empty (non-nil) slice.
   102  	WorkspaceFolders []string
   103  
   104  	// EnableStaticcheck enables staticcheck analyzers.
   105  	EnableStaticcheck bool
   106  
   107  	// AllExperiments sets the "allExperiments" configuration, which enables
   108  	// all of gopls's opt-in settings.
   109  	AllExperiments bool
   110  
   111  	// Whether to send the current process ID, for testing data that is joined to
   112  	// the PID. This can only be set by one test.
   113  	SendPID bool
   114  
   115  	// Whether to edit files with windows line endings.
   116  	WindowsLineEndings bool
   117  
   118  	// Map of language ID -> regexp to match, used to set the file type of new
   119  	// buffers. Applied as an overlay on top of the following defaults:
   120  	//  "go" -> ".*\.go"
   121  	//  "go.mod" -> "go\.mod"
   122  	//  "go.sum" -> "go\.sum"
   123  	//  "gotmpl" -> ".*tmpl"
   124  	FileAssociations map[string]string
   125  
   126  	// Settings holds arbitrary additional settings to apply to the gopls config.
   127  	// TODO(rfindley): replace existing EditorConfig fields with Settings.
   128  	Settings map[string]interface{}
   129  
   130  	ImportShortcut                 string
   131  	DirectoryFilters               []string
   132  	VerboseOutput                  bool
   133  	ExperimentalUseInvalidMetadata bool
   134  }
   135  
   136  // NewEditor Creates a new Editor.
   137  func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor {
   138  	return &Editor{
   139  		buffers:    make(map[string]buffer),
   140  		sandbox:    sandbox,
   141  		defaultEnv: sandbox.GoEnv(),
   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  //   editor, err := NewEditor(s).Connect(ctx, conn)
   152  func (e *Editor) Connect(ctx context.Context, conn jsonrpc2.Conn, hooks ClientHooks) (*Editor, error) {
   153  	e.serverConn = conn
   154  	e.Server = protocol.ServerDispatcher(conn)
   155  	e.client = &Client{editor: e, hooks: hooks}
   156  	conn.Go(ctx,
   157  		protocol.Handlers(
   158  			protocol.ClientHandler(e.client,
   159  				jsonrpc2.MethodNotFound)))
   160  	if err := e.initialize(ctx, e.Config.WorkspaceFolders); err != nil {
   161  		return nil, err
   162  	}
   163  	e.sandbox.Workdir.AddWatcher(e.onFileChanges)
   164  	return e, nil
   165  }
   166  
   167  func (e *Editor) Stats() CallCounts {
   168  	e.callsMu.Lock()
   169  	defer e.callsMu.Unlock()
   170  	return e.calls
   171  }
   172  
   173  // Shutdown issues the 'shutdown' LSP notification.
   174  func (e *Editor) Shutdown(ctx context.Context) error {
   175  	if e.Server != nil {
   176  		if err := e.Server.Shutdown(ctx); err != nil {
   177  			return errors.Errorf("Shutdown: %w", err)
   178  		}
   179  	}
   180  	return nil
   181  }
   182  
   183  // Exit issues the 'exit' LSP notification.
   184  func (e *Editor) Exit(ctx context.Context) error {
   185  	if e.Server != nil {
   186  		// Not all LSP clients issue the exit RPC, but we do so here to ensure that
   187  		// we gracefully handle it on multi-session servers.
   188  		if err := e.Server.Exit(ctx); err != nil {
   189  			return errors.Errorf("Exit: %w", err)
   190  		}
   191  	}
   192  	return nil
   193  }
   194  
   195  // Close issues the shutdown and exit sequence an editor should.
   196  func (e *Editor) Close(ctx context.Context) error {
   197  	if err := e.Shutdown(ctx); err != nil {
   198  		return err
   199  	}
   200  	if err := e.Exit(ctx); err != nil {
   201  		return err
   202  	}
   203  	// called close on the editor should result in the connection closing
   204  	select {
   205  	case <-e.serverConn.Done():
   206  		// connection closed itself
   207  		return nil
   208  	case <-ctx.Done():
   209  		return errors.Errorf("connection not closed: %w", ctx.Err())
   210  	}
   211  }
   212  
   213  // Client returns the LSP client for this editor.
   214  func (e *Editor) Client() *Client {
   215  	return e.client
   216  }
   217  
   218  func (e *Editor) overlayEnv() map[string]string {
   219  	env := make(map[string]string)
   220  	for k, v := range e.defaultEnv {
   221  		v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
   222  		env[k] = v
   223  	}
   224  	for k, v := range e.Config.Env {
   225  		v = strings.ReplaceAll(v, "$SANDBOX_WORKDIR", e.sandbox.Workdir.RootURI().SpanURI().Filename())
   226  		env[k] = v
   227  	}
   228  	return env
   229  }
   230  
   231  func (e *Editor) configuration() map[string]interface{} {
   232  	config := map[string]interface{}{
   233  		"verboseWorkDoneProgress": true,
   234  		"env":                     e.overlayEnv(),
   235  		"expandWorkspaceToModule": !e.Config.LimitWorkspaceScope,
   236  		"completionBudget":        "10s",
   237  	}
   238  
   239  	for k, v := range e.Config.Settings {
   240  		config[k] = v
   241  	}
   242  
   243  	if e.Config.BuildFlags != nil {
   244  		config["buildFlags"] = e.Config.BuildFlags
   245  	}
   246  	if e.Config.DirectoryFilters != nil {
   247  		config["directoryFilters"] = e.Config.DirectoryFilters
   248  	}
   249  	if e.Config.ExperimentalUseInvalidMetadata {
   250  		config["experimentalUseInvalidMetadata"] = true
   251  	}
   252  	if e.Config.CodeLenses != nil {
   253  		config["codelenses"] = e.Config.CodeLenses
   254  	}
   255  	if e.Config.SymbolMatcher != nil {
   256  		config["symbolMatcher"] = *e.Config.SymbolMatcher
   257  	}
   258  	if e.Config.SymbolStyle != nil {
   259  		config["symbolStyle"] = *e.Config.SymbolStyle
   260  	}
   261  	if e.Config.EnableStaticcheck {
   262  		config["staticcheck"] = true
   263  	}
   264  	if e.Config.AllExperiments {
   265  		config["allExperiments"] = true
   266  	}
   267  
   268  	if e.Config.VerboseOutput {
   269  		config["verboseOutput"] = true
   270  	}
   271  
   272  	if e.Config.ImportShortcut != "" {
   273  		config["importShortcut"] = e.Config.ImportShortcut
   274  	}
   275  
   276  	config["diagnosticsDelay"] = "10ms"
   277  
   278  	// ExperimentalWorkspaceModule is only set as a mode, not a configuration.
   279  	return config
   280  }
   281  
   282  func (e *Editor) initialize(ctx context.Context, workspaceFolders []string) error {
   283  	params := &protocol.ParamInitialize{}
   284  	params.ClientInfo.Name = "fakeclient"
   285  	params.ClientInfo.Version = "v1.0.0"
   286  
   287  	if workspaceFolders == nil {
   288  		workspaceFolders = []string{string(e.sandbox.Workdir.RelativeTo)}
   289  	}
   290  	for _, folder := range workspaceFolders {
   291  		params.WorkspaceFolders = append(params.WorkspaceFolders, protocol.WorkspaceFolder{
   292  			URI:  string(e.sandbox.Workdir.URI(folder)),
   293  			Name: filepath.Base(folder),
   294  		})
   295  	}
   296  
   297  	params.Capabilities.Workspace.Configuration = true
   298  	params.Capabilities.Window.WorkDoneProgress = true
   299  	// TODO: set client capabilities
   300  	params.Capabilities.TextDocument.Completion.CompletionItem.TagSupport.ValueSet = []protocol.CompletionItemTag{protocol.ComplDeprecated}
   301  	params.InitializationOptions = e.configuration()
   302  	if e.Config.SendPID {
   303  		params.ProcessID = int32(os.Getpid())
   304  	}
   305  
   306  	params.Capabilities.TextDocument.Completion.CompletionItem.SnippetSupport = true
   307  	params.Capabilities.TextDocument.SemanticTokens.Requests.Full = true
   308  	// copied from lsp/semantic.go to avoid import cycle in tests
   309  	params.Capabilities.TextDocument.SemanticTokens.TokenTypes = []string{
   310  		"namespace", "type", "class", "enum", "interface",
   311  		"struct", "typeParameter", "parameter", "variable", "property", "enumMember",
   312  		"event", "function", "method", "macro", "keyword", "modifier", "comment",
   313  		"string", "number", "regexp", "operator",
   314  	}
   315  
   316  	// This is a bit of a hack, since the fake editor doesn't actually support
   317  	// watching changed files that match a specific glob pattern. However, the
   318  	// editor does send didChangeWatchedFiles notifications, so set this to
   319  	// true.
   320  	params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true
   321  
   322  	params.Trace = "messages"
   323  	// TODO: support workspace folders.
   324  	if e.Server != nil {
   325  		resp, err := e.Server.Initialize(ctx, params)
   326  		if err != nil {
   327  			return errors.Errorf("initialize: %w", err)
   328  		}
   329  		e.mu.Lock()
   330  		e.serverCapabilities = resp.Capabilities
   331  		e.mu.Unlock()
   332  
   333  		if err := e.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
   334  			return errors.Errorf("initialized: %w", err)
   335  		}
   336  	}
   337  	// TODO: await initial configuration here, or expect gopls to manage that?
   338  	return nil
   339  }
   340  
   341  // onFileChanges is registered to be called by the Workdir on any writes that
   342  // go through the Workdir API. It is called synchronously by the Workdir.
   343  func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
   344  	if e.Server == nil {
   345  		return
   346  	}
   347  
   348  	// e may be locked when onFileChanges is called, but it is important that we
   349  	// synchronously increment this counter so that we can subsequently assert on
   350  	// the number of expected DidChangeWatchedFiles calls.
   351  	e.callsMu.Lock()
   352  	e.calls.DidChangeWatchedFiles++
   353  	e.callsMu.Unlock()
   354  
   355  	// Since e may be locked, we must run this mutation asynchronously.
   356  	go func() {
   357  		e.mu.Lock()
   358  		defer e.mu.Unlock()
   359  		var lspevts []protocol.FileEvent
   360  		for _, evt := range evts {
   361  			// Always send an on-disk change, even for events that seem useless
   362  			// because they're shadowed by an open buffer.
   363  			lspevts = append(lspevts, evt.ProtocolEvent)
   364  
   365  			if buf, ok := e.buffers[evt.Path]; ok {
   366  				// Following VS Code, don't honor deletions or changes to dirty buffers.
   367  				if buf.dirty || evt.ProtocolEvent.Type == protocol.Deleted {
   368  					continue
   369  				}
   370  
   371  				content, err := e.sandbox.Workdir.ReadFile(evt.Path)
   372  				if err != nil {
   373  					continue // A race with some other operation.
   374  				}
   375  				// No need to update if the buffer content hasn't changed.
   376  				if content == buf.text() {
   377  					continue
   378  				}
   379  				// During shutdown, this call will fail. Ignore the error.
   380  				_ = e.setBufferContentLocked(ctx, evt.Path, false, lines(content), nil)
   381  			}
   382  		}
   383  		e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
   384  			Changes: lspevts,
   385  		})
   386  	}()
   387  }
   388  
   389  // OpenFile creates a buffer for the given workdir-relative file.
   390  func (e *Editor) OpenFile(ctx context.Context, path string) error {
   391  	content, err := e.sandbox.Workdir.ReadFile(path)
   392  	if err != nil {
   393  		return err
   394  	}
   395  	return e.createBuffer(ctx, path, false, content)
   396  }
   397  
   398  // CreateBuffer creates a new unsaved buffer corresponding to the workdir path,
   399  // containing the given textual content.
   400  func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
   401  	return e.createBuffer(ctx, path, true, content)
   402  }
   403  
   404  func (e *Editor) createBuffer(ctx context.Context, path string, dirty bool, content string) error {
   405  	buf := buffer{
   406  		windowsLineEndings: e.Config.WindowsLineEndings,
   407  		version:            1,
   408  		path:               path,
   409  		lines:              lines(content),
   410  		dirty:              dirty,
   411  	}
   412  	e.mu.Lock()
   413  	defer e.mu.Unlock()
   414  	e.buffers[path] = buf
   415  
   416  	item := protocol.TextDocumentItem{
   417  		URI:        e.sandbox.Workdir.URI(buf.path),
   418  		LanguageID: e.languageID(buf.path),
   419  		Version:    int32(buf.version),
   420  		Text:       buf.text(),
   421  	}
   422  
   423  	if e.Server != nil {
   424  		if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
   425  			TextDocument: item,
   426  		}); err != nil {
   427  			return errors.Errorf("DidOpen: %w", err)
   428  		}
   429  		e.callsMu.Lock()
   430  		e.calls.DidOpen++
   431  		e.callsMu.Unlock()
   432  	}
   433  	return nil
   434  }
   435  
   436  var defaultFileAssociations = map[string]*regexp.Regexp{
   437  	"go":      regexp.MustCompile(`^.*\.go$`), // '$' is important: don't match .gotmpl!
   438  	"go.mod":  regexp.MustCompile(`^go\.mod$`),
   439  	"go.sum":  regexp.MustCompile(`^go(\.work)?\.sum$`),
   440  	"go.work": regexp.MustCompile(`^go\.work$`),
   441  	"gotmpl":  regexp.MustCompile(`^.*tmpl$`),
   442  }
   443  
   444  func (e *Editor) languageID(p string) string {
   445  	base := path.Base(p)
   446  	for lang, re := range e.Config.FileAssociations {
   447  		re := regexp.MustCompile(re)
   448  		if re.MatchString(base) {
   449  			return lang
   450  		}
   451  	}
   452  	for lang, re := range defaultFileAssociations {
   453  		if re.MatchString(base) {
   454  			return lang
   455  		}
   456  	}
   457  	return ""
   458  }
   459  
   460  // lines returns line-ending agnostic line representation of content.
   461  func lines(content string) []string {
   462  	lines := strings.Split(content, "\n")
   463  	for i, l := range lines {
   464  		lines[i] = strings.TrimSuffix(l, "\r")
   465  	}
   466  	return lines
   467  }
   468  
   469  // CloseBuffer removes the current buffer (regardless of whether it is saved).
   470  func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
   471  	e.mu.Lock()
   472  	_, ok := e.buffers[path]
   473  	if !ok {
   474  		e.mu.Unlock()
   475  		return ErrUnknownBuffer
   476  	}
   477  	delete(e.buffers, path)
   478  	e.mu.Unlock()
   479  
   480  	if e.Server != nil {
   481  		if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
   482  			TextDocument: e.textDocumentIdentifier(path),
   483  		}); err != nil {
   484  			return errors.Errorf("DidClose: %w", err)
   485  		}
   486  		e.callsMu.Lock()
   487  		e.calls.DidClose++
   488  		e.callsMu.Unlock()
   489  	}
   490  	return nil
   491  }
   492  
   493  func (e *Editor) textDocumentIdentifier(path string) protocol.TextDocumentIdentifier {
   494  	return protocol.TextDocumentIdentifier{
   495  		URI: e.sandbox.Workdir.URI(path),
   496  	}
   497  }
   498  
   499  // SaveBuffer writes the content of the buffer specified by the given path to
   500  // the filesystem.
   501  func (e *Editor) SaveBuffer(ctx context.Context, path string) error {
   502  	if err := e.OrganizeImports(ctx, path); err != nil {
   503  		return errors.Errorf("organizing imports before save: %w", err)
   504  	}
   505  	if err := e.FormatBuffer(ctx, path); err != nil {
   506  		return errors.Errorf("formatting before save: %w", err)
   507  	}
   508  	return e.SaveBufferWithoutActions(ctx, path)
   509  }
   510  
   511  func (e *Editor) SaveBufferWithoutActions(ctx context.Context, path string) error {
   512  	e.mu.Lock()
   513  	defer e.mu.Unlock()
   514  	buf, ok := e.buffers[path]
   515  	if !ok {
   516  		return fmt.Errorf(fmt.Sprintf("unknown buffer: %q", path))
   517  	}
   518  	content := buf.text()
   519  	includeText := false
   520  	syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions)
   521  	if ok {
   522  		includeText = syncOptions.Save.IncludeText
   523  	}
   524  
   525  	docID := e.textDocumentIdentifier(buf.path)
   526  	if e.Server != nil {
   527  		if err := e.Server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{
   528  			TextDocument: docID,
   529  			Reason:       protocol.Manual,
   530  		}); err != nil {
   531  			return errors.Errorf("WillSave: %w", err)
   532  		}
   533  	}
   534  	if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil {
   535  		return errors.Errorf("writing %q: %w", path, err)
   536  	}
   537  
   538  	buf.dirty = false
   539  	e.buffers[path] = buf
   540  
   541  	if e.Server != nil {
   542  		params := &protocol.DidSaveTextDocumentParams{
   543  			TextDocument: docID,
   544  		}
   545  		if includeText {
   546  			params.Text = &content
   547  		}
   548  		if err := e.Server.DidSave(ctx, params); err != nil {
   549  			return errors.Errorf("DidSave: %w", err)
   550  		}
   551  		e.callsMu.Lock()
   552  		e.calls.DidSave++
   553  		e.callsMu.Unlock()
   554  	}
   555  	return nil
   556  }
   557  
   558  // contentPosition returns the (Line, Column) position corresponding to offset
   559  // in the buffer referenced by path.
   560  func contentPosition(content string, offset int) (Pos, error) {
   561  	scanner := bufio.NewScanner(strings.NewReader(content))
   562  	start := 0
   563  	line := 0
   564  	for scanner.Scan() {
   565  		end := start + len([]rune(scanner.Text())) + 1
   566  		if offset < end {
   567  			return Pos{Line: line, Column: offset - start}, nil
   568  		}
   569  		start = end
   570  		line++
   571  	}
   572  	if err := scanner.Err(); err != nil {
   573  		return Pos{}, errors.Errorf("scanning content: %w", err)
   574  	}
   575  	// Scan() will drop the last line if it is empty. Correct for this.
   576  	if (strings.HasSuffix(content, "\n") || content == "") && offset == start {
   577  		return Pos{Line: line, Column: 0}, nil
   578  	}
   579  	return Pos{}, fmt.Errorf("position %d out of bounds in %q (line = %d, start = %d)", offset, content, line, start)
   580  }
   581  
   582  // ErrNoMatch is returned if a regexp search fails.
   583  var (
   584  	ErrNoMatch       = errors.New("no match")
   585  	ErrUnknownBuffer = errors.New("unknown buffer")
   586  )
   587  
   588  // regexpRange returns the start and end of the first occurrence of either re
   589  // or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match.
   590  func regexpRange(content, re string) (Pos, Pos, error) {
   591  	content = normalizeEOL(content)
   592  	var start, end int
   593  	rec, err := regexp.Compile(re)
   594  	if err != nil {
   595  		return Pos{}, Pos{}, err
   596  	}
   597  	indexes := rec.FindStringSubmatchIndex(content)
   598  	if indexes == nil {
   599  		return Pos{}, Pos{}, ErrNoMatch
   600  	}
   601  	switch len(indexes) {
   602  	case 2:
   603  		// no subgroups: return the range of the regexp expression
   604  		start, end = indexes[0], indexes[1]
   605  	case 4:
   606  		// one subgroup: return its range
   607  		start, end = indexes[2], indexes[3]
   608  	default:
   609  		return Pos{}, Pos{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1)
   610  	}
   611  	startPos, err := contentPosition(content, start)
   612  	if err != nil {
   613  		return Pos{}, Pos{}, err
   614  	}
   615  	endPos, err := contentPosition(content, end)
   616  	if err != nil {
   617  		return Pos{}, Pos{}, err
   618  	}
   619  	return startPos, endPos, nil
   620  }
   621  
   622  func normalizeEOL(content string) string {
   623  	return strings.Join(lines(content), "\n")
   624  }
   625  
   626  // RegexpRange returns the first range in the buffer bufName matching re. See
   627  // RegexpSearch for more information on matching.
   628  func (e *Editor) RegexpRange(bufName, re string) (Pos, Pos, error) {
   629  	e.mu.Lock()
   630  	defer e.mu.Unlock()
   631  	buf, ok := e.buffers[bufName]
   632  	if !ok {
   633  		return Pos{}, Pos{}, ErrUnknownBuffer
   634  	}
   635  	return regexpRange(buf.text(), re)
   636  }
   637  
   638  // RegexpSearch returns the position of the first match for re in the buffer
   639  // bufName. For convenience, RegexpSearch supports the following two modes:
   640  //  1. If re has no subgroups, return the position of the match for re itself.
   641  //  2. If re has one subgroup, return the position of the first subgroup.
   642  // It returns an error re is invalid, has more than one subgroup, or doesn't
   643  // match the buffer.
   644  func (e *Editor) RegexpSearch(bufName, re string) (Pos, error) {
   645  	start, _, err := e.RegexpRange(bufName, re)
   646  	return start, err
   647  }
   648  
   649  // RegexpReplace edits the buffer corresponding to path by replacing the first
   650  // instance of re, or its first subgroup, with the replace text. See
   651  // RegexpSearch for more explanation of these two modes.
   652  // It returns an error if re is invalid, has more than one subgroup, or doesn't
   653  // match the buffer.
   654  func (e *Editor) RegexpReplace(ctx context.Context, path, re, replace string) error {
   655  	e.mu.Lock()
   656  	defer e.mu.Unlock()
   657  	buf, ok := e.buffers[path]
   658  	if !ok {
   659  		return ErrUnknownBuffer
   660  	}
   661  	content := buf.text()
   662  	start, end, err := regexpRange(content, re)
   663  	if err != nil {
   664  		return err
   665  	}
   666  	return e.editBufferLocked(ctx, path, []Edit{{
   667  		Start: start,
   668  		End:   end,
   669  		Text:  replace,
   670  	}})
   671  }
   672  
   673  // EditBuffer applies the given test edits to the buffer identified by path.
   674  func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) error {
   675  	e.mu.Lock()
   676  	defer e.mu.Unlock()
   677  	return e.editBufferLocked(ctx, path, edits)
   678  }
   679  
   680  func (e *Editor) SetBufferContent(ctx context.Context, path, content string) error {
   681  	e.mu.Lock()
   682  	defer e.mu.Unlock()
   683  	lines := lines(content)
   684  	return e.setBufferContentLocked(ctx, path, true, lines, nil)
   685  }
   686  
   687  // HasBuffer reports whether the file name is open in the editor.
   688  func (e *Editor) HasBuffer(name string) bool {
   689  	e.mu.Lock()
   690  	defer e.mu.Unlock()
   691  	_, ok := e.buffers[name]
   692  	return ok
   693  }
   694  
   695  // BufferText returns the content of the buffer with the given name.
   696  func (e *Editor) BufferText(name string) string {
   697  	e.mu.Lock()
   698  	defer e.mu.Unlock()
   699  	return e.buffers[name].text()
   700  }
   701  
   702  // BufferVersion returns the current version of the buffer corresponding to
   703  // name (or 0 if it is not being edited).
   704  func (e *Editor) BufferVersion(name string) int {
   705  	e.mu.Lock()
   706  	defer e.mu.Unlock()
   707  	return e.buffers[name].version
   708  }
   709  
   710  func (e *Editor) editBufferLocked(ctx context.Context, path string, edits []Edit) error {
   711  	buf, ok := e.buffers[path]
   712  	if !ok {
   713  		return fmt.Errorf("unknown buffer %q", path)
   714  	}
   715  	content := make([]string, len(buf.lines))
   716  	copy(content, buf.lines)
   717  	content, err := editContent(content, edits)
   718  	if err != nil {
   719  		return err
   720  	}
   721  	return e.setBufferContentLocked(ctx, path, true, content, edits)
   722  }
   723  
   724  func (e *Editor) setBufferContentLocked(ctx context.Context, path string, dirty bool, content []string, fromEdits []Edit) error {
   725  	buf, ok := e.buffers[path]
   726  	if !ok {
   727  		return fmt.Errorf("unknown buffer %q", path)
   728  	}
   729  	buf.lines = content
   730  	buf.version++
   731  	buf.dirty = dirty
   732  	e.buffers[path] = buf
   733  	// A simple heuristic: if there is only one edit, send it incrementally.
   734  	// Otherwise, send the entire content.
   735  	var evts []protocol.TextDocumentContentChangeEvent
   736  	if len(fromEdits) == 1 {
   737  		evts = append(evts, fromEdits[0].toProtocolChangeEvent())
   738  	} else {
   739  		evts = append(evts, protocol.TextDocumentContentChangeEvent{
   740  			Text: buf.text(),
   741  		})
   742  	}
   743  	params := &protocol.DidChangeTextDocumentParams{
   744  		TextDocument: protocol.VersionedTextDocumentIdentifier{
   745  			Version:                int32(buf.version),
   746  			TextDocumentIdentifier: e.textDocumentIdentifier(buf.path),
   747  		},
   748  		ContentChanges: evts,
   749  	}
   750  	if e.Server != nil {
   751  		if err := e.Server.DidChange(ctx, params); err != nil {
   752  			return errors.Errorf("DidChange: %w", err)
   753  		}
   754  		e.callsMu.Lock()
   755  		e.calls.DidChange++
   756  		e.callsMu.Unlock()
   757  	}
   758  	return nil
   759  }
   760  
   761  // GoToDefinition jumps to the definition of the symbol at the given position
   762  // in an open buffer. It returns the path and position of the resulting jump.
   763  func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
   764  	if err := e.checkBufferPosition(path, pos); err != nil {
   765  		return "", Pos{}, err
   766  	}
   767  	params := &protocol.DefinitionParams{}
   768  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
   769  	params.Position = pos.ToProtocolPosition()
   770  
   771  	resp, err := e.Server.Definition(ctx, params)
   772  	if err != nil {
   773  		return "", Pos{}, errors.Errorf("definition: %w", err)
   774  	}
   775  	return e.extractFirstPathAndPos(ctx, resp)
   776  }
   777  
   778  // GoToTypeDefinition jumps to the type definition of the symbol at the given position
   779  // in an open buffer.
   780  func (e *Editor) GoToTypeDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
   781  	if err := e.checkBufferPosition(path, pos); err != nil {
   782  		return "", Pos{}, err
   783  	}
   784  	params := &protocol.TypeDefinitionParams{}
   785  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
   786  	params.Position = pos.ToProtocolPosition()
   787  
   788  	resp, err := e.Server.TypeDefinition(ctx, params)
   789  	if err != nil {
   790  		return "", Pos{}, errors.Errorf("type definition: %w", err)
   791  	}
   792  	return e.extractFirstPathAndPos(ctx, resp)
   793  }
   794  
   795  // extractFirstPathAndPos returns the path and the position of the first location.
   796  // It opens the file if needed.
   797  func (e *Editor) extractFirstPathAndPos(ctx context.Context, locs []protocol.Location) (string, Pos, error) {
   798  	if len(locs) == 0 {
   799  		return "", Pos{}, nil
   800  	}
   801  
   802  	newPath := e.sandbox.Workdir.URIToPath(locs[0].URI)
   803  	newPos := fromProtocolPosition(locs[0].Range.Start)
   804  	if !e.HasBuffer(newPath) {
   805  		if err := e.OpenFile(ctx, newPath); err != nil {
   806  			return "", Pos{}, errors.Errorf("OpenFile: %w", err)
   807  		}
   808  	}
   809  	return newPath, newPos, nil
   810  }
   811  
   812  // Symbol performs a workspace symbol search using query
   813  func (e *Editor) Symbol(ctx context.Context, query string) ([]SymbolInformation, error) {
   814  	params := &protocol.WorkspaceSymbolParams{}
   815  	params.Query = query
   816  
   817  	resp, err := e.Server.Symbol(ctx, params)
   818  	if err != nil {
   819  		return nil, errors.Errorf("symbol: %w", err)
   820  	}
   821  	var res []SymbolInformation
   822  	for _, si := range resp {
   823  		ploc := si.Location
   824  		path := e.sandbox.Workdir.URIToPath(ploc.URI)
   825  		start := fromProtocolPosition(ploc.Range.Start)
   826  		end := fromProtocolPosition(ploc.Range.End)
   827  		rnge := Range{
   828  			Start: start,
   829  			End:   end,
   830  		}
   831  		loc := Location{
   832  			Path:  path,
   833  			Range: rnge,
   834  		}
   835  		res = append(res, SymbolInformation{
   836  			Name:     si.Name,
   837  			Kind:     si.Kind,
   838  			Location: loc,
   839  		})
   840  	}
   841  	return res, nil
   842  }
   843  
   844  // OrganizeImports requests and performs the source.organizeImports codeAction.
   845  func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
   846  	_, err := e.applyCodeActions(ctx, path, nil, nil, protocol.SourceOrganizeImports)
   847  	return err
   848  }
   849  
   850  // RefactorRewrite requests and performs the source.refactorRewrite codeAction.
   851  func (e *Editor) RefactorRewrite(ctx context.Context, path string, rng *protocol.Range) error {
   852  	applied, err := e.applyCodeActions(ctx, path, rng, nil, protocol.RefactorRewrite)
   853  	if applied == 0 {
   854  		return errors.Errorf("no refactorings were applied")
   855  	}
   856  	return err
   857  }
   858  
   859  // ApplyQuickFixes requests and performs the quickfix codeAction.
   860  func (e *Editor) ApplyQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) error {
   861  	applied, err := e.applyCodeActions(ctx, path, rng, diagnostics, protocol.SourceFixAll, protocol.QuickFix)
   862  	if applied == 0 {
   863  		return errors.Errorf("no quick fixes were applied")
   864  	}
   865  	return err
   866  }
   867  
   868  // ApplyCodeAction applies the given code action.
   869  func (e *Editor) ApplyCodeAction(ctx context.Context, action protocol.CodeAction) error {
   870  	for _, change := range action.Edit.DocumentChanges {
   871  		path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI)
   872  		if int32(e.buffers[path].version) != change.TextDocument.Version {
   873  			// Skip edits for old versions.
   874  			continue
   875  		}
   876  		edits := convertEdits(change.Edits)
   877  		if err := e.EditBuffer(ctx, path, edits); err != nil {
   878  			return errors.Errorf("editing buffer %q: %w", path, err)
   879  		}
   880  	}
   881  	// Execute any commands. The specification says that commands are
   882  	// executed after edits are applied.
   883  	if action.Command != nil {
   884  		if _, err := e.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{
   885  			Command:   action.Command.Command,
   886  			Arguments: action.Command.Arguments,
   887  		}); err != nil {
   888  			return err
   889  		}
   890  	}
   891  	// Some commands may edit files on disk.
   892  	return e.sandbox.Workdir.CheckForFileChanges(ctx)
   893  }
   894  
   895  // GetQuickFixes returns the available quick fix code actions.
   896  func (e *Editor) GetQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
   897  	return e.getCodeActions(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
   898  }
   899  
   900  func (e *Editor) applyCodeActions(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) (int, error) {
   901  	actions, err := e.getCodeActions(ctx, path, rng, diagnostics, only...)
   902  	if err != nil {
   903  		return 0, err
   904  	}
   905  	applied := 0
   906  	for _, action := range actions {
   907  		if action.Title == "" {
   908  			return 0, errors.Errorf("empty title for code action")
   909  		}
   910  		var match bool
   911  		for _, o := range only {
   912  			if action.Kind == o {
   913  				match = true
   914  				break
   915  			}
   916  		}
   917  		if !match {
   918  			continue
   919  		}
   920  		applied++
   921  		if err := e.ApplyCodeAction(ctx, action); err != nil {
   922  			return 0, err
   923  		}
   924  	}
   925  	return applied, nil
   926  }
   927  
   928  func (e *Editor) getCodeActions(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) ([]protocol.CodeAction, error) {
   929  	if e.Server == nil {
   930  		return nil, nil
   931  	}
   932  	params := &protocol.CodeActionParams{}
   933  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
   934  	params.Context.Only = only
   935  	if diagnostics != nil {
   936  		params.Context.Diagnostics = diagnostics
   937  	}
   938  	if rng != nil {
   939  		params.Range = *rng
   940  	}
   941  	return e.Server.CodeAction(ctx, params)
   942  }
   943  
   944  func (e *Editor) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
   945  	if e.Server == nil {
   946  		return nil, nil
   947  	}
   948  	var match bool
   949  	// Ensure that this command was actually listed as a supported command.
   950  	for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands {
   951  		if command == params.Command {
   952  			match = true
   953  			break
   954  		}
   955  	}
   956  	if !match {
   957  		return nil, fmt.Errorf("unsupported command %q", params.Command)
   958  	}
   959  	result, err := e.Server.ExecuteCommand(ctx, params)
   960  	if err != nil {
   961  		return nil, err
   962  	}
   963  	// Some commands use the go command, which writes directly to disk.
   964  	// For convenience, check for those changes.
   965  	if err := e.sandbox.Workdir.CheckForFileChanges(ctx); err != nil {
   966  		return nil, err
   967  	}
   968  	return result, nil
   969  }
   970  
   971  func convertEdits(protocolEdits []protocol.TextEdit) []Edit {
   972  	var edits []Edit
   973  	for _, lspEdit := range protocolEdits {
   974  		edits = append(edits, fromProtocolTextEdit(lspEdit))
   975  	}
   976  	return edits
   977  }
   978  
   979  // FormatBuffer gofmts a Go file.
   980  func (e *Editor) FormatBuffer(ctx context.Context, path string) error {
   981  	if e.Server == nil {
   982  		return nil
   983  	}
   984  	e.mu.Lock()
   985  	version := e.buffers[path].version
   986  	e.mu.Unlock()
   987  	params := &protocol.DocumentFormattingParams{}
   988  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
   989  	resp, err := e.Server.Formatting(ctx, params)
   990  	if err != nil {
   991  		return errors.Errorf("textDocument/formatting: %w", err)
   992  	}
   993  	e.mu.Lock()
   994  	defer e.mu.Unlock()
   995  	if versionAfter := e.buffers[path].version; versionAfter != version {
   996  		return fmt.Errorf("before receipt of formatting edits, buffer version changed from %d to %d", version, versionAfter)
   997  	}
   998  	edits := convertEdits(resp)
   999  	if len(edits) == 0 {
  1000  		return nil
  1001  	}
  1002  	return e.editBufferLocked(ctx, path, edits)
  1003  }
  1004  
  1005  func (e *Editor) checkBufferPosition(path string, pos Pos) error {
  1006  	e.mu.Lock()
  1007  	defer e.mu.Unlock()
  1008  	buf, ok := e.buffers[path]
  1009  	if !ok {
  1010  		return fmt.Errorf("buffer %q is not open", path)
  1011  	}
  1012  	if !inText(pos, buf.lines) {
  1013  		return fmt.Errorf("position %v is invalid in buffer %q", pos, path)
  1014  	}
  1015  	return nil
  1016  }
  1017  
  1018  // RunGenerate runs `go generate` non-recursively in the workdir-relative dir
  1019  // path. It does not report any resulting file changes as a watched file
  1020  // change, so must be followed by a call to Workdir.CheckForFileChanges once
  1021  // the generate command has completed.
  1022  // TODO(rFindley): this shouldn't be necessary anymore. Delete it.
  1023  func (e *Editor) RunGenerate(ctx context.Context, dir string) error {
  1024  	if e.Server == nil {
  1025  		return nil
  1026  	}
  1027  	absDir := e.sandbox.Workdir.AbsPath(dir)
  1028  	cmd, err := command.NewGenerateCommand("", command.GenerateArgs{
  1029  		Dir:       protocol.URIFromSpanURI(span.URIFromPath(absDir)),
  1030  		Recursive: false,
  1031  	})
  1032  	if err != nil {
  1033  		return err
  1034  	}
  1035  	params := &protocol.ExecuteCommandParams{
  1036  		Command:   cmd.Command,
  1037  		Arguments: cmd.Arguments,
  1038  	}
  1039  	if _, err := e.ExecuteCommand(ctx, params); err != nil {
  1040  		return fmt.Errorf("running generate: %v", err)
  1041  	}
  1042  	// Unfortunately we can't simply poll the workdir for file changes here,
  1043  	// because server-side command may not have completed. In regtests, we can
  1044  	// Await this state change, but here we must delegate that responsibility to
  1045  	// the caller.
  1046  	return nil
  1047  }
  1048  
  1049  // CodeLens executes a codelens request on the server.
  1050  func (e *Editor) CodeLens(ctx context.Context, path string) ([]protocol.CodeLens, error) {
  1051  	if e.Server == nil {
  1052  		return nil, nil
  1053  	}
  1054  	e.mu.Lock()
  1055  	_, ok := e.buffers[path]
  1056  	e.mu.Unlock()
  1057  	if !ok {
  1058  		return nil, fmt.Errorf("buffer %q is not open", path)
  1059  	}
  1060  	params := &protocol.CodeLensParams{
  1061  		TextDocument: e.textDocumentIdentifier(path),
  1062  	}
  1063  	lens, err := e.Server.CodeLens(ctx, params)
  1064  	if err != nil {
  1065  		return nil, err
  1066  	}
  1067  	return lens, nil
  1068  }
  1069  
  1070  // Completion executes a completion request on the server.
  1071  func (e *Editor) Completion(ctx context.Context, path string, pos Pos) (*protocol.CompletionList, error) {
  1072  	if e.Server == nil {
  1073  		return nil, nil
  1074  	}
  1075  	e.mu.Lock()
  1076  	_, ok := e.buffers[path]
  1077  	e.mu.Unlock()
  1078  	if !ok {
  1079  		return nil, fmt.Errorf("buffer %q is not open", path)
  1080  	}
  1081  	params := &protocol.CompletionParams{
  1082  		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
  1083  			TextDocument: e.textDocumentIdentifier(path),
  1084  			Position:     pos.ToProtocolPosition(),
  1085  		},
  1086  	}
  1087  	completions, err := e.Server.Completion(ctx, params)
  1088  	if err != nil {
  1089  		return nil, err
  1090  	}
  1091  	return completions, nil
  1092  }
  1093  
  1094  // AcceptCompletion accepts a completion for the given item at the given
  1095  // position.
  1096  func (e *Editor) AcceptCompletion(ctx context.Context, path string, pos Pos, item protocol.CompletionItem) error {
  1097  	if e.Server == nil {
  1098  		return nil
  1099  	}
  1100  	e.mu.Lock()
  1101  	defer e.mu.Unlock()
  1102  	_, ok := e.buffers[path]
  1103  	if !ok {
  1104  		return fmt.Errorf("buffer %q is not open", path)
  1105  	}
  1106  	return e.editBufferLocked(ctx, path, convertEdits(append([]protocol.TextEdit{
  1107  		*item.TextEdit,
  1108  	}, item.AdditionalTextEdits...)))
  1109  }
  1110  
  1111  // Symbols executes a workspace/symbols request on the server.
  1112  func (e *Editor) Symbols(ctx context.Context, sym string) ([]protocol.SymbolInformation, error) {
  1113  	if e.Server == nil {
  1114  		return nil, nil
  1115  	}
  1116  	params := &protocol.WorkspaceSymbolParams{Query: sym}
  1117  	ans, err := e.Server.Symbol(ctx, params)
  1118  	return ans, err
  1119  }
  1120  
  1121  // References executes a reference request on the server.
  1122  func (e *Editor) References(ctx context.Context, path string, pos Pos) ([]protocol.Location, error) {
  1123  	if e.Server == nil {
  1124  		return nil, nil
  1125  	}
  1126  	e.mu.Lock()
  1127  	_, ok := e.buffers[path]
  1128  	e.mu.Unlock()
  1129  	if !ok {
  1130  		return nil, fmt.Errorf("buffer %q is not open", path)
  1131  	}
  1132  	params := &protocol.ReferenceParams{
  1133  		TextDocumentPositionParams: protocol.TextDocumentPositionParams{
  1134  			TextDocument: e.textDocumentIdentifier(path),
  1135  			Position:     pos.ToProtocolPosition(),
  1136  		},
  1137  		Context: protocol.ReferenceContext{
  1138  			IncludeDeclaration: true,
  1139  		},
  1140  	}
  1141  	locations, err := e.Server.References(ctx, params)
  1142  	if err != nil {
  1143  		return nil, err
  1144  	}
  1145  	return locations, nil
  1146  }
  1147  
  1148  func (e *Editor) Rename(ctx context.Context, path string, pos Pos, newName string) error {
  1149  	if e.Server == nil {
  1150  		return nil
  1151  	}
  1152  	params := &protocol.RenameParams{
  1153  		TextDocument: e.textDocumentIdentifier(path),
  1154  		Position:     pos.ToProtocolPosition(),
  1155  		NewName:      newName,
  1156  	}
  1157  	wsEdits, err := e.Server.Rename(ctx, params)
  1158  	if err != nil {
  1159  		return err
  1160  	}
  1161  	for _, change := range wsEdits.DocumentChanges {
  1162  		if err := e.applyProtocolEdit(ctx, change); err != nil {
  1163  			return err
  1164  		}
  1165  	}
  1166  	return nil
  1167  }
  1168  
  1169  func (e *Editor) applyProtocolEdit(ctx context.Context, change protocol.TextDocumentEdit) error {
  1170  	path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI)
  1171  	if ver := int32(e.BufferVersion(path)); ver != change.TextDocument.Version {
  1172  		return fmt.Errorf("buffer versions for %q do not match: have %d, editing %d", path, ver, change.TextDocument.Version)
  1173  	}
  1174  	if !e.HasBuffer(path) {
  1175  		err := e.OpenFile(ctx, path)
  1176  		if os.IsNotExist(err) {
  1177  			// TODO: it's unclear if this is correct. Here we create the buffer (with
  1178  			// version 1), then apply edits. Perhaps we should apply the edits before
  1179  			// sending the didOpen notification.
  1180  			e.CreateBuffer(ctx, path, "")
  1181  			err = nil
  1182  		}
  1183  		if err != nil {
  1184  			return err
  1185  		}
  1186  	}
  1187  	fakeEdits := convertEdits(change.Edits)
  1188  	return e.EditBuffer(ctx, path, fakeEdits)
  1189  }
  1190  
  1191  // CodeAction executes a codeAction request on the server.
  1192  func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) ([]protocol.CodeAction, error) {
  1193  	if e.Server == nil {
  1194  		return nil, nil
  1195  	}
  1196  	e.mu.Lock()
  1197  	_, ok := e.buffers[path]
  1198  	e.mu.Unlock()
  1199  	if !ok {
  1200  		return nil, fmt.Errorf("buffer %q is not open", path)
  1201  	}
  1202  	params := &protocol.CodeActionParams{
  1203  		TextDocument: e.textDocumentIdentifier(path),
  1204  		Context: protocol.CodeActionContext{
  1205  			Diagnostics: diagnostics,
  1206  		},
  1207  	}
  1208  	if rng != nil {
  1209  		params.Range = *rng
  1210  	}
  1211  	lens, err := e.Server.CodeAction(ctx, params)
  1212  	if err != nil {
  1213  		return nil, err
  1214  	}
  1215  	return lens, nil
  1216  }
  1217  
  1218  // Hover triggers a hover at the given position in an open buffer.
  1219  func (e *Editor) Hover(ctx context.Context, path string, pos Pos) (*protocol.MarkupContent, Pos, error) {
  1220  	if err := e.checkBufferPosition(path, pos); err != nil {
  1221  		return nil, Pos{}, err
  1222  	}
  1223  	params := &protocol.HoverParams{}
  1224  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
  1225  	params.Position = pos.ToProtocolPosition()
  1226  
  1227  	resp, err := e.Server.Hover(ctx, params)
  1228  	if err != nil {
  1229  		return nil, Pos{}, errors.Errorf("hover: %w", err)
  1230  	}
  1231  	if resp == nil {
  1232  		return nil, Pos{}, nil
  1233  	}
  1234  	return &resp.Contents, fromProtocolPosition(resp.Range.Start), nil
  1235  }
  1236  
  1237  func (e *Editor) DocumentLink(ctx context.Context, path string) ([]protocol.DocumentLink, error) {
  1238  	if e.Server == nil {
  1239  		return nil, nil
  1240  	}
  1241  	params := &protocol.DocumentLinkParams{}
  1242  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
  1243  	return e.Server.DocumentLink(ctx, params)
  1244  }
  1245  
  1246  func (e *Editor) DocumentHighlight(ctx context.Context, path string, pos Pos) ([]protocol.DocumentHighlight, error) {
  1247  	if e.Server == nil {
  1248  		return nil, nil
  1249  	}
  1250  	if err := e.checkBufferPosition(path, pos); err != nil {
  1251  		return nil, err
  1252  	}
  1253  	params := &protocol.DocumentHighlightParams{}
  1254  	params.TextDocument.URI = e.sandbox.Workdir.URI(path)
  1255  	params.Position = pos.ToProtocolPosition()
  1256  
  1257  	return e.Server.DocumentHighlight(ctx, params)
  1258  }