summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2024-07-30 23:26:09 +0100
committerGitHub <noreply@github.com>2024-07-30 23:26:09 +0100
commit3659781f88236a369aa9ca5142c0fb7d690fc898 (patch)
tree6e13b0ee47a7f5f6c8d38b4503efcf5d6551ed68
parentfe884c557a76fd288f7457aa433052f65be70c81 (diff)
feat(lsp): registry completions for import-mapped specifiers (#24792)
-rw-r--r--cli/lsp/completions.rs9
-rw-r--r--cli/lsp/registries.rs790
2 files changed, 415 insertions, 384 deletions
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 17fb0f719..a51edcb33 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -215,16 +215,13 @@ pub async fn get_import_completions(
module_registries,
)
.await;
- let offset = if position.character > range.start.character {
- (position.character - range.start.character) as usize
- } else {
- 0
- };
let maybe_list = module_registries
- .get_completions(&text, offset, &range, |s| {
+ .get_completions(&text, &range, resolved.as_ref(), |s| {
documents.exists(s, file_referrer)
})
.await;
+ let maybe_list = maybe_list
+ .or_else(|| module_registries.get_origin_completions(&text, &range));
let list = maybe_list.unwrap_or_else(|| CompletionList {
items: get_workspace_completions(specifier, &text, &range, documents),
is_incomplete: false,
diff --git a/cli/lsp/registries.rs b/cli/lsp/registries.rs
index e194c6110..c709831d4 100644
--- a/cli/lsp/registries.rs
+++ b/cli/lsp/registries.rs
@@ -33,6 +33,7 @@ use deno_graph::Dependency;
use deno_runtime::deno_permissions::PermissionsContainer;
use log::error;
use once_cell::sync::Lazy;
+use std::borrow::Cow;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
@@ -86,23 +87,23 @@ enum CompletionType {
/// Determine if a completion at a given offset is a string literal or a key/
/// variable.
fn get_completion_type(
- offset: usize,
+ char_offset: usize,
tokens: &[Token],
match_result: &MatchResult,
) -> Option<CompletionType> {
- let mut len = 0_usize;
+ let mut char_count = 0_usize;
for (index, token) in tokens.iter().enumerate() {
match token {
Token::String(s) => {
- len += s.chars().count();
- if offset < len {
+ char_count += s.chars().count();
+ if char_offset < char_count {
return Some(CompletionType::Literal(s.clone()));
}
}
Token::Key(k) => {
if let Some(prefix) = &k.prefix {
- len += prefix.chars().count();
- if offset < len {
+ char_count += prefix.chars().count();
+ if char_offset < char_count {
return Some(CompletionType::Key {
key: k.clone(),
prefix: Some(prefix.clone()),
@@ -110,7 +111,7 @@ fn get_completion_type(
});
}
}
- if offset < len {
+ if char_offset < char_count {
return None;
}
if let StringOrNumber::String(name) = &k.name {
@@ -118,8 +119,8 @@ fn get_completion_type(
.get(name)
.map(|s| s.to_string(Some(k), false))
.unwrap_or_default();
- len += value.chars().count();
- if offset <= len {
+ char_count += value.chars().count();
+ if char_offset <= char_count {
return Some(CompletionType::Key {
key: k.clone(),
prefix: None,
@@ -128,8 +129,8 @@ fn get_completion_type(
}
}
if let Some(suffix) = &k.suffix {
- len += suffix.chars().count();
- if offset <= len {
+ char_count += suffix.chars().count();
+ if char_offset <= char_count {
return Some(CompletionType::Literal(suffix.clone()));
}
}
@@ -449,49 +450,6 @@ impl ModuleRegistry {
}
}
- fn complete_literal(
- &self,
- s: String,
- completions: &mut HashMap<String, lsp::CompletionItem>,
- current_specifier: &str,
- offset: usize,
- range: &lsp::Range,
- ) {
- let label = if s.starts_with('/') {
- s[0..].to_string()
- } else {
- s.to_string()
- };
- let full_text = format!(
- "{}{}{}",
- &current_specifier[..offset],
- s,
- &current_specifier[offset..]
- );
- let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- range: *range,
- new_text: full_text.clone(),
- }));
- let filter_text = Some(full_text);
- completions.insert(
- s,
- lsp::CompletionItem {
- label,
- kind: Some(lsp::CompletionItemKind::FOLDER),
- filter_text,
- sort_text: Some("1".to_string()),
- text_edit,
- commit_characters: Some(
- REGISTRY_IMPORT_COMMIT_CHARS
- .iter()
- .map(|&c| c.into())
- .collect(),
- ),
- ..Default::default()
- },
- );
- }
-
/// Disable a registry, removing its configuration, if any, from memory.
pub fn disable(&mut self, origin: &str) {
let Ok(origin_url) = Url::parse(origin) else {
@@ -654,339 +612,366 @@ impl ModuleRegistry {
/// any, for the specifier.
pub async fn get_completions(
&self,
- current_specifier: &str,
- offset: usize,
+ text: &str,
range: &lsp::Range,
+ resolved: Option<&ModuleSpecifier>,
specifier_exists: impl Fn(&ModuleSpecifier) -> bool,
) -> Option<lsp::CompletionList> {
- if let Ok(specifier) = Url::parse(current_specifier) {
- let origin = base_url(&specifier);
- let origin_len = origin.chars().count();
- if offset >= origin_len {
- if let Some(registries) = self.origins.get(&origin) {
- let path = &specifier[Position::BeforePath..];
- let path_offset = offset - origin_len;
- let mut completions = HashMap::<String, lsp::CompletionItem>::new();
- let mut is_incomplete = false;
- let mut did_match = false;
- for registry in registries {
- let tokens = parse(&registry.schema, None)
- .map_err(|e| {
- error!(
- "Error parsing registry schema for origin \"{}\". {}",
- origin, e
- );
- })
- .ok()?;
- let mut i = tokens.len();
- let last_key_name = StringOrNumber::String(
- tokens
- .iter()
- .last()
- .map(|t| {
- if let Token::Key(key) = t {
- if let StringOrNumber::String(s) = &key.name {
- return s.clone();
- }
- }
- "".to_string()
- })
- .unwrap_or_default(),
+ let resolved = resolved
+ .map(Cow::Borrowed)
+ .or_else(|| ModuleSpecifier::parse(text).ok().map(Cow::Owned))?;
+ let resolved_str = resolved.as_str();
+ let origin = base_url(&resolved);
+ let origin_char_count = origin.chars().count();
+ let registries = self.origins.get(&origin)?;
+ let path = &resolved[Position::BeforePath..];
+ let path_char_offset = resolved_str.chars().count() - origin_char_count;
+ let mut completions = HashMap::<String, lsp::CompletionItem>::new();
+ let mut is_incomplete = false;
+ let mut did_match = false;
+ for registry in registries {
+ let tokens = parse(&registry.schema, None)
+ .map_err(|e| {
+ error!(
+ "Error parsing registry schema for origin \"{}\". {}",
+ origin, e
+ );
+ })
+ .ok()?;
+ let mut i = tokens.len();
+ let last_key_name = StringOrNumber::String(
+ tokens
+ .iter()
+ .last()
+ .map(|t| {
+ if let Token::Key(key) = t {
+ if let StringOrNumber::String(s) = &key.name {
+ return s.clone();
+ }
+ }
+ "".to_string()
+ })
+ .unwrap_or_default(),
+ );
+ loop {
+ let matcher = Matcher::new(&tokens[..i], None)
+ .map_err(|e| {
+ error!(
+ "Error creating matcher for schema for origin \"{}\". {}",
+ origin, e
);
- loop {
- let matcher = Matcher::new(&tokens[..i], None)
- .map_err(|e| {
- error!(
- "Error creating matcher for schema for origin \"{}\". {}",
- origin, e
- );
- })
- .ok()?;
- if let Some(match_result) = matcher.matches(path) {
- did_match = true;
- let completion_type =
- get_completion_type(path_offset, &tokens, &match_result);
- match completion_type {
- Some(CompletionType::Literal(s)) => self.complete_literal(
- s,
- &mut completions,
- current_specifier,
- offset,
- range,
+ })
+ .ok()?;
+ if let Some(match_result) = matcher.matches(path) {
+ did_match = true;
+ let completion_type =
+ get_completion_type(path_char_offset, &tokens, &match_result);
+ match completion_type {
+ Some(CompletionType::Literal(s)) => {
+ let label = s;
+ let full_text = format!("{text}{label}");
+ let text_edit =
+ Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: *range,
+ new_text: full_text.clone(),
+ }));
+ let filter_text = Some(full_text);
+ completions.insert(
+ label.clone(),
+ lsp::CompletionItem {
+ label,
+ kind: Some(lsp::CompletionItemKind::FOLDER),
+ filter_text,
+ sort_text: Some("1".to_string()),
+ text_edit,
+ commit_characters: Some(
+ REGISTRY_IMPORT_COMMIT_CHARS
+ .iter()
+ .map(|&c| c.into())
+ .collect(),
),
- Some(CompletionType::Key { key, prefix, index }) => {
- let maybe_url = registry.get_url_for_key(&key);
- if let Some(url) = maybe_url {
- if let Some(items) = self
- .get_variable_items(
- &key,
- url,
- &specifier,
- &tokens,
- &match_result,
- )
- .await
- {
- let compiler = Compiler::new(&tokens[..=index], None);
- let base = Url::parse(&origin).ok()?;
- let (items, preselect, incomplete) = match items {
- VariableItems::List(list) => {
- (list.items, list.preselect, list.is_incomplete)
- }
- VariableItems::Simple(items) => (items, None, false),
- };
- if incomplete {
- is_incomplete = true;
- }
- for (idx, item) in items.into_iter().enumerate() {
- let mut label = if let Some(p) = &prefix {
- format!("{p}{item}")
- } else {
- item.clone()
- };
- if label.ends_with('/') {
- label.pop();
- }
- let kind = if key.name == last_key_name
- && !item.ends_with('/')
- {
- Some(lsp::CompletionItemKind::FILE)
- } else {
- Some(lsp::CompletionItemKind::FOLDER)
- };
- let mut params = match_result.params.clone();
- params.insert(
- key.name.clone(),
- StringOrVec::from_str(&item, &key),
- );
- let mut path =
- compiler.to_path(&params).unwrap_or_default();
- if path.ends_with('/') {
- path.pop();
- }
- let item_specifier = base.join(&path).ok()?;
- let full_text = item_specifier.as_str();
- let text_edit = Some(lsp::CompletionTextEdit::Edit(
- lsp::TextEdit {
- range: *range,
- new_text: full_text.to_string(),
- },
- ));
- let command = if key.name == last_key_name
- && !item.ends_with('/')
- && !specifier_exists(&item_specifier)
- {
- Some(lsp::Command {
- title: "".to_string(),
- command: "deno.cache".to_string(),
- arguments: Some(vec![
- json!([item_specifier]),
- json!(&specifier),
- ]),
- })
- } else {
- None
- };
- let detail = Some(format!("({})", key.name));
- let filter_text = Some(full_text.to_string());
- let sort_text = Some(format!("{:0>10}", idx + 1));
- let preselect =
- get_preselect(item.clone(), preselect.clone());
- let data = get_data_with_match(
- registry,
- &specifier,
- &tokens,
- &match_result,
- &key,
- &item,
- );
- let commit_characters = if is_incomplete {
- Some(
- REGISTRY_IMPORT_COMMIT_CHARS
- .iter()
- .map(|&c| c.into())
- .collect(),
- )
- } else {
- Some(
- IMPORT_COMMIT_CHARS
- .iter()
- .map(|&c| c.into())
- .collect(),
- )
- };
- completions.insert(
- item,
- lsp::CompletionItem {
- label,
- kind,
- detail,
- sort_text,
- filter_text,
- text_edit,
- command,
- preselect,
- data,
- commit_characters,
- ..Default::default()
- },
- );
- }
- }
+ ..Default::default()
+ },
+ );
+ }
+ Some(CompletionType::Key { key, prefix, index }) => {
+ let maybe_url = registry.get_url_for_key(&key);
+ if let Some(url) = maybe_url {
+ if let Some(items) = self
+ .get_variable_items(
+ &key,
+ url,
+ &resolved,
+ &tokens,
+ &match_result,
+ )
+ .await
+ {
+ let compiler = Compiler::new(&tokens[..=index], None);
+ let base = Url::parse(&origin).ok()?;
+ let (items, preselect, incomplete) = match items {
+ VariableItems::List(list) => {
+ (list.items, list.preselect, list.is_incomplete)
}
+ VariableItems::Simple(items) => (items, None, false),
+ };
+ if incomplete {
+ is_incomplete = true;
+ }
+ for (idx, item) in items.into_iter().enumerate() {
+ let mut label = if let Some(p) = &prefix {
+ format!("{p}{item}")
+ } else {
+ item.clone()
+ };
+ if label.ends_with('/') {
+ label.pop();
+ }
+ let kind =
+ if key.name == last_key_name && !item.ends_with('/') {
+ Some(lsp::CompletionItemKind::FILE)
+ } else {
+ Some(lsp::CompletionItemKind::FOLDER)
+ };
+ let mut params = match_result.params.clone();
+ params.insert(
+ key.name.clone(),
+ StringOrVec::from_str(&item, &key),
+ );
+ let mut path =
+ compiler.to_path(&params).unwrap_or_default();
+ if path.ends_with('/') {
+ path.pop();
+ }
+ let item_specifier = base.join(&path).ok()?;
+ let full_text = if let Some(suffix) =
+ item_specifier.as_str().strip_prefix(resolved_str)
+ {
+ format!("{text}{suffix}")
+ } else {
+ item_specifier.to_string()
+ };
+ let text_edit =
+ Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: *range,
+ new_text: full_text.to_string(),
+ }));
+ let command = if key.name == last_key_name
+ && !item.ends_with('/')
+ && !specifier_exists(&item_specifier)
+ {
+ Some(lsp::Command {
+ title: "".to_string(),
+ command: "deno.cache".to_string(),
+ arguments: Some(vec![
+ json!([item_specifier]),
+ json!(&resolved),
+ ]),
+ })
+ } else {
+ None
+ };
+ let detail = Some(format!("({})", key.name));
+ let filter_text = Some(full_text.to_string());
+ let sort_text = Some(format!("{:0>10}", idx + 1));
+ let preselect =
+ get_preselect(item.clone(), preselect.clone());
+ let data = get_data_with_match(
+ registry,
+ &resolved,
+ &tokens,
+ &match_result,
+ &key,
+ &item,
+ );
+ let commit_characters = if is_incomplete {
+ Some(
+ REGISTRY_IMPORT_COMMIT_CHARS
+ .iter()
+ .map(|&c| c.into())
+ .collect(),
+ )
+ } else {
+ Some(
+ IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
+ )
+ };
+ completions.insert(
+ item,
+ lsp::CompletionItem {
+ label,
+ kind,
+ detail,
+ sort_text,
+ filter_text,
+ text_edit,
+ command,
+ preselect,
+ data,
+ commit_characters,
+ ..Default::default()
+ },
+ );
}
- None => (),
}
- break;
}
- i -= 1;
- // If we have fallen though to the first token, and we still
- // didn't get a match
- if i == 0 {
- match &tokens[i] {
- // so if the first token is a string literal, we will return
- // that as a suggestion
- Token::String(s) => {
- if s.starts_with(path) {
- let label = s.to_string();
+ }
+ None => (),
+ }
+ break;
+ }
+ i -= 1;
+ // If we have fallen though to the first token, and we still
+ // didn't get a match
+ if i == 0 {
+ match &tokens[i] {
+ // so if the first token is a string literal, we will return
+ // that as a suggestion
+ Token::String(s) => {
+ if s.starts_with(path) {
+ let label = s.to_string();
+ let kind = Some(lsp::CompletionItemKind::FOLDER);
+ let mut url = resolved.as_ref().clone();
+ url.set_path(s);
+ let full_text = if let Some(suffix) =
+ url.as_str().strip_prefix(resolved_str)
+ {
+ format!("{text}{suffix}")
+ } else {
+ url.to_string()
+ };
+ let text_edit =
+ Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: *range,
+ new_text: full_text.to_string(),
+ }));
+ let filter_text = Some(full_text.to_string());
+ completions.insert(
+ s.to_string(),
+ lsp::CompletionItem {
+ label,
+ kind,
+ filter_text,
+ sort_text: Some("1".to_string()),
+ text_edit,
+ preselect: Some(true),
+ commit_characters: Some(
+ REGISTRY_IMPORT_COMMIT_CHARS
+ .iter()
+ .map(|&c| c.into())
+ .collect(),
+ ),
+ ..Default::default()
+ },
+ );
+ }
+ }
+ // if the token though is a key, and the key has a prefix, and
+ // the path matches the prefix, we will go and get the items
+ // for that first key and return them.
+ Token::Key(k) => {
+ if let Some(prefix) = &k.prefix {
+ let maybe_url = registry.get_url_for_key(k);
+ if let Some(url) = maybe_url {
+ if let Some(items) = self.get_items(url).await {
+ let base = Url::parse(&origin).ok()?;
+ let (items, preselect, incomplete) = match items {
+ VariableItems::List(list) => {
+ (list.items, list.preselect, list.is_incomplete)
+ }
+ VariableItems::Simple(items) => (items, None, false),
+ };
+ if incomplete {
+ is_incomplete = true;
+ }
+ for (idx, item) in items.into_iter().enumerate() {
+ let path = format!("{prefix}{item}");
let kind = Some(lsp::CompletionItemKind::FOLDER);
- let mut url = specifier.clone();
- url.set_path(s);
- let full_text = url.as_str();
+ let item_specifier = base.join(&path).ok()?;
+ let full_text = if let Some(suffix) =
+ item_specifier.as_str().strip_prefix(resolved_str)
+ {
+ format!("{text}{suffix}")
+ } else {
+ item_specifier.to_string()
+ };
let text_edit =
Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: *range,
- new_text: full_text.to_string(),
+ new_text: full_text.clone(),
}));
+ let command = if k.name == last_key_name
+ && !specifier_exists(&item_specifier)
+ {
+ Some(lsp::Command {
+ title: "".to_string(),
+ command: "deno.cache".to_string(),
+ arguments: Some(vec![
+ json!([item_specifier]),
+ json!(&resolved),
+ ]),
+ })
+ } else {
+ None
+ };
+ let detail = Some(format!("({})", k.name));
let filter_text = Some(full_text.to_string());
+ let sort_text = Some(format!("{:0>10}", idx + 1));
+ let preselect =
+ get_preselect(item.clone(), preselect.clone());
+ let data = get_data(registry, &resolved, k, &path);
+ let commit_characters = if is_incomplete {
+ Some(
+ REGISTRY_IMPORT_COMMIT_CHARS
+ .iter()
+ .map(|&c| c.into())
+ .collect(),
+ )
+ } else {
+ Some(
+ IMPORT_COMMIT_CHARS
+ .iter()
+ .map(|&c| c.into())
+ .collect(),
+ )
+ };
completions.insert(
- s.to_string(),
+ item.clone(),
lsp::CompletionItem {
- label,
+ label: item,
kind,
+ detail,
+ sort_text,
filter_text,
- sort_text: Some("1".to_string()),
text_edit,
- preselect: Some(true),
- commit_characters: Some(
- REGISTRY_IMPORT_COMMIT_CHARS
- .iter()
- .map(|&c| c.into())
- .collect(),
- ),
+ command,
+ preselect,
+ data,
+ commit_characters,
..Default::default()
},
);
}
}
- // if the token though is a key, and the key has a prefix, and
- // the path matches the prefix, we will go and get the items
- // for that first key and return them.
- Token::Key(k) => {
- if let Some(prefix) = &k.prefix {
- let maybe_url = registry.get_url_for_key(k);
- if let Some(url) = maybe_url {
- if let Some(items) = self.get_items(url).await {
- let base = Url::parse(&origin).ok()?;
- let (items, preselect, incomplete) = match items {
- VariableItems::List(list) => {
- (list.items, list.preselect, list.is_incomplete)
- }
- VariableItems::Simple(items) => {
- (items, None, false)
- }
- };
- if incomplete {
- is_incomplete = true;
- }
- for (idx, item) in items.into_iter().enumerate() {
- let path = format!("{prefix}{item}");
- let kind = Some(lsp::CompletionItemKind::FOLDER);
- let item_specifier = base.join(&path).ok()?;
- let full_text = item_specifier.as_str();
- let text_edit = Some(
- lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- range: *range,
- new_text: full_text.to_string(),
- }),
- );
- let command = if k.name == last_key_name
- && !specifier_exists(&item_specifier)
- {
- Some(lsp::Command {
- title: "".to_string(),
- command: "deno.cache".to_string(),
- arguments: Some(vec![
- json!([item_specifier]),
- json!(&specifier),
- ]),
- })
- } else {
- None
- };
- let detail = Some(format!("({})", k.name));
- let filter_text = Some(full_text.to_string());
- let sort_text = Some(format!("{:0>10}", idx + 1));
- let preselect =
- get_preselect(item.clone(), preselect.clone());
- let data = get_data(registry, &specifier, k, &path);
- let commit_characters = if is_incomplete {
- Some(
- REGISTRY_IMPORT_COMMIT_CHARS
- .iter()
- .map(|&c| c.into())
- .collect(),
- )
- } else {
- Some(
- IMPORT_COMMIT_CHARS
- .iter()
- .map(|&c| c.into())
- .collect(),
- )
- };
- completions.insert(
- item.clone(),
- lsp::CompletionItem {
- label: item,
- kind,
- detail,
- sort_text,
- filter_text,
- text_edit,
- command,
- preselect,
- data,
- commit_characters,
- ..Default::default()
- },
- );
- }
- }
- }
- }
- }
}
- break;
}
}
}
- // If we return None, other sources of completions will be looked for
- // but if we did at least match part of a registry, we should send an
- // empty vector so that no-completions will be sent back to the client
- return if completions.is_empty() && !did_match {
- None
- } else {
- Some(lsp::CompletionList {
- items: completions.into_values().collect(),
- is_incomplete,
- })
- };
+ break;
}
}
}
-
- self.get_origin_completions(current_specifier, range)
+ // If we return None, other sources of completions will be looked for
+ // but if we did at least match part of a registry, we should send an
+ // empty vector so that no-completions will be sent back to the client
+ if completions.is_empty() && !did_match {
+ None
+ } else {
+ Some(lsp::CompletionList {
+ items: completions.into_values().collect(),
+ is_incomplete,
+ })
+ }
}
pub async fn get_documentation(
@@ -1316,9 +1301,7 @@ mod tests {
character: 21,
},
};
- let completions = module_registry
- .get_completions("h", 1, &range, |_| false)
- .await;
+ let completions = module_registry.get_origin_completions("h", &range);
assert!(completions.is_some());
let completions = completions.unwrap().items;
assert_eq!(completions.len(), 1);
@@ -1340,9 +1323,8 @@ mod tests {
character: 36,
},
};
- let completions = module_registry
- .get_completions("http://localhost", 16, &range, |_| false)
- .await;
+ let completions =
+ module_registry.get_origin_completions("http://localhost", &range);
assert!(completions.is_some());
let completions = completions.unwrap().items;
assert_eq!(completions.len(), 1);
@@ -1377,7 +1359,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545", 21, &range, |_| false)
+ .get_completions("http://localhost:4545", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1393,7 +1375,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/", 22, &range, |_| false)
+ .get_completions("http://localhost:4545/", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1409,7 +1391,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/x/", 24, &range, |_| false)
+ .get_completions("http://localhost:4545/x/", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap();
@@ -1434,7 +1416,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/x/a", 25, &range, |_| false)
+ .get_completions("http://localhost:4545/x/a", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap();
@@ -1470,7 +1452,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/x/a@", 26, &range, |_| false)
+ .get_completions("http://localhost:4545/x/a@", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1493,7 +1475,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/x/a@v1.", 29, &range, |_| false)
+ .get_completions("http://localhost:4545/x/a@v1.", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1516,9 +1498,12 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/x/a@v1.0.0/", 33, &range, |_| {
- false
- })
+ .get_completions(
+ "http://localhost:4545/x/a@v1.0.0/",
+ &range,
+ None,
+ |_| false,
+ )
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1541,9 +1526,12 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/x/a@v1.0.0/b", 34, &range, |_| {
- false
- })
+ .get_completions(
+ "http://localhost:4545/x/a@v1.0.0/b",
+ &range,
+ None,
+ |_| false,
+ )
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1565,8 +1553,8 @@ mod tests {
let completions = module_registry
.get_completions(
"http://localhost:4545/x/a@v1.0.0/b/",
- 35,
&range,
+ None,
|_| false,
)
.await;
@@ -1602,7 +1590,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/", 22, &range, |_| false)
+ .get_completions("http://localhost:4545/", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1631,12 +1619,16 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/cde@", 26, &range, |_| false)
+ .get_completions("http://localhost:4545/cde@", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
- assert_eq!(completions.len(), 2);
for completion in completions {
+ if let Some(filter_text) = completion.filter_text {
+ if !"http://localhost:4545/cde@".contains(&filter_text) {
+ continue;
+ }
+ }
assert!(completion.text_edit.is_some());
if let lsp::CompletionTextEdit::Edit(edit) = completion.text_edit.unwrap()
{
@@ -1674,7 +1666,7 @@ mod tests {
},
};
let completions = module_registry
- .get_completions("http://localhost:4545/", 22, &range, |_| false)
+ .get_completions("http://localhost:4545/", &range, None, |_| false)
.await;
assert!(completions.is_some());
let completions = completions.unwrap().items;
@@ -1693,6 +1685,48 @@ mod tests {
}
}
+ #[tokio::test]
+ async fn test_registry_completions_import_map() {
+ let _g = test_util::http_server();
+ let temp_dir = TempDir::new();
+ let location = temp_dir.path().join("registries").to_path_buf();
+ let mut module_registry = ModuleRegistry::new(
+ location,
+ Arc::new(HttpClientProvider::new(None, None)),
+ );
+ module_registry.enable("http://localhost:4545/").await;
+ let range = lsp::Range {
+ start: lsp::Position {
+ line: 0,
+ character: 20,
+ },
+ end: lsp::Position {
+ line: 0,
+ character: 33,
+ },
+ };
+ let completions = module_registry
+ .get_completions(
+ "localhost4545/",
+ &range,
+ Some(&ModuleSpecifier::parse("http://localhost:4545/").unwrap()),
+ |_| false,
+ )
+ .await;
+ assert!(completions.is_some());
+ let completions = completions.unwrap().items;
+ assert_eq!(completions.len(), 3);
+ for completion in completions {
+ assert!(completion.text_edit.is_some());
+ if let lsp::CompletionTextEdit::Edit(edit) = completion.text_edit.unwrap()
+ {
+ assert_eq!(edit.new_text, format!("localhost4545{}", completion.label));
+ } else {
+ unreachable!("unexpected text edit");
+ }
+ }
+ }
+
#[test]
fn test_parse_replacement_variables() {
let actual = parse_replacement_variables(