mirror of
https://github.com/golang/go
synced 2024-11-18 15:04:44 -07:00
internal/lsp: refactor server.go to separate into LSP categories
This change separates the different behaviors of server.go by the categories defined in the spec. This allows us to differentiate more easily between the language features and the text synchronization code. I also renamed the "Symbols" function to "Symbol", which fits with the specification (https://microsoft.github.io/language-server-protocol/specification#workspace_symbol), and makes clearer the distinction between DocumentSymbols and Symbol. Change-Id: I926b8a772c478f6ae426352fb12dc4403f0e736a Reviewed-on: https://go-review.googlesource.com/c/tools/+/172637 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
6149f385e4
commit
34437f544f
81
internal/lsp/definition.go
Normal file
81
internal/lsp/definition.go
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2019 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 lsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) definition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ident, err := source.Identifier(ctx, view, f, rng.Start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decSpan, err := ident.Declaration.Range.Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, decM, err := newColumnMap(ctx, view, decSpan.URI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loc, err := decM.Location(decSpan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []protocol.Location{loc}, nil
|
||||
}
|
||||
|
||||
func (s *Server) typeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ident, err := source.Identifier(ctx, view, f, rng.Start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
identSpan, err := ident.Type.Range.Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, identM, err := newColumnMap(ctx, view, identSpan.URI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loc, err := identM.Location(identSpan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []protocol.Location{loc}, nil
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
// Copyright 2018 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 lsp
|
||||
|
||||
import (
|
||||
@ -9,6 +13,27 @@ import (
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
spn := span.New(uri, span.Point{}, span.Point{})
|
||||
return formatRange(ctx, view, spn)
|
||||
}
|
||||
|
||||
func (s *Server) rangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
_, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.RangeSpan(params.Range)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return formatRange(ctx, view, spn)
|
||||
}
|
||||
|
||||
// formatRange formats a document with a given range.
|
||||
func formatRange(ctx context.Context, v source.View, s span.Span) ([]protocol.TextEdit, error) {
|
||||
f, m, err := newColumnMap(ctx, v, s.URI())
|
||||
@ -69,16 +94,3 @@ func FromProtocolEdits(m *protocol.ColumnMapper, edits []protocol.TextEdit) ([]s
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func newColumnMap(ctx context.Context, v source.View, uri span.URI) (source.File, *protocol.ColumnMapper, error) {
|
||||
f, err := v.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tok := f.GetToken(ctx)
|
||||
if tok == nil {
|
||||
return nil, nil, fmt.Errorf("no file information for %v", f.URI())
|
||||
}
|
||||
m := protocol.NewColumnMapper(f.URI(), f.GetFileSet(ctx), tok, f.GetContent(ctx))
|
||||
return f, m, nil
|
||||
}
|
||||
|
190
internal/lsp/general.go
Normal file
190
internal/lsp/general.go
Normal file
@ -0,0 +1,190 @@
|
||||
// Copyright 2019 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 lsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/jsonrpc2"
|
||||
"golang.org/x/tools/internal/lsp/cache"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
|
||||
s.initializedMu.Lock()
|
||||
defer s.initializedMu.Unlock()
|
||||
if s.isInitialized {
|
||||
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized")
|
||||
}
|
||||
s.isInitialized = true // mark server as initialized now
|
||||
|
||||
// TODO(rstambler): Change this default to protocol.Incremental (or add a
|
||||
// flag). Disabled for now to simplify debugging.
|
||||
s.textDocumentSyncKind = protocol.Full
|
||||
|
||||
s.setClientCapabilities(params.Capabilities)
|
||||
|
||||
// We need a "detached" context so it does not get timeout cancelled.
|
||||
// TODO(iancottrell): Do we need to copy any values across?
|
||||
viewContext := context.Background()
|
||||
folders := params.WorkspaceFolders
|
||||
if len(folders) == 0 {
|
||||
if params.RootURI != "" {
|
||||
folders = []protocol.WorkspaceFolder{{
|
||||
URI: params.RootURI,
|
||||
Name: path.Base(params.RootURI),
|
||||
}}
|
||||
} else {
|
||||
// no folders and no root, single file mode
|
||||
//TODO(iancottrell): not sure how to do single file mode yet
|
||||
//issue: golang.org/issue/31168
|
||||
return nil, fmt.Errorf("single file mode not supported yet")
|
||||
}
|
||||
}
|
||||
for _, folder := range folders {
|
||||
uri := span.NewURI(folder.URI)
|
||||
folderPath, err := uri.Filename()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.views = append(s.views, cache.NewView(viewContext, s.log, folder.Name, uri, &packages.Config{
|
||||
Context: ctx,
|
||||
Dir: folderPath,
|
||||
Env: os.Environ(),
|
||||
Mode: packages.LoadImports,
|
||||
Fset: token.NewFileSet(),
|
||||
Overlay: make(map[string][]byte),
|
||||
ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
|
||||
return parser.ParseFile(fset, filename, src, parser.AllErrors|parser.ParseComments)
|
||||
},
|
||||
Tests: true,
|
||||
}))
|
||||
}
|
||||
|
||||
return &protocol.InitializeResult{
|
||||
Capabilities: protocol.ServerCapabilities{
|
||||
CodeActionProvider: true,
|
||||
CompletionProvider: &protocol.CompletionOptions{
|
||||
TriggerCharacters: []string{"."},
|
||||
},
|
||||
DefinitionProvider: true,
|
||||
DocumentFormattingProvider: true,
|
||||
DocumentRangeFormattingProvider: true,
|
||||
DocumentSymbolProvider: true,
|
||||
HoverProvider: true,
|
||||
DocumentHighlightProvider: true,
|
||||
SignatureHelpProvider: &protocol.SignatureHelpOptions{
|
||||
TriggerCharacters: []string{"(", ","},
|
||||
},
|
||||
TextDocumentSync: &protocol.TextDocumentSyncOptions{
|
||||
Change: s.textDocumentSyncKind,
|
||||
OpenClose: true,
|
||||
},
|
||||
TypeDefinitionProvider: true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) setClientCapabilities(caps protocol.ClientCapabilities) {
|
||||
// Check if the client supports snippets in completion items.
|
||||
s.snippetsSupported = caps.TextDocument.Completion.CompletionItem.SnippetSupport
|
||||
// Check if the client supports configuration messages.
|
||||
s.configurationSupported = caps.Workspace.Configuration
|
||||
s.dynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
|
||||
}
|
||||
|
||||
func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
|
||||
if s.configurationSupported {
|
||||
if s.dynamicConfigurationSupported {
|
||||
s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
|
||||
Registrations: []protocol.Registration{{
|
||||
ID: "workspace/didChangeConfiguration",
|
||||
Method: "workspace/didChangeConfiguration",
|
||||
}},
|
||||
})
|
||||
}
|
||||
for _, view := range s.views {
|
||||
config, err := s.client.Configuration(ctx, &protocol.ConfigurationParams{
|
||||
Items: []protocol.ConfigurationItem{{
|
||||
ScopeURI: protocol.NewURI(view.Folder),
|
||||
Section: "gopls",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.processConfig(view, config[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) processConfig(view *cache.View, config interface{}) error {
|
||||
// TODO: We should probably store and process more of the config.
|
||||
if config == nil {
|
||||
return nil // ignore error if you don't have a config
|
||||
}
|
||||
c, ok := config.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid config gopls type %T", config)
|
||||
}
|
||||
// Get the environment for the go/packages config.
|
||||
if env := c["env"]; env != nil {
|
||||
menv, ok := env.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid config gopls.env type %T", env)
|
||||
}
|
||||
for k, v := range menv {
|
||||
view.Config.Env = applyEnv(view.Config.Env, k, v)
|
||||
}
|
||||
}
|
||||
// Check if placeholders are enabled.
|
||||
if usePlaceholders, ok := c["usePlaceholders"].(bool); ok {
|
||||
s.usePlaceholders = usePlaceholders
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyEnv(env []string, k string, v interface{}) []string {
|
||||
prefix := k + "="
|
||||
value := prefix + fmt.Sprint(v)
|
||||
for i, s := range env {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
env[i] = value
|
||||
return env
|
||||
}
|
||||
}
|
||||
return append(env, value)
|
||||
}
|
||||
|
||||
func (s *Server) shutdown(ctx context.Context) error {
|
||||
// TODO(rstambler): Cancel contexts here?
|
||||
s.initializedMu.Lock()
|
||||
defer s.initializedMu.Unlock()
|
||||
if !s.isInitialized {
|
||||
return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
|
||||
}
|
||||
s.isInitialized = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) exit(ctx context.Context) error {
|
||||
if s.isInitialized {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
@ -5,10 +5,32 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) documentHighlight(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spans := source.Highlight(ctx, f, rng.Start)
|
||||
return toProtocolHighlight(m, spans), nil
|
||||
}
|
||||
|
||||
func toProtocolHighlight(m *protocol.ColumnMapper, spans []span.Span) []protocol.DocumentHighlight {
|
||||
result := make([]protocol.DocumentHighlight, 0, len(spans))
|
||||
kind := protocol.Text
|
||||
|
54
internal/lsp/hover.go
Normal file
54
internal/lsp/hover.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2019 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 lsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) hover(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Hover, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
identRange, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ident, err := source.Identifier(ctx, view, f, identRange.Start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := ident.Hover(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
markdown := "```go\n" + content + "\n```"
|
||||
identSpan, err := ident.Range.Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := m.Range(identSpan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &protocol.Hover{
|
||||
Contents: protocol.MarkupContent{
|
||||
Kind: protocol.Markdown,
|
||||
Value: markdown,
|
||||
},
|
||||
Range: &rng,
|
||||
}, nil
|
||||
}
|
@ -20,7 +20,7 @@ type Server interface {
|
||||
DidChangeWorkspaceFolders(context.Context, *DidChangeWorkspaceFoldersParams) error
|
||||
DidChangeConfiguration(context.Context, *DidChangeConfigurationParams) error
|
||||
DidChangeWatchedFiles(context.Context, *DidChangeWatchedFilesParams) error
|
||||
Symbols(context.Context, *WorkspaceSymbolParams) ([]SymbolInformation, error)
|
||||
Symbol(context.Context, *WorkspaceSymbolParams) ([]SymbolInformation, error)
|
||||
ExecuteCommand(context.Context, *ExecuteCommandParams) (interface{}, error)
|
||||
DidOpen(context.Context, *DidOpenTextDocumentParams) error
|
||||
DidChange(context.Context, *DidChangeTextDocumentParams) error
|
||||
@ -140,7 +140,7 @@ func serverHandler(log xlog.Logger, server Server) jsonrpc2.Handler {
|
||||
sendParseError(ctx, log, conn, r, err)
|
||||
return
|
||||
}
|
||||
resp, err := server.Symbols(ctx, ¶ms)
|
||||
resp, err := server.Symbol(ctx, ¶ms)
|
||||
if err := conn.Reply(ctx, r, resp, err); err != nil {
|
||||
log.Errorf(ctx, "%v", err)
|
||||
}
|
||||
@ -502,7 +502,7 @@ func (s *serverDispatcher) DidChangeWatchedFiles(ctx context.Context, params *Di
|
||||
return s.Conn.Notify(ctx, "workspace/didChangeWatchedFiles", params)
|
||||
}
|
||||
|
||||
func (s *serverDispatcher) Symbols(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation, error) {
|
||||
func (s *serverDispatcher) Symbol(ctx context.Context, params *WorkspaceSymbolParams) ([]SymbolInformation, error) {
|
||||
var result []SymbolInformation
|
||||
if err := s.Conn.Call(ctx, "workspace/symbol", params, &result); err != nil {
|
||||
return nil, err
|
||||
|
@ -5,19 +5,11 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/jsonrpc2"
|
||||
"golang.org/x/tools/internal/lsp/cache"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
@ -67,13 +59,17 @@ func RunServerOnAddress(ctx context.Context, addr string, h func(s *Server)) err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Run(ctx context.Context) error {
|
||||
return s.Conn.Run(ctx)
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
Conn *jsonrpc2.Conn
|
||||
client protocol.Client
|
||||
log xlog.Logger
|
||||
|
||||
initializedMu sync.Mutex
|
||||
initialized bool // set once the server has received "initialize" request
|
||||
isInitialized bool // set once the server has received "initialize" request
|
||||
|
||||
// Configurations.
|
||||
// TODO(rstambler): Separate these into their own struct?
|
||||
@ -92,139 +88,26 @@ type Server struct {
|
||||
undelivered map[span.URI][]source.Diagnostic
|
||||
}
|
||||
|
||||
func (s *Server) Run(ctx context.Context) error {
|
||||
return s.Conn.Run(ctx)
|
||||
}
|
||||
// General
|
||||
|
||||
func (s *Server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
|
||||
s.initializedMu.Lock()
|
||||
defer s.initializedMu.Unlock()
|
||||
if s.initialized {
|
||||
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized")
|
||||
}
|
||||
s.initialized = true // mark server as initialized now
|
||||
|
||||
// TODO(rstambler): Change this default to protocol.Incremental (or add a
|
||||
// flag). Disabled for now to simplify debugging.
|
||||
s.textDocumentSyncKind = protocol.Full
|
||||
|
||||
s.setClientCapabilities(params.Capabilities)
|
||||
|
||||
// We need a "detached" context so it does not get timeout cancelled.
|
||||
// TODO(iancottrell): Do we need to copy any values across?
|
||||
viewContext := context.Background()
|
||||
folders := params.WorkspaceFolders
|
||||
if len(folders) == 0 {
|
||||
if params.RootURI != "" {
|
||||
folders = []protocol.WorkspaceFolder{{
|
||||
URI: params.RootURI,
|
||||
Name: path.Base(params.RootURI),
|
||||
}}
|
||||
} else {
|
||||
// no folders and no root, single file mode
|
||||
//TODO(iancottrell): not sure how to do single file mode yet
|
||||
//issue: golang.org/issue/31168
|
||||
return nil, fmt.Errorf("single file mode not supported yet")
|
||||
}
|
||||
}
|
||||
for _, folder := range folders {
|
||||
uri := span.NewURI(folder.URI)
|
||||
folderPath, err := uri.Filename()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.views = append(s.views, cache.NewView(viewContext, s.log, folder.Name, uri, &packages.Config{
|
||||
Context: ctx,
|
||||
Dir: folderPath,
|
||||
Env: os.Environ(),
|
||||
Mode: packages.LoadImports,
|
||||
Fset: token.NewFileSet(),
|
||||
Overlay: make(map[string][]byte),
|
||||
ParseFile: func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
|
||||
return parser.ParseFile(fset, filename, src, parser.AllErrors|parser.ParseComments)
|
||||
},
|
||||
Tests: true,
|
||||
}))
|
||||
}
|
||||
|
||||
return &protocol.InitializeResult{
|
||||
Capabilities: protocol.ServerCapabilities{
|
||||
CodeActionProvider: true,
|
||||
CompletionProvider: &protocol.CompletionOptions{
|
||||
TriggerCharacters: []string{"."},
|
||||
},
|
||||
DefinitionProvider: true,
|
||||
DocumentFormattingProvider: true,
|
||||
DocumentRangeFormattingProvider: true,
|
||||
DocumentSymbolProvider: true,
|
||||
HoverProvider: true,
|
||||
DocumentHighlightProvider: true,
|
||||
SignatureHelpProvider: &protocol.SignatureHelpOptions{
|
||||
TriggerCharacters: []string{"(", ","},
|
||||
},
|
||||
TextDocumentSync: &protocol.TextDocumentSyncOptions{
|
||||
Change: s.textDocumentSyncKind,
|
||||
OpenClose: true,
|
||||
},
|
||||
TypeDefinitionProvider: true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) setClientCapabilities(caps protocol.ClientCapabilities) {
|
||||
// Check if the client supports snippets in completion items.
|
||||
s.snippetsSupported = caps.TextDocument.Completion.CompletionItem.SnippetSupport
|
||||
// Check if the client supports configuration messages.
|
||||
s.configurationSupported = caps.Workspace.Configuration
|
||||
s.dynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
|
||||
return s.initialize(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) Initialized(ctx context.Context, params *protocol.InitializedParams) error {
|
||||
if s.configurationSupported {
|
||||
if s.dynamicConfigurationSupported {
|
||||
s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
|
||||
Registrations: []protocol.Registration{{
|
||||
ID: "workspace/didChangeConfiguration",
|
||||
Method: "workspace/didChangeConfiguration",
|
||||
}},
|
||||
})
|
||||
}
|
||||
for _, view := range s.views {
|
||||
config, err := s.client.Configuration(ctx, &protocol.ConfigurationParams{
|
||||
Items: []protocol.ConfigurationItem{{
|
||||
ScopeURI: protocol.NewURI(view.Folder),
|
||||
Section: "gopls",
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.processConfig(view, config[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return s.initialized(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) Shutdown(context.Context) error {
|
||||
s.initializedMu.Lock()
|
||||
defer s.initializedMu.Unlock()
|
||||
if !s.initialized {
|
||||
return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
|
||||
}
|
||||
s.initialized = false
|
||||
return nil
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
return s.shutdown(ctx)
|
||||
}
|
||||
|
||||
func (s *Server) Exit(ctx context.Context) error {
|
||||
if s.initialized {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
return nil
|
||||
return s.exit(ctx)
|
||||
}
|
||||
|
||||
// Workspace
|
||||
|
||||
func (s *Server) DidChangeWorkspaceFolders(context.Context, *protocol.DidChangeWorkspaceFoldersParams) error {
|
||||
return notImplemented("DidChangeWorkspaceFolders")
|
||||
}
|
||||
@ -237,78 +120,22 @@ func (s *Server) DidChangeWatchedFiles(context.Context, *protocol.DidChangeWatch
|
||||
return notImplemented("DidChangeWatchedFiles")
|
||||
}
|
||||
|
||||
func (s *Server) Symbols(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
|
||||
return nil, notImplemented("Symbols")
|
||||
func (s *Server) Symbol(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
|
||||
return nil, notImplemented("Symbol")
|
||||
}
|
||||
|
||||
func (s *Server) ExecuteCommand(context.Context, *protocol.ExecuteCommandParams) (interface{}, error) {
|
||||
return nil, notImplemented("ExecuteCommand")
|
||||
}
|
||||
|
||||
// Text Synchronization
|
||||
|
||||
func (s *Server) DidOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
|
||||
return s.cacheAndDiagnose(ctx, span.NewURI(params.TextDocument.URI), params.TextDocument.Text)
|
||||
}
|
||||
|
||||
func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTextDocumentParams) (string, error) {
|
||||
if len(params.ContentChanges) == 1 && params.ContentChanges[0].Range == nil {
|
||||
// If range is empty, we expect the full content of file, i.e. a single change with no range.
|
||||
change := params.ContentChanges[0]
|
||||
if change.RangeLength != 0 {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided")
|
||||
}
|
||||
return change.Text, nil
|
||||
}
|
||||
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
file, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
||||
}
|
||||
content := file.GetContent(ctx)
|
||||
for _, change := range params.ContentChanges {
|
||||
spn, err := m.RangeSpan(*change.Range)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !spn.HasOffset() {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
|
||||
}
|
||||
start, end := spn.Start().Offset(), spn.End().Offset()
|
||||
if end <= start {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.Write(content[:start])
|
||||
buf.WriteString(change.Text)
|
||||
buf.Write(content[end:])
|
||||
content = buf.Bytes()
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func (s *Server) DidChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
|
||||
if len(params.ContentChanges) < 1 {
|
||||
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "no content changes provided")
|
||||
}
|
||||
|
||||
var text string
|
||||
switch s.textDocumentSyncKind {
|
||||
case protocol.Incremental:
|
||||
var err error
|
||||
text, err = s.applyChanges(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case protocol.Full:
|
||||
// We expect the full content of file, i.e. a single change with no range.
|
||||
change := params.ContentChanges[0]
|
||||
if change.RangeLength != 0 {
|
||||
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided")
|
||||
}
|
||||
text = change.Text
|
||||
}
|
||||
return s.cacheAndDiagnose(ctx, span.NewURI(params.TextDocument.URI), text)
|
||||
return s.didChange(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) WillSave(context.Context, *protocol.WillSaveTextDocumentParams) error {
|
||||
@ -319,16 +146,16 @@ func (s *Server) WillSaveWaitUntil(context.Context, *protocol.WillSaveTextDocume
|
||||
return nil, notImplemented("WillSaveWaitUntil")
|
||||
}
|
||||
|
||||
func (s *Server) DidSave(context.Context, *protocol.DidSaveTextDocumentParams) error {
|
||||
return nil // ignore
|
||||
func (s *Server) DidSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
|
||||
return s.didSave(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
return view.SetContent(ctx, uri, nil)
|
||||
return s.didClose(ctx, params)
|
||||
}
|
||||
|
||||
// Language Features
|
||||
|
||||
func (s *Server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
|
||||
return s.completion(ctx, params)
|
||||
}
|
||||
@ -338,134 +165,19 @@ func (s *Server) CompletionResolve(context.Context, *protocol.CompletionItem) (*
|
||||
}
|
||||
|
||||
func (s *Server) Hover(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.Hover, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
identRange, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ident, err := source.Identifier(ctx, view, f, identRange.Start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, err := ident.Hover(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
markdown := "```go\n" + content + "\n```"
|
||||
identSpan, err := ident.Range.Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := m.Range(identSpan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &protocol.Hover{
|
||||
Contents: protocol.MarkupContent{
|
||||
Kind: protocol.Markdown,
|
||||
Value: markdown,
|
||||
},
|
||||
Range: &rng,
|
||||
}, nil
|
||||
return s.hover(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) SignatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := source.SignatureHelp(ctx, f, rng.Start)
|
||||
if err != nil {
|
||||
s.log.Infof(ctx, "no signature help for %s:%v:%v : %s", uri, int(params.Position.Line), int(params.Position.Character), err)
|
||||
}
|
||||
return toProtocolSignatureHelp(info), nil
|
||||
return s.signatureHelp(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) Definition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ident, err := source.Identifier(ctx, view, f, rng.Start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decSpan, err := ident.Declaration.Range.Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, decM, err := newColumnMap(ctx, view, decSpan.URI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loc, err := decM.Location(decSpan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []protocol.Location{loc}, nil
|
||||
return s.definition(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) TypeDefinition(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ident, err := source.Identifier(ctx, view, f, rng.Start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
identSpan, err := ident.Type.Range.Span()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, identM, err := newColumnMap(ctx, view, identSpan.URI())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loc, err := identM.Location(identSpan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []protocol.Location{loc}, nil
|
||||
return s.typeDefinition(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) Implementation(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.Location, error) {
|
||||
@ -477,33 +189,11 @@ func (s *Server) References(context.Context, *protocol.ReferenceParams) ([]proto
|
||||
}
|
||||
|
||||
func (s *Server) DocumentHighlight(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spans := source.Highlight(ctx, f, rng.Start)
|
||||
return toProtocolHighlight(m, spans), nil
|
||||
return s.documentHighlight(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
symbols := source.DocumentSymbols(ctx, f)
|
||||
return toProtocolDocumentSymbols(m, symbols), nil
|
||||
return s.documentSymbol(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) CodeAction(ctx context.Context, params *protocol.CodeActionParams) ([]protocol.CodeAction, error) {
|
||||
@ -535,24 +225,11 @@ func (s *Server) ColorPresentation(context.Context, *protocol.ColorPresentationP
|
||||
}
|
||||
|
||||
func (s *Server) Formatting(ctx context.Context, params *protocol.DocumentFormattingParams) ([]protocol.TextEdit, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
spn := span.New(uri, span.Point{}, span.Point{})
|
||||
return formatRange(ctx, view, spn)
|
||||
return s.formatting(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) RangeFormatting(ctx context.Context, params *protocol.DocumentRangeFormattingParams) ([]protocol.TextEdit, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
_, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.RangeSpan(params.Range)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return formatRange(ctx, view, spn)
|
||||
return s.rangeFormatting(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) OnTypeFormatting(context.Context, *protocol.DocumentOnTypeFormattingParams) ([]protocol.TextEdit, error) {
|
||||
@ -567,67 +244,6 @@ func (s *Server) FoldingRanges(context.Context, *protocol.FoldingRangeParams) ([
|
||||
return nil, notImplemented("FoldingRanges")
|
||||
}
|
||||
|
||||
func (s *Server) processConfig(view *cache.View, config interface{}) error {
|
||||
// TODO: We should probably store and process more of the config.
|
||||
if config == nil {
|
||||
return nil // ignore error if you don't have a config
|
||||
}
|
||||
c, ok := config.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid config gopls type %T", config)
|
||||
}
|
||||
// Get the environment for the go/packages config.
|
||||
if env := c["env"]; env != nil {
|
||||
menv, ok := env.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid config gopls.env type %T", env)
|
||||
}
|
||||
for k, v := range menv {
|
||||
view.Config.Env = applyEnv(view.Config.Env, k, v)
|
||||
}
|
||||
}
|
||||
// Check if placeholders are enabled.
|
||||
if usePlaceholders, ok := c["usePlaceholders"].(bool); ok {
|
||||
s.usePlaceholders = usePlaceholders
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyEnv(env []string, k string, v interface{}) []string {
|
||||
prefix := k + "="
|
||||
value := prefix + fmt.Sprint(v)
|
||||
for i, s := range env {
|
||||
if strings.HasPrefix(s, prefix) {
|
||||
env[i] = value
|
||||
return env
|
||||
}
|
||||
}
|
||||
return append(env, value)
|
||||
}
|
||||
|
||||
func notImplemented(method string) *jsonrpc2.Error {
|
||||
return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method)
|
||||
}
|
||||
|
||||
func (s *Server) findView(ctx context.Context, uri span.URI) *cache.View {
|
||||
// first see if a view already has this file
|
||||
for _, view := range s.views {
|
||||
if view.FindFile(ctx, uri) != nil {
|
||||
return view
|
||||
}
|
||||
}
|
||||
var longest *cache.View
|
||||
for _, view := range s.views {
|
||||
if longest != nil && len(longest.Folder) > len(view.Folder) {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(string(uri), string(view.Folder)) {
|
||||
longest = view
|
||||
}
|
||||
}
|
||||
if longest != nil {
|
||||
return longest
|
||||
}
|
||||
//TODO: are there any more heuristics we can use?
|
||||
return s.views[0]
|
||||
}
|
||||
|
@ -5,10 +5,35 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) signatureHelp(ctx context.Context, params *protocol.TextDocumentPositionParams) (*protocol.SignatureHelp, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spn, err := m.PointSpan(params.Position)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng, err := spn.Range(m.Converter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := source.SignatureHelp(ctx, f, rng.Start)
|
||||
if err != nil {
|
||||
s.log.Infof(ctx, "no signature help for %s:%v:%v : %s", uri, int(params.Position.Line), int(params.Position.Character), err)
|
||||
}
|
||||
return toProtocolSignatureHelp(info), nil
|
||||
}
|
||||
|
||||
func toProtocolSignatureHelp(info *source.SignatureInformation) *protocol.SignatureHelp {
|
||||
if info == nil {
|
||||
return &protocol.SignatureHelp{}
|
||||
|
@ -5,10 +5,24 @@
|
||||
package lsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) documentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
f, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
symbols := source.DocumentSymbols(ctx, f)
|
||||
return toProtocolDocumentSymbols(m, symbols), nil
|
||||
}
|
||||
|
||||
func toProtocolDocumentSymbols(m *protocol.ColumnMapper, symbols []source.Symbol) []protocol.DocumentSymbol {
|
||||
result := make([]protocol.DocumentSymbol, 0, len(symbols))
|
||||
for _, s := range symbols {
|
||||
|
86
internal/lsp/text_synchronization.go
Normal file
86
internal/lsp/text_synchronization.go
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2019 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 lsp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/jsonrpc2"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
|
||||
if len(params.ContentChanges) < 1 {
|
||||
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "no content changes provided")
|
||||
}
|
||||
|
||||
var text string
|
||||
switch s.textDocumentSyncKind {
|
||||
case protocol.Incremental:
|
||||
var err error
|
||||
text, err = s.applyChanges(ctx, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case protocol.Full:
|
||||
// We expect the full content of file, i.e. a single change with no range.
|
||||
change := params.ContentChanges[0]
|
||||
if change.RangeLength != 0 {
|
||||
return jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided")
|
||||
}
|
||||
text = change.Text
|
||||
}
|
||||
return s.cacheAndDiagnose(ctx, span.NewURI(params.TextDocument.URI), text)
|
||||
}
|
||||
|
||||
func (s *Server) applyChanges(ctx context.Context, params *protocol.DidChangeTextDocumentParams) (string, error) {
|
||||
if len(params.ContentChanges) == 1 && params.ContentChanges[0].Range == nil {
|
||||
// If range is empty, we expect the full content of file, i.e. a single change with no range.
|
||||
change := params.ContentChanges[0]
|
||||
if change.RangeLength != 0 {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "unexpected change range provided")
|
||||
}
|
||||
return change.Text, nil
|
||||
}
|
||||
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
file, m, err := newColumnMap(ctx, view, uri)
|
||||
if err != nil {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "file not found")
|
||||
}
|
||||
content := file.GetContent(ctx)
|
||||
for _, change := range params.ContentChanges {
|
||||
spn, err := m.RangeSpan(*change.Range)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !spn.HasOffset() {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
|
||||
}
|
||||
start, end := spn.Start().Offset(), spn.End().Offset()
|
||||
if end <= start {
|
||||
return "", jsonrpc2.NewErrorf(jsonrpc2.CodeInternalError, "invalid range for content change")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.Write(content[:start])
|
||||
buf.WriteString(change.Text)
|
||||
buf.Write(content[end:])
|
||||
content = buf.Bytes()
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
|
||||
return nil // ignore
|
||||
}
|
||||
|
||||
func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
|
||||
uri := span.NewURI(params.TextDocument.URI)
|
||||
view := s.findView(ctx, uri)
|
||||
return view.SetContent(ctx, uri, nil)
|
||||
}
|
54
internal/lsp/util.go
Normal file
54
internal/lsp/util.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2019 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 lsp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/cache"
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/lsp/source"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
// findView returns the view corresponding to the given URI.
|
||||
// If the file is not already associated with a view, pick one using some heuristics.
|
||||
func (s *Server) findView(ctx context.Context, uri span.URI) *cache.View {
|
||||
// first see if a view already has this file
|
||||
for _, view := range s.views {
|
||||
if view.FindFile(ctx, uri) != nil {
|
||||
return view
|
||||
}
|
||||
}
|
||||
var longest *cache.View
|
||||
for _, view := range s.views {
|
||||
if longest != nil && len(longest.Folder) > len(view.Folder) {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(string(uri), string(view.Folder)) {
|
||||
longest = view
|
||||
}
|
||||
}
|
||||
if longest != nil {
|
||||
return longest
|
||||
}
|
||||
//TODO: are there any more heuristics we can use?
|
||||
return s.views[0]
|
||||
}
|
||||
|
||||
func newColumnMap(ctx context.Context, v source.View, uri span.URI) (source.File, *protocol.ColumnMapper, error) {
|
||||
f, err := v.GetFile(ctx, uri)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tok := f.GetToken(ctx)
|
||||
if tok == nil {
|
||||
return nil, nil, fmt.Errorf("no file information for %v", f.URI())
|
||||
}
|
||||
m := protocol.NewColumnMapper(f.URI(), f.GetFileSet(ctx), tok, f.GetContent(ctx))
|
||||
return f, m, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user