Skip to content

Commit dac8073

Browse files
committed
[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."
1 parent 572c29d commit dac8073

File tree

8 files changed

+337
-22
lines changed

8 files changed

+337
-22
lines changed

server/src/core/file_mgr.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,24 +404,50 @@ impl FileInfo {
404404
Position::new(line as u32, column as u32)
405405
}
406406

407+
pub fn try_offset_to_position_with_rope(rope: &Rope, offset: usize) -> Option<Position> {
408+
let char = rope.try_byte_to_char(offset).ok()?;
409+
let line = rope.try_char_to_line(char).ok()?;
410+
let first_char_of_line = rope.try_line_to_char(line).ok()?;
411+
let column = char - first_char_of_line;
412+
Some(Position::new(line as u32, column as u32))
413+
}
414+
407415
pub fn offset_to_position(&self, offset: usize) -> Position {
408416
FileInfo::offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref().expect("no rope provided"), offset)
409417
}
410418

419+
pub fn try_offset_to_position(&self, offset: usize) -> Option<Position> {
420+
FileInfo::try_offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref()?, offset)
421+
}
422+
411423
pub fn text_range_to_range(&self, range: &TextRange) -> Range {
412424
Range {
413425
start: self.offset_to_position(range.start().to_usize()),
414426
end: self.offset_to_position(range.end().to_usize())
415427
}
416428
}
417429

430+
pub fn try_text_range_to_range(&self, range: &TextRange) -> Option<Range> {
431+
Some(Range {
432+
start: self.try_offset_to_position(range.start().to_usize())?,
433+
end: self.try_offset_to_position(range.end().to_usize())?
434+
})
435+
}
436+
418437
pub fn std_range_to_range(&self, range: &std::ops::Range<usize>) -> Range {
419438
Range {
420439
start: self.offset_to_position(range.start),
421440
end: self.offset_to_position(range.end)
422441
}
423442
}
424443

