Skip to content

Commit c1761ed

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 8c16351 commit c1761ed

File tree

8 files changed

+334
-22
lines changed

8 files changed

+334
-22
lines changed

server/src/core/file_mgr.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,24 +397,50 @@ impl FileInfo {
397397
Position::new(line as u32, column as u32)
398398
}
399399

400+
pub fn try_offset_to_position_with_rope(rope: &Rope, offset: usize) -> Option<Position> {
401+
let char = rope.try_byte_to_char(offset).ok()?;
402+
let line = rope.try_char_to_line(char).ok()?;
403+
let first_char_of_line = rope.try_line_to_char(line).ok()?;
404+
let column = char - first_char_of_line;
405+
Some(Position::new(line as u32, column as u32))
406+
}
407+
400408
pub fn offset_to_position(&self, offset: usize) -> Position {
401409
FileInfo::offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref().expect("no rope provided"), offset)
402410
}
403411

412+
pub fn try_offset_to_position(&self, offset: usize) -> Option<Position> {
413+
FileInfo::try_offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref().expect("no rope provided"), offset)
414+
}
415+
404416
pub fn text_range_to_range(&self, range: &TextRange) -> Range {
405417
Range {
406418
start: self.offset_to_position(range.start().to_usize()),
407419
end: self.offset_to_position(range.end().to_usize())
408420
}
409421
}
410422

423+
pub fn try_text_range_to_range(&self, range: &TextRange) -> Option<Range> {
424+
Some(Range {
425+
start: self.try_offset_to_position(range.start().to_usize())?,
426+
end: self.try_offset_to_position(range.end().to_usize())?
427+
})
428+
}
429+
411430
pub fn std_range_to_range(&self, range: &std::ops::Range<usize>) -> Range {
412431
Range {
413432
start: self.offset_to_position(range.start),
414433
end: self.offset_to_position(range.end)
415434
}
416435
}
417436

