diff options
Diffstat (limited to 'cli/lsp/completions.rs')
-rw-r--r-- | cli/lsp/completions.rs | 378 |
1 files changed, 56 insertions, 322 deletions
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs index 0e78b06e3..f808f9607 100644 --- a/cli/lsp/completions.rs +++ b/cli/lsp/completions.rs @@ -6,7 +6,6 @@ use super::lsp_custom; use super::tsc; use crate::fs_util::is_supported_ext; -use crate::media_type::MediaType; use deno_core::normalize_path; use deno_core::resolve_path; @@ -16,14 +15,6 @@ use deno_core::serde::Serialize; use deno_core::url::Position; use deno_core::ModuleSpecifier; use lspower::lsp; -use std::rc::Rc; -use swc_common::Loc; -use swc_common::SourceMap; -use swc_common::DUMMY_SP; -use swc_ecmascript::ast as swc_ast; -use swc_ecmascript::visit::Node; -use swc_ecmascript::visit::Visit; -use swc_ecmascript::visit::VisitWith; const CURRENT_PATH: &str = "."; const PARENT_PATH: &str = ".."; @@ -103,72 +94,61 @@ pub async fn get_import_completions( state_snapshot: &language_server::StateSnapshot, client: lspower::Client, ) -> Option<lsp::CompletionResponse> { - if let Ok(Some(source)) = state_snapshot.documents.content(specifier) { - let media_type = MediaType::from(specifier); - if let Some((current_specifier, range)) = - is_module_specifier_position(specifier, &source, &media_type, position) + let analysis::DependencyRange { + range, + specifier: text, + } = state_snapshot + .documents + .is_specifier_position(specifier, position)?; + // completions for local relative modules + if text.starts_with("./") || text.starts_with("../") { + Some(lsp::CompletionResponse::List(lsp::CompletionList { + is_incomplete: false, + items: get_local_completions(specifier, &text, &range)?, + })) + } else if !text.is_empty() { + // completion of modules from a module registry or cache + check_auto_config_registry(&text, state_snapshot, client).await; + let offset = if position.character > range.start.character { + (position.character - range.start.character) as usize + } else { + 0 + }; + let maybe_items = state_snapshot + .module_registries + .get_completions(&text, offset, &range, state_snapshot) + .await; + let items = maybe_items.unwrap_or_else(|| { + get_workspace_completions(specifier, &text, &range, state_snapshot) + }); + Some(lsp::CompletionResponse::List(lsp::CompletionList { + is_incomplete: false, + items, + })) + } else { + let mut items: Vec<lsp::CompletionItem> = LOCAL_PATHS + .iter() + .map(|s| lsp::CompletionItem { + label: s.to_string(), + kind: Some(lsp::CompletionItemKind::Folder), + detail: Some("(local)".to_string()), + sort_text: Some("1".to_string()), + insert_text: Some(s.to_string()), + ..Default::default() + }) + .collect(); + if let Some(origin_items) = state_snapshot + .module_registries + .get_origin_completions(&text, &range) { - // completions for local relative modules - if current_specifier.starts_with("./") - || current_specifier.starts_with("../") - { - return Some(lsp::CompletionResponse::List(lsp::CompletionList { - is_incomplete: false, - items: get_local_completions(specifier, ¤t_specifier, &range)?, - })); - } - // completion of modules from a module registry or cache - if !current_specifier.is_empty() { - check_auto_config_registry(¤t_specifier, state_snapshot, client) - .await; - let offset = if position.character > range.start.character { - (position.character - range.start.character) as usize - } else { - 0 - }; - let maybe_items = state_snapshot - .module_registries - .get_completions(¤t_specifier, offset, &range, state_snapshot) - .await; - let items = maybe_items.unwrap_or_else(|| { - get_workspace_completions( - specifier, - ¤t_specifier, - &range, - state_snapshot, - ) - }); - return Some(lsp::CompletionResponse::List(lsp::CompletionList { - is_incomplete: false, - items, - })); - } else { - let mut items: Vec<lsp::CompletionItem> = LOCAL_PATHS - .iter() - .map(|s| lsp::CompletionItem { - label: s.to_string(), - kind: Some(lsp::CompletionItemKind::Folder), - detail: Some("(local)".to_string()), - sort_text: Some("1".to_string()), - insert_text: Some(s.to_string()), - ..Default::default() - }) - .collect(); - if let Some(origin_items) = state_snapshot - .module_registries - .get_origin_completions(¤t_specifier, &range) - { - items.extend(origin_items); - } - return Some(lsp::CompletionResponse::List(lsp::CompletionList { - is_incomplete: false, - items, - })); - // TODO(@kitsonk) add bare specifiers from import map - } + items.extend(origin_items); } + Some(lsp::CompletionResponse::List(lsp::CompletionList { + is_incomplete: false, + items, + })) + // TODO(@kitsonk) add bare specifiers from import map } - None } /// Return local completions that are relative to the base specifier. @@ -313,134 +293,6 @@ fn get_workspace_completions( .collect() } -/// A structure that implements the visit trait to determine if the supplied -/// position falls within the module specifier of an import/export statement. -/// Once the module has been visited, -struct ImportLocator { - pub maybe_range: Option<lsp::Range>, - pub maybe_specifier: Option<String>, - position: lsp::Position, - source_map: Rc<SourceMap>, -} - -impl ImportLocator { - pub fn new(position: lsp::Position, source_map: Rc<SourceMap>) -> Self { - Self { - maybe_range: None, - maybe_specifier: None, - position, - source_map, - } - } -} - -impl Visit for ImportLocator { - fn visit_import_decl( - &mut self, - node: &swc_ast::ImportDecl, - _parent: &dyn Node, - ) { - if self.maybe_specifier.is_none() { - let start = self.source_map.lookup_char_pos(node.src.span.lo); - let end = self.source_map.lookup_char_pos(node.src.span.hi); - if span_includes_pos(&self.position, &start, &end) { - self.maybe_range = Some(get_range_from_loc(&start, &end)); - self.maybe_specifier = Some(node.src.value.to_string()); - } - } - } - - fn visit_named_export( - &mut self, - node: &swc_ast::NamedExport, - _parent: &dyn Node, - ) { - if self.maybe_specifier.is_none() { - if let Some(src) = &node.src { - let start = self.source_map.lookup_char_pos(src.span.lo); - let end = self.source_map.lookup_char_pos(src.span.hi); - if span_includes_pos(&self.position, &start, &end) { - self.maybe_range = Some(get_range_from_loc(&start, &end)); - self.maybe_specifier = Some(src.value.to_string()); - } - } - } - } - - fn visit_export_all( - &mut self, - node: &swc_ast::ExportAll, - _parent: &dyn Node, - ) { - if self.maybe_specifier.is_none() { - let start = self.source_map.lookup_char_pos(node.src.span.lo); - let end = self.source_map.lookup_char_pos(node.src.span.hi); - if span_includes_pos(&self.position, &start, &end) { - self.maybe_range = Some(get_range_from_loc(&start, &end)); - self.maybe_specifier = Some(node.src.value.to_string()); - } - } - } - - fn visit_ts_import_type( - &mut self, - node: &swc_ast::TsImportType, - _parent: &dyn Node, - ) { - if self.maybe_specifier.is_none() { - let start = self.source_map.lookup_char_pos(node.arg.span.lo); - let end = self.source_map.lookup_char_pos(node.arg.span.hi); - if span_includes_pos(&self.position, &start, &end) { - self.maybe_range = Some(get_range_from_loc(&start, &end)); - self.maybe_specifier = Some(node.arg.value.to_string()); - } - } - } -} - -/// Get LSP range from the provided SWC start and end locations. -fn get_range_from_loc(start: &Loc, end: &Loc) -> lsp::Range { - lsp::Range { - start: lsp::Position { - line: (start.line - 1) as u32, - character: (start.col_display + 1) as u32, - }, - end: lsp::Position { - line: (end.line - 1) as u32, - character: (end.col_display - 1) as u32, - }, - } -} - -/// Determine if the provided position falls into an module specifier of an -/// import/export statement, optionally returning the current value of the -/// specifier. -fn is_module_specifier_position( - specifier: &ModuleSpecifier, - source: &str, - media_type: &MediaType, - position: &lsp::Position, -) -> Option<(String, lsp::Range)> { - if let Ok(parsed_module) = - analysis::parse_module(specifier, source, media_type) - { - let mut import_locator = - ImportLocator::new(*position, parsed_module.source_map.clone()); - parsed_module - .module - .visit_with(&swc_ast::Invalid { span: DUMMY_SP }, &mut import_locator); - if let (Some(specifier), Some(range)) = - (import_locator.maybe_specifier, import_locator.maybe_range) - { - Some((specifier, range)) - } else { - None - } - } else { - None - } -} - /// Converts a specifier into a relative specifier to the provided base /// specifier as a string. If a relative path cannot be found, then the /// specifier is simply returned as a string. @@ -543,16 +395,6 @@ fn relative_specifier( } } -/// Does the position fall within the start and end location? -fn span_includes_pos(position: &lsp::Position, start: &Loc, end: &Loc) -> bool { - (position.line > (start.line - 1) as u32 - || position.line == (start.line - 1) as u32 - && position.character >= start.col_display as u32) - && (position.line < (end.line - 1) as u32 - || position.line == (end.line - 1) as u32 - && position.character <= end.col_display as u32) -} - #[cfg(test)] mod tests { use super::*; @@ -586,7 +428,10 @@ mod tests { &parsed_module, &None, ); - documents.set_dependencies(&specifier, Some(deps)).unwrap(); + let dep_ranges = analysis::analyze_dependency_ranges(&parsed_module).ok(); + documents + .set_dependencies(&specifier, Some(deps), dep_ranges) + .unwrap(); } let sources = Sources::new(location); let http_cache = HttpCache::new(location); @@ -713,117 +558,6 @@ mod tests { } #[test] - fn test_is_module_specifier_position() { - let specifier = resolve_url("file:///a/b/c.ts").unwrap(); - let import_source = r#"import * as a from """#; - let export_source = r#"export * as a from """#; - let media_type = MediaType::TypeScript; - assert_eq!( - is_module_specifier_position( - &specifier, - import_source, - &media_type, - &lsp::Position { - line: 0, - character: 0 - } - ), - None - ); - assert_eq!( - is_module_specifier_position( - &specifier, - import_source, - &media_type, - &lsp::Position { - line: 0, - character: 20 - } - ), - Some(( - "".to_string(), - lsp::Range { - start: lsp::Position { - line: 0, - character: 20 - }, - end: lsp::Position { - line: 0, - character: 20 - } - } - )) - ); - assert_eq!( - is_module_specifier_position( - &specifier, - export_source, - &media_type, - &lsp::Position { - line: 0, - character: 20 - } - ), - Some(( - "".to_string(), - lsp::Range { - start: lsp::Position { - line: 0, - character: 20 - }, - end: lsp::Position { - line: 0, - character: 20 - } - } - )) - ); - } - - #[test] - fn test_is_module_specifier_position_partial() { - let specifier = resolve_url("file:///a/b/c.ts").unwrap(); - let source = r#"import * as a from "https://""#; - let media_type = MediaType::TypeScript; - assert_eq!( - is_module_specifier_position( - &specifier, - source, - &media_type, - &lsp::Position { - line: 0, - character: 0 - } - ), - None - ); - assert_eq!( - is_module_specifier_position( - &specifier, - source, - &media_type, - &lsp::Position { - line: 0, - character: 28 - } - ), - Some(( - "https://".to_string(), - lsp::Range { - start: lsp::Position { - line: 0, - character: 20 - }, - end: lsp::Position { - line: 0, - character: 28 - } - } - )) - ); - } - - #[test] fn test_get_local_completions() { let temp_dir = TempDir::new().expect("could not create temp dir"); let fixtures = temp_dir.path().join("fixtures"); |