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