1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:04:44 -07:00

internal/lsp: wire up configuration

This connects up the configuration message, and uses it to allow the client to set the environment
in the config passed to packages.Load

Change-Id: I75e03c01c74e9b11c8b4c47b9cbdd0574cddf778
Reviewed-on: https://go-review.googlesource.com/c/tools/+/169704
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-03-28 08:49:42 -04:00
parent 8f05a32dce
commit 8889bfc21e
5 changed files with 93 additions and 17 deletions

View File

@ -27,6 +27,12 @@ type View struct {
// should be stopped.
cancel context.CancelFunc
// Name is the user visible name of this view.
Name string
// Folder is the root of this view.
Folder span.URI
// Config is the configuration used for the view's interaction with the
// go/packages API. It is shared across all views.
Config packages.Config
@ -72,13 +78,14 @@ type entry struct {
ready chan struct{} // closed to broadcast ready condition
}
func NewView(config *packages.Config) *View {
func NewView(name string, folder span.URI, config *packages.Config) *View {
ctx, cancel := context.WithCancel(context.Background())
return &View{
v := &View{
backgroundCtx: ctx,
cancel: cancel,
Config: *config,
Name: name,
Folder: folder,
filesByURI: make(map[span.URI]*File),
filesByBase: make(map[string][]*File),
contentChanges: make(map[span.URI]func()),
@ -89,6 +96,7 @@ func NewView(config *packages.Config) *View {
packages: make(map[string]*entry),
},
}
return v
}
func (v *View) BackgroundContext() context.Context {

View File

@ -61,7 +61,7 @@ func (d *definition) Run(ctx context.Context, args ...string) error {
if len(args) != 1 {
return tool.CommandLineErrorf("definition expects 1 argument")
}
view := cache.NewView(&d.query.app.Config)
view := cache.NewView("definition_test", span.FileURI(d.query.app.Config.Dir), &d.query.app.Config)
from := span.Parse(args[0])
f, err := view.GetFile(ctx, from.URI())
if err != nil {

View File

@ -18,6 +18,9 @@ func (s *server) cacheAndDiagnose(ctx context.Context, uri span.URI, content str
return err
}
go func() {
//TODO: this is an ugly hack to make the diagnostics call happen after the
// configuration is collected, we need to rewrite all the concurrency
<-s.configured
ctx := s.view.BackgroundContext()
if ctx.Err() != nil {
return

View File

@ -71,7 +71,7 @@ func testLSP(t *testing.T, exporter packagestest.Exporter) {
}
s := &server{
view: cache.NewView(&cfg),
view: cache.NewView("lsp_test", span.FileURI(cfg.Dir), &cfg),
}
// Do a first pass to collect special markers for completion.
if err := exported.Expect(map[string]interface{}{

View File

@ -13,6 +13,8 @@ import (
"go/token"
"net"
"os"
"path"
"strings"
"sync"
"golang.org/x/tools/go/packages"
@ -26,7 +28,9 @@ import (
// RunServer starts an LSP server on the supplied stream, and waits until the
// stream is closed.
func RunServer(ctx context.Context, stream jsonrpc2.Stream, opts ...interface{}) error {
s := &server{}
s := &server{
configured: make(chan struct{}),
}
conn, client := protocol.RunServer(ctx, stream, s, opts...)
s.client = client
return conn.Wait(ctx)
@ -41,7 +45,6 @@ func RunServerOnPort(ctx context.Context, port int, opts ...interface{}) error {
// RunServerOnPort starts an LSP server on the given port and does not exit.
// This function exists for debugging purposes.
func RunServerOnAddress(ctx context.Context, addr string, opts ...interface{}) error {
s := &server{}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
@ -52,11 +55,7 @@ func RunServerOnAddress(ctx context.Context, addr string, opts ...interface{}) e
return err
}
stream := jsonrpc2.NewHeaderStream(conn, conn)
go func() {
conn, client := protocol.RunServer(ctx, stream, s, opts...)
s.client = client
conn.Wait(ctx)
}()
go RunServer(ctx, stream, opts...)
}
}
@ -71,8 +70,9 @@ type server struct {
textDocumentSyncKind protocol.TextDocumentSyncKind
viewMu sync.Mutex
view *cache.View
view *cache.View
configured chan struct{}
}
func (s *server) Initialize(ctx context.Context, params *protocol.InitializeParams) (*protocol.InitializeResult, error) {
@ -103,9 +103,11 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara
// flag). Disabled for now to simplify debugging.
s.textDocumentSyncKind = protocol.Full
s.view = cache.NewView(&packages.Config{
//TODO:use workspace folders
s.view = cache.NewView(path.Base(string(rootURI)), rootURI, &packages.Config{
Context: ctx,
Dir: rootPath,
Env: os.Environ(),
Mode: packages.LoadImports,
Fset: token.NewFileSet(),
Overlay: make(map[string][]byte),
@ -143,8 +145,32 @@ func (s *server) Initialize(ctx context.Context, params *protocol.InitializePara
}, nil
}
func (s *server) Initialized(context.Context, *protocol.InitializedParams) error {
return nil // ignore
func (s *server) Initialized(ctx context.Context, params *protocol.InitializedParams) error {
go func() {
// we hae to do this in a go routine to unblock the jsonrpc processor
// but we also have to block all calls to packages.Load until this is done
// TODO: we need to rewrite all the concurrency handling hin the server
defer func() { close(s.configured) }()
s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
Registrations: []protocol.Registration{{
ID: "workspace/didChangeConfiguration",
Method: "workspace/didChangeConfiguration",
}},
})
config, err := s.client.Configuration(ctx, &protocol.ConfigurationParams{
Items: []protocol.ConfigurationItem{{
ScopeURI: protocol.NewURI(s.view.Folder),
Section: "gopls",
}},
})
if err != nil {
s.Error(err)
}
if err := s.processConfig(config[0]); err != nil {
s.Error(err)
}
}()
return nil
}
func (s *server) Shutdown(context.Context) error {
@ -532,6 +558,45 @@ func (s *server) FoldingRanges(context.Context, *protocol.FoldingRangeParams) ([
return nil, notImplemented("FoldingRanges")
}
func (s *server) Error(err error) {
s.client.LogMessage(context.Background(), &protocol.LogMessageParams{
Type: protocol.Error,
Message: fmt.Sprint(err),
})
}
func (s *server) processConfig(config interface{}) error {
//TODO: we should probably store and process more of the config
c, ok := config.(map[string]interface{})
if !ok {
return fmt.Errorf("Invalid config gopls type %T", config)
}
env := c["env"]
if env == nil {
return nil
}
menv, ok := env.(map[string]interface{})
if !ok {
return fmt.Errorf("Invalid config gopls.env type %T", env)
}
for k, v := range menv {
s.view.Config.Env = applyEnv(s.view.Config.Env, k, v)
}
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 notImplemented(method string) *jsonrpc2.Error {
return jsonrpc2.NewErrorf(jsonrpc2.CodeMethodNotFound, "method %q not yet implemented", method)
}