X-Git-Url: https://git.josue.xyz/?p=dotfiles%2F.git;a=blobdiff_plain;f=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201028153306-37f0764111ff%2Finternal%2Flsp%2Ffake%2Feditor.go;fp=.config%2Fcoc%2Fextensions%2Fcoc-go-data%2Ftools%2Fpkg%2Fmod%2Fgolang.org%2Fx%2Ftools%40v0.0.0-20201028153306-37f0764111ff%2Finternal%2Flsp%2Ffake%2Feditor.go;h=0000000000000000000000000000000000000000;hp=dba89976962110ea941beaf29c780e1b67f814f3;hb=3ddadb3c98564791f0ac36cb39771d844a63dc91;hpb=5f797af6612ed10887189b47a1efc2f915586e59 diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201028153306-37f0764111ff/internal/lsp/fake/editor.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201028153306-37f0764111ff/internal/lsp/fake/editor.go deleted file mode 100644 index dba89976..00000000 --- a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/tools@v0.0.0-20201028153306-37f0764111ff/internal/lsp/fake/editor.go +++ /dev/null @@ -1,919 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package fake - -import ( - "bufio" - "context" - "fmt" - "path/filepath" - "regexp" - "strings" - "sync" - - "golang.org/x/tools/internal/jsonrpc2" - "golang.org/x/tools/internal/lsp/protocol" - "golang.org/x/tools/internal/lsp/source" - "golang.org/x/tools/internal/span" - errors "golang.org/x/xerrors" -) - -// Editor is a fake editor client. It keeps track of client state and can be -// used for writing LSP tests. -type Editor struct { - Config EditorConfig - - // Server, client, and sandbox are concurrency safe and written only - // at construction time, so do not require synchronization. - Server protocol.Server - serverConn jsonrpc2.Conn - client *Client - sandbox *Sandbox - defaultEnv map[string]string - - // Since this editor is intended just for testing, we use very coarse - // locking. - mu sync.Mutex - // Editor state. - buffers map[string]buffer - // Capabilities / Options - serverCapabilities protocol.ServerCapabilities -} - -type buffer struct { - version int - path string - content []string -} - -func (b buffer) text() string { - return strings.Join(b.content, "\n") -} - -// EditorConfig configures the editor's LSP session. This is similar to -// source.UserOptions, but we use a separate type here so that we expose only -// that configuration which we support. -// -// The zero value for EditorConfig should correspond to its defaults. -type EditorConfig struct { - Env map[string]string - BuildFlags []string - - // CodeLens is a map defining whether codelens are enabled, keyed by the - // codeLens command. CodeLens which are not present in this map are left in - // their default state. - CodeLens map[string]bool - - // SymbolMatcher is the config associated with the "symbolMatcher" gopls - // config option. - SymbolMatcher, SymbolStyle *string - - // LimitWorkspaceScope is true if the user does not want to expand their - // workspace scope to the entire module. - LimitWorkspaceScope bool - - // WithoutWorkspaceFolders is used to simulate opening a single file in the - // editor, without a workspace root. In that case, the client sends neither - // workspace folders nor a root URI. - WithoutWorkspaceFolders bool - - // EditorRootPath specifies the root path of the workspace folder used when - // initializing gopls in the sandbox. If empty, the Workdir is used. - EditorRootPath string - - // EnableStaticcheck enables staticcheck analyzers. - EnableStaticcheck bool - - // AllExperiments sets the "allExperiments" configuration, which enables - // all of gopls's opt-in settings. - AllExperiments bool -} - -// NewEditor Creates a new Editor. -func NewEditor(sandbox *Sandbox, config EditorConfig) *Editor { - return &Editor{ - buffers: make(map[string]buffer), - sandbox: sandbox, - defaultEnv: sandbox.GoEnv(), - Config: config, - } -} - -// Connect configures the editor to communicate with an LSP server on conn. It -// is not concurrency safe, and should be called at most once, before using the -// editor. -// -// It returns the editor, so that it may be called as follows: -// editor, err := NewEditor(s).Connect(ctx, conn) -func (e *Editor) Connect(ctx context.Context, conn jsonrpc2.Conn, hooks ClientHooks) (*Editor, error) { - e.serverConn = conn - e.Server = protocol.ServerDispatcher(conn) - e.client = &Client{editor: e, hooks: hooks} - conn.Go(ctx, - protocol.Handlers( - protocol.ClientHandler(e.client, - jsonrpc2.MethodNotFound))) - if err := e.initialize(ctx, e.Config.WithoutWorkspaceFolders, e.Config.EditorRootPath); err != nil { - return nil, err - } - e.sandbox.Workdir.AddWatcher(e.onFileChanges) - return e, nil -} - -// Shutdown issues the 'shutdown' LSP notification. -func (e *Editor) Shutdown(ctx context.Context) error { - if e.Server != nil { - if err := e.Server.Shutdown(ctx); err != nil { - return errors.Errorf("Shutdown: %w", err) - } - } - return nil -} - -// Exit issues the 'exit' LSP notification. -func (e *Editor) Exit(ctx context.Context) error { - if e.Server != nil { - // Not all LSP clients issue the exit RPC, but we do so here to ensure that - // we gracefully handle it on multi-session servers. - if err := e.Server.Exit(ctx); err != nil { - return errors.Errorf("Exit: %w", err) - } - } - return nil -} - -// Close issues the shutdown and exit sequence an editor should. -func (e *Editor) Close(ctx context.Context) error { - if err := e.Shutdown(ctx); err != nil { - return err - } - if err := e.Exit(ctx); err != nil { - return err - } - // called close on the editor should result in the connection closing - select { - case <-e.serverConn.Done(): - // connection closed itself - return nil - case <-ctx.Done(): - return errors.Errorf("connection not closed: %w", ctx.Err()) - } -} - -// Client returns the LSP client for this editor. -func (e *Editor) Client() *Client { - return e.client -} - -func (e *Editor) overlayEnv() map[string]string { - env := make(map[string]string) - for k, v := range e.defaultEnv { - env[k] = v - } - for k, v := range e.Config.Env { - env[k] = v - } - return env -} - -func (e *Editor) configuration() map[string]interface{} { - config := map[string]interface{}{ - "verboseWorkDoneProgress": true, - "env": e.overlayEnv(), - "expandWorkspaceToModule": !e.Config.LimitWorkspaceScope, - "completionBudget": "10s", - } - - if e.Config.BuildFlags != nil { - config["buildFlags"] = e.Config.BuildFlags - } - - if e.Config.CodeLens != nil { - config["codelens"] = e.Config.CodeLens - } - if e.Config.SymbolMatcher != nil { - config["symbolMatcher"] = *e.Config.SymbolMatcher - } - if e.Config.SymbolStyle != nil { - config["symbolStyle"] = *e.Config.SymbolStyle - } - if e.Config.EnableStaticcheck { - config["staticcheck"] = true - } - if e.Config.AllExperiments { - config["allExperiments"] = true - } - - // TODO(rFindley): uncomment this if/when diagnostics delay is on by - // default... and probably change to the new settings name. - // config["experimentalDiagnosticsDelay"] = "10ms" - - // ExperimentalWorkspaceModule is only set as a mode, not a configuration. - return config -} - -func (e *Editor) initialize(ctx context.Context, withoutWorkspaceFolders bool, editorRootPath string) error { - params := &protocol.ParamInitialize{} - params.ClientInfo.Name = "fakeclient" - params.ClientInfo.Version = "v1.0.0" - if !withoutWorkspaceFolders { - rootURI := e.sandbox.Workdir.RootURI() - if editorRootPath != "" { - rootURI = toURI(e.sandbox.Workdir.AbsPath(editorRootPath)) - } - params.WorkspaceFolders = []protocol.WorkspaceFolder{{ - URI: string(rootURI), - Name: filepath.Base(rootURI.SpanURI().Filename()), - }} - } - params.Capabilities.Workspace.Configuration = true - params.Capabilities.Window.WorkDoneProgress = true - // TODO: set client capabilities - params.InitializationOptions = e.configuration() - - // This is a bit of a hack, since the fake editor doesn't actually support - // watching changed files that match a specific glob pattern. However, the - // editor does send didChangeWatchedFiles notifications, so set this to - // true. - params.Capabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration = true - - params.Trace = "messages" - // TODO: support workspace folders. - if e.Server != nil { - resp, err := e.Server.Initialize(ctx, params) - if err != nil { - return errors.Errorf("initialize: %w", err) - } - e.mu.Lock() - e.serverCapabilities = resp.Capabilities - e.mu.Unlock() - - if err := e.Server.Initialized(ctx, &protocol.InitializedParams{}); err != nil { - return errors.Errorf("initialized: %w", err) - } - } - // TODO: await initial configuration here, or expect gopls to manage that? - return nil -} - -func (e *Editor) onFileChanges(ctx context.Context, evts []FileEvent) { - if e.Server == nil { - return - } - var lspevts []protocol.FileEvent - for _, evt := range evts { - lspevts = append(lspevts, evt.ProtocolEvent) - } - e.Server.DidChangeWatchedFiles(ctx, &protocol.DidChangeWatchedFilesParams{ - Changes: lspevts, - }) -} - -// OpenFile creates a buffer for the given workdir-relative file. -func (e *Editor) OpenFile(ctx context.Context, path string) error { - content, err := e.sandbox.Workdir.ReadFile(path) - if err != nil { - return err - } - return e.CreateBuffer(ctx, path, content) -} - -func newBuffer(path, content string) buffer { - return buffer{ - version: 1, - path: path, - content: strings.Split(content, "\n"), - } -} - -func textDocumentItem(wd *Workdir, buf buffer) protocol.TextDocumentItem { - uri := wd.URI(buf.path) - languageID := "" - if strings.HasSuffix(buf.path, ".go") { - // TODO: what about go.mod files? What is their language ID? - languageID = "go" - } - return protocol.TextDocumentItem{ - URI: uri, - LanguageID: languageID, - Version: float64(buf.version), - Text: buf.text(), - } -} - -// CreateBuffer creates a new unsaved buffer corresponding to the workdir path, -// containing the given textual content. -func (e *Editor) CreateBuffer(ctx context.Context, path, content string) error { - buf := newBuffer(path, content) - e.mu.Lock() - e.buffers[path] = buf - item := textDocumentItem(e.sandbox.Workdir, buf) - e.mu.Unlock() - - if e.Server != nil { - if err := e.Server.DidOpen(ctx, &protocol.DidOpenTextDocumentParams{ - TextDocument: item, - }); err != nil { - return errors.Errorf("DidOpen: %w", err) - } - } - return nil -} - -// CloseBuffer removes the current buffer (regardless of whether it is saved). -func (e *Editor) CloseBuffer(ctx context.Context, path string) error { - e.mu.Lock() - _, ok := e.buffers[path] - if !ok { - e.mu.Unlock() - return ErrUnknownBuffer - } - delete(e.buffers, path) - e.mu.Unlock() - - if e.Server != nil { - if err := e.Server.DidClose(ctx, &protocol.DidCloseTextDocumentParams{ - TextDocument: e.textDocumentIdentifier(path), - }); err != nil { - return errors.Errorf("DidClose: %w", err) - } - } - return nil -} - -func (e *Editor) textDocumentIdentifier(path string) protocol.TextDocumentIdentifier { - return protocol.TextDocumentIdentifier{ - URI: e.sandbox.Workdir.URI(path), - } -} - -// SaveBuffer writes the content of the buffer specified by the given path to -// the filesystem. -func (e *Editor) SaveBuffer(ctx context.Context, path string) error { - if err := e.OrganizeImports(ctx, path); err != nil { - return errors.Errorf("organizing imports before save: %w", err) - } - if err := e.FormatBuffer(ctx, path); err != nil { - return errors.Errorf("formatting before save: %w", err) - } - return e.SaveBufferWithoutActions(ctx, path) -} - -func (e *Editor) SaveBufferWithoutActions(ctx context.Context, path string) error { - e.mu.Lock() - buf, ok := e.buffers[path] - if !ok { - e.mu.Unlock() - return fmt.Errorf(fmt.Sprintf("unknown buffer: %q", path)) - } - content := buf.text() - includeText := false - syncOptions, ok := e.serverCapabilities.TextDocumentSync.(protocol.TextDocumentSyncOptions) - if ok { - includeText = syncOptions.Save.IncludeText - } - e.mu.Unlock() - - docID := e.textDocumentIdentifier(buf.path) - if e.Server != nil { - if err := e.Server.WillSave(ctx, &protocol.WillSaveTextDocumentParams{ - TextDocument: docID, - Reason: protocol.Manual, - }); err != nil { - return errors.Errorf("WillSave: %w", err) - } - } - if err := e.sandbox.Workdir.WriteFile(ctx, path, content); err != nil { - return errors.Errorf("writing %q: %w", path, err) - } - if e.Server != nil { - params := &protocol.DidSaveTextDocumentParams{ - TextDocument: protocol.VersionedTextDocumentIdentifier{ - Version: float64(buf.version), - TextDocumentIdentifier: docID, - }, - } - if includeText { - params.Text = &content - } - if err := e.Server.DidSave(ctx, params); err != nil { - return errors.Errorf("DidSave: %w", err) - } - } - return nil -} - -// contentPosition returns the (Line, Column) position corresponding to offset -// in the buffer referenced by path. -func contentPosition(content string, offset int) (Pos, error) { - scanner := bufio.NewScanner(strings.NewReader(content)) - start := 0 - line := 0 - for scanner.Scan() { - end := start + len([]rune(scanner.Text())) + 1 - if offset < end { - return Pos{Line: line, Column: offset - start}, nil - } - start = end - line++ - } - if err := scanner.Err(); err != nil { - return Pos{}, errors.Errorf("scanning content: %w", err) - } - // Scan() will drop the last line if it is empty. Correct for this. - if (strings.HasSuffix(content, "\n") || content == "") && offset == start { - return Pos{Line: line, Column: 0}, nil - } - return Pos{}, fmt.Errorf("position %d out of bounds in %q (line = %d, start = %d)", offset, content, line, start) -} - -// ErrNoMatch is returned if a regexp search fails. -var ( - ErrNoMatch = errors.New("no match") - ErrUnknownBuffer = errors.New("unknown buffer") -) - -// regexpRange returns the start and end of the first occurrence of either re -// or its singular subgroup. It returns ErrNoMatch if the regexp doesn't match. -func regexpRange(content, re string) (Pos, Pos, error) { - var start, end int - rec, err := regexp.Compile(re) - if err != nil { - return Pos{}, Pos{}, err - } - indexes := rec.FindStringSubmatchIndex(content) - if indexes == nil { - return Pos{}, Pos{}, ErrNoMatch - } - switch len(indexes) { - case 2: - // no subgroups: return the range of the regexp expression - start, end = indexes[0], indexes[1] - case 4: - // one subgroup: return its range - start, end = indexes[2], indexes[3] - default: - return Pos{}, Pos{}, fmt.Errorf("invalid search regexp %q: expect either 0 or 1 subgroups, got %d", re, len(indexes)/2-1) - } - startPos, err := contentPosition(content, start) - if err != nil { - return Pos{}, Pos{}, err - } - endPos, err := contentPosition(content, end) - if err != nil { - return Pos{}, Pos{}, err - } - return startPos, endPos, nil -} - -// RegexpRange returns the first range in the buffer bufName matching re. See -// RegexpSearch for more information on matching. -func (e *Editor) RegexpRange(bufName, re string) (Pos, Pos, error) { - e.mu.Lock() - defer e.mu.Unlock() - buf, ok := e.buffers[bufName] - if !ok { - return Pos{}, Pos{}, ErrUnknownBuffer - } - return regexpRange(buf.text(), re) -} - -// RegexpSearch returns the position of the first match for re in the buffer -// bufName. For convenience, RegexpSearch supports the following two modes: -// 1. If re has no subgroups, return the position of the match for re itself. -// 2. If re has one subgroup, return the position of the first subgroup. -// It returns an error re is invalid, has more than one subgroup, or doesn't -// match the buffer. -func (e *Editor) RegexpSearch(bufName, re string) (Pos, error) { - start, _, err := e.RegexpRange(bufName, re) - return start, err -} - -// RegexpReplace edits the buffer corresponding to path by replacing the first -// instance of re, or its first subgroup, with the replace text. See -// RegexpSearch for more explanation of these two modes. -// It returns an error if re is invalid, has more than one subgroup, or doesn't -// match the buffer. -func (e *Editor) RegexpReplace(ctx context.Context, path, re, replace string) error { - e.mu.Lock() - defer e.mu.Unlock() - buf, ok := e.buffers[path] - if !ok { - return ErrUnknownBuffer - } - content := buf.text() - start, end, err := regexpRange(content, re) - if err != nil { - return err - } - return e.editBufferLocked(ctx, path, []Edit{{ - Start: start, - End: end, - Text: replace, - }}) -} - -// EditBuffer applies the given test edits to the buffer identified by path. -func (e *Editor) EditBuffer(ctx context.Context, path string, edits []Edit) error { - e.mu.Lock() - defer e.mu.Unlock() - return e.editBufferLocked(ctx, path, edits) -} - -// BufferText returns the content of the buffer with the given name. -func (e *Editor) BufferText(name string) string { - e.mu.Lock() - defer e.mu.Unlock() - return e.buffers[name].text() -} - -// BufferVersion returns the current version of the buffer corresponding to -// name (or 0 if it is not being edited). -func (e *Editor) BufferVersion(name string) int { - e.mu.Lock() - defer e.mu.Unlock() - return e.buffers[name].version -} - -func (e *Editor) editBufferLocked(ctx context.Context, path string, edits []Edit) error { - buf, ok := e.buffers[path] - if !ok { - return fmt.Errorf("unknown buffer %q", path) - } - var ( - content = make([]string, len(buf.content)) - err error - evts []protocol.TextDocumentContentChangeEvent - ) - copy(content, buf.content) - content, err = editContent(content, edits) - if err != nil { - return err - } - - buf.content = content - buf.version++ - e.buffers[path] = buf - // A simple heuristic: if there is only one edit, send it incrementally. - // Otherwise, send the entire content. - if len(edits) == 1 { - evts = append(evts, edits[0].toProtocolChangeEvent()) - } else { - evts = append(evts, protocol.TextDocumentContentChangeEvent{ - Text: buf.text(), - }) - } - params := &protocol.DidChangeTextDocumentParams{ - TextDocument: protocol.VersionedTextDocumentIdentifier{ - Version: float64(buf.version), - TextDocumentIdentifier: e.textDocumentIdentifier(buf.path), - }, - ContentChanges: evts, - } - if e.Server != nil { - if err := e.Server.DidChange(ctx, params); err != nil { - return errors.Errorf("DidChange: %w", err) - } - } - return nil -} - -// GoToDefinition jumps to the definition of the symbol at the given position -// in an open buffer. -func (e *Editor) GoToDefinition(ctx context.Context, path string, pos Pos) (string, Pos, error) { - if err := e.checkBufferPosition(path, pos); err != nil { - return "", Pos{}, err - } - params := &protocol.DefinitionParams{} - params.TextDocument.URI = e.sandbox.Workdir.URI(path) - params.Position = pos.ToProtocolPosition() - - resp, err := e.Server.Definition(ctx, params) - if err != nil { - return "", Pos{}, errors.Errorf("definition: %w", err) - } - if len(resp) == 0 { - return "", Pos{}, nil - } - newPath := e.sandbox.Workdir.URIToPath(resp[0].URI) - newPos := fromProtocolPosition(resp[0].Range.Start) - if err := e.OpenFile(ctx, newPath); err != nil { - return "", Pos{}, errors.Errorf("OpenFile: %w", err) - } - return newPath, newPos, nil -} - -// Symbol performs a workspace symbol search using query -func (e *Editor) Symbol(ctx context.Context, query string) ([]SymbolInformation, error) { - params := &protocol.WorkspaceSymbolParams{} - params.Query = query - - resp, err := e.Server.Symbol(ctx, params) - if err != nil { - return nil, errors.Errorf("symbol: %w", err) - } - var res []SymbolInformation - for _, si := range resp { - ploc := si.Location - path := e.sandbox.Workdir.URIToPath(ploc.URI) - start := fromProtocolPosition(ploc.Range.Start) - end := fromProtocolPosition(ploc.Range.End) - rnge := Range{ - Start: start, - End: end, - } - loc := Location{ - Path: path, - Range: rnge, - } - res = append(res, SymbolInformation{ - Name: si.Name, - Kind: si.Kind, - Location: loc, - }) - } - return res, nil -} - -// OrganizeImports requests and performs the source.organizeImports codeAction. -func (e *Editor) OrganizeImports(ctx context.Context, path string) error { - return e.codeAction(ctx, path, nil, nil, protocol.SourceOrganizeImports) -} - -// RefactorRewrite requests and performs the source.refactorRewrite codeAction. -func (e *Editor) RefactorRewrite(ctx context.Context, path string, rng *protocol.Range) error { - return e.codeAction(ctx, path, rng, nil, protocol.RefactorRewrite) -} - -// ApplyQuickFixes requests and performs the quickfix codeAction. -func (e *Editor) ApplyQuickFixes(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic) error { - return e.codeAction(ctx, path, rng, diagnostics, protocol.QuickFix, protocol.SourceFixAll) -} - -func (e *Editor) codeAction(ctx context.Context, path string, rng *protocol.Range, diagnostics []protocol.Diagnostic, only ...protocol.CodeActionKind) error { - if e.Server == nil { - return nil - } - params := &protocol.CodeActionParams{} - params.TextDocument.URI = e.sandbox.Workdir.URI(path) - params.Context.Only = only - if diagnostics != nil { - params.Context.Diagnostics = diagnostics - } - if rng != nil { - params.Range = *rng - } - actions, err := e.Server.CodeAction(ctx, params) - if err != nil { - return errors.Errorf("textDocument/codeAction: %w", err) - } - for _, action := range actions { - var match bool - for _, o := range only { - if action.Kind == o { - match = true - break - } - } - if !match { - continue - } - for _, change := range action.Edit.DocumentChanges { - path := e.sandbox.Workdir.URIToPath(change.TextDocument.URI) - if float64(e.buffers[path].version) != change.TextDocument.Version { - // Skip edits for old versions. - continue - } - edits := convertEdits(change.Edits) - if err := e.EditBuffer(ctx, path, edits); err != nil { - return errors.Errorf("editing buffer %q: %w", path, err) - } - } - // Execute any commands. The specification says that commands are - // executed after edits are applied. - if action.Command != nil { - if _, err := e.ExecuteCommand(ctx, &protocol.ExecuteCommandParams{ - Command: action.Command.Command, - Arguments: action.Command.Arguments, - }); err != nil { - return err - } - } - } - return nil -} - -func (e *Editor) ExecuteCommand(ctx context.Context, params *protocol.ExecuteCommandParams) (interface{}, error) { - if e.Server == nil { - return nil, nil - } - var match bool - // Ensure that this command was actually listed as a supported command. - for _, command := range e.serverCapabilities.ExecuteCommandProvider.Commands { - if command == params.Command { - match = true - break - } - } - if !match { - return nil, fmt.Errorf("unsupported command %q", params.Command) - } - return e.Server.ExecuteCommand(ctx, params) -} - -func convertEdits(protocolEdits []protocol.TextEdit) []Edit { - var edits []Edit - for _, lspEdit := range protocolEdits { - edits = append(edits, fromProtocolTextEdit(lspEdit)) - } - return edits -} - -// FormatBuffer gofmts a Go file. -func (e *Editor) FormatBuffer(ctx context.Context, path string) error { - if e.Server == nil { - return nil - } - e.mu.Lock() - version := e.buffers[path].version - e.mu.Unlock() - params := &protocol.DocumentFormattingParams{} - params.TextDocument.URI = e.sandbox.Workdir.URI(path) - resp, err := e.Server.Formatting(ctx, params) - if err != nil { - return errors.Errorf("textDocument/formatting: %w", err) - } - e.mu.Lock() - defer e.mu.Unlock() - if versionAfter := e.buffers[path].version; versionAfter != version { - return fmt.Errorf("before receipt of formatting edits, buffer version changed from %d to %d", version, versionAfter) - } - edits := convertEdits(resp) - return e.editBufferLocked(ctx, path, edits) -} - -func (e *Editor) checkBufferPosition(path string, pos Pos) error { - e.mu.Lock() - defer e.mu.Unlock() - buf, ok := e.buffers[path] - if !ok { - return fmt.Errorf("buffer %q is not open", path) - } - if !inText(pos, buf.content) { - return fmt.Errorf("position %v is invalid in buffer %q", pos, path) - } - return nil -} - -// RunGenerate runs `go generate` non-recursively in the workdir-relative dir -// path. It does not report any resulting file changes as a watched file -// change, so must be followed by a call to Workdir.CheckForFileChanges once -// the generate command has completed. -func (e *Editor) RunGenerate(ctx context.Context, dir string) error { - if e.Server == nil { - return nil - } - absDir := e.sandbox.Workdir.AbsPath(dir) - jsonArgs, err := source.MarshalArgs(span.URIFromPath(absDir), false) - if err != nil { - return err - } - params := &protocol.ExecuteCommandParams{ - Command: source.CommandGenerate.ID(), - Arguments: jsonArgs, - } - if _, err := e.ExecuteCommand(ctx, params); err != nil { - return fmt.Errorf("running generate: %v", err) - } - // Unfortunately we can't simply poll the workdir for file changes here, - // because server-side command may not have completed. In regtests, we can - // Await this state change, but here we must delegate that responsibility to - // the caller. - return nil -} - -// CodeLens executes a codelens request on the server. -func (e *Editor) CodeLens(ctx context.Context, path string) ([]protocol.CodeLens, error) { - if e.Server == nil { - return nil, nil - } - e.mu.Lock() - _, ok := e.buffers[path] - e.mu.Unlock() - if !ok { - return nil, fmt.Errorf("buffer %q is not open", path) - } - params := &protocol.CodeLensParams{ - TextDocument: e.textDocumentIdentifier(path), - } - lens, err := e.Server.CodeLens(ctx, params) - if err != nil { - return nil, err - } - return lens, nil -} - -// Completion executes a completion request on the server. -func (e *Editor) Completion(ctx context.Context, path string, pos Pos) (*protocol.CompletionList, error) { - if e.Server == nil { - return nil, nil - } - e.mu.Lock() - _, ok := e.buffers[path] - e.mu.Unlock() - if !ok { - return nil, fmt.Errorf("buffer %q is not open", path) - } - params := &protocol.CompletionParams{ - TextDocumentPositionParams: protocol.TextDocumentPositionParams{ - TextDocument: e.textDocumentIdentifier(path), - Position: pos.ToProtocolPosition(), - }, - } - completions, err := e.Server.Completion(ctx, params) - if err != nil { - return nil, err - } - return completions, nil -} - -// References executes a reference request on the server. -func (e *Editor) References(ctx context.Context, path string, pos Pos) ([]protocol.Location, error) { - if e.Server == nil { - return nil, nil - } - e.mu.Lock() - _, ok := e.buffers[path] - e.mu.Unlock() - if !ok { - return nil, fmt.Errorf("buffer %q is not open", path) - } - params := &protocol.ReferenceParams{ - TextDocumentPositionParams: protocol.TextDocumentPositionParams{ - TextDocument: e.textDocumentIdentifier(path), - Position: pos.ToProtocolPosition(), - }, - Context: protocol.ReferenceContext{ - IncludeDeclaration: true, - }, - } - locations, err := e.Server.References(ctx, params) - if err != nil { - return nil, err - } - return locations, nil -} - -// CodeAction executes a codeAction request on the server. -func (e *Editor) CodeAction(ctx context.Context, path string, rng *protocol.Range) ([]protocol.CodeAction, error) { - if e.Server == nil { - return nil, nil - } - e.mu.Lock() - _, ok := e.buffers[path] - e.mu.Unlock() - if !ok { - return nil, fmt.Errorf("buffer %q is not open", path) - } - params := &protocol.CodeActionParams{ - TextDocument: e.textDocumentIdentifier(path), - } - if rng != nil { - params.Range = *rng - } - lens, err := e.Server.CodeAction(ctx, params) - if err != nil { - return nil, err - } - return lens, nil -} - -// Hover triggers a hover at the given position in an open buffer. -func (e *Editor) Hover(ctx context.Context, path string, pos Pos) (*protocol.MarkupContent, Pos, error) { - if err := e.checkBufferPosition(path, pos); err != nil { - return nil, Pos{}, err - } - params := &protocol.HoverParams{} - params.TextDocument.URI = e.sandbox.Workdir.URI(path) - params.Position = pos.ToProtocolPosition() - - resp, err := e.Server.Hover(ctx, params) - if err != nil { - return nil, Pos{}, errors.Errorf("hover: %w", err) - } - if resp == nil { - return nil, Pos{}, nil - } - return &resp.Contents, fromProtocolPosition(resp.Range.Start), nil -} - -func (e *Editor) DocumentLink(ctx context.Context, path string) ([]protocol.DocumentLink, error) { - if e.Server == nil { - return nil, nil - } - params := &protocol.DocumentLinkParams{} - params.TextDocument.URI = e.sandbox.Workdir.URI(path) - return e.Server.DocumentLink(ctx, params) -}