Skip to content

Commit acdb8c1

Browse files
committed
internal/lsp: handle on-disk file deletions for opened files
Previously, we only updated the opened file's overlay, but not the snapshot. This meant that the snapshot was still operating with stale data. Invalidating the snapshot creates a new snapshot with the correct set of overlays. The test is skipped because it will flake until we have a better caching strategy for `go mod tidy` results. Updates golang/go#40269 Change-Id: Ia8d1ae75127a1d18d8877923e7a5b25b7bd965ac Reviewed-on: https://go-review.googlesource.com/c/tools/+/243537 Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent 5ea3631 commit acdb8c1

File tree

3 files changed

+107
-41
lines changed

3 files changed

+107
-41
lines changed

internal/lsp/cache/session.go

+34-19
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package cache
66

77
import (
88
"context"
9+
"fmt"
910
"strconv"
1011
"strings"
1112
"sync"
@@ -328,11 +329,6 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
328329
if c.Action == source.InvalidateMetadata {
329330
forceReloadMetadata = true
330331
}
331-
// Do nothing if the file is open in the editor and we receive
332-
// an on-disk action. The editor is the source of truth.
333-
if s.isOpen(c.URI) && c.OnDisk {
334-
continue
335-
}
336332
// Look through all of the session's views, invalidating the file for
337333
// all of the views to which it is known.
338334
for _, view := range s.views {
@@ -379,13 +375,20 @@ func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModif
379375
defer s.overlayMu.Unlock()
380376

381377
for _, c := range changes {
382-
// Don't update overlays for on-disk changes or metadata invalidations.
383-
if c.OnDisk || c.Action == source.InvalidateMetadata {
378+
// Don't update overlays for metadata invalidations.
379+
if c.Action == source.InvalidateMetadata {
384380
continue
385381
}
386382

387383
o, ok := s.overlays[c.URI]
388384

385+
// If the file is not opened in an overlay and the change is on disk,
386+
// there's no need to update an overlay. If there is an overlay, we
387+
// may need to update the overlay's saved value.
388+
if !ok && c.OnDisk {
389+
continue
390+
}
391+
389392
// Determine the file kind on open, otherwise, assume it has been cached.
390393
var kind source.FileKind
391394
switch c.Action {
@@ -408,35 +411,46 @@ func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModif
408411
}
409412

410413
// If the file is on disk, check if its content is the same as in the
411-
// overlay. Saves don't necessarily come with the file's content.
414+
// overlay. Saves and on-disk file changes don't come with the file's
415+
// content.
412416
text := c.Text
413-
if text == nil && c.Action == source.Save {
417+
if text == nil && (c.Action == source.Save || c.OnDisk) {
418+
if !ok {
419+
return nil, fmt.Errorf("no known content for overlay for %s", c.Action)
420+
}
414421
text = o.text
415422
}
423+
// On-disk changes don't come with versions.
424+
version := c.Version
425+
if c.OnDisk {
426+
version = o.version
427+
}
416428
hash := hashContents(text)
417429
var sameContentOnDisk bool
418430
switch c.Action {
419-
case source.Open:
420-
fh, err := s.cache.getFile(ctx, c.URI)
421-
if err != nil {
422-
return nil, err
423-
}
424-
_, readErr := fh.Read()
425-
sameContentOnDisk = (readErr == nil && fh.Identity().Identifier == hash)
431+
case source.Delete:
432+
// Do nothing. sameContentOnDisk should be false.
426433
case source.Save:
427434
// Make sure the version and content (if present) is the same.
428-
if o.version != c.Version {
435+
if o.version != version {
429436
return nil, errors.Errorf("updateOverlays: saving %s at version %v, currently at %v", c.URI, c.Version, o.version)
430437
}
431438
if c.Text != nil && o.hash != hash {
432439
return nil, errors.Errorf("updateOverlays: overlay %s changed on save", c.URI)
433440
}
434441
sameContentOnDisk = true
442+
default:
443+
fh, err := s.cache.getFile(ctx, c.URI)
444+
if err != nil {
445+
return nil, err
446+
}
447+
_, readErr := fh.Read()
448+
sameContentOnDisk = (readErr == nil && fh.Identity().Identifier == hash)
435449
}
436450
o = &overlay{
437451
session: s,
438452
uri: c.URI,
439-
version: c.Version,
453+
version: version,
440454
text: text,
441455
kind: kind,
442456
hash: hash,
@@ -445,7 +459,8 @@ func (s *Session) updateOverlays(ctx context.Context, changes []source.FileModif
445459
s.overlays[c.URI] = o
446460
}
447461

448-
// Get the overlays for each change while the session's overlay map is locked.
462+
// Get the overlays for each change while the session's overlay map is
463+
// locked.
449464
overlays := make(map[span.URI]*overlay)
450465
for _, c := range changes {
451466
if o, ok := s.overlays[c.URI]; ok {

internal/lsp/regtest/modfile_test.go

+52-22
Original file line numberDiff line numberDiff line change
@@ -37,28 +37,58 @@ package main
3737
import "example.com/blah"
3838
3939
func main() {
40-
fmt.Println(blah.Name)
41-
}`
42-
withOptions(WithProxy(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
43-
// Open the file and make sure that the initial workspace load does not
44-
// modify the go.mod file.
45-
goModContent := env.ReadWorkspaceFile("go.mod")
46-
env.OpenFile("main.go")
47-
env.Await(
48-
env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
49-
)
50-
if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
51-
t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
52-
}
53-
// Save the buffer, which will format and organize imports.
54-
// Confirm that the go.mod file still does not change.
55-
env.SaveBuffer("main.go")
56-
env.Await(
57-
env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
58-
)
59-
if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
60-
t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
61-
}
40+
println(blah.Name)
41+
}
42+
`
43+
t.Run("basic", func(t *testing.T) {
44+
withOptions(WithProxy(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
45+
// Open the file and make sure that the initial workspace load does not
46+
// modify the go.mod file.
47+
goModContent := env.ReadWorkspaceFile("go.mod")
48+
env.OpenFile("main.go")
49+
env.Await(
50+
env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
51+
)
52+
if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
53+
t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
54+
}
55+
// Save the buffer, which will format and organize imports.
56+
// Confirm that the go.mod file still does not change.
57+
env.SaveBuffer("main.go")
58+
env.Await(
59+
env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
60+
)
61+
if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
62+
t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
63+
}
64+
})
65+
})
66+
67+
// Reproduce golang/go#40269 by deleting and recreating main.go.
68+
t.Run("delete main.go", func(t *testing.T) {
69+
t.Skipf("This test will be flaky until golang/go#40269 is resolved.")
70+
71+
withOptions(WithProxy(proxy)).run(t, untidyModule, func(t *testing.T, env *Env) {
72+
goModContent := env.ReadWorkspaceFile("go.mod")
73+
mainContent := env.ReadWorkspaceFile("main.go")
74+
env.OpenFile("main.go")
75+
env.SaveBuffer("main.go")
76+
77+
env.RemoveWorkspaceFile("main.go")
78+
env.Await(
79+
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidOpen), 1),
80+
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidSave), 1),
81+
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 2),
82+
)
83+
84+
env.WriteWorkspaceFile("main.go", mainContent)
85+
env.Await(
86+
env.DiagnosticAtRegexp("main.go", "\"example.com/blah\""),
87+
)
88+
if got := env.ReadWorkspaceFile("go.mod"); got != goModContent {
89+
t.Fatalf("go.mod changed on disk:\n%s", tests.Diff(goModContent, got))
90+
}
91+
})
6292
})
6393
}
6494

internal/lsp/source/view.go

+21
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,27 @@ const (
274274
InvalidateMetadata
275275
)
276276

277+
func (a FileAction) String() string {
278+
switch a {
279+
case Open:
280+
return "Open"
281+
case Change:
282+
return "Change"
283+
case Close:
284+
return "Close"
285+
case Save:
286+
return "Save"
287+
case Create:
288+
return "Create"
289+
case Delete:
290+
return "Delete"
291+
case InvalidateMetadata:
292+
return "InvalidateMetadata"
293+
default:
294+
return "Unknown"
295+
}
296+
}
297+
277298
// Cache abstracts the core logic of dealing with the environment from the
278299
// higher level logic that processes the information to produce results.
279300
// The cache provides access to files and their contents, so the source

0 commit comments

Comments
 (0)