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