mirror of
https://github.com/golang/go
synced 2024-11-18 22:34:45 -07:00
4796d4bd3d
This change associates an ast.Node for some object declarations. In this case, we only handle type declarations, but future changes will support other objects as well. This is the first step in adding documentation on hover. Updates golang/go#29151 Change-Id: I39ddccf4130ee3b106725286375cd74bc51bcd38 Reviewed-on: https://go-review.googlesource.com/c/tools/+/172661 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
204 lines
6.0 KiB
Go
204 lines
6.0 KiB
Go
// 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.insertTextFormat = protocol.PlainTextTextFormat
|
|
if caps.TextDocument.Completion.CompletionItem.SnippetSupport {
|
|
s.insertTextFormat = protocol.SnippetTextFormat
|
|
}
|
|
// Check if the client supports configuration messages.
|
|
s.configurationSupported = caps.Workspace.Configuration
|
|
s.dynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
|
|
|
|
// Check which types of content format are supported by this client.
|
|
s.preferredContentFormat = protocol.PlainText
|
|
if len(caps.TextDocument.Hover.ContentFormat) > 0 {
|
|
s.preferredContentFormat = caps.TextDocument.Hover.ContentFormat[0]
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
// Check if enhancedHover is enabled.
|
|
if enhancedHover, ok := c["enhancedHover"].(bool); ok {
|
|
s.enhancedHover = enhancedHover
|
|
}
|
|
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
|
|
}
|