diff options
| author | David Sherret <dsherret@users.noreply.github.com> | 2023-07-21 09:12:26 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-21 09:12:26 -0400 |
| commit | d6e086d681fd0564bbb8e62744aca0514e3bc575 (patch) | |
| tree | 5bc4e688c0250283f696c9d0ab68d524ba5a9466 /cli/lsp | |
| parent | da709729e3dd6f310182581ca1c6380ad51443fc (diff) | |
fix(lsp): handle watched files events from symlinked config files (#19898)
Related to https://github.com/denoland/vscode_deno/issues/784
Diffstat (limited to 'cli/lsp')
| -rw-r--r-- | cli/lsp/config.rs | 76 | ||||
| -rw-r--r-- | cli/lsp/language_server.rs | 79 |
2 files changed, 113 insertions, 42 deletions
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 03464d1f8..2438e8a85 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -425,12 +425,19 @@ pub struct Settings { pub workspace: WorkspaceSettings, } +#[derive(Debug)] +struct WithCanonicalizedSpecifier<T> { + /// Stored canonicalized specifier, which is used for file watcher events. + canonicalized_specifier: ModuleSpecifier, + file: T, +} + /// Contains the config file and dependent information. #[derive(Debug)] struct LspConfigFileInfo { - config_file: ConfigFile, + config_file: WithCanonicalizedSpecifier<ConfigFile>, /// An optional deno.lock file, which is resolved relative to the config file. - maybe_lockfile: Option<Arc<Mutex<Lockfile>>>, + maybe_lockfile: Option<WithCanonicalizedSpecifier<Arc<Mutex<Lockfile>>>>, /// The canonicalized node_modules directory, which is found relative to the config file. maybe_node_modules_dir: Option<PathBuf>, excluded_paths: Vec<Url>, @@ -469,14 +476,42 @@ impl Config { } pub fn maybe_config_file(&self) -> Option<&ConfigFile> { - self.maybe_config_file_info.as_ref().map(|c| &c.config_file) + self + .maybe_config_file_info + .as_ref() + .map(|c| &c.config_file.file) + } + + /// Canonicalized specifier of the config file, which should only be used for + /// file watcher events. Otherwise, prefer using the non-canonicalized path + /// as the rest of the CLI does for config files. + pub fn maybe_config_file_canonicalized_specifier( + &self, + ) -> Option<&ModuleSpecifier> { + self + .maybe_config_file_info + .as_ref() + .map(|c| &c.config_file.canonicalized_specifier) } pub fn maybe_lockfile(&self) -> Option<&Arc<Mutex<Lockfile>>> { self .maybe_config_file_info .as_ref() - .and_then(|c| c.maybe_lockfile.as_ref()) + .and_then(|c| c.maybe_lockfile.as_ref().map(|l| &l.file)) + } + + /// Canonicalized specifier of the lockfile, which should only be used for + /// file watcher events. Otherwise, prefer using the non-canonicalized path + /// as the rest of the CLI does for config files. + pub fn maybe_lockfile_canonicalized_specifier( + &self, + ) -> Option<&ModuleSpecifier> { + self.maybe_config_file_info.as_ref().and_then(|c| { + c.maybe_lockfile + .as_ref() + .map(|l| &l.canonicalized_specifier) + }) } pub fn clear_config_file(&mut self) { @@ -485,7 +520,17 @@ impl Config { pub fn set_config_file(&mut self, config_file: ConfigFile) { self.maybe_config_file_info = Some(LspConfigFileInfo { - maybe_lockfile: resolve_lockfile_from_config(&config_file), + maybe_lockfile: resolve_lockfile_from_config(&config_file).map( + |lockfile| { + let path = canonicalize_path_maybe_not_exists(&lockfile.filename) + .unwrap_or_else(|_| lockfile.filename.clone()); + WithCanonicalizedSpecifier { + canonicalized_specifier: ModuleSpecifier::from_file_path(path) + .unwrap(), + file: Arc::new(Mutex::new(lockfile)), + } + }, + ), maybe_node_modules_dir: resolve_node_modules_dir(&config_file), excluded_paths: config_file .to_files_config() @@ -496,7 +541,16 @@ impl Config { .into_iter() .filter_map(|path| ModuleSpecifier::from_file_path(path).ok()) .collect(), - config_file, + config_file: WithCanonicalizedSpecifier { + canonicalized_specifier: config_file + .specifier + .to_file_path() + .ok() + .and_then(|p| canonicalize_path_maybe_not_exists(&p).ok()) + .and_then(|p| ModuleSpecifier::from_file_path(p).ok()) + .unwrap_or_else(|| config_file.specifier.clone()), + file: config_file, + }, }); } @@ -739,9 +793,7 @@ fn specifier_enabled( .unwrap_or_else(|| settings.workspace.enable) } -fn resolve_lockfile_from_config( - config_file: &ConfigFile, -) -> Option<Arc<Mutex<Lockfile>>> { +fn resolve_lockfile_from_config(config_file: &ConfigFile) -> Option<Lockfile> { let lockfile_path = match config_file.resolve_lockfile_path() { Ok(Some(value)) => value, Ok(None) => return None, @@ -769,15 +821,13 @@ fn resolve_node_modules_dir(config_file: &ConfigFile) -> Option<PathBuf> { canonicalize_path_maybe_not_exists(&node_modules_dir).ok() } -fn resolve_lockfile_from_path( - lockfile_path: PathBuf, -) -> Option<Arc<Mutex<Lockfile>>> { +fn resolve_lockfile_from_path(lockfile_path: PathBuf) -> Option<Lockfile> { match Lockfile::new(lockfile_path, false) { Ok(value) => { if let Ok(specifier) = ModuleSpecifier::from_file_path(&value.filename) { lsp_log!(" Resolved lock file: \"{}\"", specifier); } - Some(Arc::new(Mutex::new(value))) + Some(value) } Err(err) => { lsp_warn!("Error loading lockfile: {:#}", err); diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 5a4c3aeae..3ac9610b3 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -1458,18 +1458,8 @@ impl Inner { &mut self, params: DidChangeWatchedFilesParams, ) { - fn has_lockfile_changed( - lockfile: &Lockfile, - changed_urls: &HashSet<Url>, - ) -> bool { - let lockfile_path = lockfile.filename.clone(); - let Ok(specifier) = ModuleSpecifier::from_file_path(&lockfile_path) else { - return false; - }; - if !changed_urls.contains(&specifier) { - return false; - } - match Lockfile::new(lockfile_path, false) { + fn has_lockfile_content_changed(lockfile: &Lockfile) -> bool { + match Lockfile::new(lockfile.filename.clone(), false) { Ok(new_lockfile) => { // only update if the lockfile has changed FastInsecureHasher::hash(lockfile) @@ -1482,6 +1472,53 @@ impl Inner { } } + fn has_config_changed(config: &Config, changes: &HashSet<Url>) -> bool { + // Check the canonicalized specifier here because file watcher + // changes will be for the canonicalized path in vscode, but also check the + // non-canonicalized specifier in order to please the tests and handle + // a client that might send that instead. + if config + .maybe_config_file_canonicalized_specifier() + .map(|s| changes.contains(s)) + .unwrap_or(false) + { + return true; + } + match config.maybe_config_file() { + Some(file) => { + if changes.contains(&file.specifier) { + return true; + } + } + None => { + // check for auto-discovery + if changes.iter().any(|url| { + url.path().ends_with("/deno.json") + || url.path().ends_with("/deno.jsonc") + }) { + return true; + } + } + } + + // if the lockfile has changed, reload the config as well + if let Some(lockfile) = config.maybe_lockfile() { + let lockfile_matches = config + .maybe_lockfile_canonicalized_specifier() + .map(|s| changes.contains(s)) + .or_else(|| { + ModuleSpecifier::from_file_path(&lockfile.lock().filename) + .ok() + .map(|s| changes.contains(&s)) + }) + .unwrap_or(false); + lockfile_matches && has_lockfile_content_changed(&lockfile.lock()) + } else { + // check for auto-discovery + changes.iter().any(|url| url.path().ends_with("/deno.lock")) + } + } + let mark = self .performance .mark("did_change_watched_files", Some(¶ms)); @@ -1493,23 +1530,7 @@ impl Inner { .collect(); // if the current deno.json has changed, we need to reload it - let has_config_changed = match self.config.maybe_config_file() { - Some(config_file) => changes.contains(&config_file.specifier), - None => { - // check for auto-discovery - changes.iter().any(|url| { - url.path().ends_with("/deno.json") - || url.path().ends_with("/deno.jsonc") - }) - } - } || match self.config.maybe_lockfile() { - Some(lockfile) => has_lockfile_changed(&lockfile.lock(), &changes), - None => { - // check for auto-discovery - changes.iter().any(|url| url.path().ends_with("/deno.lock")) - } - }; - if has_config_changed { + if has_config_changed(&self.config, &changes) { if let Err(err) = self.update_config_file().await { self.client.show_message(MessageType::WARNING, err); } |
