diff options
author | Nayeem Rahman <nayeemrmn99@gmail.com> | 2024-03-26 15:52:20 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-26 15:52:20 +0000 |
commit | 3b61104e2a8984255f74827b0f1c23476ccd8209 (patch) | |
tree | 43228946e39342e6c71f87ae720f93e7f8c63901 /cli/lsp/language_server.rs | |
parent | d6452b39460ee46ade1ed9a5c95e469fd23a812c (diff) |
refactor(lsp): unify config file data into ConfigTree (#23032)
Diffstat (limited to 'cli/lsp/language_server.rs')
-rw-r--r-- | cli/lsp/language_server.rs | 763 |
1 files changed, 237 insertions, 526 deletions
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index de5f7e357..fafd9fe4c 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -18,11 +18,9 @@ use deno_lockfile::Lockfile; use deno_npm::NpmSystemInfo; use deno_runtime::deno_fs; use deno_runtime::deno_node::NodeResolver; -use deno_runtime::deno_node::PackageJson; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; use deno_semver::jsr::JsrPackageReqReference; -use import_map::ImportMap; use indexmap::IndexSet; use log::error; use serde::Deserialize; @@ -71,7 +69,6 @@ use super::documents::Document; use super::documents::Documents; use super::documents::DocumentsFilter; use super::documents::LanguageId; -use super::documents::UpdateDocumentConfigOptions; use super::jsr::CliJsrSearchApi; use super::logging::lsp_log; use super::logging::lsp_warn; @@ -92,16 +89,11 @@ use super::tsc::GetCompletionDetailsArgs; use super::tsc::TsServer; use super::urls; use crate::args::get_root_cert_store; -use crate::args::package_json; -use crate::args::resolve_import_map; use crate::args::CaData; use crate::args::CacheSetting; use crate::args::CliOptions; use crate::args::ConfigFile; use crate::args::Flags; -use crate::args::FmtOptions; -use crate::args::LintOptions; -use crate::args::TsConfig; use crate::cache::DenoDir; use crate::cache::FastInsecureHasher; use crate::cache::GlobalHttpCache; @@ -111,6 +103,7 @@ use crate::factory::CliFactory; use crate::file_fetcher::FileFetcher; use crate::graph_util; use crate::http_util::HttpClient; +use crate::lsp::config::ConfigWatchedFileType; use crate::lsp::logging::init_log_file; use crate::lsp::tsc::file_text_changes_to_workspace_edit; use crate::lsp::urls::LspUrlKind; @@ -157,10 +150,15 @@ struct LspNpmConfigHash(u64); impl LspNpmConfigHash { pub fn from_inner(inner: &Inner) -> Self { + let config_data = inner.config.tree.root_data(); + let node_modules_dir = config_data + .as_ref() + .and_then(|d| d.node_modules_dir.as_ref()); + let lockfile = config_data.as_ref().and_then(|d| d.lockfile.as_ref()); let mut hasher = FastInsecureHasher::new(); - hasher.write_hashable(inner.config.maybe_node_modules_dir_path()); + hasher.write_hashable(node_modules_dir); hasher.write_hashable(&inner.maybe_global_cache_path); - if let Some(lockfile) = inner.config.maybe_lockfile() { + if let Some(lockfile) = lockfile { hasher.write_hashable(&*lockfile.lock()); } Self(hasher.finish()) @@ -183,7 +181,6 @@ pub struct StateSnapshot { pub cache_metadata: cache::CacheMetadata, pub config: Arc<ConfigSnapshot>, pub documents: Documents, - pub maybe_import_map: Option<Arc<ImportMap>>, pub npm: Option<StateNpmSnapshot>, } @@ -254,14 +251,6 @@ pub struct Inner { /// An optional path to the DENO_DIR which has been specified in the client /// options. maybe_global_cache_path: Option<PathBuf>, - /// An optional import map which is used to resolve modules. - maybe_import_map: Option<Arc<ImportMap>>, - /// An optional package.json configuration file. - maybe_package_json: Option<PackageJson>, - /// Configuration for formatter which has been taken from specified config file. - fmt_options: FmtOptions, - /// An optional configuration for linter which has been taken from specified config file. - lint_options: LintOptions, /// A lazily create "server" for handling test run requests. maybe_testing_server: Option<testing::TestServer>, /// Services used for dealing with npm related functionality. @@ -344,7 +333,11 @@ impl LanguageServer { // do as much as possible in a read, then do a write outside let maybe_prepare_cache_result = { let inner = self.0.read().await; // ensure dropped - match inner.prepare_cache(specifiers, referrer, force_global_cache) { + match inner.prepare_cache( + specifiers, + referrer.clone(), + force_global_cache, + ) { Ok(maybe_cache_result) => maybe_cache_result, Err(err) => { lsp_warn!("Error preparing caching: {:#}", err); @@ -376,7 +369,7 @@ impl LanguageServer { } { let mut inner = self.0.write().await; - let lockfile = inner.config.maybe_lockfile().cloned(); + let lockfile = inner.config.tree.lockfile_for_specifier(&referrer); inner.documents.refresh_jsr_resolver(lockfile); inner.refresh_npm_specifiers().await; } @@ -530,9 +523,12 @@ impl Inner { let documents = Documents::new(deps_http_cache.clone()); let cache_metadata = cache::CacheMetadata::new(deps_http_cache.clone()); let performance = Arc::new(Performance::default()); - let ts_server = - Arc::new(TsServer::new(performance.clone(), deps_http_cache.clone())); - let config = Config::new(); + let config = Config::default(); + let ts_server = Arc::new(TsServer::new( + performance.clone(), + deps_http_cache.clone(), + config.tree.clone(), + )); let diagnostics_state = Arc::new(DiagnosticsState::default()); let diagnostics_server = DiagnosticsServer::new( client.clone(), @@ -558,11 +554,7 @@ impl Inner { initial_cwd: initial_cwd.clone(), jsr_search_api, maybe_global_cache_path: None, - maybe_import_map: None, - maybe_package_json: None, - fmt_options: FmtOptions::new_with_base(initial_cwd.clone()), task_queue: Default::default(), - lint_options: LintOptions::new_with_base(initial_cwd), maybe_testing_server: None, module_registries, module_registries_location, @@ -645,76 +637,6 @@ impl Inner { Ok(navigation_tree) } - fn get_config_file(&self) -> Result<Option<ConfigFile>, AnyError> { - let workspace_settings = self.config.workspace_settings(); - let maybe_config = &workspace_settings.config; - if let Some(config_str) = maybe_config { - if !config_str.is_empty() { - lsp_log!("Setting Deno configuration from: \"{}\"", config_str); - let config_url = if let Ok(url) = Url::from_file_path(config_str) { - Ok(url) - } else if let Some(root_uri) = self.config.root_uri() { - root_uri.join(config_str).map_err(|_| { - anyhow!("Bad file path for configuration file: \"{}\"", config_str) - }) - } else { - Err(anyhow!( - "The path to the configuration file (\"{}\") is not resolvable.", - config_str - )) - }?; - lsp_log!(" Resolved configuration file: \"{}\"", config_url); - - let config_file = ConfigFile::from_specifier(config_url)?; - return Ok(Some(config_file)); - } - } - - // Auto-discover config - - // It is possible that root_uri is not set, for example when having a single - // file open and not a workspace. In those situations we can't - // automatically discover the configuration - if let Some(root_uri) = self.config.root_uri() { - let root_path = specifier_to_file_path(root_uri)?; - let mut checked = std::collections::HashSet::new(); - let maybe_config = - ConfigFile::discover_from(&root_path, &mut checked, None)?; - Ok(maybe_config.map(|c| { - lsp_log!(" Auto-resolved configuration file: \"{}\"", c.specifier); - c - })) - } else { - Ok(None) - } - } - - fn get_package_json( - &self, - maybe_config_file: Option<&ConfigFile>, - ) -> Result<Option<PackageJson>, AnyError> { - if crate::args::has_flag_env_var("DENO_NO_PACKAGE_JSON") { - return Ok(None); - } - - // It is possible that root_uri is not set, for example when having a single - // file open and not a workspace. In those situations we can't - // automatically discover the configuration - if let Some(root_uri) = self.config.root_uri() { - let root_path = specifier_to_file_path(root_uri)?; - let maybe_package_json = package_json::discover_from( - &root_path, - maybe_config_file.and_then(|f| f.specifier.to_file_path().ok()), - )?; - Ok(maybe_package_json.map(|c| { - lsp_log!(" Auto-resolved package.json: \"{}\"", c.specifier()); - c - })) - } else { - Ok(None) - } - } - fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool { if specifier.scheme() == "asset" { matches!( @@ -740,23 +662,6 @@ impl Inner { } } - fn merge_user_tsconfig( - &self, - tsconfig: &mut TsConfig, - ) -> Result<(), AnyError> { - if let Some(config_file) = self.config.maybe_config_file() { - let (value, maybe_ignored_options) = config_file.to_compiler_options()?; - tsconfig.merge(&value); - if let Some(ignored_options) = maybe_ignored_options { - // TODO(@kitsonk) turn these into diagnostics that can be sent to the - // client - lsp_warn!("{}", ignored_options); - } - } - - Ok(()) - } - pub fn snapshot(&self) -> Arc<StateSnapshot> { let maybe_state_npm_snapshot = self .npm @@ -785,7 +690,6 @@ impl Inner { cache_metadata: self.cache_metadata.clone(), config: self.config.snapshot(), documents: self.documents.clone(), - maybe_import_map: self.maybe_import_map.clone(), npm: maybe_state_npm_snapshot, }) } @@ -876,7 +780,7 @@ impl Inner { self.jsr_search_api = CliJsrSearchApi::new(deps_file_fetcher.clone()); self.npm.search_api = CliNpmSearchApi::new(deps_file_fetcher); let maybe_local_cache = - self.config.maybe_vendor_dir_path().map(|local_path| { + self.config.tree.root_vendor_dir().map(|local_path| { Arc::new(LocalLspHttpCache::new(local_path, global_cache.clone())) }); let cache: Arc<dyn HttpCache> = maybe_local_cache @@ -903,14 +807,16 @@ impl Inner { if config_hash == self.npm.config_hash { return; // no need to do anything } - + let config_data = self.config.tree.root_data(); let npm_resolver = create_npm_resolver( &deno_dir, &self.initial_cwd, &self.http_client, - self.config.maybe_config_file(), - self.config.maybe_lockfile(), - self.config.maybe_node_modules_dir_path().cloned(), + config_data.as_ref().and_then(|d| d.config_file.as_deref()), + config_data.as_ref().and_then(|d| d.lockfile.as_ref()), + config_data + .as_ref() + .and_then(|d| d.node_modules_dir.clone()), ) .await; let node_resolver = Arc::new(NodeResolver::new( @@ -929,41 +835,6 @@ impl Inner { self.npm.config_hash = config_hash; } - pub async fn update_import_map(&mut self) -> Result<(), AnyError> { - let mark = self.performance.mark("lsp.update_import_map"); - - let maybe_import_map_url = self.resolve_import_map_specifier()?; - let maybe_import_map = self - .fetch_import_map( - maybe_import_map_url.as_ref(), - CacheSetting::RespectHeaders, - ) - .await?; - if let Some(import_map) = maybe_import_map { - if import_map.base_url().scheme() != "data" { - lsp_log!(" Resolved import map: \"{}\"", import_map.base_url()); - } - self.maybe_import_map = Some(Arc::new(import_map)); - } else { - self.maybe_import_map = None; - } - self.performance.measure(mark); - Ok(()) - } - - async fn fetch_import_map( - &self, - import_map_url: Option<&ModuleSpecifier>, - cache_setting: CacheSetting, - ) -> Result<Option<ImportMap>, AnyError> { - resolve_import_map( - import_map_url, - self.config.maybe_config_file(), - &self.create_file_fetcher(cache_setting), - ) - .await - } - fn create_file_fetcher(&self, cache_setting: CacheSetting) -> FileFetcher { let mut file_fetcher = FileFetcher::new( self.deps_http_cache.clone(), @@ -979,10 +850,13 @@ impl Inner { fn resolve_import_map_specifier( &self, + referrer: &ModuleSpecifier, ) -> Result<Option<ModuleSpecifier>, AnyError> { let Some(import_map_str) = self .config - .workspace_settings() + .settings + .get_for_specifier(referrer) + .0 .import_map .clone() .and_then(|s| if s.is_empty() { None } else { Some(s) }) @@ -993,7 +867,9 @@ impl Inner { "Using import map from workspace settings: \"{}\"", import_map_str ); - if let Some(config_file) = self.config.maybe_config_file() { + if let Some(config_file) = + self.config.tree.config_file_for_specifier(referrer) + { if let Some(import_map_path) = &config_file.json.import_map { lsp_log!("Warning: Import map \"{}\" configured in \"{}\" being ignored due to an import map being explicitly configured in workspace settings.", import_map_path, config_file.specifier); } @@ -1036,103 +912,6 @@ impl Inner { self.performance.measure(mark); Ok(()) } - - fn update_config_file(&mut self) -> Result<(), AnyError> { - self.config.clear_config_file(); - self.fmt_options = FmtOptions::new_with_base(self.initial_cwd.clone()); - self.lint_options = LintOptions::new_with_base(self.initial_cwd.clone()); - if let Some(config_file) = self.get_config_file()? { - let lint_options = config_file - .to_lint_config() - .and_then(|maybe_lint_config| { - LintOptions::resolve(maybe_lint_config, None, &self.initial_cwd) - }) - .map_err(|err| { - anyhow!("Unable to update lint configuration: {:?}", err) - })?; - let fmt_options = config_file - .to_fmt_config() - .and_then(|maybe_fmt_config| { - FmtOptions::resolve(maybe_fmt_config, None, &self.initial_cwd) - }) - .map_err(|err| { - anyhow!("Unable to update formatter configuration: {:?}", err) - })?; - - self.config.set_config_file(config_file); - self.lint_options = lint_options; - self.fmt_options = fmt_options; - self.recreate_http_client_and_dependents()?; - if let Some(config_file) = self.config.maybe_config_file() { - if let Ok((compiler_options, _)) = config_file.to_compiler_options() { - if let Some(compiler_options_obj) = compiler_options.as_object() { - if let Some(jsx_import_source) = - compiler_options_obj.get("jsxImportSource") - { - if let Some(jsx_import_source) = jsx_import_source.as_str() { - let specifiers = vec![Url::parse(&format!( - "data:application/typescript;base64,{}", - base64::engine::general_purpose::STANDARD.encode(format!( - "import '{jsx_import_source}/jsx-runtime';" - )) - )) - .unwrap()]; - let referrer = config_file.specifier.clone(); - self.task_queue.queue_task(Box::new(|ls: LanguageServer| { - spawn(async move { - if let Err(err) = - ls.cache(specifiers, referrer, false).await - { - lsp_warn!("{:#}", err); - } - }); - })); - } - } - } - } - } - } - - Ok(()) - } - - /// Updates the package.json. Always ensure this is done after updating - /// the configuration file as the resolution of this depends on that. - fn update_package_json(&mut self) -> Result<(), AnyError> { - self.maybe_package_json = None; - self.maybe_package_json = - self.get_package_json(self.config.maybe_config_file())?; - Ok(()) - } - - async fn update_tsconfig(&mut self) -> Result<(), AnyError> { - let mark = self.performance.mark("lsp.update_tsconfig"); - let mut tsconfig = TsConfig::new(json!({ - "allowJs": true, - "esModuleInterop": true, - "experimentalDecorators": false, - "isolatedModules": true, - "jsx": "react", - "lib": ["deno.ns", "deno.window", "deno.unstable"], - "module": "esnext", - "moduleDetection": "force", - "noEmit": true, - "resolveJsonModule": true, - "strict": true, - "target": "esnext", - "useDefineForClassFields": true, - // TODO(@kitsonk) remove for Deno 1.15 - "useUnknownInCatchVariables": false, - })); - if let Err(err) = self.merge_user_tsconfig(&mut tsconfig) { - lsp_warn!("Error merging tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - let _ok = self.ts_server.configure(self.snapshot(), tsconfig).await?; - self.performance.measure(mark); - Ok(()) - } } async fn create_npm_resolver( @@ -1272,23 +1051,12 @@ impl Inner { .start(self.config.internal_inspect().to_address()); self.update_debug_flag(); - // Check to see if we need to change the cache path + self.refresh_workspace_files(); + self.refresh_config_tree().await; if let Err(err) = self.update_cache() { lsp_warn!("Error updating cache: {:#}", err); self.client.show_message(MessageType::WARNING, err); } - if let Err(err) = self.update_config_file() { - lsp_warn!("Error updating config file: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_package_json() { - lsp_warn!("Error updating package.json: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } if capabilities.code_action_provider.is_some() { let fixable_diagnostics = self @@ -1298,11 +1066,6 @@ impl Inner { self.ts_fixable_diagnostics = fixable_diagnostics; } - // Check to see if we need to setup the import map - if let Err(err) = self.update_import_map().await { - lsp_warn!("Error updating import map: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } // Check to see if we need to setup any module registries if let Err(err) = self.update_registries().await { lsp_warn!("Error updating registries: {:#}", err); @@ -1450,15 +1213,58 @@ impl Inner { self.workspace_files_hash = enable_settings_hash; } + async fn refresh_config_tree(&mut self) { + let file_fetcher = self.create_file_fetcher(CacheSetting::RespectHeaders); + if let Some(root_uri) = self.config.root_uri() { + self + .config + .tree + .refresh( + &self.config.settings, + root_uri, + &self.workspace_files, + &file_fetcher, + ) + .await; + for config_file in self.config.tree.config_files() { + if let Ok((compiler_options, _)) = config_file.to_compiler_options() { + if let Some(compiler_options_obj) = compiler_options.as_object() { + if let Some(jsx_import_source) = + compiler_options_obj.get("jsxImportSource") + { + if let Some(jsx_import_source) = jsx_import_source.as_str() { + let specifiers = vec![Url::parse(&format!( + "data:application/typescript;base64,{}", + base64::engine::general_purpose::STANDARD.encode(format!( + "import '{jsx_import_source}/jsx-runtime';" + )) + )) + .unwrap()]; + let referrer = config_file.specifier.clone(); + self.task_queue.queue_task(Box::new(|ls: LanguageServer| { + spawn(async move { + if let Err(err) = + ls.cache(specifiers, referrer, false).await + { + lsp_warn!("{:#}", err); + } + }); + })); + } + } + } + } + } + } + } + async fn refresh_documents_config(&mut self) { - self.documents.update_config(UpdateDocumentConfigOptions { - config: &self.config, - maybe_import_map: self.maybe_import_map.clone(), - maybe_package_json: self.maybe_package_json.as_ref(), - node_resolver: self.npm.node_resolver.clone(), - npm_resolver: self.npm.resolver.clone(), - workspace_files: &self.workspace_files, - }); + self.documents.update_config( + &self.config, + self.npm.node_resolver.clone(), + self.npm.resolver.clone(), + &self.workspace_files, + ); // refresh the npm specifiers because it might have discovered // a @types/node package and now's a good time to do that anyway @@ -1591,6 +1397,8 @@ impl Inner { }; self.update_debug_flag(); + self.refresh_workspace_files(); + self.refresh_config_tree().await; if let Err(err) = self.update_cache() { lsp_warn!("Error updating cache: {:#}", err); self.client.show_message(MessageType::WARNING, err); @@ -1599,27 +1407,8 @@ impl Inner { lsp_warn!("Error updating registries: {:#}", err); self.client.show_message(MessageType::WARNING, err); } - if let Err(err) = self.update_config_file() { - lsp_warn!("Error updating config file: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_package_json() { - lsp_warn!("Error updating package.json: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_import_map().await { - lsp_warn!("Error updating import map: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Err(err) = self.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - self.recreate_npm_services_if_necessary().await; - self.refresh_workspace_files(); self.refresh_documents_config().await; - self.diagnostics_server.invalidate_all(); self.send_diagnostics_update(); self.send_testing_update(); @@ -1629,194 +1418,73 @@ impl Inner { &mut self, params: DidChangeWatchedFilesParams, ) { - 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) - != FastInsecureHasher::hash(new_lockfile) - } - Err(err) => { - lsp_warn!("Error loading lockfile: {:#}", err); - false - } - } - } - - fn has_config_changed(config: &Config, changes: &IndexSet<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_with_args("lsp.did_change_watched_files", ¶ms); - let mut touched = false; - let changes: IndexSet<Url> = params - .changes - .iter() - .map(|f| self.url_map.normalize_url(&f.uri, LspUrlKind::File)) - .collect(); - let mut config_changes = IndexSet::with_capacity(changes.len()); - - // if the current deno.json has changed, we need to reload it - if has_config_changed(&self.config, &changes) { - // Check the 'current' config specifier from both before and after it's - // updated. Check canonicalized and uncanonicalized variants for each. - // If any are included in `changes`, send our custom notification for - // `deno.json` changes: `deno/didChangeDenoConfigurationNotification`. - let mut files_to_check = IndexSet::with_capacity(4); - // Collect previous config specifiers. - if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) { - files_to_check.insert(url.clone()); - } - if let Some(url) = self.config.maybe_config_file_canonicalized_specifier() - { - files_to_check.insert(url.clone()); - } - // Update config. - if let Err(err) = self.update_config_file() { - lsp_warn!("Error updating config file: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - // Collect new config specifiers. - if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) { - files_to_check.insert(url.clone()); - } - if let Some(url) = self.config.maybe_config_file_canonicalized_specifier() - { - files_to_check.insert(url.clone()); - } - if let Some(root_uri) = self.config.root_uri() { - config_changes.extend( - params - .changes - .iter() - .filter(|e| files_to_check.contains(&e.uri)) - .map(|e| lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), - file_uri: e.uri.clone(), - typ: - lsp_custom::DenoConfigurationChangeType::from_file_change_type( - e.typ, - ), - configuration_type: lsp_custom::DenoConfigurationType::DenoJson, - }), - ); - } - if let Err(err) = self.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - touched = true; - } - - let has_package_json_changed = changes + let changes = params + .changes + .into_iter() + .map(|e| (self.url_map.normalize_url(&e.uri, LspUrlKind::File), e)) + .collect::<Vec<_>>(); + if changes .iter() - .any(|e| e.as_str().ends_with("/package.json")); - - if has_package_json_changed { - let mut files_to_check = IndexSet::with_capacity(2); - if let Some(package_json) = &self.maybe_package_json { - files_to_check.insert(package_json.specifier()); - } - if let Err(err) = self.update_package_json() { - lsp_warn!("Error updating package.json: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - if let Some(package_json) = &self.maybe_package_json { - files_to_check.insert(package_json.specifier()); - } - if let Some(root_uri) = self.config.root_uri() { - config_changes.extend( - params - .changes - .iter() - .filter(|e| files_to_check.contains(&e.uri)) - .map(|e| lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), - file_uri: e.uri.clone(), - typ: - lsp_custom::DenoConfigurationChangeType::from_file_change_type( - e.typ, - ), - configuration_type: - lsp_custom::DenoConfigurationType::PackageJson, - }), + .any(|(s, _)| self.config.tree.is_watched_file(s)) + { + let mut deno_config_changes = IndexSet::with_capacity(changes.len()); + deno_config_changes.extend(changes.iter().filter_map(|(s, e)| { + self.config.tree.watched_file_type(s).and_then(|t| { + let configuration_type = match t.1 { + ConfigWatchedFileType::DenoJson => { + lsp_custom::DenoConfigurationType::DenoJson + } + ConfigWatchedFileType::PackageJson => { + lsp_custom::DenoConfigurationType::PackageJson + } + _ => return None, + }; + Some(lsp_custom::DenoConfigurationChangeEvent { + scope_uri: t.0, + file_uri: e.uri.clone(), + typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type( + e.typ, + ), + configuration_type, + }) + }) + })); + self.workspace_files_hash = 0; + self.refresh_workspace_files(); + self.refresh_config_tree().await; + deno_config_changes.extend(changes.iter().filter_map(|(s, e)| { + self.config.tree.watched_file_type(s).and_then(|t| { + let configuration_type = match t.1 { + ConfigWatchedFileType::DenoJson => { + lsp_custom::DenoConfigurationType::DenoJson + } + ConfigWatchedFileType::PackageJson => { + lsp_custom::DenoConfigurationType::PackageJson + } + _ => return None, + }; + Some(lsp_custom::DenoConfigurationChangeEvent { + scope_uri: t.0, + file_uri: e.uri.clone(), + typ: lsp_custom::DenoConfigurationChangeType::from_file_change_type( + e.typ, + ), + configuration_type, + }) + }) + })); + if !deno_config_changes.is_empty() { + self.client.send_did_change_deno_configuration_notification( + lsp_custom::DidChangeDenoConfigurationNotificationParams { + changes: deno_config_changes.into_iter().collect(), + }, ); } - touched = true; - } - - if !config_changes.is_empty() { - self.client.send_did_change_deno_configuration_notification( - lsp_custom::DidChangeDenoConfigurationNotificationParams { - changes: config_changes.into_iter().collect(), - }, - ); - } - - // if the current import map, or config file has changed, we need to - // reload the import map - let import_map_changed = self - .maybe_import_map - .as_ref() - .map(|import_map| changes.contains(import_map.base_url())) - .unwrap_or(false); - if touched || import_map_changed { - if let Err(err) = self.update_import_map().await { - lsp_warn!("Error updating import map: {:#}", err); - self.client.show_message(MessageType::WARNING, err); - } - touched = true; - } - - if touched { self.recreate_npm_services_if_necessary().await; - self.refresh_workspace_files(); self.refresh_documents_config().await; self.diagnostics_server.invalidate_all(); self.ts_server.restart(self.snapshot()).await; @@ -1902,7 +1570,13 @@ impl Inner { .url_map .normalize_url(¶ms.text_document.uri, LspUrlKind::File); // skip formatting any files ignored by the config file - if !self.fmt_options.files.matches_specifier(&specifier) { + if !self + .config + .tree + .fmt_options_for_specifier(&specifier) + .files + .matches_specifier(&specifier) + { return Ok(None); } let document = match self.documents.get(&specifier) { @@ -1925,7 +1599,12 @@ impl Inner { // spawn a blocking task to allow doing other work while this is occurring let text_edits = deno_core::unsync::spawn_blocking({ - let fmt_options = self.fmt_options.options.clone(); + let fmt_options = self + .config + .tree + .fmt_options_for_specifier(&specifier) + .options + .clone(); let document = document.clone(); move || { let format_result = match document.maybe_parsed_source() { @@ -2152,10 +1831,14 @@ impl Inner { line_index.offset_tsc(diagnostic.range.start)? ..line_index.offset_tsc(diagnostic.range.end)?, codes, - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&specifier) + .options) + .into(), tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, ), ) @@ -2246,7 +1929,6 @@ impl Inner { ..line_index.offset_tsc(params.range.end)?, Some(tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, )), only, @@ -2305,10 +1987,14 @@ impl Inner { .get_combined_code_fix( self.snapshot(), &code_action_data, - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&code_action_data.specifier) + .options) + .into(), tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &code_action_data.specifier, ), ) @@ -2322,7 +2008,7 @@ impl Inner { fix_ts_import_changes( &code_action_data.specifier, &combined_code_actions.changes, - &self.get_ts_response_import_mapper(), + &self.get_ts_response_import_mapper(&code_action_data.specifier), ) .map_err(|err| { error!("Unable to remap changes: {:#}", err); @@ -2351,14 +2037,18 @@ impl Inner { .get_edits_for_refactor( self.snapshot(), action_data.specifier.clone(), - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&action_data.specifier) + .options) + .into(), line_index.offset_tsc(action_data.range.start)? ..line_index.offset_tsc(action_data.range.end)?, action_data.refactor_name, action_data.action_name, Some(tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &action_data.specifier, )), ) @@ -2374,10 +2064,13 @@ impl Inner { Ok(result) } - pub fn get_ts_response_import_mapper(&self) -> TsResponseImportMapper { + pub fn get_ts_response_import_mapper( + &self, + referrer: &ModuleSpecifier, + ) -> TsResponseImportMapper { TsResponseImportMapper::new( &self.documents, - self.maybe_import_map.as_deref(), + self.config.tree.import_map_for_specifier(referrer), self.npm.node_resolver.as_deref(), self.npm.resolver.as_deref(), ) @@ -2690,7 +2383,7 @@ impl Inner { &self.jsr_search_api, &self.npm.search_api, &self.documents, - self.maybe_import_map.clone(), + self.config.tree.import_map_for_specifier(&specifier), ) .await; } @@ -2716,13 +2409,17 @@ impl Inner { tsc::GetCompletionsAtPositionOptions { user_preferences: tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, ), trigger_character, trigger_kind, }, - (&self.fmt_options.options).into(), + (&self + .config + .tree + .fmt_options_for_specifier(&specifier) + .options) + .into(), ) .await; @@ -2769,11 +2466,17 @@ impl Inner { .get_completion_details( self.snapshot(), GetCompletionDetailsArgs { - format_code_settings: Some((&self.fmt_options.options).into()), + format_code_settings: Some( + (&self + .config + .tree + .fmt_options_for_specifier(specifier) + .options) + .into(), + ), preferences: Some( tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, specifier, ), ), @@ -3289,6 +2992,12 @@ impl Inner { if options.enabled == UpdateImportsOnFileMoveEnabled::Never { continue; } + let format_code_settings = (&self + .config + .tree + .fmt_options_for_specifier(&old_specifier) + .options) + .into(); changes.extend( self .ts_server @@ -3299,7 +3008,7 @@ impl Inner { &resolve_url(&rename.new_uri).unwrap(), LspUrlKind::File, ), - (&self.fmt_options.options).into(), + format_code_settings, tsc::UserPreferences { allow_text_changes_in_new_files: Some(true), ..Default::default() @@ -3350,7 +3059,6 @@ impl Inner { let snapshot = DiagnosticServerUpdateMessage { snapshot: self.snapshot(), config: self.config.snapshot(), - lint_options: self.lint_options.clone(), url_map: self.url_map.clone(), }; if let Err(err) = self.diagnostics_server.update(snapshot) { @@ -3459,31 +3167,31 @@ impl tower_lsp::LanguageServer for LanguageServer { ls.maybe_testing_server = Some(test_server); } - if let Some(root_uri) = ls.config.root_uri() { - let mut config_events = vec![]; - if let Some(config_file) = ls.config.maybe_config_file() { + let mut config_events = vec![]; + for (scope_uri, config_data) in ls.config.tree.data_by_scope() { + if let Some(config_file) = &config_data.config_file { config_events.push(lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), + scope_uri: scope_uri.clone(), file_uri: config_file.specifier.clone(), typ: lsp_custom::DenoConfigurationChangeType::Added, configuration_type: lsp_custom::DenoConfigurationType::DenoJson, }); } - if let Some(package_json) = &ls.maybe_package_json { + if let Some(package_json) = &config_data.package_json { config_events.push(lsp_custom::DenoConfigurationChangeEvent { - scope_uri: root_uri.clone(), + scope_uri: scope_uri.clone(), file_uri: package_json.specifier(), typ: lsp_custom::DenoConfigurationChangeType::Added, configuration_type: lsp_custom::DenoConfigurationType::PackageJson, }); } - if !config_events.is_empty() { - ls.client.send_did_change_deno_configuration_notification( - lsp_custom::DidChangeDenoConfigurationNotificationParams { - changes: config_events, - }, - ); - } + } + if !config_events.is_empty() { + ls.client.send_did_change_deno_configuration_notification( + lsp_custom::DidChangeDenoConfigurationNotificationParams { + changes: config_events, + }, + ); } (ls.client.clone(), ls.http_client.clone()) @@ -3504,11 +3212,6 @@ impl tower_lsp::LanguageServer for LanguageServer { { let mut ls = self.0.write().await; init_log_file(ls.config.log_file()); - if let Err(err) = ls.update_tsconfig().await { - lsp_warn!("Error updating tsconfig: {:#}", err); - ls.client.show_message(MessageType::WARNING, err); - } - ls.refresh_workspace_files(); ls.refresh_documents_config().await; ls.diagnostics_server.invalidate_all(); ls.send_diagnostics_update(); @@ -3643,6 +3346,7 @@ impl tower_lsp::LanguageServer for LanguageServer { { let mut ls = self.0.write().await; ls.refresh_workspace_files(); + ls.refresh_config_tree().await; ls.refresh_documents_config().await; ls.diagnostics_server.invalidate_all(); ls.send_diagnostics_update(); @@ -3845,10 +3549,11 @@ impl Inner { let mark = self .performance .mark_with_args("lsp.cache", (&specifiers, &referrer)); + let config_data = self.config.tree.data_for_specifier(&referrer); let roots = if !specifiers.is_empty() { specifiers } else { - vec![referrer] + vec![referrer.clone()] }; let workspace_settings = self.config.workspace_settings(); let mut cli_options = CliOptions::new( @@ -3860,21 +3565,28 @@ impl Inner { .unsafely_ignore_certificate_errors .clone(), node_modules_dir: Some( - self.config.maybe_node_modules_dir_path().is_some(), + config_data + .as_ref() + .and_then(|d| d.node_modules_dir.as_ref()) + .is_some(), ), // bit of a hack to force the lsp to cache the @types/node package type_check_mode: crate::args::TypeCheckMode::Local, ..Default::default() }, self.initial_cwd.clone(), - self.config.maybe_config_file().cloned(), - self.config.maybe_lockfile().cloned(), - self.maybe_package_json.clone(), + config_data + .as_ref() + .and_then(|d| d.config_file.as_deref().cloned()), + config_data.as_ref().and_then(|d| d.lockfile.clone()), + config_data + .as_ref() + .and_then(|d| d.package_json.as_deref().cloned()), force_global_cache, )?; // don't use the specifier in self.maybe_import_map because it's not // necessarily an import map specifier (could be a deno.json) - if let Some(import_map) = self.resolve_import_map_specifier()? { + if let Some(import_map) = self.resolve_import_map_specifier(&referrer)? { cli_options.set_import_map_specifier(Some(import_map)); } @@ -3907,7 +3619,7 @@ impl Inner { fn task_definitions(&self) -> LspResult<Vec<TaskDefinition>> { let mut result = vec![]; - if let Some(config_file) = self.config.maybe_config_file() { + for config_file in self.config.tree.config_files() { if let Some(tasks) = json!(&config_file.json.tasks).as_object() { for (name, value) in tasks { let Some(command) = value.as_str() else { @@ -3921,7 +3633,7 @@ impl Inner { } }; } - if let Some(package_json) = &self.maybe_package_json { + for package_json in self.config.tree.package_jsons() { if let Some(scripts) = &package_json.scripts { for (name, command) in scripts { result.push(TaskDefinition { @@ -3968,7 +3680,6 @@ impl Inner { text_span, tsc::UserPreferences::from_config_for_specifier( &self.config, - &self.fmt_options.options, &specifier, ), ) |