go/internal/lsp/general.go
Rebecca Stambler 1dcc99b65a internal/lsp: do not show errors for code actions on go.mod files
This change keys the supported code actions map by file kind, so that we
can extend it more easily for go.mod files.

Change-Id: Ic28f91bd517700cf070281b1c4d4ded14a702790
Reviewed-on: https://go-review.googlesource.com/c/tools/+/189039
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2019-08-06 19:11:59 +00:00

285 lines
8.6 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/lsp/telemetry/log"
"golang.org/x/tools/internal/lsp/telemetry/tag"
"golang.org/x/tools/internal/span"
)
func (s *Server) initialize(ctx context.Context, params *protocol.InitializeParams) (*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()
// TODO: Remove the option once we are certain there are no issues here.
s.textDocumentSyncKind = protocol.Incremental
if opts, ok := params.InitializationOptions.(map[string]interface{}); ok {
if opt, ok := opts["noIncrementalSync"].(bool); ok && opt {
s.textDocumentSyncKind = protocol.Full
}
}
// Default to using synopsis as a default for hover information.
s.hoverKind = source.SynopsisDocumentation
s.supportedCodeActions = map[source.FileKind]map[protocol.CodeActionKind]bool{
source.Go: {
protocol.SourceOrganizeImports: true,
protocol.QuickFix: true,
},
source.Mod: {},
source.Sum: {},
}
s.setClientCapabilities(params.Capabilities)
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 {
if err := s.addView(ctx, folder.Name, span.NewURI(folder.URI)); err != nil {
return nil, err
}
}
return &protocol.InitializeResult{
Capabilities: protocol.ServerCapabilities{
CodeActionProvider: true,
CompletionProvider: &protocol.CompletionOptions{
TriggerCharacters: []string{"."},
},
DefinitionProvider: true,
DocumentFormattingProvider: true,
DocumentSymbolProvider: true,
HoverProvider: true,
DocumentHighlightProvider: true,
DocumentLinkProvider: &protocol.DocumentLinkOptions{},
ReferencesProvider: true,
RenameProvider: true,
SignatureHelpProvider: &protocol.SignatureHelpOptions{
TriggerCharacters: []string{"(", ","},
},
TextDocumentSync: &protocol.TextDocumentSyncOptions{
Change: s.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(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 {
s.stateMu.Lock()
s.state = serverInitialized
s.stateMu.Unlock()
if s.configurationSupported {
if s.dynamicConfigurationSupported {
s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
Registrations: []protocol.Registration{{
ID: "workspace/didChangeConfiguration",
Method: "workspace/didChangeConfiguration",
}, {
ID: "workspace/didChangeWorkspaceFolders",
Method: "workspace/didChangeWorkspaceFolders",
}},
})
}
for _, view := range s.session.Views() {
if err := s.fetchConfig(ctx, view); err != nil {
return err
}
}
}
buf := &bytes.Buffer{}
debug.PrintVersionInfo(buf, true, debug.PlainText)
log.Print(ctx, buf.String())
return nil
}
func (s *Server) fetchConfig(ctx context.Context, view source.View) error {
configs, err := s.client.Configuration(ctx, &protocol.ConfigurationParams{
Items: []protocol.ConfigurationItem{{
ScopeURI: protocol.NewURI(view.Folder()),
Section: "gopls",
}, {
ScopeURI: protocol.NewURI(view.Folder()),
Section: view.Name(),
},
},
})
if err != nil {
return err
}
for _, config := range configs {
if err := s.processConfig(ctx, view, config); err != nil {
return err
}
}
return nil
}
func (s *Server) processConfig(ctx context.Context, view source.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)
}
env := view.Env()
for k, v := range menv {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
view.SetEnv(env)
}
// Get the build flags for the go/packages config.
if buildFlags := c["buildFlags"]; buildFlags != nil {
iflags, ok := buildFlags.([]interface{})
if !ok {
return fmt.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))
}
view.SetBuildFlags(flags)
}
// Check if the user wants documentation in completion items.
if wantCompletionDocumentation, ok := c["wantCompletionDocumentation"].(bool); ok {
s.wantCompletionDocumentation = wantCompletionDocumentation
}
// Check if placeholders are enabled.
if usePlaceholders, ok := c["usePlaceholders"].(bool); ok {
s.usePlaceholders = usePlaceholders
}
// Set the hover kind.
if hoverKind, ok := c["hoverKind"].(string); ok {
switch hoverKind {
case "NoDocumentation":
s.hoverKind = source.NoDocumentation
case "SingleLine":
s.hoverKind = source.SingleLine
case "SynopsisDocumentation":
s.hoverKind = source.SynopsisDocumentation
case "FullDocumentation":
s.hoverKind = source.FullDocumentation
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 wants to see suggested fixes from go/analysis.
if wantSuggestedFixes, ok := c["wantSuggestedFixes"].(bool); ok {
s.wantSuggestedFixes = wantSuggestedFixes
}
// Check if the user has explicitly disabled any analyses.
if disabledAnalyses, ok := c["experimentalDisabledAnalyses"].([]interface{}); ok {
s.disabledAnalyses = make(map[string]struct{})
for _, a := range disabledAnalyses {
if a, ok := a.(string); ok {
s.disabledAnalyses[a] = struct{}{}
}
}
}
// Check if deep completions are enabled.
if useDeepCompletions, ok := c["useDeepCompletions"].(bool); ok {
s.useDeepCompletions = useDeepCompletions
}
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
}