|
1 | 1 | use glob::glob; |
2 | | -use lsp_types::{Diagnostic, DiagnosticTag, Position, Range}; |
| 2 | +use itertools::Itertools; |
| 3 | +use lsp_types::{CompletionItemKind, Diagnostic, DiagnosticTag, Position, Range}; |
3 | 4 | use ruff_python_ast::name::Name; |
| 5 | +use serde::Serialize; |
4 | 6 | use tracing::error; |
5 | 7 | use std::collections::{HashMap, HashSet}; |
6 | 8 | use std::rc::Rc; |
@@ -263,6 +265,9 @@ fn _get_or_create_symbol(session: &mut SessionInfo, for_entry: &Rc<RefCell<Entry |
263 | 265 | let mut sym: Option<Rc<RefCell<Symbol>>> = symbol.clone(); |
264 | 266 | let mut last_symbol = symbol.clone(); |
265 | 267 | for branch in names.iter() { |
| 268 | + if branch.is_empty() { |
| 269 | + continue; |
| 270 | + } |
266 | 271 | match sym { |
267 | 272 | Some(ref s) => { |
268 | 273 | 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 |
359 | 364 | } |
360 | 365 |
|
361 | 366 | 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 | + } |
362 | 370 | if DEBUG_BORROW_GUARDS { |
363 | 371 | //Parent must be borrowable in this function |
364 | 372 | parent.borrow_mut(); |
@@ -434,64 +442,189 @@ fn _resolve_new_symbol(session: &mut SessionInfo, parent: Rc<RefCell<Symbol>>, n |
434 | 442 | return Err("Symbol not found".to_string()) |
435 | 443 | } |
436 | 444 |
|
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 |
439 | 466 | let source_root = source_file_symbol.borrow().get_root().as_ref().unwrap().upgrade().unwrap(); |
440 | 467 | let entry = source_root.borrow().get_entry().unwrap(); |
441 | 468 | let _source_file_symbol_lock = source_file_symbol.borrow_mut(); |
442 | 469 | let file_tree = _resolve_packages( |
443 | 470 | &_source_file_symbol_lock, |
444 | 471 | level, |
445 | | - from_stmt); |
| 472 | + identifier_from.as_ref()); |
446 | 473 | drop(_source_file_symbol_lock); |
447 | 474 | 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 { |
450 | 477 | start_symbol = Some(source_root.clone()); |
451 | 478 | } |
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, |
454 | 480 | &entry, |
455 | 481 | source_path.as_str(), |
456 | 482 | start_symbol, |
457 | 483 | &file_tree, |
458 | 484 | None, |
459 | 485 | level); |
460 | | - let mut result = HashSet::new(); |
| 486 | + let mut result = HashMap::new(); |
| 487 | + let mut symbols_to_browse = vec![]; |
461 | 488 | 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 | + } |
462 | 517 | return result; |
463 | 518 | } |
464 | | - let from_symbol = from_symbol.unwrap(); |
465 | 519 |
|
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(); |
470 | 535 | } |
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; |
479 | 553 | } |
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)); |
483 | 564 | } |
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 => { |
485 | 571 | } |
486 | 572 | } |
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 | + } |
492 | 607 | } |
493 | 608 | } |
494 | 609 | } |
| 610 | + res |
| 611 | +} |
495 | 612 |
|
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 |
497 | 630 | } |
0 commit comments