444+
pub fn try_std_range_to_range(&self, range: &std::ops::Range<usize>) -> Option<Range> {
445+
Some(Range {
446+
start: self.try_offset_to_position(range.start)?,
447+
end: self.try_offset_to_position(range.end)?
448+
})
449+
}
450+
425451
pub fn position_to_offset_with_rope(rope: &Rope, line: u32, char: u32) -> usize {
426452
let line_char = rope.try_line_to_char(line as usize).expect("unable to get char from line");
427453
rope.try_char_to_byte(line_char + char as usize).expect("unable to get byte from char")

server/src/core/odoo.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::core::xml_data::OdooData;
55
use crate::core::xml_validation::XmlValidator;
66
use crate::features::document_symbols::DocumentSymbolFeature;
77
use crate::features::references::ReferenceFeature;
8+
use crate::features::workspace_symbols::WorkspaceSymbolFeature;
89
use crate::threads::SessionInfo;
910
use crate::features::completion::CompletionFeature;
1011
use crate::features::definition::DefinitionFeature;
@@ -14,9 +15,9 @@ use std::cell::RefCell;
1415
use std::ffi::OsStr;
1516
use std::rc::{Rc, Weak};
1617
use std::sync::atomic::{AtomicBool, Ordering};
17-
use std::sync::Arc;
18+
use std::sync::{Arc, Mutex};
1819
use std::time::{Duration, Instant};
19-
use lsp_server::ResponseError;
20+
use lsp_server::{RequestId, ResponseError};
2021
use lsp_types::notification::{Notification, Progress};
2122
use lsp_types::request::WorkDoneProgressCreate;
2223
use lsp_types::*;
@@ -81,6 +82,8 @@ pub struct SyncOdoo {
8182
pub models: HashMap<OYarn, Rc<RefCell<Model>>>,
8283
pub interrupt_rebuild: Arc<AtomicBool>,
8384
pub terminate_rebuild: Arc<AtomicBool>,
85+
pub current_request_id: Option<RequestId>,
86+
pub running_request_ids: Arc<Mutex<Vec<RequestId>>>, //Arc to Server mutex for cancellation support
8487
pub watched_file_updates: u32,
8588
rebuild_arch: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
8689
rebuild_arch_eval: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
@@ -121,6 +124,8 @@ impl SyncOdoo {
121124
models: HashMap::new(),
122125
interrupt_rebuild: Arc::new(AtomicBool::new(false)),
123126
terminate_rebuild: Arc::new(AtomicBool::new(false)),
127+
current_request_id: None,
128+
running_request_ids: Arc::new(Mutex::new(vec![])),
124129
watched_file_updates: 0,
125130
rebuild_arch: PtrWeakHashSet::new(),
126131
rebuild_arch_eval: PtrWeakHashSet::new(),
@@ -898,6 +903,13 @@ impl SyncOdoo {
898903
false
899904
}
900905

906+
pub fn is_request_cancelled(&self) -> bool {
907+
if let Some(request_id) = self.current_request_id.as_ref() {
908+
return !self.running_request_ids.lock().unwrap().contains(request_id);
909+
}
910+
false
911+
}
912+
901913
pub fn get_file_mgr(&self) -> Rc<RefCell<FileMgr>> {
902914
self.file_mgr.clone()
903915
}
@@ -1685,6 +1697,21 @@ impl Odoo {
16851697
}
16861698
Ok(None)
16871699
}
1700+
1701+
pub fn handle_workspace_symbols(session: &mut SessionInfo<'_>, params: WorkspaceSymbolParams) -> Result<Option<WorkspaceSymbolResponse>, ResponseError> {
1702+
session.log_message(MessageType::INFO, format!("Workspace Symbol requested with query {}",
1703+
params.query,
1704+
));
1705+
WorkspaceSymbolFeature::get_workspace_symbols(session, params.query)
1706+
}
1707+
1708+
pub fn handle_workspace_symbols_resolve(session: &mut SessionInfo<'_>, symbol: WorkspaceSymbol) -> Result<WorkspaceSymbol, ResponseError> {
1709+
session.log_message(MessageType::INFO, format!("Workspace Symbol Resolve for symbol {}",
1710+
symbol.name,
1711+
));
1712+
WorkspaceSymbolFeature::resolve_workspace_symbol(session, &symbol)
1713+
}
1714+
16881715
/// Checks if the given path is a configuration file under one of the workspace folders.
16891716
fn is_config_workspace_file(session: &mut SessionInfo, path: &PathBuf) -> bool {
16901717
for (_, ws_dir) in session.sync_odoo.get_file_mgr().borrow().get_workspace_folders().iter() {

server/src/core/symbols/symbol.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use std::path::PathBuf;
2222
use std::rc::{Rc, Weak};
2323
use std::cell::RefCell;
2424
use std::vec;
25-
use lsp_types::{Diagnostic, DiagnosticTag, Position, Range};
25+
use lsp_types::{Diagnostic, DiagnosticTag, Position, Range, SymbolKind};
2626

2727
use crate::core::symbols::function_symbol::FunctionSymbol;
2828
use crate::core::symbols::module_symbol::ModuleSymbol;
@@ -2360,7 +2360,12 @@ impl Symbol {
23602360
for symbol in root.module_symbols.values().cloned() {
23612361
iter.push(symbol.clone());
23622362
}
2363-
}
2363+
},
2364+
Symbol::DiskDir(d) => {
2365+
for symbol in d.module_symbols.values().cloned() {
2366+
iter.push(symbol.clone());
2367+
}
2368+
},
23642369
_ => {}
23652370
}
23662371
iter.into_iter()
@@ -3056,6 +3061,19 @@ impl Symbol {
30563061
return -1;
30573062
}
30583063

3064+
pub fn get_lsp_symbol_kind(&self) -> SymbolKind {
3065+
match self.typ() {
3066+
SymType::CLASS => SymbolKind::CLASS,
3067+
SymType::FUNCTION => SymbolKind::FUNCTION,
3068+
SymType::VARIABLE => SymbolKind::VARIABLE,
3069+
SymType::FILE | SymType::CSV_FILE | SymType::XML_FILE => SymbolKind::FILE,
3070+
SymType::PACKAGE(_) => SymbolKind::PACKAGE,
3071+
SymType::NAMESPACE => SymbolKind::NAMESPACE,
3072+
SymType::DISK_DIR | SymType::COMPILED => SymbolKind::FILE,
3073+
SymType::ROOT => SymbolKind::NAMESPACE
3074+
}
3075+
}
3076+
30593077
/*fn _debug_print_graph_node(&self, acc: &mut String, level: u32) {
30603078
for _ in 0..level {
30613079
acc.push_str(" ");

server/src/features/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ pub mod features_utils;
66
pub mod hover;
77
pub mod node_index_ast;
88
pub mod references;
9+
pub mod workspace_symbols;
910
pub mod xml_ast_utils;
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
use std::{cell::{Ref, RefCell}, rc::Rc};
2+
3+
use lsp_server::{ErrorCode, ResponseError};
4+
use lsp_types::{Location, WorkspaceLocation, WorkspaceSymbol, WorkspaceSymbolResponse};
5+
use ruff_text_size::{TextRange, TextSize};
6+
7+
use crate::{S, constants::{PackageType, SymType}, core::{entry_point::EntryPointType, file_mgr::FileMgr, symbols::symbol::Symbol}, threads::SessionInfo, utils::string_fuzzy_contains};
8+
9+
pub struct WorkspaceSymbolFeature;
10+
11+
impl WorkspaceSymbolFeature {
12+
13+
pub fn get_workspace_symbols(session: &mut SessionInfo<'_>, query: String) -> Result<Option<WorkspaceSymbolResponse>, ResponseError> {
14+
let mut symbols = vec![];
15+
let ep_mgr = session.sync_odoo.entry_point_mgr.clone();
16+
let mut can_resolve_location_range = false;
17+
if let Some(cap_workspace) = session.sync_odoo.capabilities.workspace.as_ref() {
18+
if let Some(workspace_symb) = cap_workspace.symbol.as_ref() {
19+
if let Some(resolve_support) = workspace_symb.resolve_support.as_ref() {
20+
for resolvable_property in &resolve_support.properties {
21+
if resolvable_property == "location.range" {
22+
can_resolve_location_range = true;
23+
break;
24+
}
25+
}
26+
}
27+
}
28+
}
29+
for entry in ep_mgr.borrow().iter_all() {
30+
if entry.borrow().typ == EntryPointType::BUILTIN || entry.borrow().typ == EntryPointType::PUBLIC { //We don't want to search in builtins
31+
continue;
32+
}
33+
if WorkspaceSymbolFeature::browse_symbol(session, &entry.borrow().root, &query, None, None, can_resolve_location_range, &mut symbols) {
34+
return Err(ResponseError {
35+
code: ErrorCode::RequestCanceled as i32,
36+
message: S!("Workspace Symbol request cancelled"),
37+
data: None,
38+
});
39+
}
40+
}
41+
Ok(Some(WorkspaceSymbolResponse::Nested(symbols)))
42+
}
43+
44+
/**
45+
* Return true if the request has been cancelled and the cancellation should be propagated
46+
*/
47+
fn browse_symbol(session: &mut SessionInfo, symbol: &Rc<RefCell<Symbol>>, query: &String, parent: Option<String>, parent_path: Option<&String>, can_resolve_location_range: bool, results: &mut Vec<WorkspaceSymbol>) -> bool {
48+
let symbol_borrowed = symbol.borrow();
49+
if symbol_borrowed.typ() == SymType::VARIABLE {
50+
return false;
51+
}
52+
if symbol_borrowed.typ() == SymType::FILE { //to avoid too many locks
53+
if session.sync_odoo.is_request_cancelled() {
54+
return true;
55+
}
56+
}
57+
let container_name = match &parent {
58+
Some(p) => Some(p.clone()),
59+
None => None,
60+
};
61+
let path = symbol_borrowed.paths();
62+
let path = if path.len() == 1 {
63+
Some(&path[0])
64+
} else if path.len() == 0{
65+
parent_path
66+
} else {
67+
None
68+
};
69+
if path.is_some() && symbol_borrowed.has_range() {
70+
//Test if symbol should be returned
71+
if string_fuzzy_contains(&symbol_borrowed.name(), &query) {
72+
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);
73+
}
74+
//Test if symbol is a model
75+
if symbol_borrowed.typ() == SymType::CLASS && symbol_borrowed.as_class_sym()._model.is_some() {
76+
let model_data = symbol_borrowed.as_class_sym()._model.as_ref().unwrap();
77+
let model_name = S!("\"") + &model_data.name.to_string() + "\"";
78+
if string_fuzzy_contains(&model_name, &query) {
79+
WorkspaceSymbolFeature::add_symbol_to_results(session, &symbol_borrowed, &model_name, path.unwrap(), container_name.clone(), Some(symbol_borrowed.range()), can_resolve_location_range, results);
80+
}
81+
}
82+
}
83+
if symbol_borrowed.typ() == SymType::PACKAGE(PackageType::MODULE) {
84+
let module = symbol_borrowed.as_module_package();
85+
for xml_id_name in module.xml_id_locations.keys() {
86+
let xml_name = S!("xmlid.") + xml_id_name;
87+
if string_fuzzy_contains(&xml_name, &query) {
88+
let xml_data = module.get_xml_id(xml_id_name);
89+
for data in xml_data {
90+
let xml_file_symbol = data.get_xml_file_symbol();
91+
if let Some(xml_file_symbol) = xml_file_symbol {
92+
if let Some(path) = xml_file_symbol.borrow().paths().get(0) {
93+
let range = data.get_range();
94+
let text_range = TextRange::new(TextSize::new(range.start as u32), TextSize::new(range.end as u32));
95+
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);
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
for sym in symbol_borrowed.all_symbols() {
103+
if WorkspaceSymbolFeature::browse_symbol(session, &sym, query, Some(symbol_borrowed.name().to_string()), path, can_resolve_location_range, results) {
104+
return true;
105+
}
106+
}
107+
false
108+
}
109+
110+
fn add_symbol_to_results(session: &mut SessionInfo, symbol: &Ref<Symbol>, name: &String, path: &String, container_name: Option<String>, range: Option<&TextRange>, can_resolve_location_range: bool, results: &mut Vec<WorkspaceSymbol>) {
111+
let location = if can_resolve_location_range {
112+
lsp_types::OneOf::Right(WorkspaceLocation {
113+
uri: FileMgr::pathname2uri(path)
114+
})
115+
} else {
116+
let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(path);
117+
if let Some(file_info) = file_info {
118+
lsp_types::OneOf::Left(Location::new(
119+
FileMgr::pathname2uri(path),
120+
file_info.borrow().text_range_to_range(symbol.range())
121+
))
122+
} else {
123+
return;
124+
}
125+
};
126+
let data = if can_resolve_location_range && range.is_some() {
127+
Some(lsp_types::LSPAny::Array(vec![
128+
lsp_types::LSPAny::Number(serde_json::Number::from(range.as_ref().unwrap().start().to_u32())),
129+
lsp_types::LSPAny::Number(serde_json::Number::from(range.as_ref().unwrap().end().to_u32())),
130+
]))
131+
} else {
132+
None
133+
};
134+
results.push(WorkspaceSymbol {
135+
name: name.clone(),
136+
kind: symbol.get_lsp_symbol_kind(),
137+
tags: None,
138+
container_name,
139+
location: location,
140+
data: data,
141+
});
142+
}
143+
144+
pub fn resolve_workspace_symbol(session: &mut SessionInfo<'_>, symbol: &WorkspaceSymbol) -> Result<WorkspaceSymbol, ResponseError> {
145+
let mut resolved_symbol = symbol.clone();
146+
let location = match &symbol.location {
147+
lsp_types::OneOf::Left(_) => None,
148+
lsp_types::OneOf::Right(wl) => Some(wl.clone()),
149+
};
150+
if let Some(location) = location {
151+
let uri = FileMgr::uri2pathname(location.uri.as_str());
152+
let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&uri);
153+
if let Some(file_info) = file_info {
154+
if let Some(data) = symbol.data.as_ref() {
155+
if data.is_array() {
156+
let arr = data.as_array().unwrap();
157+
if arr.len() == 2 {
158+
let start_u32 = arr[0].as_u64().unwrap() as u32;
159+
let end_u32 = arr[1].as_u64().unwrap() as u32;
160+
let range = file_info.borrow().try_text_range_to_range(&TextRange::new(TextSize::new(start_u32), TextSize::new(end_u32)));
161+
if let Some(range) = range {
162+
resolved_symbol.location = lsp_types::OneOf::Left(Location::new(
163+
location.uri.clone(),
164+
range,
165+
));
166+
} else {
167+
return Err(ResponseError {
168+
code: ErrorCode::ContentModified as i32, message: S!("Unable to resolve Workspace Symbol - File content modified"), data: None
169+
})
170+
}
171+
return Ok(resolved_symbol)
172+
} else {
173+
return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - Invalid data to resolve range"), data: None })
174+
}
175+
} else {
176+
return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - Invalid data to resolve range"), data: None })
177+
}
178+
} else {
179+
return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - No data to resolve range"), data: None })
180+
}
181+
} else {
182+
return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - No file info"), data: None })
183+
}
184+
} else {
185+
return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - no provided location to resolve"), data: None })
186+
}
187+
}
188+
189+
}

0 commit comments

Comments
 (0)