diff --git a/server/src/core/entry_point.rs b/server/src/core/entry_point.rs index f5c0f866..9ccd9304 100644 --- a/server/src/core/entry_point.rs +++ b/server/src/core/entry_point.rs @@ -14,6 +14,7 @@ pub struct EntryPointMgr { pub main_entry_point: Option>>, pub addons_entry_points: Vec>>, pub custom_entry_points: Vec>>, + pub untitled_entry_points: Vec>>, } impl EntryPointMgr { @@ -25,8 +26,27 @@ impl EntryPointMgr { main_entry_point: None, addons_entry_points: vec![], custom_entry_points: vec![], + untitled_entry_points: vec![], } } + /// Create a new entry for an untitled (in-memory) file. + /// Returns the file symbol for the untitled entry. + pub fn add_entry_to_untitled(session: &mut SessionInfo, path: String) -> Rc> { + // For untitled files, we use a minimal tree: just the name as a single OYarn + let tree = vec![OYarn::from(path.clone())]; + let entry = EntryPoint::new( + path.clone(), + tree, + EntryPointType::UNTITLED, + None, + None, + ); + session.sync_odoo.entry_point_mgr.borrow_mut().untitled_entry_points.push(entry.clone()); + // Create one file symbol under the root for the untitled file + let name: String = PathBuf::from(&path).with_extension("").components().last().unwrap().as_os_str().to_str().unwrap().to_string(); + let file_sym = entry.borrow().root.borrow_mut().add_new_file(session, &name, &path); + file_sym.clone() + } /** * Create each required directory symbols for a given path. @@ -193,6 +213,13 @@ impl EntryPointMgr { true } + pub fn create_new_untitled_entry_for_path(session: &mut SessionInfo, file_name: &String) -> bool { + let new_sym = EntryPointMgr::add_entry_to_untitled(session, file_name.clone()); + new_sym.borrow_mut().as_file_mut().self_import = true; + SyncOdoo::add_to_rebuild_arch(session.sync_odoo, new_sym); + true + } + pub fn iter_for_import(&self, current_entry: &Rc>) -> Box>> + '_> { let mut is_main = false; for entry in self.iter_main() { @@ -214,12 +241,12 @@ impl EntryPointMgr { } pub fn iter_all(&self) -> impl Iterator>> { - self.addons_entry_points.iter().chain( - self.main_entry_point.iter()).chain( - self.builtins_entry_points.iter()).chain( - self.public_entry_points.iter()).chain( - self.custom_entry_points.iter() - ) + self.addons_entry_points.iter() + .chain(self.main_entry_point.iter()) + .chain(self.builtins_entry_points.iter()) + .chain(self.public_entry_points.iter()) + .chain(self.custom_entry_points.iter()) + .chain(self.untitled_entry_points.iter()) } //iter through all main entry points, sorted by tree length (from bigger to smaller) @@ -256,6 +283,15 @@ impl EntryPointMgr { self.clean_entries(); } + pub fn remove_untitled_entries_with_path(&mut self, path: &String) { + for entry in self.untitled_entry_points.iter() { + if &entry.borrow().path.clone() == path { + entry.borrow_mut().to_delete = true; + } + } + self.clean_entries(); + } + pub fn check_custom_entry_to_delete_with_path(&mut self, path: &String) { for entry in self.custom_entry_points.iter() { if entry.borrow().path == *path { @@ -292,7 +328,7 @@ impl EntryPointMgr { entry_index += 1; } } - let mut entry_index = 0; + entry_index = 0; while entry_index < self.custom_entry_points.len() { let entry = self.custom_entry_points[entry_index].clone(); if entry.borrow().to_delete { @@ -302,6 +338,16 @@ impl EntryPointMgr { entry_index += 1; } } + entry_index = 0; + while entry_index < self.untitled_entry_points.len() { + let entry = self.untitled_entry_points[entry_index].clone(); + if entry.borrow().to_delete { + info!("Dropping untitled entry point {}", entry.borrow().path); + self.untitled_entry_points.remove(entry_index); + } else { + entry_index += 1; + } + } } /// Transform the path of an addon to the odoo relative path. @@ -323,7 +369,8 @@ pub enum EntryPointType { BUILTIN, PUBLIC, ADDON, - CUSTOM + CUSTOM, + UNTITLED, } #[derive(Debug, Clone)] @@ -359,6 +406,9 @@ impl EntryPoint { } pub fn is_valid_for(&self, path: &PathBuf) -> bool { + if self.typ == EntryPointType::UNTITLED { + return false; + } path.starts_with(&self.path) } diff --git a/server/src/core/file_mgr.rs b/server/src/core/file_mgr.rs index f113bc67..62ff32b0 100644 --- a/server/src/core/file_mgr.rs +++ b/server/src/core/file_mgr.rs @@ -3,7 +3,7 @@ use ropey::Rope; 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 tracing::{error, warn}; +use tracing::{info, error, warn}; use std::collections::hash_map::DefaultHasher; use std::collections::HashSet; use std::hash::{Hash, Hasher}; @@ -104,9 +104,9 @@ impl FileInfo { diagnostic_filters: Vec::new(), } } - pub fn update(&mut self, session: &mut SessionInfo, uri: &str, content: Option<&Vec>, version: Option, in_workspace: bool, force: bool) -> bool { + pub fn update(&mut self, session: &mut SessionInfo, path: &str, content: Option<&Vec>, version: Option, in_workspace: bool, force: bool, is_untitled: bool) -> bool { // update the file info with the given information. - // uri: indicates the path of the file + // path: indicates the path of the file // content: if content is given, it will be used to update the ast and text_rope, if not, the loading will be from the disk // version: if the version is provided, the file_info wil be updated only if the new version is higher. // -100 can be given as version number to indicates that the file has not been opened yet, and that we have to load it ourself @@ -137,13 +137,16 @@ impl FileInfo { for change in content.iter() { self.apply_change(change); } + } else if is_untitled { + session.log_message(MessageType::ERROR, format!("Attempt to update untitled file {}, without changes", path)); + return false; } else { - match fs::read_to_string(uri) { + match fs::read_to_string(path) { Ok(content) => { self.file_info_ast.borrow_mut().text_rope = Some(ropey::Rope::from(content.as_str())); }, Err(e) => { - session.log_message(MessageType::ERROR, format!("Failed to read file {}, with error {}", uri, e)); + session.log_message(MessageType::ERROR, format!("Failed to read file {}, with error {}", path, e)); return false; }, }; @@ -441,6 +444,7 @@ impl FileInfo { #[derive(Debug)] pub struct FileMgr { pub files: HashMap>>, + untitled_files: HashMap>>, // key: untitled URI or unique name workspace_folders: HashMap, has_repeated_workspace_folders: bool, } @@ -450,6 +454,7 @@ impl FileMgr { pub fn new() -> Self { Self { files: HashMap::new(), + untitled_files: HashMap::new(), workspace_folders: HashMap::new(), has_repeated_workspace_folders: false, } @@ -463,17 +468,30 @@ impl FileMgr { } pub fn get_file_info(&self, path: &String) -> Option>> { - self.files.get(path).cloned() + if Self::is_untitled(path) { + self.untitled_files.get(path).cloned() + } else { + self.files.get(path).cloned() + } } pub fn text_range_to_range(&self, session: &mut SessionInfo, path: &String, range: &TextRange) -> Range { - let file = self.files.get(path); + let file = if Self::is_untitled(path) { + self.untitled_files.get(path) + } else { + self.files.get(path) + }; if let Some(file) = file { if file.borrow().file_info_ast.borrow().text_rope.is_none() { file.borrow_mut().prepare_ast(session); } return file.borrow().text_range_to_range(range); } + // For untitled, never try to read from disk + if Self::is_untitled(path) { + session.log_message(MessageType::ERROR, format!("Untitled file {} not found in memory", path)); + return Range::default(); + } //file not in cache, let's load rope on the fly match fs::read_to_string(path) { Ok(content) => { @@ -490,13 +508,22 @@ impl FileMgr { pub fn std_range_to_range(&self, session: &mut SessionInfo, path: &String, range: &std::ops::Range) -> Range { - let file = self.files.get(path); + let file = if Self::is_untitled(path) { + self.untitled_files.get(path) + } else { + self.files.get(path) + }; if let Some(file) = file { if file.borrow().file_info_ast.borrow().text_rope.is_none() { file.borrow_mut().prepare_ast(session); } return file.borrow().std_range_to_range(range); } + // For untitled, never try to read from disk + if Self::is_untitled(path) { + session.log_message(MessageType::ERROR, format!("Untitled file {} not found in memory", path)); + return Range::default(); + } //file not in cache, let's load rope on the fly match fs::read_to_string(path) { Ok(content) => { @@ -511,8 +538,20 @@ impl FileMgr { Range::default() } + /// Returns true if the path/uri is an untitled (in-memory) file. + /// by convention, untitled files start with "untitled:". + pub fn is_untitled(path: &str) -> bool { + path.starts_with("untitled:") + } + pub fn update_file_info(&mut self, session: &mut SessionInfo, uri: &str, content: Option<&Vec>, version: Option, force: bool) -> (bool, Rc>) { - let file_info = self.files.entry(uri.to_string()).or_insert_with(|| { + let is_untitled = Self::is_untitled(uri); + let entry = if is_untitled { + self.untitled_files.entry(uri.to_string()) + } else { + self.files.entry(uri.to_string()) + }; + let file_info = entry.or_insert_with(|| { let mut file_info = FileInfo::new(uri.to_string()); file_info.update_diagnostic_filters(session); Rc::new(RefCell::new(file_info)) @@ -522,7 +561,7 @@ impl FileMgr { let mut updated: bool = false; if (version.is_some() && version.unwrap() != -100) || !file_info.borrow().opened || force { let mut file_info_mut = (*return_info).borrow_mut(); - updated = file_info_mut.update(session, uri, content, version, self.is_in_workspace(uri), force); + updated = file_info_mut.update(session, uri, content, version, self.is_in_workspace(uri), force, is_untitled); drop(file_info_mut); } (updated, return_info) @@ -612,13 +651,18 @@ impl FileMgr { } pub fn pathname2uri(s: &String) -> lsp_types::Uri { - let mut slash = ""; - if cfg!(windows) { - slash = "/"; - } - let pre_uri = match url::Url::parse(&format!("file://{}{}", slash, s)) { - Ok(pre_uri) => pre_uri, - Err(err) => panic!("unable to transform pathname to uri: {s}, {}", err) + let pre_uri = if s.starts_with("untitled:"){ + s.clone() + } else { + let mut slash = ""; + if cfg!(windows) { + slash = "/"; + } + let pre_uri = match url::Url::parse(&format!("file://{}{}", slash, s)) { + Ok(pre_uri) => pre_uri, + Err(err) => panic!("unable to transform pathname to uri: {s}, {}", err) + }; + pre_uri.to_string() }; match lsp_types::Uri::from_str(pre_uri.as_str()) { Ok(url) => url, diff --git a/server/src/core/odoo.rs b/server/src/core/odoo.rs index 40a06ffc..5c08b3be 100644 --- a/server/src/core/odoo.rs +++ b/server/src/core/odoo.rs @@ -889,6 +889,27 @@ impl SyncOdoo { parents } + pub fn get_symbol_of_untitled_file(session: &mut SessionInfo, path: &String) -> Option>> { + let ep_mgr = session.sync_odoo.entry_point_mgr.clone(); + for entry in ep_mgr.borrow().untitled_entry_points.iter() { + if entry.borrow().path == *path { + let name = PathBuf::from(&path).with_extension("").components().last().unwrap().as_os_str().to_str().unwrap().to_string(); + let Some(file) = entry.borrow().root.borrow().as_root().module_symbols.get(&Sy!(name)).cloned() else { + continue; + }; + return Some(file); + } + } + None + } + + pub fn unload_untitled_file(session: &mut SessionInfo, path: &String) { + let Some(file) = SyncOdoo::get_symbol_of_untitled_file(session, path) else { + return; + }; + Symbol::unload(session, file); + } + /* * Give the symbol that is linked to the given path. As we consider that the file is opened, we do not search in entries that * could have it in dependencies but are not the main entry. If not found, create a new entry (is useful if the entry was dropped before @@ -1206,31 +1227,56 @@ impl Odoo { params.text_document_position_params.text_document.uri.to_string(), params.text_document_position_params.position.line, params.text_document_position_params.position.character)); - let uri = params.text_document_position_params.text_document.uri.to_string(); - let path = FileMgr::uri2pathname(uri.as_str()); - if uri.ends_with(".py") || uri.ends_with(".pyi") || uri.ends_with(".xml") || uri.ends_with(".csv") { - if let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(session, &PathBuf::from(path.clone())) { - let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); - if let Some(file_info) = file_info { - if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { - file_info.borrow_mut().prepare_ast(session); - } - let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone(); - match ast_type { - AstType::Python => { - if file_info.borrow_mut().file_info_ast.borrow().indexed_module.is_some() { - return Ok(HoverFeature::hover_python(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + match params.text_document_position_params.text_document.uri.scheme().map(|scheme| scheme.to_lowercase()) { + Some(schema) if schema == "file" => { + let uri = params.text_document_position_params.text_document.uri.to_string(); + if !uri.ends_with(".py") && !uri.ends_with(".xml") && !uri.ends_with(".csv") { + return Ok(None); + } + match params.text_document_position_params.text_document.uri.to_file_path(){ + Ok(path) => { + let path = path.sanitize(); + if let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(session, &PathBuf::from(path.clone())) { + let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { + file_info.borrow_mut().prepare_ast(session); + } + let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone(); + match ast_type { + AstType::Python => { + if file_info.borrow_mut().file_info_ast.borrow().indexed_module.is_some() { + return Ok(HoverFeature::hover_python(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } + }, + AstType::Xml => { + return Ok(HoverFeature::hover_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, + AstType::Csv => { + return Ok(HoverFeature::hover_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, + } } - }, - AstType::Xml => { - return Ok(HoverFeature::hover_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); - }, - AstType::Csv => { - return Ok(HoverFeature::hover_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); - }, + } + }, + Err(_) => { + return Ok(None); } } - } + return Ok(None); + }, + Some(schema) if schema == "untitled" => { + let path = params.text_document_position_params.text_document.uri.to_string(); + if let Some(file_symbol) = SyncOdoo::get_symbol_of_untitled_file(session, &path) { + let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_some() { + return Ok(HoverFeature::hover_python(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } + } + } + }, + _ => {} } Ok(None) } @@ -1243,31 +1289,56 @@ impl Odoo { params.text_document_position_params.text_document.uri.to_string(), params.text_document_position_params.position.line, params.text_document_position_params.position.character)); - let uri = params.text_document_position_params.text_document.uri.to_string(); - let path = FileMgr::uri2pathname(uri.as_str()); - if uri.ends_with(".py") || uri.ends_with(".pyi") ||uri.ends_with(".xml") || uri.ends_with(".csv") { - if let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(session, &PathBuf::from(path.clone())) { - let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path); - if let Some(file_info) = file_info { - if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { - file_info.borrow_mut().prepare_ast(session); - } - let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone(); - match ast_type { - AstType::Python => { - if file_info.borrow().file_info_ast.borrow().indexed_module.is_some() { - return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + match params.text_document_position_params.text_document.uri.scheme().map(|scheme| scheme.to_lowercase()) { + Some(schema) if schema == "file" => { + let uri = params.text_document_position_params.text_document.uri.to_string(); + if !uri.ends_with(".py") && !uri.ends_with(".xml") && !uri.ends_with(".csv") { + return Ok(None); + } + match params.text_document_position_params.text_document.uri.to_file_path(){ + Ok(path) => { + let path = path.sanitize(); + if let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(session, &PathBuf::from(path.clone())) { + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { + file_info.borrow_mut().prepare_ast(session); + } + let ast_type = file_info.borrow().file_info_ast.borrow().ast_type.clone(); + match ast_type { + AstType::Python => { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_some() { + return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } + }, + AstType::Xml => { + return Ok(DefinitionFeature::get_location_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, + AstType::Csv => { + return Ok(DefinitionFeature::get_location_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + }, + } } - }, - AstType::Xml => { - return Ok(DefinitionFeature::get_location_xml(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); - }, - AstType::Csv => { - return Ok(DefinitionFeature::get_location_csv(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); - }, + } + }, + Err(_) => { + return Ok(None); } } - } + return Ok(None); + }, + Some(schema) if schema == "untitled" => { + let path = params.text_document_position_params.text_document.uri.to_string(); + if let Some(file_symbol) = SyncOdoo::get_symbol_of_untitled_file(session, &path) { + let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_some() { + return Ok(DefinitionFeature::get_location(session, &file_symbol, &file_info, params.text_document_position_params.position.line, params.text_document_position_params.position.character)); + } + } + } + }, + _ => {} } Ok(None) } @@ -1318,20 +1389,45 @@ impl Odoo { params.text_document_position.position.line, params.text_document_position.position.character )); - let uri = params.text_document_position.text_document.uri.to_string(); - let path = FileMgr::uri2pathname(uri.as_str()); - if uri.ends_with(".py") ||uri.ends_with(".xml") || uri.ends_with(".csv") { - if let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(session, &PathBuf::from(path.clone())) { - let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); - if let Some(file_info) = file_info { - if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { - file_info.borrow_mut().prepare_ast(session); + match params.text_document_position.text_document.uri.scheme().map(|scheme| scheme.to_lowercase()) { + Some(schema) if schema == "file" => { + let uri = params.text_document_position.text_document.uri.to_string(); + if !uri.ends_with(".py") && !uri.ends_with(".xml") && !uri.ends_with(".csv") { + return Ok(None); + } + match params.text_document_position.text_document.uri.to_file_path(){ + Ok(path) => { + let path = path.sanitize(); + if let Some(file_symbol) = SyncOdoo::get_symbol_of_opened_file(session, &PathBuf::from(path.clone())) { + let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { + file_info.borrow_mut().prepare_ast(session); + } + if file_info.borrow_mut().file_info_ast.borrow().indexed_module.is_some() { + return Ok(CompletionFeature::autocomplete(session, &file_symbol, &file_info, params.text_document_position.position.line, params.text_document_position.position.character)); + } + } + } } - if file_info.borrow_mut().file_info_ast.borrow().indexed_module.is_some() { - return Ok(CompletionFeature::autocomplete(session, &file_symbol, &file_info, params.text_document_position.position.line, params.text_document_position.position.character)); + Err(_) => { + return Ok(None); } } - } + return Ok(None); + }, + Some(schema) if schema == "untitled" => { + let path = params.text_document_position.text_document.uri.to_string(); + if let Some(file_symbol) = SyncOdoo::get_symbol_of_untitled_file(session, &path) { + let file_info = session.sync_odoo.get_file_mgr().borrow_mut().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_some() { + return Ok(CompletionFeature::autocomplete(session, &file_symbol, &file_info, params.text_document_position.position.line, params.text_document_position.position.character)); + } + } + } + }, + _ => {} } Ok(None) } @@ -1403,51 +1499,119 @@ impl Odoo { pub fn handle_did_open(session: &mut SessionInfo, params: DidOpenTextDocumentParams) { //to implement Incremental update of file caches, we have to handle DidOpen notification, to be sure // that we use the same base version of the file for future incrementation. - if let Ok(path) = params.text_document.uri.to_file_path() { //temp file has no file path - session.log_message(MessageType::INFO, format!("File opened: {}", path.sanitize())); - let (valid, updated) = Odoo::update_file_cache(session, path.clone(), Some(&vec![TextDocumentContentChangeEvent{ - range: None, - range_length: None, - text: params.text_document.text}]), params.text_document.version); - if valid { - session.sync_odoo.opened_files.push(path.sanitize()); - if session.sync_odoo.state_init == InitState::NOT_READY { - return - } - let tree = session.sync_odoo.path_to_main_entry_tree(&path); - let tree_path = path.to_tree_path(); - if tree.is_none() || - (session.sync_odoo.get_main_entry().borrow().root.borrow().get_symbol(tree.as_ref().unwrap(), u32::MAX).is_empty() - && session.sync_odoo.get_main_entry().borrow().data_symbols.get(&path.sanitize()).is_none()) - { - //main entry doesn't handle this file. Let's test customs entries, or create a new one - let ep_mgr = session.sync_odoo.entry_point_mgr.clone(); - for custom_entry in ep_mgr.borrow().custom_entry_points.iter() { - if custom_entry.borrow().path == tree_path.sanitize() { - if updated{ + match params.text_document.uri.scheme().map(|scheme| scheme.to_lowercase()) { + Some(schema) if schema == "file" => { + match params.text_document.uri.to_file_path(){ + Ok(path) => { + session.log_message(MessageType::INFO, format!("File opened: {}", path.sanitize())); + let (valid, updated) = Odoo::update_file_cache(session, path.clone(), Some(&vec![TextDocumentContentChangeEvent{ + range: None, + range_length: None, + text: params.text_document.text}]), params.text_document.version); + if valid { + session.sync_odoo.opened_files.push(path.sanitize()); + if session.sync_odoo.state_init == InitState::NOT_READY { + return + } + let tree = session.sync_odoo.path_to_main_entry_tree(&path); + let tree_path = path.to_tree_path(); + if tree.is_none() || + (session.sync_odoo.get_main_entry().borrow().root.borrow().get_symbol(tree.as_ref().unwrap(), u32::MAX).is_empty() + && session.sync_odoo.get_main_entry().borrow().data_symbols.get(&path.sanitize()).is_none()) + { + //main entry doesn't handle this file. Let's test customs entries, or create a new one + let ep_mgr = session.sync_odoo.entry_point_mgr.clone(); + for custom_entry in ep_mgr.borrow().custom_entry_points.iter() { + if custom_entry.borrow().path == tree_path.sanitize() { + if updated{ + Odoo::update_file_index(session, path, true, false); + } + return; + } + } + EntryPointMgr::create_new_custom_entry_for_path(session, &tree_path.sanitize(), &path.sanitize()); + SyncOdoo::process_rebuilds(session, false); + } else if updated { Odoo::update_file_index(session, path, true, false); } - return; } + }, + Err(_) => { + warn!("Unable to get file path from URI: {}", params.text_document.uri.to_string()); + return; } - EntryPointMgr::create_new_custom_entry_for_path(session, &tree_path.sanitize(), &path.sanitize()); - SyncOdoo::process_rebuilds(session, false); - } else if updated { - Odoo::update_file_index(session, path, true, false); } + }, + Some(schema) if schema == "untitled" => { + if !["python"].contains(¶ms.text_document.language_id.as_str()) { + return; // We only handle python temporary files + } + let path = params.text_document.uri.to_string(); // In VSCode it is Untitled-N + let (valid, updated) = Odoo::update_untitled_file_cache(session, &path, &vec![TextDocumentContentChangeEvent{ + range: None, + range_length: None, + text: params.text_document.text + }], params.text_document.version); + if valid { + session.sync_odoo.opened_files.push(path.clone()); + if session.sync_odoo.state_init == InitState::NOT_READY { + return + } + if updated { + SyncOdoo::unload_untitled_file(session, &path); + SyncOdoo::process_rebuilds(session, false); + } + } + EntryPointMgr::create_new_untitled_entry_for_path(session, &path); + SyncOdoo::process_rebuilds(session, false); + }, // temporary file + Some(scheme) => { + warn!("Unsupported URI scheme: {}", scheme); + }, + None => { + warn!("No URI scheme found"); } } } pub fn handle_did_close(session: &mut SessionInfo, params: DidCloseTextDocumentParams) { - if let Ok(path) = params.text_document.uri.to_file_path().map(|path_buf| path_buf.sanitize()) { - session.log_message(MessageType::INFO, format!("File closed: {path}")); - session.sync_odoo.opened_files.retain(|x| x != &path); - let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path); - if let Some(file_info) = file_info { - file_info.borrow_mut().opened = false; + match params.text_document.uri.scheme().map(|scheme| scheme.to_lowercase()) { + Some(schema) if schema == "file" => { + match params.text_document.uri.to_file_path().map(|path_buf| path_buf.sanitize()){ + Ok(path) => { + session.log_message(MessageType::INFO, format!("File closed: {path}")); + session.sync_odoo.opened_files.retain(|x| x != &path); + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path); + if let Some(file_info) = file_info { + file_info.borrow_mut().opened = false; + } + session.sync_odoo.entry_point_mgr.borrow_mut().remove_entries_with_path(&path); + }, + Err(_) => { + warn!("Unable to get file path from URI: {}", params.text_document.uri.to_string()); + return; + } + } + }, + Some(schema) if schema == "untitled" => { + let path = params.text_document.uri.to_string(); + session.log_message(MessageType::INFO, format!("Untitled file closed: {}", path)); + session.sync_odoo.opened_files.retain(|x| x != &path); + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path); + if let Some(file_info) = file_info { + file_info.borrow_mut().opened = false; + } + session.sync_odoo.entry_point_mgr.borrow_mut().remove_untitled_entries_with_path(&path); + return; + }, + Some(scheme) => { + warn!("Unsupported URI scheme: {}", scheme); + return; + }, + None => { + warn!("No URI scheme found"); + return; } - session.sync_odoo.entry_point_mgr.borrow_mut().remove_entries_with_path(&path); } } @@ -1570,16 +1734,42 @@ impl Odoo { } pub fn handle_did_change(session: &mut SessionInfo, params: DidChangeTextDocumentParams) { - if let Ok(path) = params.text_document.uri.to_file_path() { - session.log_message(MessageType::INFO, format!("File changed: {}", path.sanitize())); - let version = params.text_document.version; - let (valid, updated) = Odoo::update_file_cache(session, path.clone(), Some(¶ms.content_changes), version); - if valid && updated { - if session.sync_odoo.state_init == InitState::NOT_READY { - return + match params.text_document.uri.scheme().map(|scheme| scheme.to_lowercase()) { + Some(schema) if schema == "file" => { + match params.text_document.uri.to_file_path(){ + Ok(path) => { + session.log_message(MessageType::INFO, format!("File changed: {}", path.sanitize())); + let version = params.text_document.version; + let (valid, updated) = Odoo::update_file_cache(session, path.clone(), Some(¶ms.content_changes), version); + if valid && updated { + if session.sync_odoo.state_init == InitState::NOT_READY { + return + } + Odoo::update_file_index(session, path, false, false); + } + }, + Err(_) => { + warn!("Unable to get file path from URI: {}", params.text_document.uri.to_string()); + return; + } } - Odoo::update_file_index(session, path, false, false); - } + }, + Some(schema) if schema == "untitled" => { + let path = params.text_document.uri.to_string(); + let (valid, updated) = Odoo::update_untitled_file_cache(session, &path, ¶ms.content_changes, params.text_document.version); + if valid && updated { + SyncOdoo::unload_untitled_file(session, &path); + SyncOdoo::process_rebuilds(session, false); + } + }, + Some(scheme) => { + warn!("Unsupported URI scheme: {}", scheme); + return; + }, + None => { + warn!("No URI scheme found"); + return; + }, } } @@ -1606,6 +1796,13 @@ impl Odoo { (false, false) } + fn update_untitled_file_cache(session: &mut SessionInfo, path: &String, content: &Vec, version: i32) -> (bool, bool) { + session.log_message(MessageType::INFO, format!("Untitled File Change Event: {}, version {}", path, version)); + let (file_updated, file_info) = session.sync_odoo.get_file_mgr().borrow_mut().update_file_info(session, path, Some(content), Some(version), false); + file_info.borrow_mut().publish_diagnostics(session); //To push potential syntax errors or refresh previous one + (true, file_updated) + } + pub fn update_file_index(session: &mut SessionInfo, path: PathBuf, _is_open: bool, force_delay: bool) { if matches!(path.extension().and_then(OsStr::to_str), Some(ext) if ["py", "xml", "csv"].contains(&ext)) || Odoo::is_config_workspace_file(session, &path){ SessionInfo::request_update_file_index(session, &path, force_delay); @@ -1616,15 +1813,45 @@ impl Odoo { session.log_message(MessageType::INFO, format!("Document symbol requested for {}", params.text_document.uri.as_str(), )); - let uri = params.text_document.uri.to_string(); - let path = FileMgr::uri2pathname(uri.as_str()); - if uri.ends_with(".py") || uri.ends_with(".pyi") || uri.ends_with(".xml") || uri.ends_with(".csv") { - let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path); - if let Some(file_info) = file_info { - if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { - file_info.borrow_mut().prepare_ast(session); + match params.text_document.uri.scheme().map(|scheme| scheme.to_lowercase()) { + Some(schema) if schema == "file" => { + let uri = params.text_document.uri.to_string(); + if !uri.ends_with(".py") && !uri.ends_with(".pyi") && !uri.ends_with(".xml") && !uri.ends_with(".csv") { + return Ok(None); + } + match params.text_document.uri.to_file_path(){ + Ok(path) => { + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path.sanitize()); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { + file_info.borrow_mut().prepare_ast(session); + } + return Ok(DocumentSymbolFeature::get_symbols(session, &file_info)); + } + }, + Err(_) => { + warn!("Unable to get file path from URI: {}", params.text_document.uri.to_string()); + return Ok(None); + } } - return Ok(DocumentSymbolFeature::get_symbols(session, &file_info)); + }, + Some(schema) if schema == "untitled" => { + let path = params.text_document.uri.to_string(); + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&path); + if let Some(file_info) = file_info { + if file_info.borrow().file_info_ast.borrow().indexed_module.is_none() { + file_info.borrow_mut().prepare_ast(session); + } + return Ok(DocumentSymbolFeature::get_symbols(session, &file_info)); + } + }, + Some(scheme) => { + warn!("Unsupported URI scheme: {}", scheme); + return Ok(None); + }, + None => { + warn!("No URI scheme found"); + return Ok(None); } } Ok(None)