Handle atomic folder renames/deletes in watched-files handling - #1463
Merged
Conversation
VS Code reports an atomic folder rename or delete as a single didChangeWatchedFiles event for the folder path, with no events for the files inside, and none of the registered file-extension globs match a folder path - so the server never noticed and kept stale files (e.g. test items from a renamed test/ folder). For VS Code clients, additionally register a '**' watcher for create/delete (no glob can match directories only) and restrict the extension globs to change events so every event kind has exactly one source. Other clients keep the previous registration, since some expand '**' into one OS watcher per directory. In the notification handler, sweep all tracked files below a deleted path (prefix match with a trailing slash so removing test/ does not sweep a sibling test2/) and scan the contents of a created directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pfitzseb
reviewed
Aug 26, 2026
The folder-event problem is specific to VS Code's watcher, but every
Code-OSS fork (VSCodium, Cursor, Windsurf, Positron, ...) embeds the
same LSP client and watcher while reporting a different clientInfo
name, so the previous "code" substring check missed them.
Replace the gate with a gopls-style three-state design: the
julialangDirectoryWatching initialization option ("on"/"off", booleans
accepted) explicitly overrides, and the default "auto" mode sniffs
clientInfo.name against the Code-OSS family. Other clients stay off by
default - most watcher backends synthesize per-file events on folder
renames so the file globs already suffice, and some expand ** into one
OS watcher per directory - but any client can now opt in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
Author
|
Pushed a follow-up commit that broadens the directory-watching gate beyond stock VS Code:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Renaming a folder inside a workspace (e.g.
test→test2) is never noticed by the server: the Test Explorer keeps stale test items from the old path and files under the new path are never discovered (no crash, simply no events).Root cause, two layers:
**/*.{jl,jmd,md}+ TOML lists) match only file paths — so the folder event is filtered out client-side and nothing reaches the server. (Same limitation gopls works around, see x/tools/gopls: explicitly watch for directories in file watching golang/go#42348; external tools that remove files one-by-one, e.g. git checkout, were unaffected.)Fix
init.jl): for VS Code clients (detected viaclientInfo, same heuristic already used in actions.jl and now factored intoclient_is_vscode), additionally registerFileSystemWatcher("**", Create|Delete)— no glob can match "directories only", and VS Code's workspace watcher is recursive anyway, so this only widens event delivery — and restrict the extension globs toChange, so every event kind has exactly one source (no duplicate events). Other clients keep the previous registration unchanged, since some (e.g. Emacs-based) expand**into one OS watcher per directory (the pyright problem).workspace.jl): onDeleted, sweep all tracked files below the deleted path (prefix match with a trailing slash, so removingtest/does not sweep a siblingtest2/); open files keep their in-memory content, matching the existing exact-URI behavior. OnCreatedof a directory, scan its contents like a workspace folder at startup (reusescollect_folder_files!, including theMAX_WORKSPACE_JULIA_FILEScap). Irrelevant created files are already filtered byread_text_file_from_urireturningnothing.The existing downstream machinery (test item publish sweep, diagnostics) needs no changes — it just never fired because no file was ever removed/added.
Deliberately not using LSP
workspace.fileOperations(didRenameFiles): the watcher sees the same atomic rename that explorer operations perform, and implementing both channels would double-report in-editor renames. Also deliberately not usingJuliaWorkspaces.remove_all_children!, whose prefix match lacks the trailing-slash guard and ignores open files.Merging/tagging sequencing
main, so no registry tag is required for the extension.e212895on this branch; a squash-merge would leave that commit unreachable frommain, and the julia-vscode PR would then need its pointer updated to the squashed commit before it can merge.main.Testing
test/test_watched_folders.jl(4 test items): folder delete sweeps children but not prefix-sharing siblings; folder create scans children (non-Julia files ignored); rename reported as delete+create in one notification; open files under a deleted folder stay in the workspace.🤖 Generated with Claude Code