Skip to content

Commit b858abc

Browse files
committed
[IMP] core: autocompletion for import statements
1 parent 0cc3ffe commit b858abc

File tree

2 files changed

+183
-47
lines changed

2 files changed

+183
-47
lines changed

server/src/core/import_resolver.rs

Lines changed: 165 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use glob::glob;
2-
use lsp_types::{Diagnostic, DiagnosticTag, Position, Range};
2+
use itertools::Itertools;
3+
use lsp_types::{CompletionItemKind, Diagnostic, DiagnosticTag, Position, Range};
34
use ruff_python_ast::name::Name;
5+
use serde::Serialize;
46
use tracing::error;
57
use std::collections::{HashMap, HashSet};
68
use std::rc::Rc;
@@ -263,6 +265,9 @@ fn _get_or_create_symbol(session: &mut SessionInfo, for_entry: &Rc<RefCell<Entry
263265
let mut sym: Option<Rc<RefCell<Symbol>>> = symbol.clone();
264266
let mut last_symbol = symbol.clone();
265267
for branch in names.iter() {
268+
if branch.is_empty() {
269+
continue;
270+
}
266271
match sym {
267272
Some(ref s) => {
268273
let mut next_symbol = s.borrow().get_symbol(&(vec![branch.clone()], vec![]), u32::MAX);
@@ -359,6 +364,9 @@ fn _get_or_create_symbol(session: &mut SessionInfo, for_entry: &Rc<RefCell<Entry
359364
}
360365

361366
fn _resolve_new_symbol(session: &mut SessionInfo, parent: Rc<RefCell<Symbol>>, name: &OYarn, asname: Option<String>) -> Result<Rc<RefCell<Symbol>>, String> {
367+
if name == "" {
368+
return Err("Empty name".to_string());
369+
}
362370
if DEBUG_BORROW_GUARDS {
363371
//Parent must be borrowable in this function
364372
parent.borrow_mut();
@@ -434,64 +442,189 @@ fn _resolve_new_symbol(session: &mut SessionInfo, parent: Rc<RefCell<Symbol>>, n
434442
return Err("Symbol not found".to_string())
435443
}
436444

437-
pub fn get_all_valid_names(session: &mut SessionInfo, source_file_symbol: &Rc<RefCell<Symbol>>, from_stmt: Option<&Identifier>, base_name: String, level: Option<u32>) -> HashSet<OYarn> {
438-
//A: search base of different imports
445+
/*
446+
Used for autocompletion. Given a base_name, return all valid names that can be used to complete it.
447+
is_from indicates if the import is the X in "from X import Y". Else it is Y from "import Y" or "from X import Y"
448+
*/
449+
pub fn get_all_valid_names(session: &mut SessionInfo, source_file_symbol: &Rc<RefCell<Symbol>>, from_stmt: Option<String>, import: String, level: Option<u32>, is_from: bool) -> HashMap<OYarn, SymType> {
450+
let (identifier_from, to_complete) = match from_stmt {
451+
Some(from_stmt_inner) => {
452+
if is_from {
453+
let split = from_stmt_inner.split(".").collect::<Vec<&str>>();
454+
if split.len() > 1 {
455+
(Some(Identifier::new(split[0..split.len()-1].join(".").as_str(), TextRange::default())), split.last().unwrap().to_string())
456+
} else {
457+
(None, split.last().unwrap().to_string())
458+
}
459+
} else {
460+
(Some(Identifier::new(from_stmt_inner.clone(), TextRange::default())), import.clone())
461+
}
462+
},
463+
None => (None, import.split(".").last().unwrap().to_string()),
464+
};
465+
//A: Search base to search on
439466
let source_root = source_file_symbol.borrow().get_root().as_ref().unwrap().upgrade().unwrap();
440467
let entry = source_root.borrow().get_entry().unwrap();
441468
let _source_file_symbol_lock = source_file_symbol.borrow_mut();
442469
let file_tree = _resolve_packages(
443470
&_source_file_symbol_lock,
444471
level,
445-
from_stmt);
472+
identifier_from.as_ref());
446473
drop(_source_file_symbol_lock);
447474
let mut start_symbol = None;
448-
if level.is_some() {
449-
//if level is some, resolve_pacackages already built a full tree, so we can start from root
475+
let source_path = source_file_symbol.borrow().paths()[0].clone();
476+
if !file_tree.is_empty() && level.is_some() && level.unwrap() != 0 {
450477
start_symbol = Some(source_root.clone());
451478
}
452-
let source_path = source_file_symbol.borrow().paths()[0].clone();
453-
let (from_symbol, _fallback_sym) = _get_or_create_symbol(session,
479+
let (mut from_symbol, _fallback_sym) = _get_or_create_symbol(session,
454480
&entry,
455481
source_path.as_str(),
456482
start_symbol,
457483
&file_tree,
458484
None,
459485
level);
460-
let mut result = HashSet::new();
486+
let mut result = HashMap::new();
487+
let mut symbols_to_browse = vec![];
461488
if from_symbol.is_none() {
489+
if !file_tree.is_empty() { //symbol was not found
490+
return result;
491+
} else { //nothing was provided, so we have to add the root symbol of any valid entrypoint
492+
let entry_point_mgr = session.sync_odoo.entry_point_mgr.clone();
493+
let entry_point_mgr = entry_point_mgr.borrow();
494+
let from_path = session.sync_odoo.entry_point_mgr.borrow().transform_addon_path(&PathBuf::from(source_path.clone()));
495+
let from_path = PathBuf::from(from_path);
496+
for entry in entry_point_mgr.iter_for_import(&entry) {
497+
if (entry.borrow().is_public() && (level.is_none() || level.unwrap() == 0)) || entry.borrow().is_valid_for(&from_path) {
498+
let entry_point = entry.borrow().get_symbol();
499+
if let Some(entry_point) = entry_point {
500+
symbols_to_browse.push(entry_point.clone());
501+
}
502+
}
503+
}
504+
if symbols_to_browse.is_empty() {
505+
return result;
506+
}
507+
}
508+
}
509+
if is_from {
510+
if let Some(fs) = from_symbol {
511+
symbols_to_browse.push(fs);
512+
}
513+
for symbol_to_browse in symbols_to_browse.iter() {
514+
let valid_names = valid_names_for_a_symbol(session, symbol_to_browse, &oyarn!("{}", to_complete), true);
515+
result.extend(valid_names);
516+
}
462517
return result;
463518
}
464-
let from_symbol = from_symbol.unwrap();
465519

466-
let mut sym: Option<Rc<RefCell<Symbol>>> = Some(from_symbol.clone());
467-
let mut names = vec![base_name.split(".").map(|s| oyarn!("{}", s)).next().unwrap()];
468-
if base_name.ends_with(".") {
469-
names.push(Sy!(""));
520+
let import_parts = import.split(".").collect::<Vec<&str>>();
521+
if import_parts.len() > 1 {
522+
let (next_symbol, _fallback_sym) = _get_or_create_symbol(
523+
session,
524+
&entry,
525+
source_path.as_str(),
526+
from_symbol.clone(),
527+
&import_parts[0..import_parts.len()-1].iter().map(|s| oyarn!("{}", *s)).collect(),
528+
None,
529+
level,
530+
);
531+
if next_symbol.is_none() {
532+
return result;
533+
}
534+
from_symbol = next_symbol.clone();
470535
}
471-
for (index, branch) in names.iter().enumerate() {
472-
if index != names.len() -1 {
473-
let mut next_symbol = sym.as_ref().unwrap().borrow().get_symbol(&(vec![branch.clone()], vec![]), u32::MAX);
474-
if next_symbol.is_empty() {
475-
next_symbol = match _resolve_new_symbol(session, sym.as_ref().unwrap().clone(), &branch, None) {
476-
Ok(v) => vec![v],
477-
Err(_) => vec![]
478-
}
536+
if let Some(fs) = from_symbol {
537+
symbols_to_browse.clear();
538+
symbols_to_browse.push(fs);
539+
}
540+
for symbol_to_browse in symbols_to_browse.iter() {
541+
let valid_names = valid_names_for_a_symbol(session, symbol_to_browse, &oyarn!("{}", to_complete), false);
542+
result.extend(valid_names);
543+
}
544+
result
545+
}
546+
547+
fn valid_names_for_a_symbol(session: &mut SessionInfo, symbol: &Rc<RefCell<Symbol>>, start_filter: &OYarn, only_on_disk: bool) -> HashMap<OYarn, SymType> {
548+
let mut res = HashMap::new();
549+
match symbol.borrow().typ() {
550+
SymType::FILE => {
551+
if only_on_disk {
552+
return res;
479553
}
480-
if next_symbol.is_empty() {
481-
sym = None;
482-
break;
554+
res.extend(valid_name_from_symbol(symbol, start_filter));
555+
},
556+
SymType::NAMESPACE | SymType::DISK_DIR => {
557+
for path in symbol.borrow().paths().iter() {
558+
res.extend(valid_name_from_disk(path, start_filter));
559+
}
560+
},
561+
SymType::PACKAGE(_) => {
562+
for path in symbol.borrow().paths().iter() {
563+
res.extend(valid_name_from_disk(path, start_filter));
483564
}
484-
sym = Some(next_symbol[0].clone());
565+
if only_on_disk {
566+
return res;
567+
}
568+
res.extend(valid_name_from_symbol(symbol, start_filter));
569+
}
570+
SymType::CLASS | SymType::COMPILED | SymType::CSV_FILE | SymType::XML_FILE | SymType::FUNCTION | SymType::ROOT | SymType::VARIABLE => {
485571
}
486572
}
487-
if let Some(sym) = sym {
488-
let filter = names.last().unwrap();
489-
for symbol in sym.borrow().all_symbols() {
490-
if symbol.borrow().name().starts_with(filter.as_str()) {
491-
result.insert(symbol.borrow().name().clone());
573+
res
574+
}
575+
576+
fn valid_name_from_disk(path: &String, start_filter: &OYarn) -> HashMap<OYarn, SymType> {
577+
let mut res = HashMap::new();
578+
if is_dir_cs(path.clone()) {
579+
if let Ok(entries) = std::fs::read_dir(path) {
580+
for entry in entries {
581+
if let Ok(entry) = entry {
582+
let Ok(file_type) = entry.file_type() else {
583+
continue;
584+
};
585+
if file_type.is_dir() {
586+
let dir_name = entry.file_name();
587+
let dir_name_str = dir_name.to_string_lossy();
588+
if dir_name_str.starts_with(start_filter.as_str()) {
589+
let mut typ = SymType::NAMESPACE;
590+
if Path::new(&path).join(dir_name_str.to_string()).join("__init__.py").exists() {
591+
typ = SymType::PACKAGE(PackageType::PYTHON_PACKAGE);
592+
}
593+
res.insert(Sy!(dir_name_str.to_string()), typ);
594+
}
595+
} else if file_type.is_file() {
596+
let file_name = entry.file_name();
597+
let file_name_str = file_name.to_string_lossy().to_string();
598+
if (file_name_str.ends_with(".py") || file_name_str.ends_with(".pyi")) && file_name_str.starts_with(start_filter.as_str()) {
599+
let Some(stem) = Path::new(&file_name_str).file_stem() else {continue};
600+
let Some(filename) = stem.to_str() else {continue};
601+
if filename == "__init__" {continue;}
602+
res.insert(Sy!(filename.to_string()), SymType::FILE);
603+
}
604+
}
605+
//TODO support for symlinks?
606+
}
492607
}
493608
}
494609
}
610+
res
611+
}
495612

496-
return result;
613+
fn valid_name_from_symbol(symbol: &Rc<RefCell<Symbol>>, start_filter: &OYarn) -> HashMap<OYarn, SymType> {
614+
let mut res = HashMap::new();
615+
let symbols = symbol.borrow();
616+
for s in symbols.iter_symbols() {
617+
if s.0.starts_with(&start_filter.to_string()) {
618+
let mut typ = SymType::VARIABLE;
619+
let a_section = s.1.iter().last(); //let's take the last section, anyway we can display only one icon
620+
if let Some(a_section) = a_section {
621+
let last = a_section.1.last();
622+
if let Some(last) = last {
623+
typ = last.borrow().typ();
624+
}
625+
}
626+
res.insert(s.0.clone(), typ);
627+
}
628+
}
629+
res
497630
}

server/src/features/completion.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -308,12 +308,13 @@ fn complete_assert_stmt(session: &mut SessionInfo<'_>, file: &Rc<RefCell<Symbol>
308308
fn complete_import_stmt(session: &mut SessionInfo, file: &Rc<RefCell<Symbol>>, stmt_import: &StmtImport, offset: usize) -> Option<CompletionResponse> {
309309
let mut items = vec![];
310310
for alias in stmt_import.names.iter() {
311-
if alias.name.range().end().to_usize() == offset {
312-
let names = import_resolver::get_all_valid_names(session, file, None, S!(alias.name.id.as_str()), None);
313-
for name in names {
311+
if alias.name.range().start().to_usize() < offset && alias.name.range.end().to_usize() >= offset {
312+
let to_complete = alias.name.id.to_string().get(0 .. offset - alias.name.range.start().to_usize()).unwrap_or("").to_string();
313+
let names = import_resolver::get_all_valid_names(session, file, None, to_complete, None, false);
314+
for (name, sym_typ) in names {
314315
items.push(CompletionItem {
315316
label: name.to_string(),
316-
kind: Some(lsp_types::CompletionItemKind::MODULE),
317+
kind: Some(get_completion_item_kind(&sym_typ)),
317318
..Default::default()
318319
});
319320
}
@@ -328,24 +329,26 @@ fn complete_import_stmt(session: &mut SessionInfo, file: &Rc<RefCell<Symbol>>, s
328329
fn complete_import_from_stmt(session: &mut SessionInfo, file: &Rc<RefCell<Symbol>>, stmt_import: &StmtImportFrom, offset: usize) -> Option<CompletionResponse> {
329330
let mut items = vec![];
330331
if let Some(module) = stmt_import.module.as_ref() {
331-
if module.range.end().to_usize() == offset && !stmt_import.names.is_empty() {
332-
let names = import_resolver::get_all_valid_names(session, file, None, S!(stmt_import.names[0].name.id.as_str()), Some(stmt_import.level));
333-
for name in names {
332+
if module.range.start().to_usize() < offset && module.range.end().to_usize() >= offset {
333+
let to_complete = module.id.to_string().get(0 .. offset - module.range.start().to_usize()).unwrap_or("").to_string();
334+
let names = import_resolver::get_all_valid_names(session, file, Some(to_complete), S!(""), Some(stmt_import.level), true);
335+
for (name, sym_type) in names {
334336
items.push(CompletionItem {
335337
label: name.to_string(),
336-
kind: Some(lsp_types::CompletionItemKind::MODULE),
338+
kind: Some(get_completion_item_kind(&sym_type)),
337339
..Default::default()
338340
});
339341
}
340342
}
341343
}
342344
for alias in stmt_import.names.iter() {
343-
if alias.name.range().end().to_usize() == offset {
344-
let names = import_resolver::get_all_valid_names(session, file, stmt_import.module.as_ref(), S!(alias.name.id.as_str()), Some(stmt_import.level));
345-
for name in names {
345+
if alias.name.range().start().to_usize() < offset && alias.name.range.end().to_usize() >= offset {
346+
let to_complete = alias.name.id.to_string().get(0 .. offset - alias.name.range.start().to_usize()).unwrap_or("").to_string();
347+
let names = import_resolver::get_all_valid_names(session, file, stmt_import.module.as_ref().map(|m| m.id.to_string()), to_complete, Some(stmt_import.level), false);
348+
for (name, sym_type) in names {
346349
items.push(CompletionItem {
347350
label: name.to_string(),
348-
kind: Some(lsp_types::CompletionItemKind::MODULE),
351+
kind: Some(get_completion_item_kind(&sym_type)),
349352
..Default::default()
350353
});
351354
}
@@ -1084,7 +1087,7 @@ fn build_completion_item_from_symbol(session: &mut SessionInfo, symbols: Vec<Rc<
10841087
description: label_details_description,
10851088
}),
10861089
detail: Some(type_details.iter().map(|detail| detail.to_string()).join(" | ").to_string()),
1087-
kind: Some(get_completion_item_kind(&symbols[0])),
1090+
kind: Some(get_completion_item_kind(&symbols[0].borrow().typ())),
10881091
sort_text: Some(get_sort_text_for_symbol(&symbols[0])),
10891092
documentation: Some(
10901093
lsp_types::Documentation::MarkupContent(MarkupContent {
@@ -1129,8 +1132,8 @@ fn get_sort_text_for_symbol(sym: &Rc<RefCell<Symbol>>/*, cl: Option<Rc<RefCell<S
11291132
text
11301133
}
11311134

1132-
fn get_completion_item_kind(symbol: &Rc<RefCell<Symbol>>) -> CompletionItemKind {
1133-
match symbol.borrow().typ() {
1135+
fn get_completion_item_kind(typ: &SymType) -> CompletionItemKind {
1136+
match typ {
11341137
SymType::ROOT => CompletionItemKind::TEXT,
11351138
SymType::DISK_DIR => CompletionItemKind::FOLDER,
11361139
SymType::NAMESPACE => CompletionItemKind::FOLDER,

0 commit comments

Comments
 (0)