Skip to content

fix: avoid redundant workspace reloads on startup#995

Merged
CppCXY merged 2 commits intoEmmyLuaLs:mainfrom
lewis6991:fix/startup-redundant-reloads
Mar 27, 2026
Merged

fix: avoid redundant workspace reloads on startup#995
CppCXY merged 2 commits intoEmmyLuaLs:mainfrom
lewis6991:fix/startup-redundant-reloads

Conversation

@lewis6991
Copy link
Copy Markdown
Collaborator

Summary

  • ignore workspace/didChangeConfiguration notifications when the effective client config is unchanged
  • avoid the extra full reindex when startup bootstraps through reload_workspace_files
  • keep regression coverage for both the no-op config notification path and the startup reload path

Testing

  • cargo test -p emmylua_code_analysis reload_workspace_files_
  • cargo test -p emmylua_ls config_reload_is_not_cancelled_by_reindex

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces optimizations to the workspace reloading process by skipping reindexing when no non-standard local files are present and avoiding configuration reloads if the client configuration remains unchanged. It also adds unit tests to verify these behaviors. Feedback focuses on improving the idiomatic nature of the Rust code by avoiding side effects in iterator filters and ensuring tests use temporary directories for better isolation.

Comment on lines +254 to +258
.filter(|file_id| {
let is_non_std = !module_index.is_std(file_id);
had_existing_non_std_local_files |= is_non_std;
is_non_std
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a side-effect inside a filter closure makes the code less readable and harder to reason about. A more idiomatic and clearer approach would be to separate the check for existing files from the filtering logic. For example, you could use peekable() on the iterator to check if it's empty before proceeding to collect the stale URIs. This would make the intent of the code more explicit.

Comment on lines +439 to +477
mod tests {
use super::*;

#[test]
fn reload_workspace_files_skips_reindex_when_bootstrapping_workspace() {
let mut analysis = EmmyLuaAnalysis::new();
let workspace_root = std::env::current_dir().unwrap();
let file_path = workspace_root.join("__reload_workspace_startup_test.lua");
analysis.add_main_workspace(workspace_root);

analysis.reload_workspace_files(
vec![(file_path.clone(), Some("return true\n".to_string()))],
Vec::new(),
);

assert_eq!(analysis.reindex_count, 0);
assert!(
analysis
.get_file_id(&file_path_to_uri(&file_path).unwrap())
.is_some()
);
}

#[test]
fn reload_workspace_files_reindexes_existing_workspace_files() {
let mut analysis = EmmyLuaAnalysis::new();
let workspace_root = std::env::current_dir().unwrap();
let file_path = workspace_root.join("__reload_workspace_existing_test.lua");
analysis.add_main_workspace(workspace_root);
analysis.update_files_by_path(vec![(file_path.clone(), Some("return true\n".to_string()))]);

analysis.reload_workspace_files(
vec![(file_path, Some("return false\n".to_string()))],
Vec::new(),
);

assert_eq!(analysis.reindex_count, 1);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

These tests create files in the current working directory and do not clean them up, which can pollute the development environment. It's best practice for tests to be self-contained. Please use a temporary directory for test files and ensure they are cleaned up. The tempfile crate is great for this and will handle cleanup automatically. You'll need to add tempfile to your [dev-dependencies].

mod tests {
    use super::*;

    #[test]
    fn reload_workspace_files_skips_reindex_when_bootstrapping_workspace() {
        let temp_dir = tempfile::tempdir().unwrap();
        let workspace_root = temp_dir.path().to_path_buf();
        let mut analysis = EmmyLuaAnalysis::new();
        let file_path = workspace_root.join("__reload_workspace_startup_test.lua");
        analysis.add_main_workspace(workspace_root);

        analysis.reload_workspace_files(
            vec![(file_path.clone(), Some("return true\n".to_string()))],
            Vec::new(),
        );

        assert_eq!(analysis.reindex_count, 0);
        assert!(
            analysis
                .get_file_id(&file_path_to_uri(&file_path).unwrap())
                .is_some()
        );
    }

    #[test]
    fn reload_workspace_files_reindexes_existing_workspace_files() {
        let temp_dir = tempfile::tempdir().unwrap();
        let workspace_root = temp_dir.path().to_path_buf();
        let mut analysis = EmmyLuaAnalysis::new();
        let file_path = workspace_root.join("__reload_workspace_existing_test.lua");
        analysis.add_main_workspace(workspace_root);
        analysis.update_files_by_path(vec![(file_path.clone(), Some("return true\n".to_string()))]);

        analysis.reload_workspace_files(
            vec![(file_path, Some("return false\n".to_string()))],
            Vec::new(),
        );

        assert_eq!(analysis.reindex_count, 1);
    }
}

Some clients send `workspace/didChangeConfiguration` during
startup even when the effective configuration has not
changed. The language server would fetch the config again
and always schedule a full workspace reload, which rebuilt
watch registrations and analysis state for no reason.

Compare the newly fetched `ClientConfig` with the current
value and skip the reload when they are identical. Keep a
regression test around the configuration handler so
unchanged notifications do not trigger watched-file
re-registration.
`reload_workspace_files` was introduced to support full
workspace reloads, but startup now also routes through
that helper. Because the helper always called `reindex()`
after `update_files_by_path(...)`, bootstrapping an empty
workspace analyzed the same files twice.

Only run the final reindex when non-stdlib local workspace
files already exist. That preserves reload behavior for
existing workspaces while skipping the extra startup pass.
@lewis6991 lewis6991 force-pushed the fix/startup-redundant-reloads branch from 6d86413 to 08ec37f Compare March 27, 2026 09:49
@CppCXY CppCXY merged commit acfb9f1 into EmmyLuaLs:main Mar 27, 2026
22 checks passed
@lewis6991 lewis6991 deleted the fix/startup-redundant-reloads branch March 27, 2026 11:42
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.

2 participants