mirror of https://github.com/golang/go.git
internal/lsp: process configuration options more thoroughly
Change-Id: Ic3e2f948f857c697564fa6ab02008444dd9392c7 Reviewed-on: https://go-review.googlesource.com/c/tools/+/194458 Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
c9a5ac55f0
commit
16c5e0f7d1
|
|
@ -7,7 +7,6 @@ package lsp
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
|
|
@ -17,7 +16,6 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
||||
|
|
@ -35,30 +33,9 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitia) (
|
|||
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)
|
||||
//TODO: handle the options results
|
||||
source.SetOptions(&options, params.InitializationOptions)
|
||||
options.ForClientCapabilities(params.Capabilities)
|
||||
|
||||
s.pendingFolders = params.WorkspaceFolders
|
||||
if len(s.pendingFolders) == 0 {
|
||||
|
|
@ -140,27 +117,6 @@ func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitia) (
|
|||
}, 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
|
||||
|
|
@ -216,8 +172,8 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, options *source.SessionOptions, vo *source.ViewOptions) error {
|
||||
if !options.ConfigurationSupported {
|
||||
func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, vo *source.ViewOptions) error {
|
||||
if !s.session.Options().ConfigurationSupported {
|
||||
return nil
|
||||
}
|
||||
v := protocol.ParamConfig{
|
||||
|
|
@ -237,92 +193,12 @@ func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI,
|
|||
return err
|
||||
}
|
||||
for _, config := range configs {
|
||||
if err := s.processConfig(ctx, options, vo, config); err != nil {
|
||||
return err
|
||||
}
|
||||
//TODO: handle the options results
|
||||
source.SetOptions(vo, config)
|
||||
}
|
||||
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()
|
||||
|
|
|
|||
|
|
@ -5,16 +5,20 @@
|
|||
package source
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/telemetry/tag"
|
||||
errors "golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultSessionOptions = SessionOptions{
|
||||
TextDocumentSyncKind: protocol.Incremental,
|
||||
HoverKind: SynopsisDocumentation,
|
||||
InsertTextFormat: protocol.PlainTextTextFormat,
|
||||
TextDocumentSyncKind: protocol.Incremental,
|
||||
HoverKind: SynopsisDocumentation,
|
||||
InsertTextFormat: protocol.PlainTextTextFormat,
|
||||
PreferredContentFormat: protocol.PlainText,
|
||||
SupportedCodeActions: map[FileKind]map[protocol.CodeActionKind]bool{
|
||||
Go: {
|
||||
protocol.SourceOrganizeImports: true,
|
||||
|
|
@ -50,6 +54,7 @@ type SessionOptions struct {
|
|||
|
||||
SupportedCodeActions map[FileKind]map[protocol.CodeActionKind]bool
|
||||
|
||||
// TODO: Remove the option once we are certain there are no issues here.
|
||||
TextDocumentSyncKind protocol.TextDocumentSyncKind
|
||||
|
||||
Completion CompletionOptions
|
||||
|
|
@ -76,6 +81,10 @@ type CompletionOptions struct {
|
|||
|
||||
type HoverKind int
|
||||
|
||||
type Options interface {
|
||||
set(name string, value interface{}) OptionResult
|
||||
}
|
||||
|
||||
const (
|
||||
SingleLine = HoverKind(iota)
|
||||
NoDocumentation
|
||||
|
|
@ -89,3 +98,173 @@ const (
|
|||
// This should only be used by clients that support this behavior.
|
||||
Structured
|
||||
)
|
||||
|
||||
type OptionResults []OptionResult
|
||||
|
||||
type OptionResult struct {
|
||||
Name string
|
||||
Value interface{}
|
||||
State OptionState
|
||||
Error error
|
||||
}
|
||||
|
||||
type OptionState int
|
||||
|
||||
const (
|
||||
OptionHandled = OptionState(iota)
|
||||
OptionDeprecated
|
||||
OptionUnexpected
|
||||
)
|
||||
|
||||
func SetOptions(options Options, opts interface{}) OptionResults {
|
||||
var results OptionResults
|
||||
switch opts := opts.(type) {
|
||||
case nil:
|
||||
case map[string]interface{}:
|
||||
for name, value := range opts {
|
||||
results = append(results, options.set(name, value))
|
||||
}
|
||||
default:
|
||||
results = append(results, OptionResult{
|
||||
Value: opts,
|
||||
Error: errors.Errorf("Invalid options type %T", opts),
|
||||
})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (o *SessionOptions) ForClientCapabilities(caps protocol.ClientCapabilities) {
|
||||
// Check if the client supports snippets in completion items.
|
||||
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.
|
||||
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 (o *SessionOptions) set(name string, value interface{}) OptionResult {
|
||||
result := OptionResult{Name: name, Value: value}
|
||||
switch name {
|
||||
case "noIncrementalSync":
|
||||
if v, ok := result.asBool(); ok && v {
|
||||
o.TextDocumentSyncKind = protocol.Full
|
||||
}
|
||||
case "watchFileChanges":
|
||||
result.setBool(&o.WatchFileChanges)
|
||||
case "wantCompletionDocumentation":
|
||||
result.setBool(&o.Completion.Documentation)
|
||||
case "usePlaceholders":
|
||||
result.setBool(&o.Completion.Placeholders)
|
||||
case "disableDeepCompletion":
|
||||
result.setNotBool(&o.Completion.Deep)
|
||||
case "disableFuzzyMatching":
|
||||
result.setNotBool(&o.Completion.FuzzyMatching)
|
||||
case "wantUnimportedCompletions":
|
||||
result.setBool(&o.Completion.Unimported)
|
||||
|
||||
case "hoverKind":
|
||||
hoverKind, ok := value.(string)
|
||||
if !ok {
|
||||
result.errorf("Invalid type %T for string option %q", value, name)
|
||||
break
|
||||
}
|
||||
switch hoverKind {
|
||||
case "NoDocumentation":
|
||||
o.HoverKind = NoDocumentation
|
||||
case "SingleLine":
|
||||
o.HoverKind = SingleLine
|
||||
case "SynopsisDocumentation":
|
||||
o.HoverKind = SynopsisDocumentation
|
||||
case "FullDocumentation":
|
||||
o.HoverKind = FullDocumentation
|
||||
case "Structured":
|
||||
o.HoverKind = Structured
|
||||
default:
|
||||
result.errorf("Unsupported hover kind", tag.Of("HoverKind", hoverKind))
|
||||
}
|
||||
|
||||
case "experimentalDisabledAnalyses":
|
||||
disabledAnalyses, ok := value.([]interface{})
|
||||
if !ok {
|
||||
result.errorf("Invalid type %T for []string option %q", value, name)
|
||||
break
|
||||
}
|
||||
o.DisabledAnalyses = make(map[string]struct{})
|
||||
for _, a := range disabledAnalyses {
|
||||
o.DisabledAnalyses[fmt.Sprint(a)] = struct{}{}
|
||||
}
|
||||
|
||||
case "wantSuggestedFixes":
|
||||
result.State = OptionDeprecated
|
||||
|
||||
default:
|
||||
return o.DefaultViewOptions.set(name, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (o *ViewOptions) set(name string, value interface{}) OptionResult {
|
||||
result := OptionResult{Name: name, Value: value}
|
||||
switch name {
|
||||
case "env":
|
||||
menv, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
result.errorf("invalid config gopls.env type %T", value)
|
||||
break
|
||||
}
|
||||
for k, v := range menv {
|
||||
o.Env = append(o.Env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
case "buildFlags":
|
||||
iflags, ok := value.([]interface{})
|
||||
if !ok {
|
||||
result.errorf("invalid config gopls.buildFlags type %T", value)
|
||||
break
|
||||
}
|
||||
flags := make([]string, 0, len(iflags))
|
||||
for _, flag := range iflags {
|
||||
flags = append(flags, fmt.Sprintf("%s", flag))
|
||||
}
|
||||
o.BuildFlags = flags
|
||||
|
||||
default:
|
||||
result.State = OptionUnexpected
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (r *OptionResult) errorf(msg string, values ...interface{}) {
|
||||
r.Error = errors.Errorf(msg, values...)
|
||||
}
|
||||
|
||||
func (r *OptionResult) asBool() (bool, bool) {
|
||||
b, ok := r.Value.(bool)
|
||||
if !ok {
|
||||
r.errorf("Invalid type %T for bool option %q", r.Value, r.Name)
|
||||
return false, false
|
||||
}
|
||||
return b, true
|
||||
}
|
||||
|
||||
func (r *OptionResult) setBool(b *bool) {
|
||||
if v, ok := r.asBool(); ok {
|
||||
*b = v
|
||||
}
|
||||
}
|
||||
|
||||
func (r *OptionResult) setNotBool(b *bool) {
|
||||
if v, ok := r.asBool(); ok {
|
||||
*b = !v
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,11 +38,8 @@ func (s *Server) addView(ctx context.Context, name string, uri span.URI) error {
|
|||
return errors.Errorf("addView called before server initialized")
|
||||
}
|
||||
|
||||
options := s.session.Options()
|
||||
viewOptions := options.DefaultViewOptions
|
||||
//TODO: take this out, we only allow new session options here
|
||||
defer func() { s.session.SetOptions(options) }()
|
||||
s.fetchConfig(ctx, name, uri, &options, &viewOptions)
|
||||
viewOptions := s.session.Options().DefaultViewOptions
|
||||
s.fetchConfig(ctx, name, uri, &viewOptions)
|
||||
s.session.NewView(ctx, name, uri, viewOptions)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue