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 (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2019-08-02 17:45:56 -06:00
|
|
|
"fmt"
|
2020-06-25 23:34:55 -06:00
|
|
|
"path/filepath"
|
2020-05-29 13:51:01 -06:00
|
|
|
"sync"
|
2019-04-17 13:37:20 -06:00
|
|
|
|
2020-06-25 23:34:55 -06:00
|
|
|
"golang.org/x/tools/internal/gocommand"
|
2019-04-17 13:37:20 -06:00
|
|
|
"golang.org/x/tools/internal/jsonrpc2"
|
|
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
2019-06-14 13:08:02 -06:00
|
|
|
"golang.org/x/tools/internal/lsp/source"
|
2019-04-17 13:37:20 -06:00
|
|
|
"golang.org/x/tools/internal/span"
|
2019-08-06 13:13:11 -06:00
|
|
|
errors "golang.org/x/xerrors"
|
2019-04-17 13:37:20 -06:00
|
|
|
)
|
|
|
|
|
2020-04-22 15:54:30 -06:00
|
|
|
// ModificationSource identifies the originating cause of a file modification.
|
|
|
|
type ModificationSource int
|
|
|
|
|
|
|
|
const (
|
|
|
|
// FromDidOpen is a file modification caused by opening a file.
|
|
|
|
FromDidOpen = ModificationSource(iota)
|
|
|
|
// FromDidChange is a file modification caused by changing a file.
|
|
|
|
FromDidChange
|
|
|
|
// FromDidChangeWatchedFiles is a file modification caused by a change to a watched file.
|
|
|
|
FromDidChangeWatchedFiles
|
|
|
|
// FromDidSave is a file modification caused by a file save.
|
|
|
|
FromDidSave
|
|
|
|
// FromDidClose is a file modification caused by closing a file.
|
|
|
|
FromDidClose
|
2020-05-14 12:02:48 -06:00
|
|
|
FromRegenerateCgo
|
2020-04-22 15:54:30 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
func (m ModificationSource) String() string {
|
|
|
|
switch m {
|
|
|
|
case FromDidOpen:
|
|
|
|
return "opened files"
|
|
|
|
case FromDidChange:
|
|
|
|
return "changed files"
|
|
|
|
case FromDidChangeWatchedFiles:
|
|
|
|
return "files changed on disk"
|
|
|
|
case FromDidSave:
|
|
|
|
return "saved files"
|
2020-05-14 12:02:48 -06:00
|
|
|
case FromRegenerateCgo:
|
|
|
|
return "regenerate cgo"
|
2020-04-22 15:54:30 -06:00
|
|
|
default:
|
|
|
|
return "unknown file modification"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-14 14:23:46 -06:00
|
|
|
func (s *Server) didOpen(ctx context.Context, params *protocol.DidOpenTextDocumentParams) error {
|
2020-02-13 11:46:49 -07:00
|
|
|
uri := params.TextDocument.URI.SpanURI()
|
|
|
|
if !uri.IsFile() {
|
|
|
|
return nil
|
|
|
|
}
|
2020-06-25 23:34:55 -06:00
|
|
|
// There may not be any matching view in the current session. If that's
|
|
|
|
// the case, try creating a new view based on the opened file path.
|
|
|
|
//
|
|
|
|
// TODO(rstambler): This seems like it would continuously add new
|
|
|
|
// views, but it won't because ViewOf only returns an error when there
|
|
|
|
// are no views in the session. I don't know if that logic should go
|
|
|
|
// here, or if we can continue to rely on that implementation detail.
|
|
|
|
if _, err := s.session.ViewOf(uri); err != nil {
|
|
|
|
// Run `go env GOMOD` to detect a module root. If we are not in a module,
|
|
|
|
// just use the current directory as the root.
|
|
|
|
dir := filepath.Dir(uri.Filename())
|
|
|
|
stdout, err := (&gocommand.Runner{}).Run(ctx, gocommand.Invocation{
|
|
|
|
Verb: "env",
|
|
|
|
Args: []string{"GOMOD"},
|
|
|
|
BuildFlags: s.session.Options().BuildFlags,
|
|
|
|
Env: s.session.Options().Env,
|
|
|
|
WorkingDir: dir,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if stdout.String() != "" {
|
|
|
|
dir = filepath.Dir(stdout.String())
|
|
|
|
}
|
|
|
|
if err := s.addFolders(ctx, []protocol.WorkspaceFolder{{
|
|
|
|
URI: string(protocol.URIFromPath(dir)),
|
|
|
|
Name: filepath.Base(dir),
|
|
|
|
}}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2020-02-13 11:46:49 -07:00
|
|
|
|
2020-01-22 19:58:50 -07:00
|
|
|
_, err := s.didModifyFiles(ctx, []source.FileModification{
|
|
|
|
{
|
2020-02-13 11:46:49 -07:00
|
|
|
URI: uri,
|
2020-01-22 19:58:50 -07:00
|
|
|
Action: source.Open,
|
|
|
|
Version: params.TextDocument.Version,
|
|
|
|
Text: []byte(params.TextDocument.Text),
|
|
|
|
LanguageID: params.TextDocument.LanguageID,
|
|
|
|
},
|
2020-04-22 15:54:30 -06:00
|
|
|
}, FromDidOpen)
|
2020-01-11 21:59:57 -07:00
|
|
|
return err
|
2019-12-04 16:45:25 -07:00
|
|
|
}
|
2019-06-28 17:32:53 -06:00
|
|
|
|
2019-12-04 16:45:25 -07:00
|
|
|
func (s *Server) didChange(ctx context.Context, params *protocol.DidChangeTextDocumentParams) error {
|
2020-02-12 14:36:46 -07:00
|
|
|
uri := params.TextDocument.URI.SpanURI()
|
2020-02-13 11:46:49 -07:00
|
|
|
if !uri.IsFile() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-12-04 16:45:25 -07:00
|
|
|
text, err := s.changedText(ctx, uri, params.ContentChanges)
|
2019-11-15 10:43:45 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-11 21:59:57 -07:00
|
|
|
c := source.FileModification{
|
2019-12-04 16:45:25 -07:00
|
|
|
URI: uri,
|
|
|
|
Action: source.Change,
|
|
|
|
Version: params.TextDocument.Version,
|
|
|
|
Text: text,
|
2019-12-10 10:29:37 -07:00
|
|
|
}
|
2020-04-22 15:54:30 -06:00
|
|
|
snapshots, err := s.didModifyFiles(ctx, []source.FileModification{c}, FromDidChange)
|
2019-11-15 10:43:45 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-22 19:58:50 -07:00
|
|
|
snapshot := snapshots[uri]
|
|
|
|
if snapshot == nil {
|
|
|
|
return errors.Errorf("no snapshot for %s", uri)
|
|
|
|
}
|
2019-12-10 10:29:37 -07:00
|
|
|
// Ideally, we should be able to specify that a generated file should be opened as read-only.
|
|
|
|
// Tell the user that they should not be editing a generated file.
|
2020-01-11 21:59:57 -07:00
|
|
|
if s.wasFirstChange(uri) && source.IsGenerated(ctx, snapshot, uri) {
|
|
|
|
if err := s.client.ShowMessage(ctx, &protocol.ShowMessageParams{
|
2019-12-10 10:29:37 -07:00
|
|
|
Message: fmt.Sprintf("Do not edit this file! %s is a generated file.", uri.Filename()),
|
|
|
|
Type: protocol.Warning,
|
2020-01-11 21:59:57 -07:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-09 16:18:27 -07:00
|
|
|
}
|
|
|
|
return nil
|
2019-12-17 14:53:57 -07:00
|
|
|
}
|
|
|
|
|
2020-01-09 20:45:06 -07:00
|
|
|
func (s *Server) didChangeWatchedFiles(ctx context.Context, params *protocol.DidChangeWatchedFilesParams) error {
|
2020-01-22 19:58:50 -07:00
|
|
|
var modifications []source.FileModification
|
2020-02-19 12:25:12 -07:00
|
|
|
deletions := make(map[span.URI]struct{})
|
2020-01-09 20:45:06 -07:00
|
|
|
for _, change := range params.Changes {
|
2020-02-13 11:46:49 -07:00
|
|
|
uri := change.URI.SpanURI()
|
|
|
|
if !uri.IsFile() {
|
|
|
|
continue
|
|
|
|
}
|
2020-02-19 12:25:12 -07:00
|
|
|
action := changeTypeToFileAction(change.Type)
|
2020-01-22 19:58:50 -07:00
|
|
|
modifications = append(modifications, source.FileModification{
|
2020-02-13 11:46:49 -07:00
|
|
|
URI: uri,
|
2020-02-19 12:25:12 -07:00
|
|
|
Action: action,
|
2020-01-22 19:58:50 -07:00
|
|
|
OnDisk: true,
|
2020-01-09 20:45:06 -07:00
|
|
|
})
|
2020-02-19 12:25:12 -07:00
|
|
|
// Keep track of deleted files so that we can clear their diagnostics.
|
|
|
|
// A file might be re-created after deletion, so only mark files that
|
|
|
|
// have truly been deleted.
|
|
|
|
switch action {
|
|
|
|
case source.Delete:
|
|
|
|
deletions[uri] = struct{}{}
|
|
|
|
case source.Close:
|
|
|
|
default:
|
|
|
|
delete(deletions, uri)
|
|
|
|
}
|
2020-01-09 20:45:06 -07:00
|
|
|
}
|
2020-04-22 15:54:30 -06:00
|
|
|
snapshots, err := s.didModifyFiles(ctx, modifications, FromDidChangeWatchedFiles)
|
2020-02-19 12:25:12 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-27 05:32:44 -06:00
|
|
|
// Clear the diagnostics for any deleted files that are not open in the editor.
|
2020-02-19 12:25:12 -07:00
|
|
|
for uri := range deletions {
|
|
|
|
if snapshot := snapshots[uri]; snapshot == nil || snapshot.IsOpen(uri) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
|
|
|
URI: protocol.URIFromSpanURI(uri),
|
|
|
|
Diagnostics: []protocol.Diagnostic{},
|
|
|
|
Version: 0,
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2020-01-09 20:45:06 -07:00
|
|
|
}
|
|
|
|
|
2019-12-17 14:53:57 -07:00
|
|
|
func (s *Server) didSave(ctx context.Context, params *protocol.DidSaveTextDocumentParams) error {
|
2020-02-13 11:46:49 -07:00
|
|
|
uri := params.TextDocument.URI.SpanURI()
|
|
|
|
if !uri.IsFile() {
|
|
|
|
return nil
|
|
|
|
}
|
2019-12-17 14:53:57 -07:00
|
|
|
c := source.FileModification{
|
2020-02-13 11:46:49 -07:00
|
|
|
URI: uri,
|
2019-12-17 14:53:57 -07:00
|
|
|
Action: source.Save,
|
|
|
|
Version: params.TextDocument.Version,
|
|
|
|
}
|
|
|
|
if params.Text != nil {
|
|
|
|
c.Text = []byte(*params.Text)
|
|
|
|
}
|
2020-04-22 15:54:30 -06:00
|
|
|
_, err := s.didModifyFiles(ctx, []source.FileModification{c}, FromDidSave)
|
2019-12-17 14:53:57 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) didClose(ctx context.Context, params *protocol.DidCloseTextDocumentParams) error {
|
2020-02-13 11:46:49 -07:00
|
|
|
uri := params.TextDocument.URI.SpanURI()
|
|
|
|
if !uri.IsFile() {
|
|
|
|
return nil
|
|
|
|
}
|
2020-02-19 12:25:12 -07:00
|
|
|
snapshots, err := s.didModifyFiles(ctx, []source.FileModification{
|
2020-01-22 19:58:50 -07:00
|
|
|
{
|
2020-02-13 11:46:49 -07:00
|
|
|
URI: uri,
|
2020-01-22 19:58:50 -07:00
|
|
|
Action: source.Close,
|
|
|
|
Version: -1,
|
|
|
|
Text: nil,
|
|
|
|
},
|
2020-04-22 15:54:30 -06:00
|
|
|
}, FromDidClose)
|
2020-02-19 12:25:12 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
snapshot := snapshots[uri]
|
|
|
|
if snapshot == nil {
|
|
|
|
return errors.Errorf("no snapshot for %s", uri)
|
|
|
|
}
|
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-19 12:25:12 -07:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// If a file has been closed and is not on disk, clear its diagnostics.
|
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 _, err := fh.Read(); err != nil {
|
2020-02-19 12:25:12 -07:00
|
|
|
return s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
|
|
|
|
URI: protocol.URIFromSpanURI(uri),
|
|
|
|
Diagnostics: []protocol.Diagnostic{},
|
|
|
|
Version: 0,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil
|
2019-12-17 14:53:57 -07:00
|
|
|
}
|
|
|
|
|
2020-04-22 15:54:30 -06:00
|
|
|
func (s *Server) didModifyFiles(ctx context.Context, modifications []source.FileModification, cause ModificationSource) (map[span.URI]source.Snapshot, error) {
|
2020-05-29 13:51:01 -06:00
|
|
|
// diagnosticWG tracks outstanding diagnostic work as a result of this file
|
|
|
|
// modification.
|
|
|
|
var diagnosticWG sync.WaitGroup
|
|
|
|
if s.session.Options().VerboseWorkDoneProgress {
|
|
|
|
work := s.StartWork(ctx, DiagnosticWorkTitle(cause), "Calculating file diagnostics...", nil)
|
|
|
|
defer func() {
|
|
|
|
go func() {
|
|
|
|
diagnosticWG.Wait()
|
|
|
|
work.End(ctx, "Done.")
|
|
|
|
}()
|
|
|
|
}()
|
|
|
|
}
|
2020-01-22 19:58:50 -07:00
|
|
|
snapshots, err := s.session.DidModifyFiles(ctx, modifications)
|
2020-01-11 21:59:57 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-23 11:58:25 -07:00
|
|
|
snapshotByURI := make(map[span.URI]source.Snapshot)
|
2020-01-22 19:58:50 -07:00
|
|
|
for _, c := range modifications {
|
2020-01-23 11:58:25 -07:00
|
|
|
snapshotByURI[c.URI] = nil
|
2020-01-11 21:59:57 -07:00
|
|
|
}
|
2020-01-22 19:58:50 -07:00
|
|
|
// Avoid diagnosing the same snapshot twice.
|
2020-01-23 11:58:25 -07:00
|
|
|
snapshotSet := make(map[source.Snapshot][]span.URI)
|
|
|
|
for uri := range snapshotByURI {
|
2020-01-22 19:58:50 -07:00
|
|
|
view, err := s.session.ViewOf(uri)
|
2020-01-11 21:59:57 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-22 19:58:50 -07:00
|
|
|
var snapshot source.Snapshot
|
|
|
|
for _, s := range snapshots {
|
|
|
|
if s.View() == view {
|
|
|
|
if snapshot != nil {
|
|
|
|
return nil, errors.Errorf("duplicate snapshots for the same view")
|
|
|
|
}
|
|
|
|
snapshot = s
|
|
|
|
}
|
2020-01-11 21:59:57 -07:00
|
|
|
}
|
2020-01-23 15:05:44 -07:00
|
|
|
// If the file isn't in any known views (for example, if it's in a dependency),
|
|
|
|
// we may not have a snapshot to map it to. As a result, we won't try to
|
|
|
|
// diagnose it. TODO(rstambler): Figure out how to handle this better.
|
2020-01-23 11:58:25 -07:00
|
|
|
if snapshot == nil {
|
2020-01-23 15:05:44 -07:00
|
|
|
continue
|
2020-01-23 11:58:25 -07:00
|
|
|
}
|
|
|
|
snapshotByURI[uri] = snapshot
|
|
|
|
snapshotSet[snapshot] = append(snapshotSet[snapshot], uri)
|
2020-01-11 21:59:57 -07:00
|
|
|
}
|
2020-01-23 11:58:25 -07:00
|
|
|
for snapshot, uris := range snapshotSet {
|
2020-03-27 11:11:41 -06:00
|
|
|
// If a modification comes in for the view's go.mod file and the view
|
|
|
|
// was never properly initialized, or the view does not have
|
|
|
|
// a go.mod file, try to recreate the associated view.
|
2020-06-10 23:11:52 -06:00
|
|
|
if modfile := snapshot.View().ModFile(); modfile == "" {
|
2020-03-27 11:11:41 -06:00
|
|
|
for _, uri := range uris {
|
|
|
|
// Don't rebuild the view until the go.mod is on disk.
|
|
|
|
if !snapshot.IsSaved(uri) {
|
2020-01-31 11:59:11 -07:00
|
|
|
continue
|
|
|
|
}
|
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-01-23 11:58:25 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
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
|
|
|
switch fh.Kind() {
|
2020-03-27 11:11:41 -06:00
|
|
|
case source.Mod:
|
|
|
|
newSnapshot, err := snapshot.View().Rebuild(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Update the snapshot to the rebuilt one.
|
|
|
|
snapshot = newSnapshot
|
|
|
|
snapshotByURI[uri] = newSnapshot
|
|
|
|
}
|
2020-01-23 11:58:25 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-29 13:51:01 -06:00
|
|
|
diagnosticWG.Add(1)
|
2020-04-22 15:54:30 -06:00
|
|
|
go func(snapshot source.Snapshot) {
|
2020-05-29 13:51:01 -06:00
|
|
|
defer diagnosticWG.Done()
|
2020-04-22 15:54:30 -06:00
|
|
|
s.diagnoseSnapshot(snapshot)
|
|
|
|
}(snapshot)
|
2019-12-17 14:53:57 -07:00
|
|
|
}
|
2020-01-23 11:58:25 -07:00
|
|
|
return snapshotByURI, nil
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
|
|
|
|
2020-04-22 15:54:30 -06:00
|
|
|
// DiagnosticWorkTitle returns the title of the diagnostic work resulting from a
|
|
|
|
// file change originating from the given cause.
|
|
|
|
func DiagnosticWorkTitle(cause ModificationSource) string {
|
|
|
|
return fmt.Sprintf("diagnosing %v", cause)
|
|
|
|
}
|
|
|
|
|
2019-11-21 22:52:09 -07:00
|
|
|
func (s *Server) wasFirstChange(uri span.URI) bool {
|
|
|
|
if s.changedFiles == nil {
|
|
|
|
s.changedFiles = make(map[span.URI]struct{})
|
|
|
|
}
|
|
|
|
_, ok := s.changedFiles[uri]
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
|
2019-12-04 16:45:25 -07:00
|
|
|
func (s *Server) changedText(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) {
|
|
|
|
if len(changes) == 0 {
|
2020-04-09 20:37:52 -06:00
|
|
|
return nil, fmt.Errorf("%w: no content changes provided", jsonrpc2.ErrInternal)
|
2019-06-17 11:07:16 -06:00
|
|
|
}
|
2019-12-04 16:45:25 -07:00
|
|
|
|
|
|
|
// Check if the client sent the full content of the file.
|
|
|
|
// We accept a full content change even if the server expected incremental changes.
|
|
|
|
if len(changes) == 1 && changes[0].Range == nil && changes[0].RangeLength == 0 {
|
|
|
|
return []byte(changes[0].Text), nil
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
2019-12-04 16:45:25 -07:00
|
|
|
return s.applyIncrementalChanges(ctx, uri, changes)
|
2019-06-17 11:07:16 -06:00
|
|
|
}
|
2019-04-17 13:37:20 -06:00
|
|
|
|
2019-12-04 16:45:25 -07:00
|
|
|
func (s *Server) applyIncrementalChanges(ctx context.Context, uri span.URI, changes []protocol.TextDocumentContentChangeEvent) ([]byte, error) {
|
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 := s.session.GetFile(ctx, uri)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
content, err := fh.Read()
|
2019-06-03 23:04:18 -06:00
|
|
|
if err != nil {
|
2020-04-09 20:37:52 -06:00
|
|
|
return nil, fmt.Errorf("%w: file not found (%v)", jsonrpc2.ErrInternal, err)
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
2019-06-17 11:07:16 -06:00
|
|
|
for _, change := range changes {
|
2019-12-04 16:45:25 -07:00
|
|
|
// Make sure to update column mapper along with the content.
|
2019-09-09 22:36:39 -06:00
|
|
|
converter := span.NewContentConverter(uri.Filename(), content)
|
|
|
|
m := &protocol.ColumnMapper{
|
|
|
|
URI: uri,
|
|
|
|
Converter: converter,
|
|
|
|
Content: content,
|
|
|
|
}
|
2019-12-04 16:45:25 -07:00
|
|
|
if change.Range == nil {
|
2020-04-09 20:37:52 -06:00
|
|
|
return nil, fmt.Errorf("%w: unexpected nil range for change", jsonrpc2.ErrInternal)
|
2019-12-04 16:45:25 -07:00
|
|
|
}
|
|
|
|
spn, err := m.RangeSpan(*change.Range)
|
2019-04-17 13:37:20 -06:00
|
|
|
if err != nil {
|
2019-12-04 16:45:25 -07:00
|
|
|
return nil, err
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
|
|
|
if !spn.HasOffset() {
|
2020-04-09 20:37:52 -06:00
|
|
|
return nil, fmt.Errorf("%w: invalid range for content change", jsonrpc2.ErrInternal)
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
|
|
|
start, end := spn.Start().Offset(), spn.End().Offset()
|
2019-05-02 20:35:49 -06:00
|
|
|
if end < start {
|
2020-04-09 20:37:52 -06:00
|
|
|
return nil, fmt.Errorf("%w: invalid range for content change", jsonrpc2.ErrInternal)
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
buf.Write(content[:start])
|
|
|
|
buf.WriteString(change.Text)
|
|
|
|
buf.Write(content[end:])
|
|
|
|
content = buf.Bytes()
|
|
|
|
}
|
2019-12-04 16:45:25 -07:00
|
|
|
return content, nil
|
2019-04-17 13:37:20 -06:00
|
|
|
}
|
2020-01-09 20:45:06 -07:00
|
|
|
|
|
|
|
func changeTypeToFileAction(ct protocol.FileChangeType) source.FileAction {
|
|
|
|
switch ct {
|
|
|
|
case protocol.Changed:
|
|
|
|
return source.Change
|
|
|
|
case protocol.Created:
|
|
|
|
return source.Create
|
|
|
|
case protocol.Deleted:
|
|
|
|
return source.Delete
|
|
|
|
}
|
|
|
|
return source.UnknownFileAction
|
|
|
|
}
|