Skip to content

perf(workspace): bound nested configuration discovery - #895

Open
psteinroe wants to merge 2 commits into
feat/nested-configsfrom
fix/nested-config-discovery
Open

psteinroe wants to merge 2 commits into
feat/nested-configsfrom
fix/nested-config-discovery

Conversation

@psteinroe

@psteinroe psteinroe commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #881. Must merge after #881.

#881 looks for nested configuration files by walking the whole workspace folder, synchronously on the async runtime, at startup and on every configuration change. With the home directory open as a workspace (a common Neovim root_dir fallback, or "open folder" on a parent), the walk took 159 s and loaded 149 configuration files of unrelated projects. The CLI walked the whole working directory even for check one.sql, and every watched-file event ran a full reload, including events from node_modules.

What changed

  • Off the runtime. The LSP runs the search (and loading the nested files) in spawn_blocking. The session methods that reload take self: &Arc<Self> so the blocking task can own the session.
  • Pruned while walking. FileSystem::find_files takes a FindFiles with a skip_directory predicate. The callers pass the root configuration's files.ignore/files.include and gitignore (FilesSettings::is_ignored, moved out of server.rs). A folder below a nested configuration file is not pruned by the root settings, because that file's settings decide there, which is what update_nested_settings already does.
  • Not a project folder. When the root is the home directory or a filesystem root, nested configuration files are not looked up, and the LSP logs (CLI prints) why.
  • Limits. The walk stops after 100,000 entries or 12 levels below the root and warns: "Stopped looking for configuration files below … Ignore the folders that hold no SQL files in files.ignore or .gitignore."
  • CLI. check/format only look at the folders between the working directory and each checked path (cached per folder), and walk only below the paths that are folders. check a.sql no longer reads every configuration file in the tree, so a broken one in tests/fixtures/ no longer fails it.
  • Reloads. A configuration change schedules one reload 200 ms later; further events until it starts are folded into it. Events for configuration files in folders the search skips (hidden, node_modules, target, too deep, ignored by the root settings) are dropped.

Measurements

Release build, warm cache, same machine (shared, so ±50%), cargo test -p pgls_fs --release --test find_files -- --ignored --nocapture (benchmark included in this PR):

Tree Before After
synthetic, 80k .sql files: src/ 10k, vendor/ 30k, build/ 20k gitignored, node_modules/ 20k 32–81 ms 32–87 ms (same walk)
same, root config ignores vendor 32–81 ms 8–14 ms
home directory as workspace 159 s, 149 configs loaded skipped, 0 configs
a huge folder that is not the home directory (~ walked with the cap) unbounded 0.39 s, then a warning
this repo incl. target/ 15 ms 13 ms
CLI check one.sql full walk of the working directory stats of the folders on the way to the file

Behaviour changes

  • Opening the home directory or / as a workspace no longer applies nested configuration files there.
  • After a configuration file changes, the LSP applies it about 200 ms later. A document opened in that window is checked once with the old settings and refreshed after the reload.

Tests

  • pgls_fs/tests/find_files.rs: hidden/vendored/gitignored folders, skip_directory up to the first configuration file, depth and entry limits; plus the ignored benchmark.
  • pgls_workspace: the_search_skips_the_folders_the_root_configuration_ignores, the_search_skips_the_home_directory, the_search_for_paths_only_looks_at_their_folders, the_search_reports_its_depth_limit, configuration_changes_in_skipped_folders_are_ignored, the_user_configuration_directories_are_not_nested_configurations. nested_configurations_do_not_merge_the_user_configuration now uses a repo below the home directory instead of the home directory itself.
  • LSP: configuration_changes_are_coalesced (five events → one refresh; a change in node_modules → none). Two existing tests now wait for the debounced reload.
  • CLI: check_only_loads_the_configuration_files_of_its_paths.

Not covered

  • The limits aren't configurable. I'd only add an option if someone hits them.
  • A changed nested file still reloads the whole project, not just its subtree.
  • The walk still honours .gitignore even with vcs.enabled: false, as before.

Locks in pgls_lsp

The new session state (the root files settings per folder here, and the watched files and real folder paths later in the stack) uses parking_lot locks instead of std::sync ones, so a panicking handler can't poison them, in line with #943's unwrap_used/expect_used lints for pgls_lsp. The locks that come from #881 (projects, workspace_folders) are still std with .unwrap(), and this PR reads projects once more in is_configuration_change. Whichever of this stack and #943 lands second has to switch that call too.

Run the search off the async runtime, prune the folders the root
configuration ignores while walking, skip the home directory and
filesystem roots, cap the walk at 100k entries / 12 levels with a
warning, and only look at the ancestors of the checked paths in the CLI.
Coalesce reloads from watched-file events and ignore events in folders
the search skips.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant