Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / internal / lsp / fake / editor.go
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         "path/filepath"
12         "regexp"
13         "strings"
14         "sync"
15
16         "golang.org/x/tools/internal/jsonrpc2"
17         "golang.org/x/tools/internal/lsp/protocol"
18         "golang.org/x/tools/internal/lsp/source"
19         "golang.org/x/tools/internal/span"
20         errors "golang.org/x/xerrors"
21 )
22
23 // Editor is a fake editor client.  It keeps track of client state and can be
24 // used for writing LSP tests.
25 type Editor struct {
26         Config EditorConfig
27
28         // Server, client, and sandbox are concurrency safe and written only
29         // at construction time, so do not require synchronization.
30         Server     protocol.Server
31         serverConn jsonrpc2.Conn
32         client     *Client
33         sandbox    *Sandbox
34         defaultEnv map[string]string
35
36         // Since this editor is intended just for testing, we use very coarse
37         // locking.
38         mu sync.Mutex
39         // Editor state.
40         buffers map[string]buffer
41         // Capabilities / Options
42         serverCapabilities protocol.ServerCapabilities
43 }
44
45 type buffer struct {
46         version int
47         path    string
48         content []string
49 }
50
51 func (b buffer) text() string {
52         return strings.Join(b.content, "\n")
53 }
54
55 // EditorConfig configures the editor's LSP session. This is similar to
56 // source.UserOptions, but we use a separate type here so that we expose only
57 // that configuration which we support.
58 //
59 // The zero value for EditorConfig should correspond to its defaults.
60 type EditorConfig struct {
61         Env        map[string]string
62         BuildFlags []string
63
64         // CodeLens is a map defining whether codelens are enabled, keyed by the
65         // codeLens command. CodeLens which are not present in this map are left in
66         // their default state.
67         CodeLens map[string]bool
68
69         // SymbolMatcher is the config associated with the "symbolMatcher" gopls
70         // config option.
71         SymbolMatcher, SymbolStyle *string
72
73         // LimitWorkspaceScope is true if the user does not want to expand their
74         // workspace scope to the entire module.
75         LimitWorkspaceScope bool
76
77         // WithoutWorkspaceFolders is used to simulate opening a single file in the
78         // editor, without a workspace root. In that case, the client sends neither
79         // workspace folders nor a root URI.
80         WithoutWorkspaceFolders bool
81
82         // EditorRootPath specifies the root path of the workspace folder used when
83         // initializing gopls in the sandbox. If empty, the Workdir is used.
84         EditorRootPath string
85
86         // EnableStaticcheck enables staticcheck analyzers.
87         EnableStaticcheck bool
88
89         // AllExperiments sets the "allExperiments" configuration, which enables
90         // all of gopls's opt-in settings.
91         AllExperiments bool
92 }
93
94 // NewEditor Creates a new Editor.
95 func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor {
96         return &Editor{
97                 buffers:    make(map[string]buffer),
98                 sandbox:    sandbox,
99                 defaultEnv: sandbox.GoEnv(),
100                 Config:     config,
101         }
102 }
103
104 // Connect configures the editor to communicate with an LSP server on conn. It
105 // is not concurrency safe, and should be called at most once, before using the
106 // editor.
107 //
108 // It returns the editor, so that it may be called as follows:
109 //   editor, err := NewEditor(s).Connect(ctx, conn)
110 func (e *Editor) Connect(ctx context.Context, conn jsonrpc2.Conn, hooks ClientHooks) (*Editor, error) {
111         e.serverConn = conn
112         e.Server = protocol.ServerDispatcher(conn)
113         e.client = &Client{editor: e, hooks: hooks}
114         conn.Go(ctx,
115                 protocol.Handlers(
116                         protocol.ClientHandler(e.client,
117                                 jsonrpc2.MethodNotFound)))
118         if err := e.initialize(ctx, e.Config.WithoutWorkspaceFolders, e.Config.EditorRootPath); err != nil {
119                 return nil, err
120         }
121         e.sandbox.Workdir.AddWatcher(e.onFileChanges)
122         return e, nil
123 }
124
125 // Shutdown issues the 'shutdown' LSP notification.
126 func (e *Editor) Shutdown(ctx context.Context) error {
127         if e.Server != nil {
128                 if err := e.Server.Shutdown(ctx); err != nil {
129                         return errors.Errorf("Shutdown: %w", err)
130                 }
131         }
132         return nil
133 }
134
135 // Exit issues the 'exit' LSP notification.
136 func (e *Editor) Exit(ctx context.Context) error {
137         if e.Server != nil {
138                 // Not all LSP clients issue the exit RPC, but we do so here to ensure that
139                 // we gracefully handle it on multi-session servers.
140                 if err := e.Server.Exit(ctx); err != nil {
141                         return errors.Errorf("Exit: %w", err)
142                 }
143         }
144         return nil
145 }
146
147 // Close issues the shutdown and exit sequence an editor should.
148 func (e *Editor) Close(ctx context.Context) error {
149         if err := e.Shutdown(ctx); err != nil {
150                 return err
151         }
152         if err := e.Exit(ctx); err != nil {
153                 return err
154         }
155         // called close on the editor should result in the connection closing
156         select {
157         case <-e.serverConn.Done():
158                 // connection closed itself
159                 return nil
160         case <-ctx.Done():
161                 return errors.Errorf("connection not closed: %w", ctx.Err())
162         }
163 }
164
165 // Client returns the LSP client for this editor.
166 func (e *Editor) Client() *Client {
167         return e.client
168 }
169
170 func (e *Editor) overlayEnv() map[string]string {
171         env := make(map[string]string)
172         for k, v := range e.defaultEnv {
173                 env[k] = v
174         }
175         for k, v := range e.Config.Env {
176                 env[k] = v
177         }
178         return env
179 }
180
181 func (e *Editor) configuration() map[string]interface{} {
182         config := map[string]interface{}{
183                 "verboseWorkDoneProgress": true,
184                 "env":                     e.overlayEnv(),
185                 "expandWorkspaceToModule": !e.Config.LimitWorkspaceScope,
186                 "completionBudget":        "10s",
187         }
188
189         if e.Config.BuildFlags != nil {
190                 config["buildFlags"] = e.Config.BuildFlags
191         }
192
193         if e.Config.CodeLens != nil {
194                 config["codelens"] = e.Config.CodeLens
195         }
196         if e.Config.SymbolMatcher != nil {
197                 config["symbolMatcher"] = *e.Config.SymbolMatcher
198         }
199         if e.Config.SymbolStyle != nil {
200                 config["symbolStyle"] = *e.Config.SymbolStyle
201         }
202         if e.Config.EnableStaticcheck {
203                 config["staticcheck"] = true
204         }
205         if e.Config.AllExperiments {
206                 config["allExperiments"] = true
207         }
208
209         // TODO(rFindley): uncomment this if/when diagnostics delay is on by
210         // default... and probably change to the new settings name.
211         // config["experimentalDiagnosticsDelay"] = "10ms"
212
213         // ExperimentalWorkspaceModule is only set as a mode, not a configuration.
214         return config
215 }
216
217 func (e *Editor) initialize(ctx context.Context, withoutWorkspaceFolders bool, editorRootPath string) error {
218         params := &protocol.ParamInitialize{}
219         params.ClientInfo.Name = "fakeclient"
220         params.ClientInfo.Version = "v1.0.0"
221         if !withoutWorkspaceFolders {
222                 rootURI := e.sandbox.Workdir.RootURI()
223                 if editorRootPath != "" {
224                         rootURI = toURI(e.sandbox.Workdir.AbsPath(editorRootPath))
225                 }
226                 params.WorkspaceFolders = []protocol.WorkspaceFolder{{
227                         URI:  string(rootURI),
228                         Name: filepath.Base(rootURI.SpanURI().Filename()),
229                 }}
230         }
231         params.Capabilities.Workspace.Configuration = true
232         params.Capabilities.Window.WorkDoneProgress = true
233         // TODO: set client capabilities
234         params.InitializationOptions = e.configuration()
235
236         // This is a bit of a hack, since the fake editor doesn't actually support
237         // watching changed files that match a specific glob pattern. However, the
238         // editor does send didChangeWatchedFiles notifications, so set this to
239         // true.
240         params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true
241
242         params.Trace = "messages"
243         // TODO: support workspace folders.
244         if e.Server != nil {
245                 resp, err := e.Server.Initialize(ctx, params)
246                 if err != nil {
247                         return errors.Errorf("initialize: %w", err)
248                 }
249                 e.mu.Lock()
250                 e.serverCapabilities = resp.Capabilities
251                 e.mu.Unlock()
252
253                 if err := e.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil {
254                         return errors.Errorf("initialized: %w", err)
255                 }
256         }
257         // TODO: await initial configuration here, or expect gopls to manage that?
258         return nil
259 }
260
261 func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) {
262         if e.Server == nil {
263                 return
264         }
265         var lspevts []protocol.FileEvent
266         for _, evt := range evts {
267                 lspevts = append(lspevts, evt.ProtocolEvent)
268         }
269         e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{
270                 Changes: lspevts,
271         })
272 }
273
274 // OpenFile creates a buffer for the given workdir-relative file.
275 func (e *Editor) OpenFile(ctx context.Context, path string) error {
276         content, err := e.sandbox.Workdir.ReadFile(path)
277         if err != nil {
278                 return err
279         }
280         return e.CreateBuffer(ctx, path, content)
281 }
282
283 func newBuffer(path, content string) buffer {
284         return buffer{
285                 version: 1,
286                 path:    path,
287                 content: strings.Split(content, "\n"),
288         }
289 }
290
291 func textDocumentItem(wd *Workdir, buf buffer) protocol.TextDocumentItem {
292         uri := wd.URI(buf.path)
293         languageID := ""
294         if strings.HasSuffix(buf.path, ".go") {
295                 // TODO: what about go.mod files? What is their language ID?
296                 languageID = "go"
297         }
298         return protocol.TextDocumentItem{
299                 URI:        uri,
300                 LanguageID: languageID,
301                 Version:    float64(buf.version),
302                 Text:       buf.text(),
303         }
304 }
305
306 // CreateBuffer creates a new unsaved buffer corresponding to the workdir path,
307 // containing the given textual content.
308 func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error {
309         buf := newBuffer(path, content)
310         e.mu.Lock()
311         e.buffers[path] = buf
312         item := textDocumentItem(e.sandbox.Workdir, buf)
313         e.mu.Unlock()
314
315         if e.Server != nil {
316                 if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{
317                         TextDocument: item,
318                 }); err != nil {
319                         return errors.Errorf("DidOpen: %w", err)
320                 }
321         }
322         return nil
323 }
324
325 // CloseBuffer removes the current buffer (regardless of whether it is saved).
326 func (e *Editor) CloseBuffer(ctx context.Context, path string) error {
327         e.mu.Lock()
328         _, ok := e.buffers[path]
329         if !ok {
330                 e.mu.Unlock()
331                 return ErrUnknownBuffer
332         }
333         delete(e.buffers, path)
334         e.mu.Unlock()
335
336         if e.Server != nil {
337                 if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{
338                         TextDocument: e.textDocumentIdentifier(path),
339                 }); err != nil {
340                         return errors.Errorf("DidClose: %w", err)
341                 }
342         }
343         return nil
344 }
345
346 func (e *Editor) textDocumentIdentifier(path string) protocol.TextDocumentIdentifier {
347         return protocol.TextDocumentIdentifier{
348                 URI: e.sandbox.Workdir.URI(path),
349         }
350 }
351
352 // SaveBuffer writes the content of the buffer specified by the given path to
353 // the filesystem.
354 func (e *Editor) SaveBuffer(ctx context.Context, path string) error {
355         if err := e.OrganizeImports(ctx, path); err != nil {
356                 return errors.Errorf("organizing imports before save: %w", err)
357         }
358         if err := e.FormatBuffer(ctx, path); err != nil {
359                 return errors.Errorf("formatting before save: %w", err)
360         }
361         return e.SaveBufferWithoutActions(ctx, path)
362 }
363
364 func (e *Editor) SaveBufferWithoutActions(ctx context.Context, path string) error {
365         e.mu.Lock()
366         buf, ok := e.buffers[path]
367         if !ok {
368                 e.mu.Unlock()
369                 return fmt.Errorf(fmt.Sprintf("unknown buffer: %q", path))
370         }
371         content := buf.text()
372         includeText := false
373         syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions)
374         if ok {
375                 includeText = syncOptions.Save.IncludeText
376         }
377         e.mu.Unlock()
378
379         docID := e.textDocumentIdentifier(buf.path)
380         if e.Server != nil {
381                 if err := e.Server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{
382                         TextDocument: docID,
383                         Reason:       protocol.Manual,
384                 }); err != nil {
385                         return errors.Errorf("WillSave: %w", err)
386                 }
387         }
388         if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil {
389                 return errors.Errorf("writing %q: %w", path, err)
390         }
391         if e.Server != nil {
392                 params := &protocol.DidSaveTextDocumentParams{
393                         TextDocument: protocol.VersionedTextDocumentIdentifier{
394                                 Version:                float64(buf.version),
395                                 TextDocumentIdentifier: docID,
396                         },
397                 }
398                 if includeText {
399                         params.Text = &content
400                 }
401                 if err := e.Server.DidSave(ctx, params); err != nil {
402                         return errors.Errorf("DidSave: %w", err)
403                 }
404         }
405         return nil
406 }
407
408 // contentPosition returns the (Line, Column) position corresponding to offset
409 // in the buffer referenced by path.
410 func contentPosition(content string, offset int) (Pos, error) {
411         scanner := bufio.NewScanner(strings.NewReader(content))
412         start := 0
413         line := 0
414         for scanner.Scan() {
415                 end := start + len([]rune(scanner.Text())) + 1
416                 if offset < end {
417                         return Pos{Line: line, Column: offset - start}, nil
418                 }
419                 start = end
420                 line++
421         }
422         if err := scanner.Err(); err != nil {
423                 return Pos{}, errors.Errorf("scanning content: %w", err)
424         }
425         // Scan() will drop the last line if it is empty. Correct for this.
426         if (strings.HasSuffix(content, "\n") || content == "") && offset == start {
427                 return Pos{Line: line, Column: 0}, nil
428         }
429         return Pos{}, fmt.Errorf("position %d out of bounds in %q (line = %d, start = %d)", offset, content, line, start)
430 }
431
432 // ErrNoMatch is returned if a regexp search fails.
433 var (
434         ErrNoMatch       = errors.New("no match")
435         ErrUnknownBuffer = errors.New("unknown buffer")
436 )
437
438 // regexpRange returns the start and end of the first occurrence of either re
439 // or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match.
440 func regexpRange(content, re string) (Pos, Pos, error) {
441         var start, end int
442         rec, err := regexp.Compile(re)
443         if err != nil {
444                 return Pos{}, Pos{}, err
445         }
446         indexes := rec.FindStringSubmatchIndex(content)
447         if indexes == nil {
448                 return Pos{}, Pos{}, ErrNoMatch
449         }
450         switch len(indexes) {
451         case 2:
452                 // no subgroups: return the range of the regexp expression
453                 start, end = indexes[0], indexes[1]
454         case 4:
455                 // one subgroup: return its range
456                 start, end = indexes[2], indexes[3]
457         default:
458                 return Pos{}, Pos{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1)
459         }
460         startPos, err := contentPosition(content, start)
461         if err != nil {
462                 return Pos{}, Pos{}, err
463         }
464         endPos, err := contentPosition(content, end)
465         if err != nil {
466                 return Pos{}, Pos{}, err
467         }
468         return startPos, endPos, nil
469 }
470
471 // RegexpRange returns the first range in the buffer bufName matching re. See
472 // RegexpSearch for more information on matching.
473 func (e *Editor) RegexpRange(bufName, re string) (Pos, Pos, error) {
474         e.mu.Lock()
475         defer e.mu.Unlock()
476         buf, ok := e.buffers[bufName]
477         if !ok {
478                 return Pos{}, Pos{}, ErrUnknownBuffer
479         }
480         return regexpRange(buf.text(), re)
481 }
482
483 // RegexpSearch returns the position of the first match for re in the buffer
484 // bufName. For convenience, RegexpSearch supports the following two modes:
485 //  1. If re has no subgroups, return the position of the match for re itself.
486 //  2. If re has one subgroup, return the position of the first subgroup.
487 // It returns an error re is invalid, has more than one subgroup, or doesn't
488 // match the buffer.
489 func (e *Editor) RegexpSearch(bufName, re string) (Pos, error) {
490         start, _, err := e.RegexpRange(bufName, re)
491         return start, err
492 }
493
494 // RegexpReplace edits the buffer corresponding to path by replacing the first
495 // instance of re, or its first subgroup, with the replace text. See
496 // RegexpSearch for more explanation of these two modes.
497 // It returns an error if re is invalid, has more than one subgroup, or doesn't
498 // match the buffer.
499 func (e *Editor) RegexpReplace(ctx context.Context, path, re, replace string) error {
500         e.mu.Lock()
501         defer e.mu.Unlock()
502         buf, ok := e.buffers[path]
503         if !ok {
504                 return ErrUnknownBuffer
505         }
506         content := buf.text()
507         start, end, err := regexpRange(content, re)
508         if err != nil {
509                 return err
510         }
511         return e.editBufferLocked(ctx, path, []Edit{{
512                 Start: start,
513                 End:   end,
514                 Text:  replace,
515         }})
516 }
517
518 // EditBuffer applies the given test edits to the buffer identified by path.
519 func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) error {
520         e.mu.Lock()
521         defer e.mu.Unlock()
522         return e.editBufferLocked(ctx, path, edits)
523 }
524
525 // BufferText returns the content of the buffer with the given name.
526 func (e *Editor) BufferText(name string) string {
527         e.mu.Lock()
528         defer e.mu.Unlock()
529         return e.buffers[name].text()
530 }
531
532 // BufferVersion returns the current version of the buffer corresponding to
533 // name (or 0 if it is not being edited).
534 func (e *Editor) BufferVersion(name string) int {
535         e.mu.Lock()
536         defer e.mu.Unlock()
537         return e.buffers[name].version
538 }
539
540 func (e *Editor) editBufferLocked(ctx context.Context, path string, edits []Edit) error {
541         buf, ok := e.buffers[path]
542         if !ok {
543                 return fmt.Errorf("unknown buffer %q", path)
544         }
545         var (
546                 content = make([]string, len(buf.content))
547                 err     error
548                 evts    []protocol.TextDocumentContentChangeEvent
549         )
550         copy(content, buf.content)
551         content, err = editContent(content, edits)
552         if err != nil {
553                 return err
554         }
555
556         buf.content = content
557         buf.version++
558         e.buffers[path] = buf
559         // A simple heuristic: if there is only one edit, send it incrementally.
560         // Otherwise, send the entire content.
561         if len(edits) == 1 {
562                 evts = append(evts, edits[0].toProtocolChangeEvent())
563         } else {
564                 evts = append(evts, protocol.TextDocumentContentChangeEvent{
565                         Text: buf.text(),
566                 })
567         }
568         params := &protocol.DidChangeTextDocumentParams{
569                 TextDocument: protocol.VersionedTextDocumentIdentifier{
570                         Version:                float64(buf.version),
571                         TextDocumentIdentifier: e.textDocumentIdentifier(buf.path),
572                 },
573                 ContentChanges: evts,
574         }
575         if e.Server != nil {
576                 if err := e.Server.DidChange(ctx, params); err != nil {
577                         return errors.Errorf("DidChange: %w", err)
578                 }
579         }
580         return nil
581 }
582
583 // GoToDefinition jumps to the definition of the symbol at the given position
584 // in an open buffer.
585 func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) {
586         if err := e.checkBufferPosition(path, pos); err != nil {
587                 return "", Pos{}, err
588         }
589         params := &protocol.DefinitionParams{}
590         params.TextDocument.URI = e.sandbox.Workdir.URI(path)
591         params.Position = pos.ToProtocolPosition()
592
593         resp, err := e.Server.Definition(ctx, params)
594         if err != nil {
595                 return "", Pos{}, errors.Errorf("definition: %w", err)
596         }
597         if len(resp) == 0 {
598                 return "", Pos{}, nil
599         }
600         newPath := e.sandbox.Workdir.URIToPath(resp[0].URI)
601         newPos := fromProtocolPosition(resp[0].Range.Start)
602         if err := e.OpenFile(ctx, newPath); err != nil {
603                 return "", Pos{}, errors.Errorf("OpenFile: %w", err)
604         }
605         return newPath, newPos, nil
606 }
607
608 // Symbol performs a workspace symbol search using query
609 func (e *Editor) Symbol(ctx context.Context, query string) ([]SymbolInformation, error) {
610         params := &protocol.WorkspaceSymbolParams{}
611         params.Query = query
612
613         resp, err := e.Server.Symbol(ctx, params)
614         if err != nil {
615                 return nil, errors.Errorf("symbol: %w", err)
616         }
617         var res []SymbolInformation
618         for _, si := range resp {
619                 ploc := si.Location
620                 path := e.sandbox.Workdir.URIToPath(ploc.URI)
621                 start := fromProtocolPosition(ploc.Range.Start)
622                 end := fromProtocolPosition(ploc.Range.End)
623                 rnge := Range{
624                         Start: start,
625                         End:   end,
626                 }
627                 loc := Location{
628                         Path:  path,
629                         Range: rnge,
630                 }
631                 res = append(res, SymbolInformation{
632                         Name:     si.Name,
633                         Kind:     si.Kind,
634                         Location: loc,
635                 })
636         }
637         return res, nil
638 }
639
640 // OrganizeImports requests and performs the source.organizeImports codeAction.
641 func (e *Editor) OrganizeImports(ctx context.Context, path string) error {
642         return e.codeAction(ctx, path, nil, nil, protocol.SourceOrganizeImports)
643 }
644
645 // RefactorRewrite requests and performs the source.refactorRewrite codeAction.
646 func (e *Editor) RefactorRewrite(ctx context.Context, path string, rng *protocol.Range) error {
647         return e.codeAction(ctx, path, rng, nil, protocol.RefactorRewrite)
648 }
649
650 // ApplyQuickFixes requests and performs the quickfix codeAction.
651 func (e *Editor) ApplyQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) error {
652         return e.codeAction(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll)
653 }
654
655 func (e *Editor) codeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) error {
656         if e.Server == nil {
657                 return nil
658         }
659         params := &protocol.CodeActionParams{}
660         params.TextDocument.URI = e.sandbox.Workdir.URI(path)
661         params.Context.Only = only
662         if diagnostics != nil {
663                 params.Context.Diagnostics = diagnostics
664         }
665         if rng != nil {
666                 params.Range = *rng
667         }
668         actions, err := e.Server.CodeAction(ctx, params)
669         if err != nil {
670                 return errors.Errorf("textDocument/codeAction: %w", err)
671         }
672         for _, action := range actions {
673                 var match bool
674                 for _, o := range only {
675                         if action.Kind == o {
676                                 match = true
677                                 break
678                         }
679                 }
680                 if !match {
681                         continue
682                 }
683                 for _, change := range action.Edit.DocumentChanges {
684                         path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI)
685                         if float64(e.buffers[path].version) != change.TextDocument.Version {
686                                 // Skip edits for old versions.
687                                 continue
688                         }
689                         edits := convertEdits(change.Edits)
690                         if err := e.EditBuffer(ctx, path, edits); err != nil {
691                                 return errors.Errorf("editing buffer %q: %w", path, err)
692                         }
693                 }
694                 // Execute any commands. The specification says that commands are
695                 // executed after edits are applied.
696                 if action.Command != nil {
697                         if _, err := e.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{
698                                 Command:   action.Command.Command,
699                                 Arguments: action.Command.Arguments,
700                         }); err != nil {
701                                 return err
702                         }
703                 }
704         }
705         return nil
706 }
707
708 func (e *Editor) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) {
709         if e.Server == nil {
710                 return nil, nil
711         }
712         var match bool
713         // Ensure that this command was actually listed as a supported command.
714         for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands {
715                 if command == params.Command {
716                         match = true
717                         break
718                 }
719         }
720         if !match {
721                 return nil, fmt.Errorf("unsupported command %q", params.Command)
722         }
723         return e.Server.ExecuteCommand(ctx, params)
724 }
725
726 func convertEdits(protocolEdits []protocol.TextEdit) []Edit {
727         var edits []Edit
728         for _, lspEdit := range protocolEdits {
729                 edits = append(edits, fromProtocolTextEdit(lspEdit))
730         }
731         return edits
732 }
733
734 // FormatBuffer gofmts a Go file.
735 func (e *Editor) FormatBuffer(ctx context.Context, path string) error {
736         if e.Server == nil {
737                 return nil
738         }
739         e.mu.Lock()
740         version := e.buffers[path].version
741         e.mu.Unlock()
742         params := &protocol.DocumentFormattingParams{}
743         params.TextDocument.URI = e.sandbox.Workdir.URI(path)
744         resp, err := e.Server.Formatting(ctx, params)
745         if err != nil {
746                 return errors.Errorf("textDocument/formatting: %w", err)
747         }
748         e.mu.Lock()
749         defer e.mu.Unlock()
750         if versionAfter := e.buffers[path].version; versionAfter != version {
751                 return fmt.Errorf("before receipt of formatting edits, buffer version changed from %d to %d", version, versionAfter)
752         }
753         edits := convertEdits(resp)
754         return e.editBufferLocked(ctx, path, edits)
755 }
756
757 func (e *Editor) checkBufferPosition(path string, pos Pos) error {
758         e.mu.Lock()
759         defer e.mu.Unlock()
760         buf, ok := e.buffers[path]
761         if !ok {
762                 return fmt.Errorf("buffer %q is not open", path)
763         }
764         if !inText(pos, buf.content) {
765                 return fmt.Errorf("position %v is invalid in buffer %q", pos, path)
766         }
767         return nil
768 }
769
770 // RunGenerate runs `go generate` non-recursively in the workdir-relative dir
771 // path. It does not report any resulting file changes as a watched file
772 // change, so must be followed by a call to Workdir.CheckForFileChanges once
773 // the generate command has completed.
774 func (e *Editor) RunGenerate(ctx context.Context, dir string) error {
775         if e.Server == nil {
776                 return nil
777         }
778         absDir := e.sandbox.Workdir.AbsPath(dir)
779         jsonArgs, err := source.MarshalArgs(span.URIFromPath(absDir), false)
780         if err != nil {
781                 return err
782         }
783         params := &protocol.ExecuteCommandParams{
784                 Command:   source.CommandGenerate.ID(),
785                 Arguments: jsonArgs,
786         }
787         if _, err := e.ExecuteCommand(ctx, params); err != nil {
788                 return fmt.Errorf("running generate: %v", err)
789         }
790         // Unfortunately we can't simply poll the workdir for file changes here,
791         // because server-side command may not have completed. In regtests, we can
792         // Await this state change, but here we must delegate that responsibility to
793         // the caller.
794         return nil
795 }
796
797 // CodeLens executes a codelens request on the server.
798 func (e *Editor) CodeLens(ctx context.Context, path string) ([]protocol.CodeLens, error) {
799         if e.Server == nil {
800                 return nil, nil
801         }
802         e.mu.Lock()
803         _, ok := e.buffers[path]
804         e.mu.Unlock()
805         if !ok {
806                 return nil, fmt.Errorf("buffer %q is not open", path)
807         }
808         params := &protocol.CodeLensParams{
809                 TextDocument: e.textDocumentIdentifier(path),
810         }
811         lens, err := e.Server.CodeLens(ctx, params)
812         if err != nil {
813                 return nil, err
814         }
815         return lens, nil
816 }
817
818 // Completion executes a completion request on the server.
819 func (e *Editor) Completion(ctx context.Context, path string, pos Pos) (*protocol.CompletionList, error) {
820         if e.Server == nil {
821                 return nil, nil
822         }
823         e.mu.Lock()
824         _, ok := e.buffers[path]
825         e.mu.Unlock()
826         if !ok {
827                 return nil, fmt.Errorf("buffer %q is not open", path)
828         }
829         params := &protocol.CompletionParams{
830                 TextDocumentPositionParams: protocol.TextDocumentPositionParams{
831                         TextDocument: e.textDocumentIdentifier(path),
832                         Position:     pos.ToProtocolPosition(),
833                 },
834         }
835         completions, err := e.Server.Completion(ctx, params)
836         if err != nil {
837                 return nil, err
838         }
839         return completions, nil
840 }
841
842 // References executes a reference request on the server.
843 func (e *Editor) References(ctx context.Context, path string, pos Pos) ([]protocol.Location, error) {
844         if e.Server == nil {
845                 return nil, nil
846         }
847         e.mu.Lock()
848         _, ok := e.buffers[path]
849         e.mu.Unlock()
850         if !ok {
851                 return nil, fmt.Errorf("buffer %q is not open", path)
852         }
853         params := &protocol.ReferenceParams{
854                 TextDocumentPositionParams: protocol.TextDocumentPositionParams{
855                         TextDocument: e.textDocumentIdentifier(path),
856                         Position:     pos.ToProtocolPosition(),
857                 },
858                 Context: protocol.ReferenceContext{
859                         IncludeDeclaration: true,
860                 },
861         }
862         locations, err := e.Server.References(ctx, params)
863         if err != nil {
864                 return nil, err
865         }
866         return locations, nil
867 }
868
869 // CodeAction executes a codeAction request on the server.
870 func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range) ([]protocol.CodeAction, error) {
871         if e.Server == nil {
872                 return nil, nil
873         }
874         e.mu.Lock()
875         _, ok := e.buffers[path]
876         e.mu.Unlock()
877         if !ok {
878                 return nil, fmt.Errorf("buffer %q is not open", path)
879         }
880         params := &protocol.CodeActionParams{
881                 TextDocument: e.textDocumentIdentifier(path),
882         }
883         if rng != nil {
884                 params.Range = *rng
885         }
886         lens, err := e.Server.CodeAction(ctx, params)
887         if err != nil {
888                 return nil, err
889         }
890         return lens, nil
891 }
892
893 // Hover triggers a hover at the given position in an open buffer.
894 func (e *Editor) Hover(ctx context.Context, path string, pos Pos) (*protocol.MarkupContent, Pos, error) {
895         if err := e.checkBufferPosition(path, pos); err != nil {
896                 return nil, Pos{}, err
897         }
898         params := &protocol.HoverParams{}
899         params.TextDocument.URI = e.sandbox.Workdir.URI(path)
900         params.Position = pos.ToProtocolPosition()
901
902         resp, err := e.Server.Hover(ctx, params)
903         if err != nil {
904                 return nil, Pos{}, errors.Errorf("hover: %w", err)
905         }
906         if resp == nil {
907                 return nil, Pos{}, nil
908         }
909         return &resp.Contents, fromProtocolPosition(resp.Range.Start), nil
910 }
911
912 func (e *Editor) DocumentLink(ctx context.Context, path string) ([]protocol.DocumentLink, error) {
913         if e.Server == nil {
914                 return nil, nil
915         }
916         params := &protocol.DocumentLinkParams{}
917         params.TextDocument.URI = e.sandbox.Workdir.URI(path)
918         return e.Server.DocumentLink(ctx, params)
919 }