diff --git a/server/src/cli_backend.rs b/server/src/cli_backend.rs index b65db740..7f5165fd 100644 --- a/server/src/cli_backend.rs +++ b/server/src/cli_backend.rs @@ -1,6 +1,6 @@ use lsp_server::Message; use lsp_types::notification::{LogMessage, Notification, PublishDiagnostics}; -use lsp_types::{LogMessageParams, PublishDiagnosticsParams}; +use lsp_types::{LogMessageParams, PublishDiagnosticsParams, Uri}; use tracing::{error, info}; use crate::core::config::ConfigEntry; @@ -10,6 +10,7 @@ use crate::args::Cli; use std::io::Write; use std::path::PathBuf; use std::fs::{self, File}; +use std::str::FromStr; use serde_json::json; use crate::core::{config::{DiagMissingImportsMode}, odoo::SyncOdoo}; use crate::S; @@ -43,13 +44,17 @@ impl CliBackend { for (id, tracked_folder) in workspace_folders.into_iter().enumerate() { let tf = fs::canonicalize(tracked_folder.clone()); - if let Ok(tf) = tf { - let tf = tf.sanitize(); - session.sync_odoo.get_file_mgr().borrow_mut().add_workspace_folder(format!("{}", id), tf); - } else { - error!("Unable to resolve tracked folder: {}", tracked_folder); - } - + let uri = match tf + .map(|p| p.sanitize()) + .and_then(|tf| Uri::from_str(&tf).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))) + { + Ok(uri) => uri, + Err(e) => { + error!("Unable to resolve tracked folder: {}, error: {}", tracked_folder, e); + continue; + } + }; + session.sync_odoo.get_file_mgr().borrow_mut().add_workspace_folder(format!("{}", id), uri); } let mut config = ConfigEntry::new(); diff --git a/server/src/core/config.rs b/server/src/core/config.rs index 59726193..de052217 100644 --- a/server/src/core/config.rs +++ b/server/src/core/config.rs @@ -15,6 +15,8 @@ use tracing::error; use crate::constants::{CONFIG_WIKI_URL}; use crate::core::diagnostics::{DiagnosticCode, DiagnosticSetting, SchemaDiagnosticCodes}; +use crate::core::file_mgr::FileMgr; +use crate::threads::SessionInfo; use crate::utils::{fill_validate_path, get_python_command, has_template, is_addon_path, is_odoo_path, is_python_path, PathSanitizer}; use crate::S; @@ -518,12 +520,16 @@ fn parse_manifest_version(contents: String) -> Option { None } -fn process_version(var: Sourced, ws_folders: &HashMap, workspace_name: Option<&String>) -> Sourced { +fn process_version( + var: Sourced, + unique_ws_folders: &HashMap, + current_ws: Option<&(String, String)> +) -> Sourced { let Some(config_path) = var.sources.iter().next().map(PathBuf::from) else { unreachable!("Expected at least one source for sourced_path: {:?}", var); }; let config_dir = config_path.parent().map(PathBuf::from).unwrap_or_else(|| PathBuf::from(".")); - match fill_validate_path(ws_folders, workspace_name, var.value(), |p| PathBuf::from(p).exists(), HashMap::new(), &config_dir) { + match fill_validate_path(unique_ws_folders, current_ws, var.value(), |p| PathBuf::from(p).exists(), HashMap::new(), &config_dir) { Ok(filled_path) => { let var_pb = PathBuf::from(&filled_path); if var_pb.is_file() { @@ -770,7 +776,13 @@ pub fn default_profile_name() -> String { "default".to_string() } -fn fill_or_canonicalize(sourced_path: &Sourced, ws_folders: &HashMap, workspace_name: Option<&String>, predicate: &F, var_map: HashMap) -> Result, String> +fn fill_or_canonicalize( + sourced_path: &Sourced, + unique_ws_folders: &HashMap, + current_ws: Option<&(String, String)>, + predicate: &F, + var_map: HashMap +) -> Result, String> where F: Fn(&String) -> bool, { @@ -779,7 +791,7 @@ F: Fn(&String) -> bool, }; let config_dir = config_path.parent().map(PathBuf::from).unwrap_or_else(|| PathBuf::from(".")); if has_template(&sourced_path.value) { - return fill_validate_path(ws_folders, workspace_name, &sourced_path.value, predicate, var_map, &config_dir) + return fill_validate_path(unique_ws_folders, current_ws, &sourced_path.value, predicate, var_map, &config_dir) .and_then(|p| std::fs::canonicalize(PathBuf::from(p)).map_err(|e| e.to_string())) .map(|p| p.sanitize()) .map(|path| Sourced { value: path, sources: sourced_path.sources.clone(), ..Default::default()}); @@ -797,10 +809,12 @@ F: Fn(&String) -> bool, Ok(Sourced { value: path, sources: sourced_path.sources.clone(), ..Default::default() }) } +/// Process patterns and canonicalize paths in the configuration entry +/// unique_ws_folders: mapping of **unique** workspace folder names to their paths fn process_paths( entry: &mut ConfigEntryRaw, - ws_folders: &HashMap, - workspace_name: Option<&String>, + unique_ws_folders: &HashMap, + current_ws: Option<&(String, String)>, ){ let mut var_map: HashMap = HashMap::new(); if let Some(v) = entry.version.clone() { @@ -810,7 +824,7 @@ fn process_paths( var_map.insert(S!("base"), b.value().clone()); } entry.odoo_path = entry.odoo_path.as_ref() - .and_then(|p| fill_or_canonicalize(p, ws_folders, workspace_name, &is_odoo_path, var_map.clone()) + .and_then(|p| fill_or_canonicalize(p, unique_ws_folders, current_ws, &is_odoo_path, var_map.clone()) .map_err(|err| error!("Failed to process odoo path for variable {:?}: {}", p, err)) .ok() ); @@ -822,13 +836,13 @@ fn process_paths( }); entry.addons_paths = entry.addons_paths.as_ref().map(|paths| paths.iter().filter_map(|sourced| { - fill_or_canonicalize(sourced, ws_folders, workspace_name, &is_addon_path, var_map.clone()) + fill_or_canonicalize(sourced, unique_ws_folders, current_ws, &is_addon_path, var_map.clone()) .map_err(|err| error!("Failed to process addons path for variable {:?}: {}", sourced, err)) .ok() }).collect() ); if infer { - if let Some((name, workspace_path)) = workspace_name.and_then(|name| ws_folders.get(name).map(|p| (name, p))) { + if let Some((name, workspace_path)) = current_ws { let workspace_path = PathBuf::from(workspace_path).sanitize(); if is_addon_path(&workspace_path) { let addon_path = Sourced { value: workspace_path.clone(), sources: HashSet::from([S!(format!("$workspaceFolder:{name}"))]), ..Default::default()}; @@ -844,7 +858,7 @@ fn process_paths( if is_python_path(&p.value) { Some(p.clone()) } else { - fill_or_canonicalize(p, ws_folders, workspace_name, &is_python_path, var_map.clone()) + fill_or_canonicalize(p, unique_ws_folders, current_ws, &is_python_path, var_map.clone()) .map_err(|err| error!("Failed to fill or canonicalize python path for variable {:?}: {}", p, err)) .ok() } @@ -868,7 +882,7 @@ fn process_paths( let config_dir = config_path.parent().map(PathBuf::from).unwrap_or_else(|| PathBuf::from(".")); filter.value.paths = filter.value.paths.iter().filter_map(|pattern| { let pattern_string = pattern.to_string(); - let processed_pattern = fill_validate_path(ws_folders, workspace_name, &pattern_string, &|_: &String| true, var_map.clone(), &config_dir) + let processed_pattern = fill_validate_path(unique_ws_folders, current_ws, &pattern_string, &|_: &String| true, var_map.clone(), &config_dir) .and_then(|p| Pattern::new(&p) .map_err(|e| e.to_string())); match processed_pattern { @@ -966,7 +980,10 @@ fn merge_sourced_diagnostic_setting_map( } -fn apply_merge(child: &ConfigEntryRaw, parent: &ConfigEntryRaw) -> ConfigEntryRaw { +fn apply_merge( + child: &ConfigEntryRaw, + parent: &ConfigEntryRaw, +) -> ConfigEntryRaw { let odoo_path = child.odoo_path.clone().or(parent.odoo_path.clone()); let python_path = child.python_path.clone().or(parent.python_path.clone()); // Simple combination of paths, sources will be merged after paths are processed @@ -1027,7 +1044,9 @@ fn apply_merge(child: &ConfigEntryRaw, parent: &ConfigEntryRaw) -> ConfigEntryRa } } -fn apply_extends(config: &mut HashMap) -> Result<(), String> { +fn apply_extends( + config: &mut HashMap +) -> Result<(), String> { /* each profile has a parent, Option each profile can have multiple children, Vec @@ -1117,24 +1136,27 @@ fn merge_configs( merged } -fn load_config_from_file(path: String, ws_folders: &HashMap,) -> Result, String> { +fn load_config_from_file( + session: &mut SessionInfo, + path: String, +) -> Result, String> { let path = PathBuf::from(path); if !path.exists() || !path.is_file() { return Err(S!(format!("Config file not found: {}", path.display()))); } process_config( read_config_from_file(path)?, - ws_folders, + &session.sync_odoo.get_file_mgr().borrow().get_unique_workspace_folders(), None, ) } fn load_config_from_workspace( - ws_folders: &HashMap, - workspace_name: &String, - workspace_path: &String, + unique_ws_folders: &HashMap, + current_ws: &(String, String), ) -> Result, String> { - let mut current_dir = PathBuf::from(workspace_path); + let ws_path_pb = PathBuf::from(¤t_ws.1); + let mut current_dir = ws_path_pb.clone(); let mut visited_dirs = HashSet::new(); let mut merged_config: HashMap = HashMap::new(); merged_config.insert("default".to_string(), ConfigEntryRaw::new()); @@ -1171,7 +1193,6 @@ fn load_config_from_workspace( Err(e) => return Err(S!(format!("Failed to canonicalize base path: {} ({})", base_prefix_pb.display(), e))), }; let base_prefix_pb = PathBuf::from(abs_base.sanitize()); - let ws_path_pb: PathBuf = PathBuf::from(workspace_path); let base_prefix_components: Vec<_> = base_prefix_pb.components().collect(); let ws_path_components: Vec<_> = ws_path_pb.components().collect(); if ws_path_components.len() > base_prefix_components.len() @@ -1203,8 +1224,8 @@ fn load_config_from_workspace( }; let Ok(parent_dir) = fill_or_canonicalize( &{Sourced { value: parent_dir.sanitize(), sources: version_var.sources.clone(), ..Default::default() }}, - ws_folders, - Some(workspace_name), + unique_ws_folders, + Some(current_ws), &|p| PathBuf::from(p).is_dir(), HashMap::new(), ) else { @@ -1231,15 +1252,15 @@ fn load_config_from_workspace( for new_entry in new_configs { merged_config.insert(new_entry.name.clone(), new_entry); } - let merged_config = process_config(merged_config, ws_folders, Some(workspace_name))?; + let merged_config = process_config(merged_config, unique_ws_folders, Some(current_ws))?; Ok(merged_config) } fn process_config( mut config_map: HashMap, - ws_folders: &HashMap, - workspace_name: Option<&String>, + unique_ws_folders: &HashMap, + current_ws: Option<&(String, String)>, ) -> Result, String> { apply_extends(&mut config_map)?; // Process vars @@ -1247,13 +1268,13 @@ fn process_config( .for_each(|entry| { // apply process_var to all vars if entry.abstract_ { return; } - entry.version = entry.version.clone().map(|v| process_version(v, ws_folders, workspace_name)); + entry.version = entry.version.clone().map(|v| process_version(v, unique_ws_folders, current_ws)); }); // Process paths in the merged config config_map.values_mut() .for_each(|entry| { if entry.abstract_ { return; } - process_paths(entry, ws_folders, workspace_name); + process_paths(entry, unique_ws_folders, current_ws); }); // Merge sourced paths config_map.values_mut() @@ -1267,8 +1288,8 @@ fn process_config( } fn merge_all_workspaces( + unique_ws_folders: &HashMap, workspace_configs: Vec>, - ws_folders: &HashMap ) -> Result<(ConfigNew, ConfigFile), String> { let mut merged_raw_config: HashMap = HashMap::new(); @@ -1357,7 +1378,7 @@ fn merge_all_workspaces( // Only infer odoo_path from workspace folders at this stage, to give priority to the user-defined one for (_, entry) in merged_raw_config.iter_mut() { if entry.odoo_path.is_none() { - for (name, path) in ws_folders.iter() { + for (name, path) in unique_ws_folders.iter() { if is_odoo_path(path) { if entry.odoo_path.is_some() { return Err( @@ -1402,22 +1423,24 @@ fn merge_all_workspaces( Ok((final_config, config_file)) } -pub fn get_configuration(ws_folders: &HashMap, cli_config_file: &Option) -> Result<(ConfigNew, ConfigFile), String> { +pub fn get_configuration(session: &mut SessionInfo) -> Result<(ConfigNew, ConfigFile), String> { let mut ws_confs: Vec> = Vec::new(); - if let Some(path) = cli_config_file { - let config_from_file = load_config_from_file(path.clone(), ws_folders)?; + if let Some(path) = session.sync_odoo.config_path.as_ref() { + let config_from_file = load_config_from_file(session, path.clone())?; ws_confs.push(config_from_file); } + let ws_folders = session.sync_odoo.get_file_mgr().borrow().get_processed_workspace_folders(); + let unique_ws_folders = &session.sync_odoo.get_file_mgr().borrow().get_unique_workspace_folders(); let ws_confs_result: Result, _> = ws_folders .iter() - .map(|ws_f| load_config_from_workspace(ws_folders, ws_f.0, ws_f.1)) + .map(|ws_f| load_config_from_workspace(unique_ws_folders, ws_f)) .collect(); ws_confs.extend(ws_confs_result?); - merge_all_workspaces(ws_confs, ws_folders) + merge_all_workspaces(unique_ws_folders, ws_confs) } /// Check if the old and new configuration entries are different enough to require a restart. diff --git a/server/src/core/file_mgr.rs b/server/src/core/file_mgr.rs index 672bc147..138610ec 100644 --- a/server/src/core/file_mgr.rs +++ b/server/src/core/file_mgr.rs @@ -1,6 +1,6 @@ use ruff_python_ast::{ModModule, PySourceType, Stmt}; use ruff_python_parser::{Parsed, Token, TokenKind}; -use lsp_types::{Diagnostic, DiagnosticSeverity, MessageType, NumberOrString, Position, PublishDiagnosticsParams, Range, TextDocumentContentChangeEvent}; +use lsp_types::{Diagnostic, DiagnosticSeverity, MessageType, NumberOrString, Position, PublishDiagnosticsParams, Range, TextDocumentContentChangeEvent, Uri}; use lsp_types::notification::{Notification, PublishDiagnostics}; use ruff_source_file::{OneIndexed, PositionEncoding, SourceLocation}; use tracing::{error, warn}; @@ -478,8 +478,7 @@ impl FileInfo { pub struct FileMgr { pub files: HashMap>>, untitled_files: HashMap>>, // key: untitled URI or unique name - workspace_folders: HashMap, - has_repeated_workspace_folders: bool, + workspace_folders: HashSet<(String, Uri)>, } impl FileMgr { @@ -488,8 +487,7 @@ impl FileMgr { Self { files: HashMap::new(), untitled_files: HashMap::new(), - workspace_folders: HashMap::new(), - has_repeated_workspace_folders: false, + workspace_folders: HashSet::new(), } } @@ -656,34 +654,47 @@ impl FileMgr { session.sync_odoo.get_file_mgr().borrow_mut().files.clear(); } - pub fn add_workspace_folder(&mut self, name: String, path: String) { - if self.workspace_folders.contains_key(&name) { - warn!("Workspace folder with name {} already exists", name); - self.has_repeated_workspace_folders = true; - } - let sanitized = PathBuf::from(path).sanitize(); - self.workspace_folders.insert(name, sanitized); + /// Add workspace folder by name and uri + /// Same format as received from the client + pub fn add_workspace_folder(&mut self, name: String, uri: Uri) { + self.workspace_folders.insert((name, uri)); } - pub fn remove_workspace_folder(&mut self, name: String) { - self.workspace_folders.remove(&name); + /// Remove workspace folder by name and uri + /// Same format as received from the client + pub fn remove_workspace_folder(&mut self, name: String, uri: Uri) { + self.workspace_folders.remove(&(name, uri)); } - pub fn has_repeated_workspace_folders(&self) -> bool { - self.has_repeated_workspace_folders + pub fn get_workspace_folders(&self) -> &HashSet<(String, Uri)> { + &self.workspace_folders } - pub fn get_workspace_folders(&self) -> &HashMap { - &self.workspace_folders + /// Get workspace folders with sanitized path strings instead of URIs + pub fn get_processed_workspace_folders(&self) -> HashSet<(String, String)> { + self.workspace_folders.iter().map(|(name, uri)| { + (name.clone(), FileMgr::uri2pathname(uri.as_str())) + }).collect() } - pub fn is_in_workspace(&self, path: &str) -> bool { - for p in self.workspace_folders.values() { - if path.starts_with(p) { - return true; + /// Get a map of workspace folder name to sanitized path string + /// of only unique workspace names, repeated names are skipped + pub fn get_unique_workspace_folders(&self) -> HashMap { + let mut visited_names= HashSet::new(); + let mut unique_folders = HashMap::new(); + for (name, uri) in self.workspace_folders.iter() { + if visited_names.insert(name.clone()) { + unique_folders.insert(name.clone(), FileMgr::uri2pathname(uri.as_str())); + } else { + unique_folders.remove(name); + warn!("Workspace folder name '{}' is not unique, skipping it for unique workspace folder retrieval", name); } } - false + unique_folders + } + + pub fn is_in_workspace(&self, path: &str) -> bool { + self.workspace_folders.iter().any(|(_, uri)| path.starts_with(&FileMgr::uri2pathname(uri.as_str()))) } pub fn pathname2uri(s: &String) -> lsp_types::Uri { diff --git a/server/src/core/odoo.rs b/server/src/core/odoo.rs index bf42bd51..03d69066 100644 --- a/server/src/core/odoo.rs +++ b/server/src/core/odoo.rs @@ -1213,11 +1213,7 @@ impl Odoo { pub fn init(session: &mut SessionInfo) { let start = std::time::Instant::now(); session.log_message(MessageType::LOG, String::from("Building new Odoo knowledge database")); - if session.sync_odoo.get_file_mgr().borrow().has_repeated_workspace_folders() { - session.show_message(MessageType::ERROR, String::from("There are repeated workspace folders names, which is not supported by OdooLS. Please remove the repeated folders and restart the server.")); - return; - } - let config = get_configuration(session.sync_odoo.get_file_mgr().borrow().get_workspace_folders(), &session.sync_odoo.config_path); + let config = get_configuration(session); if let Ok((_, config_file)) = &config { session.sync_odoo.config_file = Some(config_file.clone()); Odoo::send_all_configurations(session); @@ -1488,10 +1484,10 @@ impl Odoo { let file_mgr = session.sync_odoo.get_file_mgr(); let mut file_mgr = file_mgr.borrow_mut(); for added in params.event.added { - file_mgr.add_workspace_folder(added.name.clone(),added.uri.to_string()); + file_mgr.add_workspace_folder(added.name.clone(), added.uri); } for removed in params.event.removed { - file_mgr.remove_workspace_folder(removed.uri.to_string()); + file_mgr.remove_workspace_folder(removed.name.clone(), removed.uri); } } @@ -1891,12 +1887,12 @@ impl Odoo { /// Checks if the given path is a configuration file under one of the workspace folders. fn is_config_workspace_file(session: &mut SessionInfo, path: &PathBuf) -> bool { - for (_, ws_dir) in session.sync_odoo.get_file_mgr().borrow().get_workspace_folders().iter() { - if path.starts_with(ws_dir) && path.ends_with("odools.toml") { - return true; - } - } - false + session.sync_odoo + .get_file_mgr() + .borrow() + .get_processed_workspace_folders() + .iter() + .any(|(_, ws_dir)| path.starts_with(ws_dir) && path.ends_with("odools.toml")) } /// Checks if the given path is a configuration file and handles the update accordingly. @@ -1904,7 +1900,7 @@ impl Odoo { fn check_handle_config_file_update(session: &mut SessionInfo, path: &PathBuf) -> bool { // Check if the change is affecting a config file if Odoo::is_config_workspace_file(session, path) { - let config_result = config::get_configuration(session.sync_odoo.get_file_mgr().borrow().get_workspace_folders(), &session.sync_odoo.config_path) + let config_result = config::get_configuration(session) .and_then(|(cfg_map, cfg_file)| { let config_name = Odoo::read_selected_configuration(session)?.unwrap_or(default_profile_name()); cfg_map.get(&config_name) diff --git a/server/src/server.rs b/server/src/server.rs index 15a7cb04..4d1da769 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -9,7 +9,7 @@ use serde_json::json; use nix; use tracing::{error, info, warn}; -use crate::{constants::{DEBUG_THREADS, EXTENSION_VERSION}, core::{file_mgr::FileMgr, odoo::SyncOdoo}, threads::{delayed_changes_process_thread, message_processor_thread_main, DelayedProcessingMessage}, S, crash_buffer}; +use crate::{constants::{DEBUG_THREADS, EXTENSION_VERSION}, core::odoo::SyncOdoo, threads::{delayed_changes_process_thread, message_processor_thread_main, DelayedProcessingMessage}, S, crash_buffer}; /** @@ -135,15 +135,13 @@ impl Server { let file_mgr = sync_odoo.get_file_mgr(); let mut file_mgr = file_mgr.borrow_mut(); for added in workspace_folders.iter() { - let path = FileMgr::uri2pathname(added.uri.as_str()); - file_mgr.add_workspace_folder(added.name.clone(), path); + file_mgr.add_workspace_folder(added.name.clone(), added.uri.clone()); } } else if let Some( root_uri) = initialize_params.root_uri.as_ref() { //keep for backward compatibility let sync_odoo = self.sync_odoo.lock().unwrap(); let file_mgr = sync_odoo.get_file_mgr(); let mut file_mgr = file_mgr.borrow_mut(); - let path = FileMgr::uri2pathname(root_uri.as_str()); - file_mgr.add_workspace_folder(S!("_root"), path); + file_mgr.add_workspace_folder(S!("_root"), root_uri.clone()); } let initialize_data = InitializeResult { server_info: Some(ServerInfo { diff --git a/server/src/threads.rs b/server/src/threads.rs index e20afd08..eae6e37d 100644 --- a/server/src/threads.rs +++ b/server/src/threads.rs @@ -22,6 +22,17 @@ pub struct SessionInfo<'a> { } impl <'a> SessionInfo<'a> { + pub fn new(sync_odoo: &'a mut SyncOdoo) -> Self { + let (s, r) = crossbeam_channel::unbounded(); + Self { + sender: s, + receiver: r, + sync_odoo, + delayed_process_sender: None, + noqas_stack: vec![], + current_noqa: NoqaInfo::None, + } + } pub fn log_message(&self, msg_type: MessageType, msg: String) { self.sender.send( Message::Notification(lsp_server::Notification{ diff --git a/server/src/utils.rs b/server/src/utils.rs index f63f61cd..59e1934c 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -274,13 +274,13 @@ pub fn fill_template(template: &str, vars: &HashMap) -> Result) -> HashMap { +pub fn build_pattern_map(unique_ws_folders: &HashMap) -> HashMap { // TODO: Maybe cache this let mut pattern_map = HashMap::new(); if let Some(home_dir) = HOME_DIR.as_ref() { pattern_map.insert(S!("userHome"), home_dir.clone()); } - for (ws_name, ws_path) in ws_folders.iter(){ + for (ws_name, ws_path) in unique_ws_folders.iter(){ pattern_map.insert(format!("workspaceFolder:{}", ws_name.clone()), ws_path.clone()); } pattern_map @@ -291,27 +291,45 @@ pub fn build_pattern_map(ws_folders: &HashMap) -> HashMap(ws_folders: &HashMap, workspace_name: Option<&String>, template: &str, predicate: F, var_map: HashMap, parent_path: P) -> Result +/// unique_ws_folders: mapping of **unique** workspace folder names to their paths +pub fn fill_validate_path( + unique_ws_folders: &HashMap, + current_ws: Option<&(String, String)>, + template: &str, + predicate: F, + var_map: HashMap, + parent_path: P, +) -> Result where F: Fn(&String) -> bool, P: AsRef { - let mut pattern_map: HashMap = build_pattern_map(ws_folders).into_iter().chain(var_map.into_iter()).collect(); - if let Some(path) = workspace_name.and_then(|name| ws_folders.get(name)) { - pattern_map.insert(S!("workspaceFolder"), path.clone()); - } - let path = fill_template(template, &pattern_map)?; - if predicate(&path) { - return Ok(path); - } - // Attempt to convert the path to an absolute path - if let Ok(abs_path) = std::fs::canonicalize(parent_path.as_ref().join(&path)) { - let abs_path = abs_path.sanitize(); - if predicate(&abs_path) { - return Ok(abs_path); + let mut pattern_map: HashMap = build_pattern_map(unique_ws_folders).into_iter().chain(var_map.into_iter()).collect(); + if let Some((_, path)) = current_ws { + pattern_map.insert(S!("workspaceFolder"), path.clone()); + } + // Check for ambiguous workspaceFolder: pattern + if template.contains("workspaceFolder:") { + let re = Regex::new(r"\$\{workspaceFolder:([^}]+)\}").unwrap(); + for cap in re.captures_iter(template) { + let ws_name = &cap[1]; + if !unique_ws_folders.contains_key(ws_name) { + return Err(format!("Pattern '${{workspaceFolder:{}}}' ignored due to ambiguous or missing workspace name.", ws_name)); } } - Err(format!("Failed to fill and validate path: {} from template {}", path, template)) + } + let path = fill_template(template, &pattern_map)?; + if predicate(&path) { + return Ok(path); + } + // Attempt to convert the path to an absolute path + if let Ok(abs_path) = std::fs::canonicalize(parent_path.as_ref().join(&path)) { + let abs_path = abs_path.sanitize(); + if predicate(&abs_path) { + return Ok(abs_path); + } + } + Err(format!("Failed to fill and validate path: {} from template {}", path, template)) } fn is_really_module(directory_path: &str, entry: &DirEntry) -> bool { diff --git a/server/tests/config_tests.rs b/server/tests/config_tests.rs index 830471fa..d5d557c5 100644 --- a/server/tests/config_tests.rs +++ b/server/tests/config_tests.rs @@ -1,10 +1,26 @@ -use assert_fs::prelude::*; use assert_fs::TempDir; +use assert_fs::prelude::*; +use lsp_types::Uri; +use odoo_ls_server::S; +use odoo_ls_server::core::config::get_configuration; +use odoo_ls_server::core::odoo::SyncOdoo; +use odoo_ls_server::threads::SessionInfo; use odoo_ls_server::utils::PathSanitizer; -use std::collections::HashMap; use std::collections::HashSet; -use odoo_ls_server::core::config::get_configuration; -use odoo_ls_server::S; +use std::str::FromStr; + + +/// Utility to create a mocked SessionInfo with workspace folders +pub fn mock_session_with_workspaces(ws_folders: &[(String, String)]) -> SessionInfo<'static> { + let sync_odoo = Box::leak(Box::new(SyncOdoo::new())); + let file_mgr = sync_odoo.get_file_mgr(); + let mut file_mgr = file_mgr.borrow_mut(); + for (name, path) in ws_folders.iter() { + let uri = Uri::from_str(path).unwrap(); + file_mgr.add_workspace_folder(name.clone(), uri); + } + SessionInfo::new(sync_odoo) +} #[test] fn test_config_entry_single_workspace_with_addons_path() { @@ -16,10 +32,9 @@ fn test_config_entry_single_workspace_with_addons_path() { addon_dir.create_dir_all().unwrap(); addon_dir.child("__manifest__.py").touch().unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert!(config.addons_paths.iter().any(|p| p == &ws_folder.path().sanitize())); @@ -38,11 +53,12 @@ fn test_config_entry_multiple_workspaces_with_various_addons() { addon1.child("__manifest__.py").touch().unwrap(); // ws2 has no addon subdir, so it is not an addon path - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert!(config.addons_paths.iter().any(|p| p == &ws1.path().sanitize())); @@ -57,10 +73,9 @@ fn test_config_entry_with_odoo_path_detection() { ws_folder.child("odoo").create_dir_all().unwrap(); ws_folder.child("odoo").child("release.py").touch().unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("odoo_ws"), ws_folder.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("odoo_ws"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert!(config.odoo_path.as_ref().map(|p| p == &ws_folder.path().sanitize()).unwrap_or(false)); @@ -82,10 +97,9 @@ fn test_single_odools_toml_config() { "#; ws_folder.child("odools.toml").write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.python_path, "python"); @@ -124,10 +138,9 @@ fn test_multiple_odools_toml_shadowing() { "#; ws_folder.child("odools.toml").write_str(ws_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // ws_folder/odools.toml should take priority @@ -171,10 +184,9 @@ fn test_extends_and_shadowing() { "#; ws_folder.child("odools.toml").write_str(ws_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // Should extend from base, but shadow python_path and auto_refresh_delay @@ -217,11 +229,12 @@ fn test_workspacefolder_template_variable_variations() { "#; ws_folder.child("odools.toml").write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2_folder.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![ + (S!("ws1"), ws_folder.path().sanitize().to_string()), + (S!("ws2"), ws2_folder.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // "${workspaceFolder}" should resolve to ws1 (the current workspace) @@ -262,12 +275,13 @@ fn test_workspacefolder_template_ws2_in_ws1_add_workspace_addon_path_behavior() "#; ws1.child("odools.toml").write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); // By default, ws1 should NOT be added as an addon path, only ws2 - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert!(!config.addons_paths.iter().any(|p| p == &ws1.path().sanitize())); assert!(config.addons_paths.iter().any(|p| p == &ws2.path().sanitize())); @@ -280,7 +294,8 @@ fn test_workspacefolder_template_ws2_in_ws1_add_workspace_addon_path_behavior() "#; ws1.child("odools.toml").write_str(toml_content_with_flag).unwrap(); - let (config_map2, _config_file2) = get_configuration(&ws_folders, &None).unwrap(); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map2, _config_file2) = get_configuration(&mut session2).unwrap(); let config2 = config_map2.get("default").unwrap(); assert!(config2.addons_paths.iter().any(|p| p == &ws1.path().sanitize())); assert!(config2.addons_paths.iter().any(|p| p == &ws2.path().sanitize())); @@ -302,10 +317,9 @@ fn test_config_file_sources_single_file() { let odools_path = ws_folder.child("odools.toml"); odools_path.write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (_config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (_config_map, config_file) = get_configuration(&mut session).unwrap(); let config_entry = &config_file.config[0]; // All sourced fields should have the odools.toml as their only source @@ -349,10 +363,9 @@ fn test_config_file_sources_multiple_files_and_extends() { let ws_odools = ws_folder.child("odools.toml"); ws_odools.write_str(ws_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (_config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (_config_map, config_file) = get_configuration(&mut session).unwrap(); let config_entry = config_file.config.iter().find(|c| c.name == "default").unwrap(); // python_path should be sourced from parent odools.toml (root config) @@ -393,11 +406,12 @@ fn test_config_file_sources_template_variable_workspacefolder() { let ws1_odools = ws1.child("odools.toml"); ws1_odools.write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - - let (_config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (_config_map, config_file) = get_configuration(&mut session).unwrap(); let config_entry = config_file.config.iter().find(|c| c.name == "default").unwrap(); // Both ws1 and ws2 should be present in addons_paths, each sourced from ws1_odools @@ -413,10 +427,9 @@ fn test_config_file_sources_default_values() { ws_folder.create_dir_all().unwrap(); // No odools.toml, so all values should be defaults and sourced from "$default" - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (_config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (_config_map, config_file) = get_configuration(&mut session).unwrap(); // The $default source is only visible in the HTML serialization let config_file_str = config_file.to_html_string(); @@ -449,11 +462,12 @@ fn test_config_file_sources_multiple_workspace_folders_and_shadowing() { let ws2_odools = ws2.child("odools.toml"); ws2_odools.write_str(ws2_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - - let (_config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (_config_map, config_file) = get_configuration(&mut session).unwrap(); // There should be only one config entry for "default" (merged) let root_entry = config_file.config.iter().find(|c| c.name == "default").unwrap(); @@ -482,10 +496,9 @@ fn test_config_file_sources_json_serialization() { "#; ws1.child("odools.toml").write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - - let (_config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws1.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (_config_map, config_file) = get_configuration(&mut session).unwrap(); // Serialize to JSON and check sources for python_path and addons_paths let json = serde_json::to_value(&config_file).unwrap(); @@ -517,12 +530,13 @@ fn test_conflict_two_workspace_folders_both_odoo_path() { ws2.child("odoo").create_dir_all().unwrap(); ws2.child("odoo").child("release.py").touch().unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); // Should error due to ambiguous odoo_path - let result = get_configuration(&ws_folders, &None); + let result = get_configuration(&mut session); assert!(result.is_err()); assert!(result.err().unwrap().contains("More than one workspace folder is a valid odoo_path")); } @@ -549,12 +563,13 @@ fn test_no_conflict_when_config_files_point_to_same_odoo_path() { ws1.child("odools.toml").write_str(&ws1_toml).unwrap(); ws2.child("odools.toml").write_str(&ws1_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); // Should NOT error, odoo_path is unambiguous - let result = get_configuration(&ws_folders, &None); + let result = get_configuration(&mut session); assert!(result.is_ok()); let (config_map, _config_file) = result.unwrap(); let config = config_map.get("default").unwrap(); @@ -620,11 +635,12 @@ fn test_merge_different_odoo_paths_and_addons_paths() { ws1.child("odools.toml").write_str(&ws1_toml).unwrap(); ws2.child("odools.toml").write_str(&ws2_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - - let result = get_configuration(&ws_folders, &None); + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session); assert!(result.is_ok()); let (config_map, config_file) = result.unwrap(); let config = config_map.get("default").unwrap(); @@ -706,11 +722,10 @@ fn test_addons_paths_merge_method_override_vs_merge() { ); ws_folder.child("odools.toml").write_str(&ws_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); // With override, only workspace's addons_paths should be present - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.addons_paths, vec![ws_addons.path().sanitize()].into_iter().collect::>()); @@ -729,7 +744,8 @@ fn test_addons_paths_merge_method_override_vs_merge() { ws_folder.child("odools.toml").write_str(&ws_toml).unwrap(); // Re-run config - let (config_map2, _config_file2) = get_configuration(&ws_folders, &None).unwrap(); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map2, _config_file2) = get_configuration(&mut session2).unwrap(); let config2 = config_map2.get("default").unwrap(); let expected = vec![ parent_addons1.path().sanitize(), @@ -762,11 +778,12 @@ fn test_conflict_and_merge_of_boolean_fields() { ws1.child("odools.toml").write_str(ws1_toml).unwrap(); ws2.child("odools.toml").write_str(ws2_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - ws_folders.insert(S!("ws2"), ws2.path().sanitize().to_string()); - - let result = get_configuration(&ws_folders, &None); + let ws_folders = vec![ + (S!("ws1"), ws1.path().sanitize().to_string()), + (S!("ws2"), ws2.path().sanitize().to_string()), + ]; + let mut session = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session); assert!(result.is_err()); assert!(result.err().unwrap().contains("Conflict detected")); @@ -787,10 +804,9 @@ fn test_conflict_and_merge_of_boolean_fields() { "#; ws1.child("odools.toml").write_str(ws1_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws1.path().sanitize().to_string())]; + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session2).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.file_cache, false); // inherited from parent assert_eq!(config.ac_filter_model_names, true); // overridden by workspace @@ -825,11 +841,9 @@ fn test_path_case_and_trailing_slash_normalization() { ); ws_folder.child("odools.toml").write_str(&toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - // Use different case for workspace folder - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // Should only have one normalized path for the addon @@ -853,7 +867,8 @@ fn test_path_case_and_trailing_slash_normalization() { ); ws_folder.child("odools.toml").write_str(&toml_content_slash).unwrap(); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session2).unwrap(); let config = config_map.get("default").unwrap(); assert!(config.addons_paths.iter().any(|p| p == &normalized_addon)); @@ -870,7 +885,8 @@ fn test_path_case_and_trailing_slash_normalization() { ); ws_folder.child("odools.toml").write_str(&toml_content_noslash).unwrap(); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let mut session3 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session3).unwrap(); let config = config_map.get("default").unwrap(); assert!(config.addons_paths.iter().any(|p| p == &normalized_addon)); } @@ -908,10 +924,9 @@ fn test_extends_chain_multiple_profiles_and_order() { "#; ws_folder.child("odools.toml").write_str(ws_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // Should inherit file_cache from mid, auto_refresh_delay from base, diag_missing_imports from root, ac_filter_model_names from workspace @@ -939,7 +954,8 @@ fn test_extends_chain_multiple_profiles_and_order() { "#; temp.child("odools.toml").write_str(parent_toml_swapped).unwrap(); - let (config_map2, _config_file2) = get_configuration(&ws_folders, &None).unwrap(); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map2, _config_file2) = get_configuration(&mut session2).unwrap(); let config2 = config_map2.get("default").unwrap(); assert_eq!(config2.file_cache, false); assert_eq!(config2.auto_refresh_delay, 1111); @@ -967,7 +983,8 @@ fn test_extends_chain_multiple_profiles_and_order() { "#; temp.child("odools.toml").write_str(parent_toml_mid_root).unwrap(); - let (config_map3, _config_file3) = get_configuration(&ws_folders, &None).unwrap(); + let mut session3 = mock_session_with_workspaces(&ws_folders); + let (config_map3, _config_file3) = get_configuration(&mut session3).unwrap(); let config3 = config_map3.get("default").unwrap(); // Should still resolve the chain correctly assert_eq!(config3.file_cache, false); @@ -1008,10 +1025,9 @@ fn test_extends_cycle_detection() { "#; ws_folder.child("odools.toml").write_str(ws_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let result = get_configuration(&ws_folders, &None); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session); assert!(result.is_err()); assert!(result.err().unwrap().contains("Circular dependency detected")); } @@ -1031,10 +1047,9 @@ fn test_extends_nonexistent_profile_error() { "#; temp.child("odools.toml").write_str(parent_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let result = get_configuration(&ws_folders, &None); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session); assert!(result.is_err()); assert!(result.err().unwrap().to_lowercase().contains("extends non-existing profile")); } @@ -1054,10 +1069,9 @@ fn test_invalid_toml_config() { "#; ws_folder.child("odools.toml").write_str(invalid_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let result = get_configuration(&ws_folders, &None); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session); assert!(result.is_err()); assert!(result.err().unwrap().to_lowercase().contains("toml")); } @@ -1075,11 +1089,10 @@ fn test_malformed_config_missing_required_fields() { "#; ws_folder.child("odools.toml").write_str(toml_missing_name).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); // Should not error, should default name to "default" - let result = get_configuration(&ws_folders, &None); + let result = get_configuration(&mut session); assert!(result.is_ok()); let (config_map, _) = result.unwrap(); assert!(config_map.contains_key("default")); @@ -1088,7 +1101,8 @@ fn test_malformed_config_missing_required_fields() { let empty_toml = ""; ws_folder.child("odools.toml").write_str(empty_toml).unwrap(); - let result = get_configuration(&ws_folders, &None); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session2); assert!(result.is_ok()); let (config_map, _) = result.unwrap(); // Should still have a default "default" config entry @@ -1125,10 +1139,9 @@ fn test_template_variable_expansion_userhome_and_workspacefolder() { ); ws1.child("odools.toml").write_str(&toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws1.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // Both expanded paths should be present in addons_paths @@ -1168,10 +1181,9 @@ fn test_config_with_relative_addons_paths() { "#; ws.child("odools.toml").write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws"), ws.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws"), ws.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // The expected absolute, sanitized paths @@ -1212,10 +1224,9 @@ fn test_relative_addons_paths_in_parent_config() { temp.child("odools.toml").write_str(toml_content).unwrap(); // Workspace folder does not have its own odools.toml - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws"), ws.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws"), ws.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // The expected absolute, sanitized paths @@ -1240,10 +1251,9 @@ fn test_auto_refresh_delay_boundaries() { "#; ws_folder.child("odools.toml").write_str(toml_content_min).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.auto_refresh_delay, 1000); @@ -1255,7 +1265,8 @@ fn test_auto_refresh_delay_boundaries() { "#; ws_folder.child("odools.toml").write_str(toml_content_max).unwrap(); - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session2).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.auto_refresh_delay, 15000); @@ -1267,7 +1278,8 @@ fn test_auto_refresh_delay_boundaries() { "#; ws_folder.child("odools.toml").write_str(toml_content_ok).unwrap(); - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let mut session3 = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session3).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.auto_refresh_delay, 1234); } @@ -1296,10 +1308,9 @@ fn test_odoo_path_with_version_variable_and_workspace_folder() { temp.child("odools.toml").write_str(toml_content).unwrap(); // Workspace: temp (simulate as workspace root) - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws"), temp.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws"), temp.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); let expected_odoo_path = temp.child("18.0").child("odoo").path().sanitize(); assert_eq!( @@ -1321,10 +1332,9 @@ fn test_odoo_path_with_version_variable_and_workspace_folder() { "#; ws_18_addons.child("odools.toml").write_str(ws18_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws18"), ws_18_addons.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws18"), ws_18_addons.path().sanitize().to_string())]; + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session2).unwrap(); let config = config_map.get("default").unwrap(); let expected_odoo_path = temp.child("18.0").child("odoo").path().sanitize(); assert_eq!( @@ -1346,10 +1356,9 @@ fn test_odoo_path_with_version_variable_and_workspace_folder() { "#; ws_17_addons.child("odools.toml").write_str(ws17_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws17"), ws_17_addons.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws17"), ws_17_addons.path().sanitize().to_string())]; + let mut session3 = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session3).unwrap(); let config = config_map.get("default").unwrap(); let expected_odoo_path = temp.child("17.0").child("odoo").path().sanitize(); assert_eq!( @@ -1395,10 +1404,9 @@ fn test_odoo_path_with_version_from_manifest_file() { "#; ws_18_addons.child("odools.toml").write_str(ws18_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws18"), ws_18_addons.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws18"), ws_18_addons.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); let expected_odoo_path = temp.child("18.0").child("odoo").path().sanitize(); assert_eq!( @@ -1421,10 +1429,9 @@ fn test_odoo_path_with_version_from_manifest_file() { "#; ws_17_addons.child("odools.toml").write_str(ws17_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws17"), ws_17_addons.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws17"), ws_17_addons.path().sanitize().to_string())]; + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session2).unwrap(); let config = config_map.get("default").unwrap(); let expected_odoo_path = temp.child("17.0").child("odoo").path().sanitize(); assert_eq!( @@ -1452,10 +1459,9 @@ fn test_addons_paths_unset_vs_empty_behavior() { "#; ws.child("odools.toml").write_str(toml_unset).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws"), ws.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws"), ws.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert!(config.addons_paths.contains(&ws.path().sanitize()), "Workspace should be added to addons_paths when addons_paths is not set"); @@ -1467,7 +1473,8 @@ fn test_addons_paths_unset_vs_empty_behavior() { "#; ws.child("odools.toml").write_str(toml_empty).unwrap(); - let (config_map2, _) = get_configuration(&ws_folders, &None).unwrap(); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map2, _) = get_configuration(&mut session2).unwrap(); let config2 = config_map2.get("default").unwrap(); assert!(!config2.addons_paths.contains(&ws.path().sanitize()), "Workspace should NOT be added to addons_paths when addons_paths is set to []"); } @@ -1517,10 +1524,9 @@ fn test_addons_merge_override_cases() { ); ws.child("odools.toml").write_str(&child_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws"), ws.path().sanitize().to_string()); - - let (config_map, _) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws"), ws.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!( config.addons_paths, @@ -1541,7 +1547,8 @@ fn test_addons_merge_override_cases() { ws_mod3.create_dir_all().unwrap(); ws_mod3.child("__manifest__.py").touch().unwrap(); - let (config_map2, _) = get_configuration(&ws_folders, &None).unwrap(); + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map2, _) = get_configuration(&mut session2).unwrap(); let config2 = config_map2.get("default").unwrap(); assert!( config2.addons_paths.contains(&ws.path().sanitize()), @@ -1557,7 +1564,8 @@ fn test_addons_merge_override_cases() { "#; ws.child("odools.toml").write_str(child_toml3).unwrap(); - let (config_map3, _) = get_configuration(&ws_folders, &None).unwrap(); + let mut session3 = mock_session_with_workspaces(&ws_folders); + let (config_map3, _) = get_configuration(&mut session3).unwrap(); let config3 = config_map3.get("default").unwrap(); assert!( config3.addons_paths.is_empty(), @@ -1595,10 +1603,9 @@ fn test_detect_version_variable_creates_profiles_for_each_version() { "#; ws1.child("odools.toml").write_str(toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - - let (config_map, config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws1.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, config_file) = get_configuration(&mut session).unwrap(); // There should be three profiles: "root" (abstract), "root-17.0", "root-18.0" assert!(config_map.contains_key("root"), "Should contain abstract root profile"); @@ -1657,11 +1664,11 @@ fn test_config_file_path_priority() { let ext_config = temp.child("external_config.toml"); ext_config.write_str(&ext_toml).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); let config_path = ext_config.path().sanitize(); - let (config_map, _config_file) = get_configuration(&ws_folders, &Some(config_path)).unwrap(); + session.sync_odoo.config_path = Some(config_path); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); // Should use values from external config @@ -1681,13 +1688,13 @@ fn test_config_file_path_nonexistent_errors() { let ws_folder = temp.child("workspace1"); ws_folder.create_dir_all().unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws_folder.path().sanitize().to_string()); - + let ws_folders = vec![(S!("ws1"), ws_folder.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); // Provide a non-existent config file path let non_existent = temp.child("does_not_exist.toml"); let config_path = non_existent.path().sanitize(); - let result = get_configuration(&ws_folders, &Some(config_path)); + session.sync_odoo.config_path = Some(config_path); + let result = get_configuration(&mut session); assert!(result.is_err(), "Expected error when config file path does not exist"); } @@ -1715,25 +1722,25 @@ fn test_base_and_version_resolve_for_workspace_subpaths() { temp.child("odools.toml").write_str(&toml_content).unwrap(); // Test with workspace at /temp/17.0 - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), vdir.path().sanitize().to_string()); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), vdir.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize()); assert!(config.addons_paths.contains(&addon_dir.path().sanitize())); // Test with workspace at /temp/17.0/odoo - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws2"), odoo_dir.path().sanitize().to_string()); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws2"), odoo_dir.path().sanitize().to_string())]; + let mut session2 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session2).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize()); assert!(config.addons_paths.contains(&addon_dir.path().sanitize())); // Test with workspace at /temp/17.0/addon-path - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws3"), addon_dir.path().sanitize().to_string()); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws3"), addon_dir.path().sanitize().to_string())]; + let mut session3 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session3).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize()); assert!(config.addons_paths.contains(&addon_dir.path().sanitize())); @@ -1749,25 +1756,25 @@ fn test_base_and_version_resolve_for_workspace_subpaths() { temp.child("odools.toml").write_str(&toml_content_version).unwrap(); // Test with workspace at /temp/17.0 - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), vdir.path().sanitize().to_string()); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), vdir.path().sanitize().to_string())]; + let mut session4 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session4).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize()); assert!(config.addons_paths.contains(&addon_dir.path().sanitize())); // Test with workspace at /temp/17.0/odoo - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws2"), odoo_dir.path().sanitize().to_string()); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws2"), odoo_dir.path().sanitize().to_string())]; + let mut session5 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session5).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize()); assert!(config.addons_paths.contains(&addon_dir.path().sanitize())); // Test with workspace at /temp/17.0/addon-path - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws3"), addon_dir.path().sanitize().to_string()); - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws3"), addon_dir.path().sanitize().to_string())]; + let mut session6 = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session6).unwrap(); let config = config_map.get("default").unwrap(); assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize()); assert!(config.addons_paths.contains(&addon_dir.path().sanitize())); @@ -1780,9 +1787,9 @@ fn test_base_and_version_resolve_for_workspace_subpaths() { addons_paths = [ "${{base}}/addon-path" ] "#); temp.child("odools.toml").write_str(&toml_content_abs).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws_abs"), vdir.path().sanitize().to_string()); - let result = get_configuration(&ws_folders, &None); + let ws_folders = vec![(S!("ws_abs"), vdir.path().sanitize().to_string())]; + let mut session7 = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session7); assert!(result.is_err(), "Expected error when $base is an absolute path"); // --- Crash scenario: $base is not a valid path (should error) --- @@ -1794,9 +1801,9 @@ fn test_base_and_version_resolve_for_workspace_subpaths() { addons_paths = [ "${base}/addon-path" ] "#; temp.child("odools.toml").write_str(toml_content_invalid).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws_invalid"), vdir.path().sanitize().to_string()); - let result = get_configuration(&ws_folders, &None); + let ws_folders = vec![(S!("ws_invalid"), vdir.path().sanitize().to_string())]; + let mut session8 = mock_session_with_workspaces(&ws_folders); + let result = get_configuration(&mut session8); assert!(result.is_err(), "Expected error when $base is not a valid path"); } @@ -1820,10 +1827,9 @@ fn test_diagnostic_filter_path_variable_expansion() { "#, version); ws1.child("odools.toml").write_str(&toml_content).unwrap(); - let mut ws_folders = HashMap::new(); - ws_folders.insert(S!("ws1"), ws1.path().sanitize().to_string()); - - let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap(); + let ws_folders = vec![(S!("ws1"), ws1.path().sanitize().to_string())]; + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); let config = config_map.get("default").unwrap(); let filters = &config.diagnostic_filters; assert!(!filters.is_empty(), "Expected at least one diagnostic filter"); @@ -1836,3 +1842,57 @@ fn test_diagnostic_filter_path_variable_expansion() { // Check that $version was expanded assert!(patterns.iter().any(|p| p.ends_with("/bar") && p.contains(version)), "version variable not expanded: {:?}", patterns); } + +#[test] +fn test_ambiguous_workspace_names_pattern_ignored() { + let temp = TempDir::new().unwrap(); + let ws1 = temp.child("ws1"); + let ws2 = temp.child("ws2"); + let addon_dir = ws1.child("my_module"); + addon_dir.create_dir_all().unwrap(); + addon_dir.child("__manifest__.py").touch().unwrap(); + let addon_dir = ws2.child("my_module"); + addon_dir.create_dir_all().unwrap(); + addon_dir.child("__manifest__.py").touch().unwrap(); + + // Both workspaces have the same name + let ws_name = S!("duplicate"); + let ws_folders = vec![ + (ws_name.clone(), ws1.path().sanitize().to_string()), + (ws_name.clone(), ws2.path().sanitize().to_string()), + ]; + // Write odools.toml with ambiguous pattern + let toml_content = r#" + [[config]] + name = "default" + addons_paths = [ + "${workspaceFolder:duplicate}" + ] + "#; + temp.child("odools.toml").write_str(toml_content).unwrap(); + // Should not resolve the ambiguous pattern + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); + let config = config_map.get("default").unwrap(); + // Neither ws1 nor ws2 should be present in addons_paths + assert!(!config.addons_paths.iter().any(|p| p == &ws1.path().sanitize())); + assert!(!config.addons_paths.iter().any(|p| p == &ws2.path().sanitize())); + // The set should be empty + assert!(config.addons_paths.is_empty()); + // Write odools.toml with general pattern + let toml_content = r#" + [[config]] + name = "default" + addons_paths = [ + "${workspaceFolder}" + ] + "#; + temp.child("odools.toml").write_str(toml_content).unwrap(); + // Should resolve the general pattern + let mut session = mock_session_with_workspaces(&ws_folders); + let (config_map, _config_file) = get_configuration(&mut session).unwrap(); + let config = config_map.get("default").unwrap(); + // both ws1 and ws2 should be present in addons_paths + assert!(config.addons_paths.iter().any(|p| p == &ws1.path().sanitize())); + assert!(config.addons_paths.iter().any(|p| p == &ws2.path().sanitize())); +} \ No newline at end of file diff --git a/server/tests/setup/setup.rs b/server/tests/setup/setup.rs index 39f6acbc..a3fb6e6d 100644 --- a/server/tests/setup/setup.rs +++ b/server/tests/setup/setup.rs @@ -1,12 +1,13 @@ use core::str; use std::collections::HashMap; +use std::str::FromStr; use std::{env, fs}; use std::path::PathBuf; use lsp_server::Message; -use lsp_types::{Diagnostic, PublishDiagnosticsParams, TextDocumentContentChangeEvent}; +use lsp_types::{Diagnostic, PublishDiagnosticsParams, TextDocumentContentChangeEvent, Uri}; use lsp_types::notification::{Notification, PublishDiagnostics}; use odoo_ls_server::S; use odoo_ls_server::core::file_mgr::FileMgr; @@ -51,7 +52,7 @@ pub fn setup_server(with_odoo: bool) -> (SyncOdoo, ConfigEntry) { let mut config = ConfigEntry::new(); config.addons_paths = vec![test_addons_path.sanitize()].into_iter().collect(); - server.get_file_mgr().borrow_mut().add_workspace_folder(S!("test_addons_path"), test_addons_path.sanitize()); + server.get_file_mgr().borrow_mut().add_workspace_folder(S!("test_addons_path"), Uri::from_str(&test_addons_path.sanitize()).unwrap()); config.odoo_path = community_path.map(|x| PathBuf::from(x).sanitize()); let Some(python_cmd) = get_python_command() else { panic!("Python not found")