437+
pub fn try_std_range_to_range(&self, range: &std::ops::Range<usize>) -> Option<Range> {
438+
Some(Range {
439+
start: self.try_offset_to_position(range.start)?,
440+
end: self.try_offset_to_position(range.end)?
441+
})
442+
}
443+
418444
pub fn position_to_offset_with_rope(rope: &Rope, line: u32, char: u32) -> usize {
419445
let line_char = rope.try_line_to_char(line as usize).expect("unable to get char from line");
420446
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::Instant;
19-
use lsp_server::ResponseError;
20+
use lsp_server::{RequestId, ResponseError};
2021
use lsp_types::*;
2122
use request::{RegisterCapability, Request, WorkspaceConfiguration};
2223
use serde_json::Value;
@@ -78,6 +79,8 @@ pub struct SyncOdoo {
7879
pub models: HashMap<OYarn, Rc<RefCell<Model>>>,
7980
pub interrupt_rebuild: Arc<AtomicBool>,
8081
pub terminate_rebuild: Arc<AtomicBool>,
82+
pub current_request_id: Option<RequestId>,
83+
pub running_request_ids: Arc<Mutex<Vec<RequestId>>>, //Arc to Server mutex for cancellation support
8184
pub watched_file_updates: u32,
8285
rebuild_arch: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
8386
rebuild_arch_eval: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
@@ -117,6 +120,8 @@ impl SyncOdoo {
117120
models: HashMap::new(),
118121
interrupt_rebuild: Arc::new(AtomicBool::new(false)),
119122
terminate_rebuild: Arc::new(AtomicBool::new(false)),
123+
current_request_id: None,
124+
running_request_ids: Arc::new(Mutex::new(vec![])),
120125
watched_file_updates: 0,
121126
rebuild_arch: PtrWeakHashSet::new(),
122127
rebuild_arch_eval: PtrWeakHashSet::new(),
@@ -842,6 +847,13 @@ impl SyncOdoo {
842847
false
843848
}
844849

850+
pub fn is_request_cancelled(&self) -> bool {
851+
if let Some(request_id) = self.current_request_id.as_ref() {
852+
return !self.running_request_ids.lock().unwrap().contains(request_id);
853+
}
854+
false
855+
}
856+
845857
pub fn get_file_mgr(&self) -> Rc<RefCell<FileMgr>> {
846858
self.file_mgr.clone()
847859
}
@@ -1629,6 +1641,21 @@ impl Odoo {
16291641
}
16301642
Ok(None)
16311643
}
1644+
1645+
pub fn handle_workspace_symbols(session: &mut SessionInfo<'_>, params: WorkspaceSymbolParams) -> Result<Option<WorkspaceSymbolResponse>, ResponseError> {
1646+
session.log_message(MessageType::INFO, format!("Workspace Symbol requested with query {}",
1647+
params.query,
1648+
));
1649+
WorkspaceSymbolFeature::get_workspace_symbols(session, params.query)
1650+
}
1651+
1652+
pub fn handle_workspace_symbols_resolve(session: &mut SessionInfo<'_>, symbol: WorkspaceSymbol) -> Result<WorkspaceSymbol, ResponseError> {
1653+
session.log_message(MessageType::INFO, format!("Workspace Symbol Resolve for symbol {}",
1654+
symbol.name,
1655+
));
1656+
WorkspaceSymbolFeature::resolve_workspace_symbol(session, &symbol)
1657+
}
1658+
16321659
/// Checks if the given path is a configuration file under one of the workspace folders.
16331660
fn is_config_workspace_file(session: &mut SessionInfo, path: &PathBuf) -> bool {
16341661
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;
@@ -2354,7 +2354,12 @@ impl Symbol {
23542354
for symbol in root.module_symbols.values().cloned() {
23552355
iter.push(symbol.clone());
23562356
}
2357-
}
2357+
},
2358+
Symbol::DiskDir(d) => {
2359+
for symbol in d.module_symbols.values().cloned() {
2360+
iter.push(symbol.clone());
2361+
}
2362+
},
23582363
_ => {}
23592364
}
23602365
iter.into_iter()
@@ -3050,6 +3055,19 @@ impl Symbol {
30503055
return -1;
30513056
}
30523057

3058+
pub fn get_lsp_symbol_kind(&self) -> SymbolKind {
3059+
match self.typ() {
3060+
SymType::CLASS => SymbolKind::CLASS,
3061+
SymType::FUNCTION => SymbolKind::FUNCTION,
3062+
SymType::VARIABLE => SymbolKind::VARIABLE,
3063+
SymType::FILE | SymType::CSV_FILE | SymType::XML_FILE => SymbolKind::FILE,
3064+
SymType::PACKAGE(_) => SymbolKind::PACKAGE,
3065+
SymType::NAMESPACE => SymbolKind::NAMESPACE,
3066+
SymType::DISK_DIR | SymType::COMPILED => SymbolKind::FILE,
3067+
SymType::ROOT => SymbolKind::NAMESPACE
3068+
}
3069+
}
3070+
30533071
/*fn _debug_print_graph_node(&self, acc: &mut String, level: u32) {
30543072
for _ in 0..level {
30553073
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: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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+
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 {
45+
let symbol_borrowed = symbol.borrow();
46+
if symbol_borrowed.typ() == SymType::VARIABLE {
47+
return false;
48+
}
49+
if symbol_borrowed.typ() == SymType::FILE { //to avoid too many locks
50+
if session.sync_odoo.is_request_cancelled() {
51+
return true;
52+
}
53+
}
54+
let container_name = match &parent {
55+
Some(p) => Some(p.clone()),
56+
None => None,
57+
};
58+
let path = symbol_borrowed.paths();
59+
let path = if path.len() == 1 {
60+
Some(&path[0])
61+
} else if path.len() == 0{
62+
parent_path
63+
} else {
64+
None
65+
};
66+
if path.is_some() && symbol_borrowed.has_range() {
67+
//Test if symbol should be returned
68+
if string_fuzzy_contains(&symbol_borrowed.name(), &query) {
69+
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);
70+
}
71+
//Test if symbol is a model
72+
if symbol_borrowed.typ() == SymType::CLASS && symbol_borrowed.as_class_sym()._model.is_some() {
73+
let model_data = symbol_borrowed.as_class_sym()._model.as_ref().unwrap();
74+
let model_name = S!("\"") + &model_data.name.to_string() + "\"";
75+
if string_fuzzy_contains(&model_name, &query) {
76+
WorkspaceSymbolFeature::add_symbol_to_results(session, &symbol_borrowed, &model_name, path.unwrap(), container_name.clone(), Some(symbol_borrowed.range()), can_resolve_location_range, results);
77+
}
78+
}
79+
}
80+
if symbol_borrowed.typ() == SymType::PACKAGE(PackageType::MODULE) {
81+
let module = symbol_borrowed.as_module_package();
82+
for xml_id_name in module.xml_id_locations.keys() {
83+
let xml_name = S!("xmlid.") + xml_id_name;
84+
if string_fuzzy_contains(&xml_name, &query) {
85+
let xml_data = module.get_xml_id(xml_id_name);
86+
for data in xml_data {
87+
let xml_file_symbol = data.get_xml_file_symbol();
88+
if let Some(xml_file_symbol) = xml_file_symbol {
89+
if let Some(path) = xml_file_symbol.borrow().paths().get(0) {
90+
let range = data.get_range();
91+
let text_range = TextRange::new(TextSize::new(range.start as u32), TextSize::new(range.end as u32));
92+
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);
93+
}
94+
}
95+
}
96+
}
97+
}
98+
}
99+
for sym in symbol_borrowed.all_symbols() {
100+
if WorkspaceSymbolFeature::browse_symbol(session, &sym, query, Some(symbol_borrowed.name().to_string()), path, can_resolve_location_range, results) {
101+
return true;
102+
}
103+
}
104+
false
105+
}
106+
107+
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>) {
108+
let location = if can_resolve_location_range {
109+
lsp_types::OneOf::Right(WorkspaceLocation {
110+
uri: FileMgr::pathname2uri(path)
111+
})
112+
} else {
113+
let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(path);
114+
if let Some(file_info) = file_info {
115+
lsp_types::OneOf::Left(Location::new(
116+
FileMgr::pathname2uri(path),
117+
file_info.borrow().text_range_to_range(symbol.range())
118+
))
119+
} else {
120+
return;
121+
}
122+
};
123+
let data = if can_resolve_location_range && range.is_some() {
124+
Some(lsp_types::LSPAny::Array(vec![
125+
lsp_types::LSPAny::Number(serde_json::Number::from(range.as_ref().unwrap().start().to_u32())),
126+
lsp_types::LSPAny::Number(serde_json::Number::from(range.as_ref().unwrap().end().to_u32())),
127+
]))
128+
} else {
129+
None
130+
};
131+
results.push(WorkspaceSymbol {
132+
name: name.clone(),
133+
kind: symbol.get_lsp_symbol_kind(),
134+
tags: None,
135+
container_name,
136+
location: location,
137+
data: data,
138+
});
139+
}
140+
141+
pub fn resolve_workspace_symbol(session: &mut SessionInfo<'_>, symbol: &WorkspaceSymbol) -> Result<WorkspaceSymbol, ResponseError> {
142+
let mut resolved_symbol = symbol.clone();
143+
let location = match &symbol.location {
144+
lsp_types::OneOf::Left(_) => None,
145+
lsp_types::OneOf::Right(wl) => Some(wl.clone()),
146+
};
147+
if let Some(location) = location {
148+
let uri = FileMgr::uri2pathname(location.uri.as_str());
149+
let file_info = session.sync_odoo.get_file_mgr().borrow().get_file_info(&uri);
150+
if let Some(file_info) = file_info {
151+
if let Some(data) = symbol.data.as_ref() {
152+
if data.is_array() {
153+
let arr = data.as_array().unwrap();
154+
if arr.len() == 2 {
155+
let start_u32 = arr[0].as_u64().unwrap() as u32;
156+
let end_u32 = arr[1].as_u64().unwrap() as u32;
157+
let range = file_info.borrow().try_text_range_to_range(&TextRange::new(TextSize::new(start_u32), TextSize::new(end_u32)));
158+
if let Some(range) = range {
159+
resolved_symbol.location = lsp_types::OneOf::Left(Location::new(
160+
location.uri.clone(),
161+
range,
162+
));
163+
} else {
164+
return Err(ResponseError {
165+
code: ErrorCode::ContentModified as i32, message: S!("Unable to resolve Workspace Symbol - File content modified"), data: None
166+
})
167+
}
168+
return Ok(resolved_symbol)
169+
} else {
170+
return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - Invalid data to resolve range"), data: None })
171+
}
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 - No 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 file info"), data: None })
180+
}
181+
} else {
182+
return Err(ResponseError { code: ErrorCode::InternalError as i32, message: S!("Unable to resolve Workspace Symbol - no provided location to resolve"), data: None })
183+
}
184+
}
185+
186+
}

0 commit comments

Comments
 (0)