Skip to content

Commit 565f509

Browse files
authoredAug 1, 2024··
lsp: Poll workspace state to detect state changes (#954)
* WIP: attempt to use fsnotify reliably Signed-off-by: Charlie Egan <charlie@styra.com> * Revert "WIP: attempt to use fsnotify reliably" This reverts commit f0bce94. I am going to use a simple polling approach. * lsp: Poll workspace state to detect state changes Fixes #857 I will add some notes to the issue too, but in short I found that it wasn't possible to catch new files added to new directories without some polling-based delay. So I have opted for the keep it simple approach for now. Signed-off-by: Charlie Egan <charlie@styra.com> --------- Signed-off-by: Charlie Egan <charlie@styra.com>
1 parent d070132 commit 565f509

File tree

3 files changed

+95
-12
lines changed

3 files changed

+95
-12
lines changed
 

‎cmd/languageserver.go

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ func init() {
4444
go ls.StartHoverWorker(ctx)
4545
go ls.StartCommandWorker(ctx)
4646
go ls.StartConfigWorker(ctx)
47+
go ls.StartWorkspaceStateWorker(ctx)
4748

4849
sigChan := make(chan os.Signal, 1)
4950
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)

‎internal/lsp/cache/cache.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -330,20 +330,20 @@ func (c *Cache) Delete(fileURI string) {
330330
c.ignoredFileContentsMu.Unlock()
331331
}
332332

333-
func UpdateCacheForURIFromDisk(cache *Cache, fileURI, path string) (string, error) {
333+
func UpdateCacheForURIFromDisk(cache *Cache, fileURI, path string) (bool, string, error) {
334334
content, err := os.ReadFile(path)
335335
if err != nil {
336-
return "", fmt.Errorf("failed to read file: %w", err)
336+
return false, "", fmt.Errorf("failed to read file: %w", err)
337337
}
338338

339339
currentContent := string(content)
340340

341341
cachedContent, ok := cache.GetFileContents(fileURI)
342342
if ok && cachedContent == currentContent {
343-
return cachedContent, nil
343+
return false, cachedContent, nil
344344
}
345345

346346
cache.SetFileContents(fileURI, currentContent)
347347

348-
return currentContent, nil
348+
return true, currentContent, nil
349349
}

‎internal/lsp/server.go

+90-8
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ func (l *LanguageServer) StartDiagnosticsWorker(ctx context.Context) {
196196
return
197197
case evt := <-l.diagnosticRequestFile:
198198
// if file has been deleted, clear diagnostics in the client
199-
if evt.Reason == "textDocument/didDelete" {
199+
if evt.Reason == "textDocument/didDelete" ||
200+
evt.Reason == "internal/workspaceStateWorker/missingFile" {
200201
err := l.sendFileDiagnostics(ctx, evt.URI)
201202
if err != nil {
202203
l.logError(fmt.Errorf("failed to send diagnostic: %w", err))
@@ -460,6 +461,72 @@ func (l *LanguageServer) StartCommandWorker(ctx context.Context) {
460461
}
461462
}
462463

464+
// StartWorkspaceStateWorker will poll for changes to the workspaces state that
465+
// are not sent from the client. For example, when a file a is removed from the
466+
// workspace after changing branch.
467+
func (l *LanguageServer) StartWorkspaceStateWorker(ctx context.Context) {
468+
timer := time.NewTicker(2 * time.Second)
469+
470+
for {
471+
select {
472+
case <-ctx.Done():
473+
return
474+
case <-timer.C:
475+
// first clear files that are missing from the workspaceDir
476+
for fileURI := range l.cache.GetAllFiles() {
477+
filePath := uri.ToPath(l.clientIdentifier, fileURI)
478+
479+
_, err := os.Stat(filePath)
480+
if !os.IsNotExist(err) {
481+
// if the file is not missing, we have no work to do
482+
continue
483+
}
484+
485+
// if the diagnostics for the file are empty, or missing
486+
// then we do not need to send anything to the client and can
487+
// remove the file from the cache.
488+
diagnostics, ok := l.cache.GetFileDiagnostics(fileURI)
489+
if !ok || len(diagnostics) == 0 {
490+
l.cache.Delete(fileURI)
491+
492+
continue
493+
}
494+
495+
// if there are diagnostics, we need to clear them and send a
496+
// notification to the client.
497+
l.cache.SetFileDiagnostics(fileURI, []types.Diagnostic{})
498+
l.diagnosticRequestFile <- fileUpdateEvent{
499+
URI: fileURI,
500+
Reason: "internal/workspaceStateWorker/missingFile",
501+
}
502+
}
503+
504+
// for this next operation, the workspace root must be set as it's
505+
// used to scan for new files.
506+
if l.workspaceRootURI == "" {
507+
continue
508+
}
509+
510+
// next, check if there are any new files that are not ignored and
511+
// need to be loaded. We get new only so that files being worked
512+
// on are not loaded from disk during editing.
513+
changedOrNewURIs, err := l.loadWorkspaceContents(ctx, true)
514+
if err != nil {
515+
l.logError(fmt.Errorf("failed to refresh workspace contents: %w", err))
516+
517+
continue
518+
}
519+
520+
for _, uri := range changedOrNewURIs {
521+
l.diagnosticRequestFile <- fileUpdateEvent{
522+
URI: uri,
523+
Reason: "internal/workspaceStateWorker/changedOrNewFile",
524+
}
525+
}
526+
}
527+
}
528+
}
529+
463530
func (l *LanguageServer) fixEditParams(
464531
label string,
465532
fix fixes.Fix,
@@ -1271,7 +1338,7 @@ func (l *LanguageServer) handleWorkspaceDidCreateFiles(
12711338
}
12721339

12731340
for _, createOp := range params.Files {
1274-
_, err = cache.UpdateCacheForURIFromDisk(
1341+
_, _, err = cache.UpdateCacheForURIFromDisk(
12751342
l.cache,
12761343
uri.FromPath(l.clientIdentifier, createOp.URI),
12771344
uri.ToPath(l.clientIdentifier, createOp.URI),
@@ -1335,7 +1402,7 @@ func (l *LanguageServer) handleWorkspaceDidRenameFiles(
13351402
continue
13361403
}
13371404

1338-
content, err := cache.UpdateCacheForURIFromDisk(
1405+
_, content, err := cache.UpdateCacheForURIFromDisk(
13391406
l.cache,
13401407
uri.FromPath(l.clientIdentifier, renameOp.NewURI),
13411408
uri.ToPath(l.clientIdentifier, renameOp.NewURI),
@@ -1475,7 +1542,7 @@ func (l *LanguageServer) handleInitialize(
14751542
l.configWatcher.Watch(configFile.Name())
14761543
}
14771544

1478-
err = l.loadWorkspaceContents(ctx)
1545+
_, err = l.loadWorkspaceContents(ctx, false)
14791546
if err != nil {
14801547
return nil, fmt.Errorf("failed to load workspace contents: %w", err)
14811548
}
@@ -1486,9 +1553,11 @@ func (l *LanguageServer) handleInitialize(
14861553
return result, nil
14871554
}
14881555

1489-
func (l *LanguageServer) loadWorkspaceContents(ctx context.Context) error {
1556+
func (l *LanguageServer) loadWorkspaceContents(ctx context.Context, newOnly bool) ([]string, error) {
14901557
workspaceRootPath := uri.ToPath(l.clientIdentifier, l.workspaceRootURI)
14911558

1559+
changedOrNewURIs := make([]string, 0)
1560+
14921561
err := filepath.WalkDir(workspaceRootPath, func(path string, d os.DirEntry, err error) error {
14931562
if err != nil {
14941563
return fmt.Errorf("failed to walk workspace dir %q: %w", path, err)
@@ -1505,23 +1574,36 @@ func (l *LanguageServer) loadWorkspaceContents(ctx context.Context) error {
15051574
return nil
15061575
}
15071576

1508-
_, err = cache.UpdateCacheForURIFromDisk(l.cache, fileURI, path)
1577+
// if the caller has requested only new files, then we can exit early
1578+
if _, ok := l.cache.GetModule(fileURI); newOnly && ok {
1579+
return nil
1580+
}
1581+
1582+
changed, _, err := cache.UpdateCacheForURIFromDisk(l.cache, fileURI, path)
15091583
if err != nil {
15101584
return fmt.Errorf("failed to update cache for uri %q: %w", path, err)
15111585
}
15121586

1587+
// there is no need to update the parse if the file contents
1588+
// was not changed in the above operation.
1589+
if !changed {
1590+
return nil
1591+
}
1592+
15131593
_, err = updateParse(ctx, l.cache, l.regoStore, fileURI)
15141594
if err != nil {
15151595
return fmt.Errorf("failed to update parse: %w", err)
15161596
}
15171597

1598+
changedOrNewURIs = append(changedOrNewURIs, fileURI)
1599+
15181600
return nil
15191601
})
15201602
if err != nil {
1521-
return fmt.Errorf("failed to walk workspace dir %q: %w", workspaceRootPath, err)
1603+
return nil, fmt.Errorf("failed to walk workspace dir %q: %w", workspaceRootPath, err)
15221604
}
15231605

1524-
return nil
1606+
return changedOrNewURIs, nil
15251607
}
15261608

15271609
func (l *LanguageServer) handleInitialized(

0 commit comments

Comments
 (0)
Please sign in to comment.