mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
internal/lsp: start handling watched file change events
Now we register for and handle didChangeWatchedFiles "change" events. We don't handle "create" or "delete" yet. When a file changes on disk, there are two basic cases. If the editor has the file open, we want to ignore the change since we need to respect the file contents in the editor. If the file isn't open in the editor then we need to re-type check (and re-diagnose) any packages it belongs to. We will need special handling of go.mod changes, but start with just *.go files for now. I'm putting the new behavior behind an initialization flag while it is under development. Updates golang/go#31553 Change-Id: I81a767ebe12f5f82657752dcdfb069c5820cbaa0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/190857 Reviewed-by: Ian Cottrell <iancottrell@google.com> Run-TryBot: Ian Cottrell <iancottrell@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
11cc3c157e
commit
1f0dd0289f
4
internal/lsp/cache/session.go
vendored
4
internal/lsp/cache/session.go
vendored
@ -327,6 +327,10 @@ func (s *session) buildOverlay() map[string][]byte {
|
||||
return overlays
|
||||
}
|
||||
|
||||
func (s *session) DidChangeOutOfBand(uri span.URI) {
|
||||
s.filesWatchMap.Notify(uri)
|
||||
}
|
||||
|
||||
func (o *overlay) FileSystem() source.FileSystem {
|
||||
return o.session
|
||||
}
|
||||
|
@ -38,6 +38,9 @@ func (s *Server) initialize(ctx context.Context, params *protocol.InitializePara
|
||||
if opt, ok := opts["noIncrementalSync"].(bool); ok && opt {
|
||||
s.textDocumentSyncKind = protocol.Full
|
||||
}
|
||||
|
||||
// Check if user has enabled watching for file changes.
|
||||
s.watchFileChanges, _ = opts["watchFileChanges"].(bool)
|
||||
}
|
||||
|
||||
// Default to using synopsis as a default for hover information.
|
||||
@ -126,6 +129,7 @@ func (s *Server) setClientCapabilities(caps protocol.ClientCapabilities) {
|
||||
// Check if the client supports configuration messages.
|
||||
s.configurationSupported = caps.Workspace.Configuration
|
||||
s.dynamicConfigurationSupported = caps.Workspace.DidChangeConfiguration.DynamicRegistration
|
||||
s.dynamicWatchedFilesSupported = caps.Workspace.DidChangeWatchedFiles.DynamicRegistration
|
||||
|
||||
// Check which types of content format are supported by this client.
|
||||
s.preferredContentFormat = protocol.PlainText
|
||||
@ -139,18 +143,40 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa
|
||||
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",
|
||||
var registrations []protocol.Registration
|
||||
if s.configurationSupported && s.dynamicConfigurationSupported {
|
||||
registrations = append(registrations,
|
||||
protocol.Registration{
|
||||
ID: "workspace/didChangeConfiguration",
|
||||
Method: "workspace/didChangeConfiguration",
|
||||
},
|
||||
protocol.Registration{
|
||||
ID: "workspace/didChangeWorkspaceFolders",
|
||||
Method: "workspace/didChangeWorkspaceFolders",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if s.watchFileChanges && s.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,
|
||||
})
|
||||
}
|
||||
|
||||
if s.configurationSupported {
|
||||
for _, view := range s.session.Views() {
|
||||
if err := s.fetchConfig(ctx, view); err != nil {
|
||||
return err
|
||||
@ -190,10 +216,12 @@ func (s *Server) processConfig(ctx context.Context, view source.View, config int
|
||||
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{})
|
||||
@ -206,6 +234,7 @@ func (s *Server) processConfig(ctx context.Context, view source.View, config int
|
||||
}
|
||||
view.SetEnv(env)
|
||||
}
|
||||
|
||||
// Get the build flags for the go/packages config.
|
||||
if buildFlags := c["buildFlags"]; buildFlags != nil {
|
||||
iflags, ok := buildFlags.([]interface{})
|
||||
@ -218,6 +247,7 @@ func (s *Server) processConfig(ctx context.Context, view source.View, config int
|
||||
}
|
||||
view.SetBuildFlags(flags)
|
||||
}
|
||||
|
||||
// Check if the user wants documentation in completion items.
|
||||
if wantCompletionDocumentation, ok := c["wantCompletionDocumentation"].(bool); ok {
|
||||
s.wantCompletionDocumentation = wantCompletionDocumentation
|
||||
@ -244,10 +274,12 @@ func (s *Server) processConfig(ctx context.Context, view source.View, config int
|
||||
// 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{})
|
||||
@ -257,14 +289,17 @@ func (s *Server) processConfig(ctx context.Context, view source.View, config int
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if deep completions are enabled.
|
||||
if useDeepCompletions, ok := c["useDeepCompletions"].(bool); ok {
|
||||
s.useDeepCompletions = useDeepCompletions
|
||||
}
|
||||
|
||||
// Check if want unimported package completions.
|
||||
if wantUnimportedCompletions, ok := c["wantUnimportedCompletions"].(bool); ok {
|
||||
s.wantUnimportedCompletions = wantUnimportedCompletions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -81,11 +81,13 @@ type Server struct {
|
||||
usePlaceholders bool
|
||||
hoverKind hoverKind
|
||||
useDeepCompletions bool
|
||||
watchFileChanges bool
|
||||
wantCompletionDocumentation bool
|
||||
wantUnimportedCompletions bool
|
||||
insertTextFormat protocol.InsertTextFormat
|
||||
configurationSupported bool
|
||||
dynamicConfigurationSupported bool
|
||||
dynamicWatchedFilesSupported bool
|
||||
preferredContentFormat protocol.MarkupKind
|
||||
disabledAnalyses map[string]struct{}
|
||||
wantSuggestedFixes bool
|
||||
@ -130,8 +132,8 @@ func (s *Server) DidChangeConfiguration(context.Context, *protocol.DidChangeConf
|
||||
return notImplemented("DidChangeConfiguration")
|
||||
}
|
||||
|
||||
func (s *Server) DidChangeWatchedFiles(context.Context, *protocol.DidChangeWatchedFilesParams) error {
|
||||
return notImplemented("DidChangeWatchedFiles")
|
||||
func (s *Server) DidChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
|
||||
return s.didChangeWatchedFiles(ctx, params)
|
||||
}
|
||||
|
||||
func (s *Server) Symbol(context.Context, *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) {
|
||||
|
@ -181,11 +181,15 @@ type Session interface {
|
||||
// DidClose is invoked each time an open file is closed in the editor.
|
||||
DidClose(uri span.URI)
|
||||
|
||||
// IsOpen can be called to check if the editor has a file currently open.
|
||||
// IsOpen returns whether the editor currently has a file open.
|
||||
IsOpen(uri span.URI) bool
|
||||
|
||||
// Called to set the effective contents of a file from this session.
|
||||
SetOverlay(uri span.URI, data []byte) (wasFirstChange bool)
|
||||
|
||||
// DidChangeOutOfBand is called when a file under the root folder
|
||||
// changes. The file is not necessarily open in the editor.
|
||||
DidChangeOutOfBand(uri span.URI)
|
||||
}
|
||||
|
||||
// View represents a single workspace.
|
||||
@ -204,9 +208,14 @@ type View interface {
|
||||
// BuiltinPackage returns the ast for the special "builtin" package.
|
||||
BuiltinPackage() *ast.Package
|
||||
|
||||
// GetFile returns the file object for a given uri.
|
||||
// GetFile returns the file object for a given URI, initializing it
|
||||
// if it is not already part of the view.
|
||||
GetFile(ctx context.Context, uri span.URI) (File, error)
|
||||
|
||||
// FindFile returns the file object for a given URI if it is
|
||||
// already part of the view.
|
||||
FindFile(ctx context.Context, uri span.URI) File
|
||||
|
||||
// Called to set the effective contents of a file from this view.
|
||||
SetContent(ctx context.Context, uri span.URI, content []byte) (wasFirstChange bool, err error)
|
||||
|
||||
|
53
internal/lsp/watched_files.go
Normal file
53
internal/lsp/watched_files.go
Normal file
@ -0,0 +1,53 @@
|
||||
// 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 (
|
||||
"context"
|
||||
|
||||
"golang.org/x/tools/internal/lsp/protocol"
|
||||
"golang.org/x/tools/internal/span"
|
||||
"golang.org/x/tools/internal/telemetry/log"
|
||||
"golang.org/x/tools/internal/telemetry/tag"
|
||||
)
|
||||
|
||||
func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
|
||||
if !s.watchFileChanges {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, change := range params.Changes {
|
||||
uri := span.NewURI(change.URI)
|
||||
|
||||
switch change.Type {
|
||||
case protocol.Changed:
|
||||
view := s.session.ViewOf(uri)
|
||||
|
||||
// If we have never seen this file before, there is nothing to do.
|
||||
if view.FindFile(ctx, uri) == nil {
|
||||
break
|
||||
}
|
||||
|
||||
log.Print(ctx, "watched file changed", tag.Of("uri", uri))
|
||||
|
||||
// If client has this file open, don't do anything. The client's contents
|
||||
// must remain the source of truth.
|
||||
if s.session.IsOpen(uri) {
|
||||
break
|
||||
}
|
||||
|
||||
s.session.DidChangeOutOfBand(uri)
|
||||
|
||||
// Refresh diagnostics to reflect updated file contents.
|
||||
s.Diagnostics(ctx, view, uri)
|
||||
case protocol.Created:
|
||||
log.Print(ctx, "watched file created", tag.Of("uri", uri))
|
||||
case protocol.Deleted:
|
||||
log.Print(ctx, "watched file deleted", tag.Of("uri", uri))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user