2019-04-17 13:37:20 -06:00
|
|
|
// 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 (
|
2019-05-03 12:31:03 -06:00
|
|
|
"bytes"
|
2019-04-17 13:37:20 -06:00
|
|
|
"context"
|
2019-09-11 11:13:44 -06:00
|
|
|
"fmt"
|
2020-04-30 13:59:37 -06:00
|
|
|
"io"
|
2019-04-17 13:37:20 -06:00
|
|
|
"os"
|
|
|
|
"path"
|
2020-08-10 11:19:11 -06:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2020-07-07 12:50:57 -06:00
|
|
|
"sync"
|
2019-04-17 13:37:20 -06:00
|
|
|
|
2020-04-17 07:32:56 -06:00
|
|
|
"golang.org/x/tools/internal/event"
|
2019-04-17 13:37:20 -06:00
|
|
|
"golang.org/x/tools/internal/jsonrpc2"
|
2019-05-29 17:58:27 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/debug"
|
2020-03-23 20:47:52 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/debug/tag"
|
2019-04-17 13:37:20 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-05-14 21:04:23 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/source"
|
2019-04-17 13:37:20 -06:00
|
|
|
"golang.org/x/tools/internal/span"
|
2020-08-26 15:41:45 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2019-04-17 13:37:20 -06:00
|
|
|
)
|
|
|
|
|
2019-11-17 12:29:15 -07:00
|
|
|
func (s *Server) initialize(ctx context.Context, params *protocol.ParamInitialize) (*protocol.InitializeResult, error) {
|
2019-07-29 21:48:11 -06:00
|
|
|
s.stateMu.Lock()
|
2020-03-09 12:17:49 -06:00
|
|
|
if s.state >= serverInitializing {
|
|
|
|
defer s.stateMu.Unlock()
|
2020-08-26 15:41:45 -06:00
|
|
|
return nil, errors.Errorf("%w: initialize called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
2019-07-27 12:59:14 -06:00
|
|
|
s.state = serverInitializing
|
2019-07-29 21:48:11 -06:00
|
|
|
s.stateMu.Unlock()
|
2019-04-17 13:37:20 -06:00
|
|
|
|
2020-08-07 13:52:01 -06:00
|
|
|
s.progress.supportsWorkDoneProgress = params.Capabilities.Window.WorkDoneProgress
|
2020-03-12 14:31:00 -06:00
|
|
|
|
2019-09-05 22:17:36 -06:00
|
|
|
options := s.session.Options()
|
|
|
|
defer func() { s.session.SetOptions(options) }()
|
|
|
|
|
2020-04-02 14:25:14 -06:00
|
|
|
if err := s.handleOptionResults(ctx, source.SetOptions(&options, params.InitializationOptions)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-09-09 07:28:25 -06:00
|
|
|
options.ForClientCapabilities(params.Capabilities)
|
2019-04-17 13:37:20 -06:00
|
|
|
|
2020-09-01 13:15:33 -06:00
|
|
|
folders := params.WorkspaceFolders
|
|
|
|
if len(folders) == 0 {
|
2019-04-17 13:37:20 -06:00
|
|
|
if params.RootURI != "" {
|
2020-09-01 13:15:33 -06:00
|
|
|
folders = []protocol.WorkspaceFolder{{
|
2020-02-12 14:36:46 -07:00
|
|
|
URI: string(params.RootURI),
|
|
|
|
Name: path.Base(params.RootURI.SpanURI().Filename()),
|
2019-04-17 13:37:20 -06:00
|
|
|
}}
|
|
|
|
}
|
|
|
|
}
|
2020-09-01 13:15:33 -06:00
|
|
|
for _, folder := range folders {
|
|
|
|
uri := span.URIFromURI(folder.URI)
|
|
|
|
if !uri.IsFile() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
s.pendingFolders = append(s.pendingFolders, folder)
|
|
|
|
}
|
|
|
|
// gopls only supports URIs with a file:// scheme, so if we have no
|
|
|
|
// workspace folders with a supported scheme, fail to initialize.
|
|
|
|
if len(folders) > 0 && len(s.pendingFolders) == 0 {
|
|
|
|
return nil, fmt.Errorf("unsupported URI schemes: %v (gopls only supports file URIs)", folders)
|
|
|
|
}
|
2019-05-06 14:44:43 -06:00
|
|
|
|
2019-11-21 21:42:27 -07:00
|
|
|
var codeActionProvider interface{} = true
|
2019-11-17 12:29:15 -07:00
|
|
|
if ca := params.Capabilities.TextDocument.CodeAction; len(ca.CodeActionLiteralSupport.CodeActionKind.ValueSet) > 0 {
|
2019-08-14 13:24:21 -06:00
|
|
|
// If the client has specified CodeActionLiteralSupport,
|
|
|
|
// send the code actions we support.
|
|
|
|
//
|
|
|
|
// Using CodeActionOptions is only valid if codeActionLiteralSupport is set.
|
|
|
|
codeActionProvider = &protocol.CodeActionOptions{
|
|
|
|
CodeActionKinds: s.getSupportedCodeActions(),
|
|
|
|
}
|
|
|
|
}
|
2019-11-21 21:42:27 -07:00
|
|
|
var renameOpts interface{} = true
|
|
|
|
if r := params.Capabilities.TextDocument.Rename; r.PrepareSupport {
|
|
|
|
renameOpts = protocol.RenameOptions{
|
|
|
|
PrepareProvider: r.PrepareSupport,
|
|
|
|
}
|
2019-08-22 11:31:03 -06:00
|
|
|
}
|
2020-04-15 13:59:30 -06:00
|
|
|
|
|
|
|
goplsVer := &bytes.Buffer{}
|
|
|
|
debug.PrintVersionInfo(ctx, goplsVer, true, debug.PlainText)
|
|
|
|
|
2019-04-17 13:37:20 -06:00
|
|
|
return &protocol.InitializeResult{
|
|
|
|
Capabilities: protocol.ServerCapabilities{
|
2020-08-03 12:16:44 -06:00
|
|
|
CallHierarchyProvider: true,
|
|
|
|
CodeActionProvider: codeActionProvider,
|
2019-11-17 12:29:15 -07:00
|
|
|
CompletionProvider: protocol.CompletionOptions{
|
2019-04-17 13:37:20 -06:00
|
|
|
TriggerCharacters: []string{"."},
|
|
|
|
},
|
2019-05-08 03:44:01 -06:00
|
|
|
DefinitionProvider: true,
|
2019-10-31 16:19:51 -06:00
|
|
|
TypeDefinitionProvider: true,
|
|
|
|
ImplementationProvider: true,
|
2019-05-08 03:44:01 -06:00
|
|
|
DocumentFormattingProvider: true,
|
|
|
|
DocumentSymbolProvider: true,
|
2020-01-05 03:46:20 -07:00
|
|
|
WorkspaceSymbolProvider: true,
|
2019-11-17 12:29:15 -07:00
|
|
|
ExecuteCommandProvider: protocol.ExecuteCommandOptions{
|
2019-09-18 23:21:54 -06:00
|
|
|
Commands: options.SupportedCommands,
|
|
|
|
},
|
|
|
|
FoldingRangeProvider: true,
|
|
|
|
HoverProvider: true,
|
|
|
|
DocumentHighlightProvider: true,
|
2019-11-17 12:29:15 -07:00
|
|
|
DocumentLinkProvider: protocol.DocumentLinkOptions{},
|
2019-09-18 23:21:54 -06:00
|
|
|
ReferencesProvider: true,
|
|
|
|
RenameProvider: renameOpts,
|
2019-11-17 12:29:15 -07:00
|
|
|
SignatureHelpProvider: protocol.SignatureHelpOptions{
|
2019-04-17 13:37:20 -06:00
|
|
|
TriggerCharacters: []string{"(", ","},
|
|
|
|
},
|
|
|
|
TextDocumentSync: &protocol.TextDocumentSyncOptions{
|
2019-12-24 14:16:06 -07:00
|
|
|
Change: protocol.Incremental,
|
2019-04-17 13:37:20 -06:00
|
|
|
OpenClose: true,
|
2019-11-17 12:29:15 -07:00
|
|
|
Save: protocol.SaveOptions{
|
2019-06-18 10:41:22 -06:00
|
|
|
IncludeText: false,
|
|
|
|
},
|
2019-04-17 13:37:20 -06:00
|
|
|
},
|
2019-11-17 12:29:15 -07:00
|
|
|
Workspace: protocol.WorkspaceGn{
|
2019-11-20 14:38:43 -07:00
|
|
|
WorkspaceFolders: protocol.WorkspaceFoldersGn{
|
2019-05-06 14:44:43 -06:00
|
|
|
Supported: true,
|
|
|
|
ChangeNotifications: "workspace/didChangeWorkspaceFolders",
|
|
|
|
},
|
|
|
|
},
|
2019-04-17 13:37:20 -06:00
|
|
|
},
|
2020-04-15 13:59:30 -06:00
|
|
|
ServerInfo: struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Version string `json:"version,omitempty"`
|
|
|
|
}{
|
|
|
|
Name: "gopls",
|
|
|
|
Version: goplsVer.String(),
|
|
|
|
},
|
2019-04-17 13:37:20 -06:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) initialized(ctx context.Context, params *protocol.InitializedParams) error {
|
2019-07-29 21:48:11 -06:00
|
|
|
s.stateMu.Lock()
|
2020-03-09 12:17:49 -06:00
|
|
|
if s.state >= serverInitialized {
|
|
|
|
defer s.stateMu.Unlock()
|
2020-08-26 15:41:45 -06:00
|
|
|
return errors.Errorf("%w: initalized called while server in %v state", jsonrpc2.ErrInvalidRequest, s.state)
|
2020-03-09 12:17:49 -06:00
|
|
|
}
|
2019-07-29 21:48:11 -06:00
|
|
|
s.state = serverInitialized
|
|
|
|
s.stateMu.Unlock()
|
|
|
|
|
2019-09-05 22:17:36 -06:00
|
|
|
options := s.session.Options()
|
|
|
|
defer func() { s.session.SetOptions(options) }()
|
|
|
|
|
2020-06-25 23:34:55 -06:00
|
|
|
// TODO: this event logging may be unnecessary.
|
|
|
|
// The version info is included in the initialize response.
|
2019-05-03 12:31:03 -06:00
|
|
|
buf := &bytes.Buffer{}
|
2020-03-23 20:47:52 -06:00
|
|
|
debug.PrintVersionInfo(ctx, buf, true, debug.PlainText)
|
2020-04-20 10:14:12 -06:00
|
|
|
event.Log(ctx, buf.String())
|
2019-09-09 11:04:12 -06:00
|
|
|
|
2020-06-25 23:34:55 -06:00
|
|
|
if err := s.addFolders(ctx, s.pendingFolders); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-11-21 16:55:49 -07:00
|
|
|
s.pendingFolders = nil
|
|
|
|
|
2020-08-10 11:19:11 -06:00
|
|
|
if options.ConfigurationSupported && options.DynamicConfigurationSupported {
|
2020-07-28 16:18:43 -06:00
|
|
|
if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
|
2020-08-10 11:19:11 -06:00
|
|
|
Registrations: []protocol.Registration{
|
|
|
|
{
|
|
|
|
ID: "workspace/didChangeConfiguration",
|
|
|
|
Method: "workspace/didChangeConfiguration",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: "workspace/didChangeWorkspaceFolders",
|
|
|
|
Method: "workspace/didChangeWorkspaceFolders",
|
|
|
|
},
|
|
|
|
},
|
2020-07-28 16:18:43 -06:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
2020-06-20 14:11:01 -06:00
|
|
|
}
|
|
|
|
}
|
2019-11-21 16:55:49 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-25 23:34:55 -06:00
|
|
|
func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFolder) error {
|
2019-11-21 16:55:49 -07:00
|
|
|
originalViews := len(s.session.Views())
|
2019-11-15 10:43:45 -07:00
|
|
|
viewErrors := make(map[span.URI]error)
|
2019-11-21 16:55:49 -07:00
|
|
|
|
2020-07-07 12:50:57 -06:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
if s.session.Options().VerboseWorkDoneProgress {
|
2020-08-07 13:52:01 -06:00
|
|
|
work := s.progress.start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil)
|
2020-07-07 12:50:57 -06:00
|
|
|
defer func() {
|
|
|
|
go func() {
|
|
|
|
wg.Wait()
|
2020-08-17 20:50:34 -06:00
|
|
|
work.end("Done.")
|
2020-07-07 12:50:57 -06:00
|
|
|
}()
|
|
|
|
}()
|
|
|
|
}
|
2020-07-28 16:18:43 -06:00
|
|
|
dirsToWatch := map[span.URI]struct{}{}
|
2019-11-21 16:55:49 -07:00
|
|
|
for _, folder := range folders {
|
2020-02-12 14:36:46 -07:00
|
|
|
uri := span.URIFromURI(folder.URI)
|
2020-09-01 13:15:33 -06:00
|
|
|
// Ignore non-file URIs.
|
|
|
|
if !uri.IsFile() {
|
|
|
|
continue
|
|
|
|
}
|
2020-08-12 10:05:41 -06:00
|
|
|
work := s.progress.start(ctx, "Setting up workspace", "Loading packages...", nil, nil)
|
2020-07-02 16:34:10 -06:00
|
|
|
view, snapshot, release, err := s.addView(ctx, folder.Name, uri)
|
2019-11-15 12:47:29 -07:00
|
|
|
if err != nil {
|
2019-11-15 10:43:45 -07:00
|
|
|
viewErrors[uri] = err
|
2020-08-17 20:50:34 -06:00
|
|
|
work.end(fmt.Sprintf("Error loading packages: %s", err))
|
2019-11-15 12:47:29 -07:00
|
|
|
continue
|
2019-09-09 11:04:12 -06:00
|
|
|
}
|
2020-08-12 10:05:41 -06:00
|
|
|
go func() {
|
|
|
|
view.AwaitInitialized(ctx)
|
2020-08-17 20:50:34 -06:00
|
|
|
work.end("Finished loading packages.")
|
2020-08-12 10:05:41 -06:00
|
|
|
}()
|
|
|
|
|
2020-07-28 16:18:43 -06:00
|
|
|
for _, dir := range snapshot.WorkspaceDirectories(ctx) {
|
|
|
|
dirsToWatch[dir] = struct{}{}
|
|
|
|
}
|
|
|
|
|
2020-03-23 20:47:52 -06:00
|
|
|
// Print each view's environment.
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
if err := view.WriteEnv(ctx, buf); err != nil {
|
2020-07-20 23:34:22 -06:00
|
|
|
event.Error(ctx, "failed to write environment", err, tag.Directory.Of(view.Folder().Filename()))
|
2020-03-23 20:47:52 -06:00
|
|
|
continue
|
|
|
|
}
|
2020-04-20 10:14:12 -06:00
|
|
|
event.Log(ctx, buf.String())
|
2020-03-23 20:47:52 -06:00
|
|
|
|
|
|
|
// Diagnose the newly created view.
|
2020-07-07 12:50:57 -06:00
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
s.diagnoseDetached(snapshot)
|
2020-07-02 16:34:10 -06:00
|
|
|
release()
|
2020-07-07 12:50:57 -06:00
|
|
|
wg.Done()
|
|
|
|
}()
|
2019-09-09 11:04:12 -06:00
|
|
|
}
|
2020-07-28 16:18:43 -06:00
|
|
|
// Register for file watching notifications, if they are supported.
|
|
|
|
s.watchedDirectoriesMu.Lock()
|
|
|
|
err := s.registerWatchedDirectoriesLocked(ctx, dirsToWatch)
|
|
|
|
s.watchedDirectoriesMu.Unlock()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-11-15 10:43:45 -07:00
|
|
|
if len(viewErrors) > 0 {
|
2019-11-21 16:55:49 -07:00
|
|
|
errMsg := fmt.Sprintf("Error loading workspace folders (expected %v, got %v)\n", len(folders), len(s.session.Views())-originalViews)
|
2019-11-15 10:43:45 -07:00
|
|
|
for uri, err := range viewErrors {
|
|
|
|
errMsg += fmt.Sprintf("failed to load view for %s: %v\n", uri, err)
|
|
|
|
}
|
2020-06-25 23:34:55 -06:00
|
|
|
return s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
|
2019-11-15 10:43:45 -07:00
|
|
|
Type: protocol.Error,
|
|
|
|
Message: errMsg,
|
|
|
|
})
|
|
|
|
}
|
2020-06-25 23:34:55 -06:00
|
|
|
return nil
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
|
|
|
|
2020-07-28 16:18:43 -06:00
|
|
|
// updateWatchedDirectories compares the current set of directories to watch
|
|
|
|
// with the previously registered set of directories. If the set of directories
|
|
|
|
// has changed, we unregister and re-register for file watching notifications.
|
|
|
|
// updatedSnapshots is the set of snapshots that have been updated.
|
|
|
|
func (s *Server) updateWatchedDirectories(ctx context.Context, updatedSnapshots []source.Snapshot) error {
|
|
|
|
dirsToWatch := map[span.URI]struct{}{}
|
|
|
|
seenViews := map[source.View]struct{}{}
|
|
|
|
|
|
|
|
// Collect all of the workspace directories from the updated snapshots.
|
|
|
|
for _, snapshot := range updatedSnapshots {
|
|
|
|
seenViews[snapshot.View()] = struct{}{}
|
|
|
|
for _, dir := range snapshot.WorkspaceDirectories(ctx) {
|
|
|
|
dirsToWatch[dir] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Not all views were necessarily updated, so check the remaining views.
|
|
|
|
for _, view := range s.session.Views() {
|
|
|
|
if _, ok := seenViews[view]; ok {
|
|
|
|
continue
|
|
|
|
}
|
internal/memoize: switch from GC-driven to explicit deletion
The GC-based cache has given us a number of problems. First, memory
leaks driven by reference cycles: the Go runtime cannot collect cycles
involving finalizers, which prevents us from writing natural code in
Bind callbacks. If we screw it up, we get a mysterious leak that takes a
long time to track down. Second, the behavior is generally mysterious;
it's hard to predict how long a value lasts, and harder to tell if a
value being live is a bug. Third, we think that it may be interacting
poorly with the GC, resulting in unnecessary memory usage.
The structure of the values we put in the cache is not actually that
complicated -- there are only 5 significant types: parse, typecheck,
analyze, parse mod, and analyze mod. Managing them manually should not
be conceptually difficult, and in fact we already do most of the work
in (*snapshot).clone.
In this CL the cache adds the concept of "generations", which function
as reference counts on cache entries. Entries are still global and
shared across generations, but will be explicitly deleted once no
generations refer to them. The idea is that each snapshot is a new
generation, and can inherit entries from the previous snapshot or leave
them behind to be deleted.
One obvious risk of this scheme is that we'll leave dangling references
to values without actually inheriting them across generations. To
prevent that, getting a value requires passing in the generation at
which it's being read, and an error will be returned if that generation
is dead.
Change-Id: I4b30891efd7be4e10f2b84f4c067b0dee43dcf9c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242838
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-07-24 15:17:13 -06:00
|
|
|
snapshot, release := view.Snapshot(ctx)
|
2020-07-28 16:18:43 -06:00
|
|
|
for _, dir := range snapshot.WorkspaceDirectories(ctx) {
|
|
|
|
dirsToWatch[dir] = struct{}{}
|
|
|
|
}
|
|
|
|
release()
|
|
|
|
}
|
|
|
|
|
|
|
|
s.watchedDirectoriesMu.Lock()
|
|
|
|
defer s.watchedDirectoriesMu.Unlock()
|
|
|
|
|
|
|
|
// Nothing to do if the set of workspace directories is unchanged.
|
|
|
|
if equalURISet(s.watchedDirectories, dirsToWatch) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the set of directories to watch has changed, register the updates and
|
|
|
|
// unregister the previously watched directories. This ordering avoids a
|
|
|
|
// period where no files are being watched. Still, if a user makes on-disk
|
|
|
|
// changes before these updates are complete, we may miss them for the new
|
|
|
|
// directories.
|
|
|
|
if s.watchRegistrationCount > 0 {
|
|
|
|
prevID := s.watchRegistrationCount - 1
|
|
|
|
if err := s.registerWatchedDirectoriesLocked(ctx, dirsToWatch); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return s.client.UnregisterCapability(ctx, &protocol.UnregistrationParams{
|
|
|
|
Unregisterations: []protocol.Unregistration{{
|
|
|
|
ID: watchedFilesCapabilityID(prevID),
|
|
|
|
Method: "workspace/didChangeWatchedFiles",
|
|
|
|
}},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func watchedFilesCapabilityID(id uint64) string {
|
|
|
|
return fmt.Sprintf("workspace/didChangeWatchedFiles-%d", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func equalURISet(m1, m2 map[span.URI]struct{}) bool {
|
|
|
|
if len(m1) != len(m2) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for k := range m1 {
|
|
|
|
_, ok := m2[k]
|
|
|
|
if !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// registerWatchedDirectoriesLocked sends the workspace/didChangeWatchedFiles
|
|
|
|
// registrations to the client and updates s.watchedDirectories.
|
|
|
|
func (s *Server) registerWatchedDirectoriesLocked(ctx context.Context, dirs map[span.URI]struct{}) error {
|
|
|
|
if !s.session.Options().DynamicWatchedFilesSupported {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for k := range s.watchedDirectories {
|
|
|
|
delete(s.watchedDirectories, k)
|
|
|
|
}
|
2020-08-10 11:19:11 -06:00
|
|
|
// Work-around microsoft/vscode#100870 by making sure that we are,
|
|
|
|
// at least, watching the user's entire workspace. This will still be
|
|
|
|
// applied to every folder in the workspace.
|
|
|
|
watchers := []protocol.FileSystemWatcher{{
|
|
|
|
GlobPattern: "**/*.{go,mod,sum}",
|
|
|
|
Kind: float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
|
|
|
|
}}
|
2020-07-28 16:18:43 -06:00
|
|
|
for dir := range dirs {
|
2020-08-10 11:19:11 -06:00
|
|
|
filename := dir.Filename()
|
2020-09-01 13:15:33 -06:00
|
|
|
// If the directory is within a workspace folder, we're already
|
|
|
|
// watching it via the relative path above.
|
|
|
|
for _, view := range s.session.Views() {
|
|
|
|
if isSubdirectory(view.Folder().Filename(), filename) {
|
|
|
|
continue
|
|
|
|
}
|
2020-08-10 11:19:11 -06:00
|
|
|
}
|
|
|
|
// If microsoft/vscode#100870 is resolved before
|
|
|
|
// microsoft/vscode#104387, we will need a work-around for Windows
|
|
|
|
// drive letter casing.
|
2020-07-28 16:18:43 -06:00
|
|
|
watchers = append(watchers, protocol.FileSystemWatcher{
|
2020-08-10 11:19:11 -06:00
|
|
|
GlobPattern: fmt.Sprintf("%s/**/*.{go,mod,sum}", filename),
|
2020-07-28 16:18:43 -06:00
|
|
|
Kind: float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
if err := s.client.RegisterCapability(ctx, &protocol.RegistrationParams{
|
|
|
|
Registrations: []protocol.Registration{{
|
|
|
|
ID: watchedFilesCapabilityID(s.watchRegistrationCount),
|
|
|
|
Method: "workspace/didChangeWatchedFiles",
|
|
|
|
RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
|
|
|
|
Watchers: watchers,
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.watchRegistrationCount++
|
|
|
|
|
|
|
|
for dir := range dirs {
|
|
|
|
s.watchedDirectories[dir] = struct{}{}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-10 11:19:11 -06:00
|
|
|
func isSubdirectory(root, leaf string) bool {
|
|
|
|
rel, err := filepath.Rel(root, leaf)
|
|
|
|
return err == nil && !strings.HasPrefix(rel, "..")
|
|
|
|
}
|
|
|
|
|
2019-09-11 11:13:44 -06:00
|
|
|
func (s *Server) fetchConfig(ctx context.Context, name string, folder span.URI, o *source.Options) error {
|
2019-09-09 07:28:25 -06:00
|
|
|
if !s.session.Options().ConfigurationSupported {
|
2019-09-09 11:04:12 -06:00
|
|
|
return nil
|
|
|
|
}
|
2019-11-17 12:29:15 -07:00
|
|
|
v := protocol.ParamConfiguration{
|
2019-09-16 22:38:16 -06:00
|
|
|
ConfigurationParams: protocol.ConfigurationParams{
|
2019-09-07 15:01:26 -06:00
|
|
|
Items: []protocol.ConfigurationItem{{
|
2020-02-12 14:36:46 -07:00
|
|
|
ScopeURI: string(folder),
|
2019-09-07 15:01:26 -06:00
|
|
|
Section: "gopls",
|
|
|
|
}, {
|
2020-02-12 14:36:46 -07:00
|
|
|
ScopeURI: string(folder),
|
2019-09-18 10:32:40 -06:00
|
|
|
Section: fmt.Sprintf("gopls-%s", name),
|
2019-09-16 22:38:16 -06:00
|
|
|
}},
|
|
|
|
},
|
2019-09-07 15:01:26 -06:00
|
|
|
}
|
|
|
|
configs, err := s.client.Configuration(ctx, &v)
|
2019-07-27 12:59:14 -06:00
|
|
|
if err != nil {
|
2020-06-30 15:55:40 -06:00
|
|
|
return fmt.Errorf("failed to get workspace configuration from client (%s): %v", folder, err)
|
2019-07-27 12:59:14 -06:00
|
|
|
}
|
|
|
|
for _, config := range configs {
|
2020-04-02 14:25:14 -06:00
|
|
|
if err := s.handleOptionResults(ctx, source.SetOptions(o, config)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) handleOptionResults(ctx context.Context, results source.OptionResults) error {
|
|
|
|
for _, result := range results {
|
|
|
|
if result.Error != nil {
|
|
|
|
if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
|
|
|
|
Type: protocol.Error,
|
|
|
|
Message: result.Error.Error(),
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch result.State {
|
|
|
|
case source.OptionUnexpected:
|
|
|
|
if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
|
|
|
|
Type: protocol.Error,
|
2020-08-27 08:17:30 -06:00
|
|
|
Message: fmt.Sprintf("unexpected gopls setting %q", result.Name),
|
2020-04-02 14:25:14 -06:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case source.OptionDeprecated:
|
2020-08-27 08:17:30 -06:00
|
|
|
msg := fmt.Sprintf("gopls setting %q is deprecated", result.Name)
|
2020-04-02 14:25:14 -06:00
|
|
|
if result.Replacement != "" {
|
2020-08-27 08:17:30 -06:00
|
|
|
msg = fmt.Sprintf("%s, use %q instead", msg, result.Replacement)
|
2019-09-11 11:13:44 -06:00
|
|
|
}
|
2020-04-02 14:25:14 -06:00
|
|
|
if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
|
|
|
|
Type: protocol.Warning,
|
|
|
|
Message: msg,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
2019-09-11 11:13:44 -06:00
|
|
|
}
|
|
|
|
}
|
2019-07-27 12:59:14 -06:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-13 11:46:49 -07:00
|
|
|
// beginFileRequest checks preconditions for a file-oriented request and routes
|
|
|
|
// it to a snapshot.
|
|
|
|
// We don't want to return errors for benign conditions like wrong file type,
|
|
|
|
// so callers should do if !ok { return err } rather than if err != nil.
|
2020-07-26 16:01:39 -06:00
|
|
|
func (s *Server) beginFileRequest(ctx context.Context, pURI protocol.DocumentURI, expectKind source.FileKind) (source.Snapshot, source.VersionedFileHandle, bool, func(), error) {
|
2020-02-13 11:46:49 -07:00
|
|
|
uri := pURI.SpanURI()
|
|
|
|
if !uri.IsFile() {
|
|
|
|
// Not a file URI. Stop processing the request, but don't return an error.
|
2020-07-02 16:34:10 -06:00
|
|
|
return nil, nil, false, func() {}, nil
|
2020-02-13 11:46:49 -07:00
|
|
|
}
|
|
|
|
view, err := s.session.ViewOf(uri)
|
|
|
|
if err != nil {
|
2020-07-02 16:34:10 -06:00
|
|
|
return nil, nil, false, func() {}, err
|
2020-02-13 11:46:49 -07:00
|
|
|
}
|
internal/memoize: switch from GC-driven to explicit deletion
The GC-based cache has given us a number of problems. First, memory
leaks driven by reference cycles: the Go runtime cannot collect cycles
involving finalizers, which prevents us from writing natural code in
Bind callbacks. If we screw it up, we get a mysterious leak that takes a
long time to track down. Second, the behavior is generally mysterious;
it's hard to predict how long a value lasts, and harder to tell if a
value being live is a bug. Third, we think that it may be interacting
poorly with the GC, resulting in unnecessary memory usage.
The structure of the values we put in the cache is not actually that
complicated -- there are only 5 significant types: parse, typecheck,
analyze, parse mod, and analyze mod. Managing them manually should not
be conceptually difficult, and in fact we already do most of the work
in (*snapshot).clone.
In this CL the cache adds the concept of "generations", which function
as reference counts on cache entries. Entries are still global and
shared across generations, but will be explicitly deleted once no
generations refer to them. The idea is that each snapshot is a new
generation, and can inherit entries from the previous snapshot or leave
them behind to be deleted.
One obvious risk of this scheme is that we'll leave dangling references
to values without actually inheriting them across generations. To
prevent that, getting a value requires passing in the generation at
which it's being read, and an error will be returned if that generation
is dead.
Change-Id: I4b30891efd7be4e10f2b84f4c067b0dee43dcf9c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/242838
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
2020-07-24 15:17:13 -06:00
|
|
|
snapshot, release := view.Snapshot(ctx)
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
fh, err := snapshot.GetFile(ctx, uri)
|
2020-02-13 11:46:49 -07:00
|
|
|
if err != nil {
|
2020-07-02 16:34:10 -06:00
|
|
|
release()
|
|
|
|
return nil, nil, false, func() {}, err
|
2020-02-13 11:46:49 -07:00
|
|
|
}
|
internal/lsp: read files eagerly
We use file identities pervasively throughout gopls. Prior to this
change, the identity is the modification date of an unopened file, or
the hash of an opened file. That means that opening a file changes its
identity, which causes unnecessary churn in the cache.
Unfortunately, there isn't an easy way to fix this. Changing the
cache key to something else, such as the modification time, means that
we won't unify cache entries if a change is made and then undone. The
approach here is to read files eagerly in GetFile, so that we know their
hashes immediately. That resolves the churn, but means that we do a ton
of file IO at startup.
Incidental changes:
Remove the FileSystem interface; there was only one implementation and
it added a fair amount of cruft. We have many other places that assume
os.Stat and such work.
Add direct accessors to FileHandle for URI, Kind, and Version. Most uses
of (FileHandle).Identity were for stuff that we derive solely from the
URI, and this helped me disentangle them. It is a *ton* of churn,
though. I can revert it if you want.
Change-Id: Ia2133bc527f71daf81c9d674951726a232ca5bc9
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237037
Run-TryBot: Heschi Kreinick <heschi@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2020-06-08 13:21:24 -06:00
|
|
|
if expectKind != source.UnknownKind && fh.Kind() != expectKind {
|
2020-02-13 11:46:49 -07:00
|
|
|
// Wrong kind of file. Nothing to do.
|
2020-07-02 16:34:10 -06:00
|
|
|
release()
|
|
|
|
return nil, nil, false, func() {}, nil
|
2020-02-13 11:46:49 -07:00
|
|
|
}
|
2020-07-02 16:34:10 -06:00
|
|
|
return snapshot, fh, true, release, nil
|
2020-02-13 11:46:49 -07:00
|
|
|
}
|
|
|
|
|
2019-04-17 13:37:20 -06:00
|
|
|
func (s *Server) shutdown(ctx context.Context) error {
|
2019-07-29 21:48:11 -06:00
|
|
|
s.stateMu.Lock()
|
|
|
|
defer s.stateMu.Unlock()
|
2019-07-27 12:59:14 -06:00
|
|
|
if s.state < serverInitialized {
|
2020-04-20 10:14:12 -06:00
|
|
|
event.Log(ctx, "server shutdown without initialization")
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
2020-02-21 15:31:45 -07:00
|
|
|
if s.state != serverShutDown {
|
|
|
|
// drop all the active views
|
|
|
|
s.session.Shutdown(ctx)
|
|
|
|
s.state = serverShutDown
|
|
|
|
}
|
2019-04-17 13:37:20 -06:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) exit(ctx context.Context) error {
|
2019-07-29 21:48:11 -06:00
|
|
|
s.stateMu.Lock()
|
|
|
|
defer s.stateMu.Unlock()
|
2020-06-11 22:24:37 -06:00
|
|
|
|
|
|
|
// TODO: We need a better way to find the conn close method.
|
2020-04-30 13:59:37 -06:00
|
|
|
s.client.(io.Closer).Close()
|
2020-06-11 22:24:37 -06:00
|
|
|
|
2019-07-27 12:59:14 -06:00
|
|
|
if s.state != serverShutDown {
|
2020-06-11 22:24:37 -06:00
|
|
|
// TODO: We should be able to do better than this.
|
2020-04-30 13:59:37 -06:00
|
|
|
os.Exit(1)
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
2020-04-30 13:59:37 -06:00
|
|
|
// we don't terminate the process on a normal exit, we just allow it to
|
|
|
|
// close naturally if needed after the connection is closed.
|
2019-04-17 13:37:20 -06:00
|
|
|
return nil
|
|
|
|
}
|