mirror of
https://github.com/golang/go
synced 2024-11-18 23:24:39 -07:00
3d22a3cfff
We now wait to build views until we have the options for that view, and pass the options in to the view constructor. The environment and build flags are now part of the view options. Change-Id: I303c8ba1eefd01b66962ba9cadb4847d3d2e1d3b Reviewed-on: https://go-review.googlesource.com/c/tools/+/194278 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
359 lines
11 KiB
Go
359 lines
11 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 (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
|
|
"golang.org/x/tools/internal/jsonrpc2"
|
|
"golang.org/x/tools/internal/lsp/debug"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/source"
|
|
"golang.org/x/tools/internal/span"
|
|
"golang.org/x/tools/internal/telemetry/log"
|
|
"golang.org/x/tools/internal/telemetry/tag"
|
|
errors "golang.org/x/xerrors"
|
|
)
|
|
|
|
func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitia) (*protocol.InitializeResult, error) {
|
|
s.stateMu.Lock()
|
|
state := s.state
|
|
s.stateMu.Unlock()
|
|
if state >= serverInitializing {
|
|
return nil, jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server already initialized")
|
|
}
|
|
s.stateMu.Lock()
|
|
s.state = serverInitializing
|
|
s.stateMu.Unlock()
|
|
|
|
options := s.session.Options()
|
|
defer func() { s.session.SetOptions(options) }()
|
|
|
|
// TODO: Remove the option once we are certain there are no issues here.
|
|
options.TextDocumentSyncKind = protocol.Incremental
|
|
if opts, ok := params.InitializationOptions.(map[string]interface{}); ok {
|
|
if opt, ok := opts["noIncrementalSync"].(bool); ok && opt {
|
|
options.TextDocumentSyncKind = protocol.Full
|
|
}
|
|
|
|
// Check if user has enabled watching for file changes.
|
|
setBool(&options.WatchFileChanges, opts, "watchFileChanges")
|
|
}
|
|
|
|
// Default to using synopsis as a default for hover information.
|
|
options.HoverKind = source.SynopsisDocumentation
|
|
|
|
options.SupportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
|
|
source.Go: {
|
|
protocol.SourceOrganizeImports: true,
|
|
protocol.QuickFix: true,
|
|
},
|
|
source.Mod: {},
|
|
source.Sum: {},
|
|
}
|
|
|
|
s.setClientCapabilities(&options, params.Capabilities)
|
|
|
|
s.pendingFolders = params.WorkspaceFolders
|
|
if len(s.pendingFolders) == 0 {
|
|
if params.RootURI != "" {
|
|
s.pendingFolders = []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, errors.Errorf("single file mode not supported yet")
|
|
}
|
|
}
|
|
|
|
var codeActionProvider interface{}
|
|
if params.Capabilities.TextDocument.CodeAction.CodeActionLiteralSupport != nil &&
|
|
len(params.Capabilities.TextDocument.CodeAction.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 {
|
|
// If the client has specified CodeActionLiteralSupport,
|
|
// send the code actions we support.
|
|
//
|
|
// Using CodeActionOptions is only valid if codeActionLiteralSupport is set.
|
|
codeActionProvider = &protocol.CodeActionOptions{
|
|
CodeActionKinds: s.getSupportedCodeActions(),
|
|
}
|
|
} else {
|
|
codeActionProvider = true
|
|
}
|
|
var renameOpts interface{}
|
|
if params.Capabilities.TextDocument.Rename.PrepareSupport {
|
|
renameOpts = &protocol.RenameOptions{
|
|
PrepareProvider: true,
|
|
}
|
|
} else {
|
|
renameOpts = true
|
|
}
|
|
return &protocol.InitializeResult{
|
|
Capabilities: protocol.ServerCapabilities{
|
|
CodeActionProvider: codeActionProvider,
|
|
CompletionProvider: &protocol.CompletionOptions{
|
|
TriggerCharacters: []string{"."},
|
|
},
|
|
DefinitionProvider: true,
|
|
DocumentFormattingProvider: true,
|
|
DocumentSymbolProvider: true,
|
|
FoldingRangeProvider: true,
|
|
HoverProvider: true,
|
|
DocumentHighlightProvider: true,
|
|
DocumentLinkProvider: &protocol.DocumentLinkOptions{},
|
|
ReferencesProvider: true,
|
|
RenameProvider: renameOpts,
|
|
SignatureHelpProvider: &protocol.SignatureHelpOptions{
|
|
TriggerCharacters: []string{"(", ","},
|
|
},
|
|
TextDocumentSync: &protocol.TextDocumentSyncOptions{
|
|
Change: options.TextDocumentSyncKind,
|
|
OpenClose: true,
|
|
Save: &protocol.SaveOptions{
|
|
IncludeText: false,
|
|
},
|
|
},
|
|
TypeDefinitionProvider: true,
|
|
Workspace: &struct {
|
|
WorkspaceFolders *struct {
|
|
Supported bool "json:\"supported,omitempty\""
|
|
ChangeNotifications string "json:\"changeNotifications,omitempty\""
|
|
} "json:\"workspaceFolders,omitempty\""
|
|
}{
|
|
WorkspaceFolders: &struct {
|
|
Supported bool "json:\"supported,omitempty\""
|
|
ChangeNotifications string "json:\"changeNotifications,omitempty\""
|
|
}{
|
|
Supported: true,
|
|
ChangeNotifications: "workspace/didChangeWorkspaceFolders",
|
|
},
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (s *Server) setClientCapabilities(o *source.SessionOptions, caps protocol.ClientCapabilities) {
|
|
// Check if the client supports snippets in completion items.
|
|
o.InsertTextFormat = protocol.PlainTextTextFormat
|
|
if caps.TextDocument.Completion.CompletionItem != nil &&
|
|
caps.TextDocument.Completion.CompletionItem.SnippetSupport {
|
|
o.InsertTextFormat = protocol.SnippetTextFormat
|
|
}
|
|
// Check if the client supports configuration messages.
|
|
o.ConfigurationSupported = caps.Workspace.Configuration
|
|
o.DynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
|
|
o.DynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration
|
|
|
|
// Check which types of content format are supported by this client.
|
|
o.PreferredContentFormat = protocol.PlainText
|
|
if len(caps.TextDocument.Hover.ContentFormat) > 0 {
|
|
o.PreferredContentFormat = caps.TextDocument.Hover.ContentFormat[0]
|
|
}
|
|
// Check if the client supports only line folding.
|
|
o.LineFoldingOnly = caps.TextDocument.FoldingRange.LineFoldingOnly
|
|
}
|
|
|
|
func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
|
|
s.stateMu.Lock()
|
|
s.state = serverInitialized
|
|
s.stateMu.Unlock()
|
|
|
|
options := s.session.Options()
|
|
defer func() { s.session.SetOptions(options) }()
|
|
|
|
var registrations []protocol.Registration
|
|
if options.ConfigurationSupported && options.DynamicConfigurationSupported {
|
|
registrations = append(registrations,
|
|
protocol.Registration{
|
|
ID: "workspace/didChangeConfiguration",
|
|
Method: "workspace/didChangeConfiguration",
|
|
},
|
|
protocol.Registration{
|
|
ID: "workspace/didChangeWorkspaceFolders",
|
|
Method: "workspace/didChangeWorkspaceFolders",
|
|
},
|
|
)
|
|
}
|
|
|
|
if options.WatchFileChanges && options.DynamicWatchedFilesSupported {
|
|
registrations = append(registrations, protocol.Registration{
|
|
ID: "workspace/didChangeWatchedFiles",
|
|
Method: "workspace/didChangeWatchedFiles",
|
|
RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
|
|
Watchers: []protocol.FileSystemWatcher{{
|
|
GlobPattern: "**/*.go",
|
|
Kind: float64(protocol.WatchChange),
|
|
}},
|
|
},
|
|
})
|
|
}
|
|
|
|
if len(registrations) > 0 {
|
|
s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
|
|
Registrations: registrations,
|
|
})
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
|
debug.PrintVersionInfo(buf, true, debug.PlainText)
|
|
log.Print(ctx, buf.String())
|
|
|
|
for _, folder := range s.pendingFolders {
|
|
if err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
s.pendingFolders = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, options *source.SessionOptions, vo *source.ViewOptions) error {
|
|
if !options.ConfigurationSupported {
|
|
return nil
|
|
}
|
|
v := protocol.ParamConfig{
|
|
protocol.ConfigurationParams{
|
|
Items: []protocol.ConfigurationItem{{
|
|
ScopeURI: protocol.NewURI(folder),
|
|
Section: "gopls",
|
|
}, {
|
|
ScopeURI: protocol.NewURI(folder),
|
|
Section: name,
|
|
},
|
|
},
|
|
}, protocol.PartialResultParams{},
|
|
}
|
|
configs, err := s.client.Configuration(ctx, &v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, config := range configs {
|
|
if err := s.processConfig(ctx, options, vo, config); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) processConfig(ctx context.Context, options *source.SessionOptions, vo *source.ViewOptions, 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 errors.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 errors.Errorf("invalid config gopls.env type %T", env)
|
|
}
|
|
for k, v := range menv {
|
|
vo.Env = append(vo.Env, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
}
|
|
|
|
// Get the build flags for the go/packages config.
|
|
if buildFlags := c["buildFlags"]; buildFlags != nil {
|
|
iflags, ok := buildFlags.([]interface{})
|
|
if !ok {
|
|
return errors.Errorf("invalid config gopls.buildFlags type %T", buildFlags)
|
|
}
|
|
flags := make([]string, 0, len(iflags))
|
|
for _, flag := range iflags {
|
|
flags = append(flags, fmt.Sprintf("%s", flag))
|
|
}
|
|
vo.BuildFlags = flags
|
|
}
|
|
|
|
// Set the hover kind.
|
|
if hoverKind, ok := c["hoverKind"].(string); ok {
|
|
switch hoverKind {
|
|
case "NoDocumentation":
|
|
options.HoverKind = source.NoDocumentation
|
|
case "SingleLine":
|
|
options.HoverKind = source.SingleLine
|
|
case "SynopsisDocumentation":
|
|
options.HoverKind = source.SynopsisDocumentation
|
|
case "FullDocumentation":
|
|
options.HoverKind = source.FullDocumentation
|
|
case "Structured":
|
|
options.HoverKind = source.Structured
|
|
default:
|
|
log.Error(ctx, "unsupported hover kind", nil, tag.Of("HoverKind", hoverKind))
|
|
// The default value is already be set to synopsis.
|
|
}
|
|
}
|
|
|
|
// Check if the user has explicitly disabled any analyses.
|
|
if disabledAnalyses, ok := c["experimentalDisabledAnalyses"].([]interface{}); ok {
|
|
options.DisabledAnalyses = make(map[string]struct{})
|
|
for _, a := range disabledAnalyses {
|
|
if a, ok := a.(string); ok {
|
|
options.DisabledAnalyses[a] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set completion options. For now, we allow disabling of completion documentation,
|
|
// deep completion, and fuzzy matching.
|
|
setBool(&options.Completion.Documentation, c, "wantCompletionDocumentation")
|
|
setNotBool(&options.Completion.Deep, c, "disableDeepCompletion")
|
|
setNotBool(&options.Completion.FuzzyMatching, c, "disableFuzzyMatching")
|
|
|
|
// Unimported package completion is still experimental, so not enabled by default.
|
|
setBool(&options.Completion.Unimported, c, "wantUnimportedCompletions")
|
|
|
|
// If the user wants placeholders for autocompletion results.
|
|
setBool(&options.Completion.Placeholders, c, "usePlaceholders")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) shutdown(ctx context.Context) error {
|
|
s.stateMu.Lock()
|
|
defer s.stateMu.Unlock()
|
|
if s.state < serverInitialized {
|
|
return jsonrpc2.NewErrorf(jsonrpc2.CodeInvalidRequest, "server not initialized")
|
|
}
|
|
// drop all the active views
|
|
s.session.Shutdown(ctx)
|
|
s.state = serverShutDown
|
|
return nil
|
|
}
|
|
|
|
func (s *Server) exit(ctx context.Context) error {
|
|
s.stateMu.Lock()
|
|
defer s.stateMu.Unlock()
|
|
if s.state != serverShutDown {
|
|
os.Exit(1)
|
|
}
|
|
os.Exit(0)
|
|
return nil
|
|
}
|
|
|
|
func setBool(b *bool, m map[string]interface{}, name string) {
|
|
if v, ok := m[name].(bool); ok {
|
|
*b = v
|
|
}
|
|
}
|
|
|
|
func setNotBool(b *bool, m map[string]interface{}, name string) {
|
|
if v, ok := m[name].(bool); ok {
|
|
*b = !v
|
|
}
|
|
}
|