Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions server/src/core/file_mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,24 +397,50 @@ impl FileInfo {
Position::new(line as u32, column as u32)
}

pub fn try_offset_to_position_with_rope(rope: &Rope, offset: usize) -> Option<Position> {
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<Position> {
FileInfo::try_offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref().expect("no rope provided"), offset)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you want to also not expect here?

Perhaps:

Suggested change
FileInfo::try_offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref().expect("no rope provided"), offset)
FileInfo::try_offset_to_position_with_rope(self.file_info_ast.borrow().text_rope.as_ref()?, offset)

}

pub fn text_range_to_range(&self, range: &TextRange) -> Range {
Range {
start: self.offset_to_position(range.start().to_usize()),
end: self.offset_to_position(range.end().to_usize())
}
}

pub fn try_text_range_to_range(&self, range: &TextRange) -> Option<Range> {
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<usize>) -> Range {
Range {
start: self.offset_to_position(range.start),
end: self.offset_to_position(range.end)
}
}

pub fn try_std_range_to_range(&self, range: &std::ops::Range<usize>) -> Option<Range> {
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")
Expand Down
31 changes: 29 additions & 2 deletions server/src/core/odoo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -78,6 +79,8 @@ pub struct SyncOdoo {
pub models: HashMap<OYarn, Rc<RefCell<Model>>>,
pub interrupt_rebuild: Arc<AtomicBool>,
pub terminate_rebuild: Arc<AtomicBool>,
pub current_request_id: Option<RequestId>,
pub running_request_ids: Arc<Mutex<Vec<RequestId>>>, //Arc to Server mutex for cancellation support
pub watched_file_updates: u32,
rebuild_arch: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
rebuild_arch_eval: PtrWeakHashSet<Weak<RefCell<Symbol>>>,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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<RefCell<FileMgr>> {
self.file_mgr.clone()
}
Expand Down Expand Up @@ -1629,6 +1641,21 @@ impl Odoo {
}
Ok(None)
}

pub fn handle_workspace_symbols(session: &mut SessionInfo<'_>, params: WorkspaceSymbolParams) -> Result<Option<WorkspaceSymbolResponse>, 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<WorkspaceSymbol, ResponseError> {
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() {
Expand Down
22 changes: 20 additions & 2 deletions server/src/core/symbols/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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(" ");
Expand Down
1 change: 1 addition & 0 deletions server/src/features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
186 changes: 186 additions & 0 deletions server/src/features/workspace_symbols.rs
Original file line number Diff line number Diff line change
@@ -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<Option<WorkspaceSymbolResponse>, 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<RefCell<Symbol>>, query: &String, parent: Option<String>, parent_path: Option<&String>, can_resolve_location_range: bool, results: &mut Vec<WorkspaceSymbol>) -> bool {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be nice to explain what the boolean means?

It is slightly confusing that it returns true if it is cancelled

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<Symbol>, name: &String, path: &String, container_name: Option<String>, range: Option<&TextRange>, can_resolve_location_range: bool, results: &mut Vec<WorkspaceSymbol>) {
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<WorkspaceSymbol, ResponseError> {
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 })
}
}

}
Loading