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:
Ian Cottrell 2019-09-09 09:28:25 -04:00
parent c9a5ac55f0
commit 16c5e0f7d1
3 changed files with 191 additions and 139 deletions

View File

@ -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()

View File

@ -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
}
}

View File

@ -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
}