From c1761edf74db15ecaa44805b1dc42731dfdd0940 Mon Sep 17 00:00:00 2001 From: fda-odoo Date: Thu, 30 Oct 2025 15:11:15 +0100 Subject: [PATCH] [IMP] Workspace Symbol request and $/Cancel support Result contains classes and functions that contains the query. It will return model names too, pointing to all the classes implementing it. An empty query is returning nothing, to prevent too many results XML_ID are returned too, but prefixed with "xmlid." --- server/src/core/file_mgr.rs | 26 ++++ server/src/core/odoo.rs | 31 +++- server/src/core/symbols/symbol.rs | 22 ++- server/src/features/mod.rs | 1 + server/src/features/workspace_symbols.rs | 186 +++++++++++++++++++++++ server/src/server.rs | 42 +++-- server/src/threads.rs | 29 +++- server/src/utils.rs | 19 +++ 8 files changed, 334 insertions(+), 22 deletions(-) create mode 100644 server/src/features/workspace_symbols.rs diff --git a/server/src/core/file_mgr.rs b/server/src/core/file_mgr.rs index f113bc67..34a993e0 100644 --- a/server/src/core/file_mgr.rs +++ b/server/src/core/file_mgr.rs @@ -397,10 +397,22 @@ impl FileInfo { Position::new(line as u32, column as u32) } + pub fn try_offset_to_position_with_rope(rope: &Rope, offset: usize) -> Option { + let char = rope.try_byte_to_char(offset).ok()?; + let line = rope.try_char_to_line(char).ok()?; + let first_char_of_line = rope.try_line_to_char(line).ok()?; + let column = char - first_char_of_line; + Some(Position::new(line as u32, column as u32)) + } + pub fn offset_to_position(&self, offset: usize) -> Position { FileInfo::offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref().expect("no rope provided"), offset) } + pub fn try_offset_to_position(&self, offset: usize) -> Option { + FileInfo::try_offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref().expect("no rope provided"), offset) + } + pub fn text_range_to_range(&self, range: &TextRange) -> Range { Range { start: self.offset_to_position(range.start().to_usize()), @@ -408,6 +420,13 @@ impl FileInfo { } } + pub fn try_text_range_to_range(&self, range: &TextRange) -> Option { + Some(Range { + start: self.try_offset_to_position(range.start().to_usize())?, + end: self.try_offset_to_position(range.end().to_usize())? + }) + } + pub fn std_range_to_range(&self, range: &std::ops::Range) -> Range { Range { start: self.offset_to_position(range.start), @@ -415,6 +434,13 @@ impl FileInfo { } } + pub fn try_std_range_to_range(&self, range: &std::ops::Range) -> Option { + Some(Range { + start: self.try_offset_to_position(range.start)?, + end: self.try_offset_to_position(range.end)? + }) + } + pub fn position_to_offset_with_rope(rope: &Rope, line: u32, char: u32) -> usize { let line_char = rope.try_line_to_char(line as usize).expect("unable to get char from line"); rope.try_char_to_byte(line_char + char as usize).expect("unable to get byte from char") diff --git a/server/src/core/odoo.rs b/server/src/core/odoo.rs index 40a06ffc..54016288 100644 --- a/server/src/core/odoo.rs +++ b/server/src/core/odoo.rs @@ -5,6 +5,7 @@ use crate::core::xml_data::OdooData; use crate::core::xml_validation::XmlValidator; use crate::features::document_symbols::DocumentSymbolFeature; use crate::features::references::ReferenceFeature; +use crate::features::workspace_symbols::WorkspaceSymbolFeature; use crate::threads::SessionInfo; use crate::features::completion::CompletionFeature; use crate::features::definition::DefinitionFeature; @@ -14,9 +15,9 @@ use std::cell::RefCell; use std::ffi::OsStr; use std::rc::{Rc, Weak}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use std::time::Instant; -use lsp_server::ResponseError; +use lsp_server::{RequestId, ResponseError}; use lsp_types::*; use request::{RegisterCapability, Request, WorkspaceConfiguration}; use serde_json::Value; @@ -78,6 +79,8 @@ pub struct SyncOdoo { pub models: HashMap>>, pub interrupt_rebuild: Arc, pub terminate_rebuild: Arc, + pub current_request_id: Option, + pub running_request_ids: Arc>>, //Arc to Server mutex for cancellation support pub watched_file_updates: u32, rebuild_arch: PtrWeakHashSet>>, rebuild_arch_eval: PtrWeakHashSet>>, @@ -117,6 +120,8 @@ impl SyncOdoo { models: HashMap::new(), interrupt_rebuild: Arc::new(AtomicBool::new(false)), terminate_rebuild: Arc::new(AtomicBool::new(false)), + current_request_id: None, + running_request_ids: Arc::new(Mutex::new(vec![])), watched_file_updates: 0, rebuild_arch: PtrWeakHashSet::new(), rebuild_arch_eval: PtrWeakHashSet::new(), @@ -842,6 +847,13 @@ impl SyncOdoo { false } + pub fn is_request_cancelled(&self) -> bool { + if let Some(request_id) = self.current_request_id.as_ref() { + return !self.running_request_ids.lock().unwrap().contains(request_id); + } + false + } + pub fn get_file_mgr(&self) -> Rc> { self.file_mgr.clone() } @@ -1629,6 +1641,21 @@ impl Odoo { } Ok(None) } + + pub fn handle_workspace_symbols(session: &mut SessionInfo<'_>, params: WorkspaceSymbolParams) -> Result, ResponseError> { + session.log_message(MessageType::INFO, format!("Workspace Symbol requested with query {}", + params.query, + )); + WorkspaceSymbolFeature::get_workspace_symbols(session, params.query) + } + + pub fn handle_workspace_symbols_resolve(session: &mut SessionInfo<'_>, symbol: WorkspaceSymbol) -> Result { + session.log_message(MessageType::INFO, format!("Workspace Symbol Resolve for symbol {}", + symbol.name, + )); + WorkspaceSymbolFeature::resolve_workspace_symbol(session, &symbol) + } + /// 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() { diff --git a/server/src/core/symbols/symbol.rs b/server/src/core/symbols/symbol.rs index 27953c11..9e15bf46 100644 --- a/server/src/core/symbols/symbol.rs +++ b/server/src/core/symbols/symbol.rs @@ -22,7 +22,7 @@ use std::path::PathBuf; use std::rc::{Rc, Weak}; use std::cell::RefCell; use std::vec; -use lsp_types::{Diagnostic, DiagnosticTag, Position, Range}; +use lsp_types::{Diagnostic, DiagnosticTag, Position, Range, SymbolKind}; use crate::core::symbols::function_symbol::FunctionSymbol; use crate::core::symbols::module_symbol::ModuleSymbol; @@ -2354,7 +2354,12 @@ impl Symbol { for symbol in root.module_symbols.values().cloned() { iter.push(symbol.clone()); } - } + }, + Symbol::DiskDir(d) => { + for symbol in d.module_symbols.values().cloned() { + iter.push(symbol.clone()); + } + }, _ => {} } iter.into_iter() @@ -3050,6 +3055,19 @@ impl Symbol { return -1; } + pub fn get_lsp_symbol_kind(&self) -> SymbolKind { + match self.typ() { + SymType::CLASS => SymbolKind::CLASS, + SymType::FUNCTION => SymbolKind::FUNCTION, + SymType::VARIABLE => SymbolKind::VARIABLE, + SymType::FILE | SymType::CSV_FILE | SymType::XML_FILE => SymbolKind::FILE, + SymType::PACKAGE(_) => SymbolKind::PACKAGE, + SymType::NAMESPACE => SymbolKind::NAMESPACE, + SymType::DISK_DIR | SymType::COMPILED => SymbolKind::FILE, + SymType::ROOT => SymbolKind::NAMESPACE + } + } + /*fn _debug_print_graph_node(&self, acc: &mut String, level: u32) { for _ in 0..level { acc.push_str(" "); diff --git a/server/src/features/mod.rs b/server/src/features/mod.rs index 15be2845..e64f22f5 100644 --- a/server/src/features/mod.rs +++ b/server/src/features/mod.rs @@ -6,4 +6,5 @@ pub mod features_utils; pub mod hover; pub mod node_index_ast; pub mod references; +pub mod workspace_symbols; pub mod xml_ast_utils; \ No newline at end of file diff --git a/server/src/features/workspace_symbols.rs b/server/src/features/workspace_symbols.rs new file mode 100644 index 00000000..b8086b63 --- /dev/null +++ b/server/src/features/workspace_symbols.rs @@ -0,0 +1,186 @@ +use std::{cell::{Ref, RefCell}, rc::Rc}; + +use lsp_server::{ErrorCode, ResponseError}; +use lsp_types::{Location, WorkspaceLocation, WorkspaceSymbol, WorkspaceSymbolResponse}; +use ruff_text_size::{TextRange, TextSize}; + +use crate::{S, constants::{PackageType, SymType}, core::{entry_point::EntryPointType, file_mgr::FileMgr, symbols::symbol::Symbol}, threads::SessionInfo, utils::string_fuzzy_contains}; + +pub struct WorkspaceSymbolFeature; + +impl WorkspaceSymbolFeature { + + pub fn get_workspace_symbols(session: &mut SessionInfo<'_>, query: String) -> Result, ResponseError> { + let mut symbols = vec![]; + let ep_mgr = session.sync_odoo.entry_point_mgr.clone(); + let mut can_resolve_location_range = false; + if let Some(cap_workspace) = session.sync_odoo.capabilities.workspace.as_ref() { + if let Some(workspace_symb) = cap_workspace.symbol.as_ref() { + if let Some(resolve_support) = workspace_symb.resolve_support.as_ref() { + for resolvable_property in &resolve_support.properties { + if resolvable_property == "location.range" { + can_resolve_location_range = true; + break; + } + } + } + } + } + for entry in ep_mgr.borrow().iter_all() { + if entry.borrow().typ == EntryPointType::BUILTIN || entry.borrow().typ == EntryPointType::PUBLIC { //We don't want to search in builtins + continue; + } + if WorkspaceSymbolFeature::browse_symbol(session, &entry.borrow().root, &query, None, None, can_resolve_location_range, &mut symbols) { + return Err(ResponseError { + code: ErrorCode::RequestCanceled as i32, + message: S!("Workspace Symbol request cancelled"), + data: None, + }); + } + } + Ok(Some(WorkspaceSymbolResponse::Nested(symbols))) + } + + fn browse_symbol(session: &mut SessionInfo, symbol: &Rc>, query: &String, parent: Option, parent_path: Option<&String>, can_resolve_location_range: bool, results: &mut Vec) -> bool { + let symbol_borrowed = symbol.borrow(); + if symbol_borrowed.typ() == SymType::VARIABLE { + return false; + } + if symbol_borrowed.typ() == SymType::FILE { //to avoid too many locks + if session.sync_odoo.is_request_cancelled() { + return true; + } + } + let container_name = match &parent { + Some(p) => Some(p.clone()), + None => None, + }; + let path = symbol_borrowed.paths(); + let path = if path.len() == 1 { + Some(&path[0]) + } else if path.len() == 0{ + parent_path + } else { + None + }; + if path.is_some() && symbol_borrowed.has_range() { + //Test if symbol should be returned + if string_fuzzy_contains(&symbol_borrowed.name(), &query) { + WorkspaceSymbolFeature::add_symbol_to_results(session, &symbol_borrowed, &symbol_borrowed.name().to_string(), path.unwrap(), container_name.clone(), Some(symbol_borrowed.range()), can_resolve_location_range, results); + } + //Test if symbol is a model + if symbol_borrowed.typ() == SymType::CLASS && symbol_borrowed.as_class_sym()._model.is_some() { + let model_data = symbol_borrowed.as_class_sym()._model.as_ref().unwrap(); + let model_name = S!("\"") + &model_data.name.to_string() + "\""; + if string_fuzzy_contains(&model_name, &query) { + WorkspaceSymbolFeature::add_symbol_to_results(session, &symbol_borrowed, &model_name, path.unwrap(), container_name.clone(), Some(symbol_borrowed.range()), can_resolve_location_range, results); + } + } + } + if symbol_borrowed.typ() == SymType::PACKAGE(PackageType::MODULE) { + let module = symbol_borrowed.as_module_package(); + for xml_id_name in module.xml_id_locations.keys() { + let xml_name = S!("xmlid.") + xml_id_name; + if string_fuzzy_contains(&xml_name, &query) { + let xml_data = module.get_xml_id(xml_id_name); + for data in xml_data { + let xml_file_symbol = data.get_xml_file_symbol(); + if let Some(xml_file_symbol) = xml_file_symbol { + if let Some(path) = xml_file_symbol.borrow().paths().get(0) { + let range = data.get_range(); + let text_range = TextRange::new(TextSize::new(range.start as u32), TextSize::new(range.end as u32)); + WorkspaceSymbolFeature::add_symbol_to_results(session, &xml_file_symbol.borrow(), &xml_name, path, Some(symbol_borrowed.name().to_string()), Some(&text_range), can_resolve_location_range, results); + } + } + } + } + } + } + for sym in symbol_borrowed.all_symbols() { + if WorkspaceSymbolFeature::browse_symbol(session, &sym, query, Some(symbol_borrowed.name().to_string()), path, can_resolve_location_range, results) { + return true; + } + } + false + } + + fn add_symbol_to_results(session: &mut SessionInfo, symbol: &Ref, name: &String, path: &String, container_name: Option, range: Option<&TextRange>, can_resolve_location_range: bool, results: &mut Vec) { + let location = if can_resolve_location_range { + lsp_types::OneOf::Right(WorkspaceLocation { + uri: FileMgr::pathname2uri(path) + }) + } else { + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(path); + if let Some(file_info) = file_info { + lsp_types::OneOf::Left(Location::new( + FileMgr::pathname2uri(path), + file_info.borrow().text_range_to_range(symbol.range()) + )) + } else { + return; + } + }; + let data = if can_resolve_location_range && range.is_some() { + Some(lsp_types::LSPAny::Array(vec![ + lsp_types::LSPAny::Number(serde_json::Number::from(range.as_ref().unwrap().start().to_u32())), + lsp_types::LSPAny::Number(serde_json::Number::from(range.as_ref().unwrap().end().to_u32())), + ])) + } else { + None + }; + results.push(WorkspaceSymbol { + name: name.clone(), + kind: symbol.get_lsp_symbol_kind(), + tags: None, + container_name, + location: location, + data: data, + }); + } + + pub fn resolve_workspace_symbol(session: &mut SessionInfo<'_>, symbol: &WorkspaceSymbol) -> Result { + let mut resolved_symbol = symbol.clone(); + let location = match &symbol.location { + lsp_types::OneOf::Left(_) => None, + lsp_types::OneOf::Right(wl) => Some(wl.clone()), + }; + if let Some(location) = location { + let uri = FileMgr::uri2pathname(location.uri.as_str()); + let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&uri); + if let Some(file_info) = file_info { + if let Some(data) = symbol.data.as_ref() { + if data.is_array() { + let arr = data.as_array().unwrap(); + if arr.len() == 2 { + let start_u32 = arr[0].as_u64().unwrap() as u32; + let end_u32 = arr[1].as_u64().unwrap() as u32; + let range = file_info.borrow().try_text_range_to_range(&TextRange::new(TextSize::new(start_u32), TextSize::new(end_u32))); + if let Some(range) = range { + resolved_symbol.location = lsp_types::OneOf::Left(Location::new( + location.uri.clone(), + range, + )); + } else { + return Err(ResponseError { + code: ErrorCode::ContentModified as i32, message: S!("Unable to resolve Workspace Symbol - File content modified"), data: None + }) + } + return Ok(resolved_symbol) + } else { + return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - Invalid data to resolve range"), data: None }) + } + } else { + return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - Invalid data to resolve range"), data: None }) + } + } else { + return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - No data to resolve range"), data: None }) + } + } else { + return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - No file info"), data: None }) + } + } else { + return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - no provided location to resolve"), data: None }) + } + } + +} \ No newline at end of file diff --git a/server/src/server.rs b/server/src/server.rs index 6f6e4541..3dbe75de 100644 --- a/server/src/server.rs +++ b/server/src/server.rs @@ -1,9 +1,8 @@ -use std::{io::Error, panic, sync::{atomic::AtomicBool, Arc, Mutex}, thread::JoinHandle}; +use std::{io::Error, panic, sync::{self, Arc, Mutex, atomic::AtomicBool}, thread::JoinHandle}; use crossbeam_channel::{Receiver, Select, Sender}; use lsp_server::{Connection, IoThreads, Message, ProtocolError, RequestId, ResponseError}; -use lsp_types::{notification::{DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, - DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, Notification}, request::{Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, References, Request, ResolveCompletionItem, Shutdown}, CompletionOptions, DefinitionOptions, DocumentSymbolOptions, FileOperationFilter, FileOperationPattern, FileOperationRegistrationOptions, HoverProviderCapability, InitializeParams, InitializeResult, OneOf, ReferencesOptions, SaveOptions, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities}; +use lsp_types::{CancelParams, CompletionOptions, DefinitionOptions, DocumentSymbolOptions, FileOperationFilter, FileOperationPattern, FileOperationRegistrationOptions, HoverProviderCapability, InitializeParams, InitializeResult, OneOf, ReferencesOptions, SaveOptions, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, WorkspaceSymbolOptions, notification::{Cancel, DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, Notification}, request::{Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, References, Request, ResolveCompletionItem, Shutdown, WorkspaceSymbolRequest, WorkspaceSymbolResolve}}; use serde_json::json; #[cfg(target_os = "linux")] use nix; @@ -28,8 +27,9 @@ pub struct Server { delayed_process_thread: JoinHandle<()>, sender_to_delayed_process: Sender, //unique channel to delayed process thread sync_odoo: Arc>, - interrupt_rebuild_boolean: Arc, - terminate_rebuild_boolean: Arc, + interrupt_rebuild_boolean: Arc, //ref to the one on sync_odoo + terminate_rebuild_boolean: Arc, //ref to the one on sync_odoo + running_request_ids: Arc>>, //ref to the one on sync_odoo, but with dedicated mutex } #[derive(Debug)] @@ -73,6 +73,7 @@ impl Server { let sync_odoo = Arc::new(Mutex::new(SyncOdoo::new())); let interrupt_rebuild_boolean = sync_odoo.lock().unwrap().interrupt_rebuild.clone(); let terminate_rebuild_boolean = sync_odoo.lock().unwrap().terminate_rebuild.clone(); + let running_request_ids = sync_odoo.lock().unwrap().running_request_ids.clone(); let mut receivers_w_to_s = vec![]; let (sender_to_delayed_process, receiver_delayed_process) = crossbeam_channel::unbounded(); let (req_sender_s_to_main, generic_receiver_s_to_main) = crossbeam_channel::unbounded(); //unique channel to dispatch to any ready main thread @@ -83,8 +84,9 @@ impl Server { let main_thread = { let sync_odoo = sync_odoo.clone(); let sender_to_delayed_process = sender_to_delayed_process.clone(); + let running_request_ids = running_request_ids.clone(); std::thread::spawn(move || { - message_processor_thread_main(sync_odoo, generic_receiver_s_to_main, sender_main_to_s.clone(), receiver_s_to_main.clone(), sender_to_delayed_process); + message_processor_thread_main(sync_odoo, generic_receiver_s_to_main, sender_main_to_s.clone(), receiver_s_to_main.clone(), sender_to_delayed_process, running_request_ids); }) }; @@ -108,7 +110,8 @@ impl Server { delayed_process_thread, sync_odoo: sync_odoo, interrupt_rebuild_boolean: interrupt_rebuild_boolean, - terminate_rebuild_boolean + terminate_rebuild_boolean, + running_request_ids: running_request_ids, } } @@ -175,6 +178,12 @@ impl Server { work_done_progress: Some(false) }, })), + workspace_symbol_provider: Some(OneOf::Right(WorkspaceSymbolOptions { + resolve_provider: Some(true), + work_done_progress_options: WorkDoneProgressOptions { + work_done_progress: Some(false) + }, + })), workspace: Some(WorkspaceServerCapabilities { workspace_folders: Some(WorkspaceFoldersServerCapabilities { supported: Some(true), @@ -310,6 +319,14 @@ impl Server { } self.shutdown_threads("Got a client exit notification. Exiting."); break; + } else if n.method == Cancel::METHOD { + let cancel_params = serde_json::from_value::(n.params.clone()).unwrap(); + let req_id: RequestId = match cancel_params.id { + lsp_types::NumberOrString::Number(id) => id.into(), + lsp_types::NumberOrString::String(id) => id.into(), + }; + self.running_request_ids.lock().unwrap().retain(|id| id != &req_id); + continue; } } self.forward_message(msg); @@ -352,15 +369,10 @@ impl Server { fn forward_message(&mut self, msg: Message) { match msg { Message::Request(r) => { + self.running_request_ids.lock().unwrap().push(r.id.clone()); match r.method.as_str() { - HoverRequest::METHOD | GotoDefinition::METHOD | References::METHOD | DocumentSymbolRequest::METHOD=> { - self.interrupt_rebuild_boolean.store(true, std::sync::atomic::Ordering::SeqCst); - if DEBUG_THREADS { - info!("Sending request to main thread : {} - {}", r.method, r.id); - } - self.req_sender_s_to_main.send(Message::Request(r)).unwrap(); - }, - Completion::METHOD => { + HoverRequest::METHOD | GotoDefinition::METHOD | References::METHOD | DocumentSymbolRequest::METHOD | + WorkspaceSymbolRequest::METHOD | WorkspaceSymbolResolve::METHOD | Completion::METHOD => { self.interrupt_rebuild_boolean.store(true, std::sync::atomic::Ordering::SeqCst); if DEBUG_THREADS { info!("Sending request to main thread : {} - {}", r.method, r.id); diff --git a/server/src/threads.rs b/server/src/threads.rs index c7bb760c..dabb66d3 100644 --- a/server/src/threads.rs +++ b/server/src/threads.rs @@ -2,9 +2,9 @@ use std::{collections::VecDeque, path::PathBuf, sync::{Arc, Mutex}, time::Instan use crossbeam_channel::{Receiver, Sender, TryRecvError}; use lsp_server::{Message, RequestId, Response, ResponseError}; -use lsp_types::{notification::{DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, +use lsp_types::{CompletionResponse, DocumentSymbolResponse, Hover, Location, LogMessageParams, MessageType, ShowMessageParams, WorkspaceSymbol, WorkspaceSymbolResponse, notification::{DidChangeConfiguration, DidChangeTextDocument, DidChangeWatchedFiles, DidChangeWorkspaceFolders, DidCloseTextDocument, DidCreateFiles, DidDeleteFiles, DidOpenTextDocument, DidRenameFiles, DidSaveTextDocument, LogMessage, - Notification, ShowMessage}, request::{Completion, DocumentSymbolRequest, GotoDefinition, GotoTypeDefinitionResponse, HoverRequest, References, Request, Shutdown}, CompletionResponse, DocumentSymbolResponse, Hover, Location, LogMessageParams, MessageType, ShowMessageParams}; + Notification, ShowMessage}, request::{Completion, DocumentSymbolRequest, GotoDefinition, GotoTypeDefinitionResponse, HoverRequest, References, Request, Shutdown, WorkspaceSymbolRequest, WorkspaceSymbolResolve}}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::Value; use tracing::{error, info, warn}; @@ -153,6 +153,18 @@ fn to_value(result: Result, ResponseEr (value, error) } +fn to_value_not_null(result: Result) -> (Option, Option) { + let value = match &result { + Ok(r) => Some(serde_json::json!(r)), + Err(_) => None + }; + let mut error = None; + if result.is_err() { + error = Some(result.unwrap_err()); + } + (value, error) +} + #[derive(Debug)] pub struct UpdateFileIndexData { pub path: PathBuf, @@ -252,7 +264,7 @@ pub fn delayed_changes_process_thread(sender_session: Sender, receiver_ } } -pub fn message_processor_thread_main(sync_odoo: Arc>, generic_receiver: Receiver, sender: Sender, receiver: Receiver, delayed_process_sender: Sender) { +pub fn message_processor_thread_main(sync_odoo: Arc>, generic_receiver: Receiver, sender: Sender, receiver: Receiver, delayed_process_sender: Sender, running_request_ids: Arc>>) { let mut buffer = VecDeque::new(); loop { // Drain all available messages into buffer @@ -295,6 +307,7 @@ pub fn message_processor_thread_main(sync_odoo: Arc>, generic_re if let Some(msg) = buffer.pop_front() { match msg { Message::Request(r) => { + sync_odoo.lock().unwrap().current_request_id = Some(r.id.clone()); let (value, error) = match r.method.as_str() { HoverRequest::METHOD => { let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); @@ -315,6 +328,14 @@ pub fn message_processor_thread_main(sync_odoo: Arc>, generic_re let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); to_value::(Odoo::handle_document_symbols(&mut session, serde_json::from_value(r.params).unwrap())) }, + WorkspaceSymbolRequest::METHOD => { + let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); + to_value::(Odoo::handle_workspace_symbols(&mut session, serde_json::from_value(r.params).unwrap())) + }, + WorkspaceSymbolResolve::METHOD => { + let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); + to_value_not_null::(Odoo::handle_workspace_symbols_resolve(&mut session, serde_json::from_value(r.params).unwrap())) + }, Completion::METHOD => { let mut session = create_session!(sender, receiver, sync_odoo, delayed_process_sender); SyncOdoo::process_rebuilds(&mut session, true); @@ -326,6 +347,8 @@ pub fn message_processor_thread_main(sync_odoo: Arc>, generic_re data: None }))} }; + sync_odoo.lock().unwrap().current_request_id = None; + running_request_ids.lock().unwrap().retain(|id| id != &r.id); sender.send(Message::Response(Response { id: r.id, result: value, error: error })).unwrap(); }, Message::Notification(n) => { diff --git a/server/src/utils.rs b/server/src/utils.rs index 85902788..717706ed 100644 --- a/server/src/utils.rs +++ b/server/src/utils.rs @@ -334,6 +334,25 @@ pub fn is_python_path(path: &String) -> bool { } } +pub fn string_fuzzy_contains(string: &str, pattern: &str) -> bool { + let mut pattern_char_iter = pattern.chars(); + let mut pattern_char = match pattern_char_iter.next() { + Some(c) => c.to_ascii_lowercase(), + None => return true, + }; + for char in string.chars() { + if char.to_ascii_lowercase() == pattern_char { + pattern_char = match pattern_char_iter.next() { + Some(c) => c.to_ascii_lowercase(), + None => { + return true; + } + }; + } + } + false +} + #[macro_export] macro_rules! warn_or_panic { ($($arg:tt)*) => {