mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 20:29:11 +00:00
fix(lsp): complete exports for import mapped jsr specifiers (#24054)
This commit is contained in:
parent
13924fdb1b
commit
72088f2f52
3 changed files with 557 additions and 477 deletions
|
@ -9,17 +9,19 @@ use super::jsr::CliJsrSearchApi;
|
||||||
use super::lsp_custom;
|
use super::lsp_custom;
|
||||||
use super::npm::CliNpmSearchApi;
|
use super::npm::CliNpmSearchApi;
|
||||||
use super::registries::ModuleRegistry;
|
use super::registries::ModuleRegistry;
|
||||||
|
use super::resolver::LspResolver;
|
||||||
use super::search::PackageSearchApi;
|
use super::search::PackageSearchApi;
|
||||||
use super::tsc;
|
use super::tsc;
|
||||||
|
|
||||||
use crate::jsr::JsrFetchResolver;
|
use crate::jsr::JsrFetchResolver;
|
||||||
use crate::util::path::is_importable_ext;
|
use crate::util::path::is_importable_ext;
|
||||||
use crate::util::path::relative_specifier;
|
use crate::util::path::relative_specifier;
|
||||||
|
use deno_graph::source::ResolutionMode;
|
||||||
|
use deno_graph::Range;
|
||||||
use deno_runtime::fs_util::specifier_to_file_path;
|
use deno_runtime::fs_util::specifier_to_file_path;
|
||||||
|
|
||||||
use deno_ast::LineAndColumnIndex;
|
use deno_ast::LineAndColumnIndex;
|
||||||
use deno_ast::SourceTextInfo;
|
use deno_ast::SourceTextInfo;
|
||||||
use deno_core::normalize_path;
|
|
||||||
use deno_core::resolve_path;
|
use deno_core::resolve_path;
|
||||||
use deno_core::resolve_url;
|
use deno_core::resolve_url;
|
||||||
use deno_core::serde::Deserialize;
|
use deno_core::serde::Deserialize;
|
||||||
|
@ -30,6 +32,8 @@ use deno_core::ModuleSpecifier;
|
||||||
use deno_semver::jsr::JsrPackageReqReference;
|
use deno_semver::jsr::JsrPackageReqReference;
|
||||||
use deno_semver::package::PackageNv;
|
use deno_semver::package::PackageNv;
|
||||||
use import_map::ImportMap;
|
use import_map::ImportMap;
|
||||||
|
use indexmap::IndexSet;
|
||||||
|
use lsp_types::CompletionList;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use tower_lsp::lsp_types as lsp;
|
use tower_lsp::lsp_types as lsp;
|
||||||
|
@ -154,46 +158,53 @@ pub async fn get_import_completions(
|
||||||
jsr_search_api: &CliJsrSearchApi,
|
jsr_search_api: &CliJsrSearchApi,
|
||||||
npm_search_api: &CliNpmSearchApi,
|
npm_search_api: &CliNpmSearchApi,
|
||||||
documents: &Documents,
|
documents: &Documents,
|
||||||
|
resolver: &LspResolver,
|
||||||
maybe_import_map: Option<&ImportMap>,
|
maybe_import_map: Option<&ImportMap>,
|
||||||
) -> Option<lsp::CompletionResponse> {
|
) -> Option<lsp::CompletionResponse> {
|
||||||
let document = documents.get(specifier)?;
|
let document = documents.get(specifier)?;
|
||||||
let file_referrer = document.file_referrer();
|
let file_referrer = document.file_referrer();
|
||||||
let (text, _, range) = document.get_maybe_dependency(position)?;
|
let (text, _, range) = document.get_maybe_dependency(position)?;
|
||||||
let range = to_narrow_lsp_range(&document.text_info(), &range);
|
let range = to_narrow_lsp_range(&document.text_info(), &range);
|
||||||
if let Some(completion_list) = get_import_map_completions(
|
let resolved = resolver
|
||||||
specifier,
|
.as_graph_resolver(file_referrer)
|
||||||
&text,
|
.resolve(
|
||||||
&range,
|
&text,
|
||||||
maybe_import_map,
|
&Range {
|
||||||
documents,
|
specifier: specifier.clone(),
|
||||||
) {
|
start: deno_graph::Position::zeroed(),
|
||||||
// completions for import map specifiers
|
end: deno_graph::Position::zeroed(),
|
||||||
Some(lsp::CompletionResponse::List(completion_list))
|
},
|
||||||
} else if text.starts_with("./") || text.starts_with("../") {
|
ResolutionMode::Execution,
|
||||||
// completions for local relative modules
|
)
|
||||||
Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
.ok();
|
||||||
is_incomplete: false,
|
if let Some(completion_list) = get_jsr_completions(
|
||||||
items: get_local_completions(specifier, &text, &range)?,
|
|
||||||
}))
|
|
||||||
} else if text.starts_with("jsr:") {
|
|
||||||
let items = get_jsr_completions(
|
|
||||||
specifier,
|
specifier,
|
||||||
&text,
|
&text,
|
||||||
&range,
|
&range,
|
||||||
|
resolved.as_ref(),
|
||||||
jsr_search_api,
|
jsr_search_api,
|
||||||
Some(jsr_search_api.get_resolver()),
|
Some(jsr_search_api.get_resolver()),
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
{
|
||||||
is_incomplete: !items.is_empty(),
|
Some(lsp::CompletionResponse::List(completion_list))
|
||||||
items,
|
} else if let Some(completion_list) =
|
||||||
}))
|
get_npm_completions(specifier, &text, &range, npm_search_api).await
|
||||||
} else if text.starts_with("npm:") {
|
{
|
||||||
let items =
|
Some(lsp::CompletionResponse::List(completion_list))
|
||||||
get_npm_completions(specifier, &text, &range, npm_search_api).await?;
|
} else if let Some(completion_list) =
|
||||||
Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
get_import_map_completions(specifier, &text, &range, maybe_import_map)
|
||||||
is_incomplete: !items.is_empty(),
|
{
|
||||||
items,
|
// completions for import map specifiers
|
||||||
|
Some(lsp::CompletionResponse::List(completion_list))
|
||||||
|
} else if text.starts_with("./")
|
||||||
|
|| text.starts_with("../")
|
||||||
|
|| text.starts_with('/')
|
||||||
|
{
|
||||||
|
// completions for local relative modules
|
||||||
|
Some(lsp::CompletionResponse::List(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: get_local_completions(specifier, &text, &range, resolver)?,
|
||||||
}))
|
}))
|
||||||
} else if !text.is_empty() {
|
} else if !text.is_empty() {
|
||||||
// completion of modules from a module registry or cache
|
// completion of modules from a module registry or cache
|
||||||
|
@ -214,7 +225,7 @@ pub async fn get_import_completions(
|
||||||
documents.exists(s, file_referrer)
|
documents.exists(s, file_referrer)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
let list = maybe_list.unwrap_or_else(|| lsp::CompletionList {
|
let list = maybe_list.unwrap_or_else(|| CompletionList {
|
||||||
items: get_workspace_completions(specifier, &text, &range, documents),
|
items: get_workspace_completions(specifier, &text, &range, documents),
|
||||||
is_incomplete: false,
|
is_incomplete: false,
|
||||||
});
|
});
|
||||||
|
@ -246,7 +257,7 @@ pub async fn get_import_completions(
|
||||||
is_incomplete = origin_items.is_incomplete;
|
is_incomplete = origin_items.is_incomplete;
|
||||||
items.extend(origin_items.items);
|
items.extend(origin_items.items);
|
||||||
}
|
}
|
||||||
Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
Some(lsp::CompletionResponse::List(CompletionList {
|
||||||
is_incomplete,
|
is_incomplete,
|
||||||
items,
|
items,
|
||||||
}))
|
}))
|
||||||
|
@ -298,15 +309,14 @@ fn get_base_import_map_completions(
|
||||||
/// that the path post the `/` should be appended to resolved specifier. This
|
/// that the path post the `/` should be appended to resolved specifier. This
|
||||||
/// handles both cases, pulling any completions from the workspace completions.
|
/// handles both cases, pulling any completions from the workspace completions.
|
||||||
fn get_import_map_completions(
|
fn get_import_map_completions(
|
||||||
specifier: &ModuleSpecifier,
|
_specifier: &ModuleSpecifier,
|
||||||
text: &str,
|
text: &str,
|
||||||
range: &lsp::Range,
|
range: &lsp::Range,
|
||||||
maybe_import_map: Option<&ImportMap>,
|
maybe_import_map: Option<&ImportMap>,
|
||||||
documents: &Documents,
|
) -> Option<CompletionList> {
|
||||||
) -> Option<lsp::CompletionList> {
|
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
if let Some(import_map) = maybe_import_map {
|
if let Some(import_map) = maybe_import_map {
|
||||||
let mut items = Vec::new();
|
let mut specifiers = IndexSet::new();
|
||||||
for key in import_map.imports().keys() {
|
for key in import_map.imports().keys() {
|
||||||
// for some reason, the import_map stores keys that begin with `/` as
|
// for some reason, the import_map stores keys that begin with `/` as
|
||||||
// `file:///` in its index, so we have to reverse that here
|
// `file:///` in its index, so we have to reverse that here
|
||||||
|
@ -315,101 +325,67 @@ fn get_import_map_completions(
|
||||||
} else {
|
} else {
|
||||||
key.to_string()
|
key.to_string()
|
||||||
};
|
};
|
||||||
if text.starts_with(&key) && key.ends_with('/') {
|
if key.starts_with(text) && key != text {
|
||||||
if let Ok(resolved) = import_map.resolve(&key, specifier) {
|
specifiers.insert(key.trim_end_matches('/').to_string());
|
||||||
let resolved = resolved.to_string();
|
}
|
||||||
let workspace_items: Vec<lsp::CompletionItem> = documents
|
}
|
||||||
.documents(DocumentsFilter::AllDiagnosable)
|
if !specifiers.is_empty() {
|
||||||
|
let items = specifiers
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|d| {
|
.map(|specifier| lsp::CompletionItem {
|
||||||
let specifier_str = d.specifier().to_string();
|
label: specifier.clone(),
|
||||||
let new_text = specifier_str.replace(&resolved, &key);
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
if specifier_str.starts_with(&resolved) {
|
|
||||||
let label = specifier_str.replace(&resolved, "");
|
|
||||||
let text_edit =
|
|
||||||
Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
|
||||||
range: *range,
|
|
||||||
new_text: new_text.clone(),
|
|
||||||
}));
|
|
||||||
Some(lsp::CompletionItem {
|
|
||||||
label,
|
|
||||||
kind: Some(lsp::CompletionItemKind::MODULE),
|
|
||||||
detail: Some("(import map)".to_string()),
|
detail: Some("(import map)".to_string()),
|
||||||
sort_text: Some("1".to_string()),
|
sort_text: Some("1".to_string()),
|
||||||
filter_text: Some(new_text),
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
text_edit,
|
range: *range,
|
||||||
|
new_text: specifier,
|
||||||
|
})),
|
||||||
commit_characters: Some(
|
commit_characters: Some(
|
||||||
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
items.extend(workspace_items);
|
return Some(CompletionList {
|
||||||
}
|
|
||||||
} else if key.starts_with(text) && text != key {
|
|
||||||
let mut label = key.to_string();
|
|
||||||
let kind = if key.ends_with('/') {
|
|
||||||
label.pop();
|
|
||||||
Some(lsp::CompletionItemKind::FOLDER)
|
|
||||||
} else {
|
|
||||||
Some(lsp::CompletionItemKind::MODULE)
|
|
||||||
};
|
|
||||||
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
|
||||||
range: *range,
|
|
||||||
new_text: label.clone(),
|
|
||||||
}));
|
|
||||||
items.push(lsp::CompletionItem {
|
|
||||||
label,
|
|
||||||
kind,
|
|
||||||
detail: Some("(import map)".to_string()),
|
|
||||||
sort_text: Some("1".to_string()),
|
|
||||||
text_edit,
|
|
||||||
commit_characters: Some(
|
|
||||||
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if !items.is_empty() {
|
|
||||||
return Some(lsp::CompletionList {
|
|
||||||
items,
|
items,
|
||||||
is_incomplete: false,
|
is_incomplete: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return local completions that are relative to the base specifier.
|
/// Return local completions that are relative to the base specifier.
|
||||||
fn get_local_completions(
|
fn get_local_completions(
|
||||||
base: &ModuleSpecifier,
|
base: &ModuleSpecifier,
|
||||||
current: &str,
|
text: &str,
|
||||||
range: &lsp::Range,
|
range: &lsp::Range,
|
||||||
|
resolver: &LspResolver,
|
||||||
) -> Option<Vec<lsp::CompletionItem>> {
|
) -> Option<Vec<lsp::CompletionItem>> {
|
||||||
if base.scheme() != "file" {
|
if base.scheme() != "file" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let parent = base.join(text).ok()?.join(".").ok()?;
|
||||||
let mut base_path = specifier_to_file_path(base).ok()?;
|
let resolved_parent = resolver
|
||||||
base_path.pop();
|
.as_graph_resolver(Some(base))
|
||||||
let mut current_path = normalize_path(base_path.join(current));
|
.resolve(
|
||||||
// if the current text does not end in a `/` then we are still selecting on
|
parent.as_str(),
|
||||||
// the parent and should show all completions from there.
|
&Range {
|
||||||
let is_parent = if !current.ends_with('/') {
|
specifier: base.clone(),
|
||||||
current_path.pop();
|
start: deno_graph::Position::zeroed(),
|
||||||
true
|
end: deno_graph::Position::zeroed(),
|
||||||
} else {
|
},
|
||||||
false
|
ResolutionMode::Execution,
|
||||||
};
|
)
|
||||||
|
.ok()?;
|
||||||
|
let resolved_parent_path = specifier_to_file_path(&resolved_parent).ok()?;
|
||||||
|
let raw_parent =
|
||||||
|
&text[..text.char_indices().rfind(|(_, c)| *c == '/')?.0 + 1];
|
||||||
|
if resolved_parent_path.is_dir() {
|
||||||
let cwd = std::env::current_dir().ok()?;
|
let cwd = std::env::current_dir().ok()?;
|
||||||
if current_path.is_dir() {
|
let items = std::fs::read_dir(resolved_parent_path).ok()?;
|
||||||
let items = std::fs::read_dir(current_path).ok()?;
|
|
||||||
Some(
|
Some(
|
||||||
items
|
items
|
||||||
.filter_map(|de| {
|
.filter_map(|de| {
|
||||||
|
@ -419,27 +395,17 @@ fn get_local_completions(
|
||||||
if entry_specifier == *base {
|
if entry_specifier == *base {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let full_text = relative_specifier(base, &entry_specifier)?;
|
let full_text = format!("{raw_parent}{label}");
|
||||||
// this weeds out situations where we are browsing in the parent, but
|
|
||||||
// we want to filter out non-matches when the completion is manually
|
|
||||||
// invoked by the user, but still allows for things like `../src/../`
|
|
||||||
// which is silly, but no reason to not allow it.
|
|
||||||
if is_parent && !full_text.starts_with(current) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
range: *range,
|
range: *range,
|
||||||
new_text: full_text.clone(),
|
new_text: full_text.clone(),
|
||||||
}));
|
}));
|
||||||
let filter_text = if full_text.starts_with(current) {
|
let filter_text = Some(full_text);
|
||||||
Some(full_text)
|
|
||||||
} else {
|
|
||||||
Some(format!("{current}{label}"))
|
|
||||||
};
|
|
||||||
match de.file_type() {
|
match de.file_type() {
|
||||||
Ok(file_type) if file_type.is_dir() => Some(lsp::CompletionItem {
|
Ok(file_type) if file_type.is_dir() => Some(lsp::CompletionItem {
|
||||||
label,
|
label,
|
||||||
kind: Some(lsp::CompletionItemKind::FOLDER),
|
kind: Some(lsp::CompletionItemKind::FOLDER),
|
||||||
|
detail: Some("(local)".to_string()),
|
||||||
filter_text,
|
filter_text,
|
||||||
sort_text: Some("1".to_string()),
|
sort_text: Some("1".to_string()),
|
||||||
text_edit,
|
text_edit,
|
||||||
|
@ -517,11 +483,15 @@ async fn get_jsr_completions(
|
||||||
referrer: &ModuleSpecifier,
|
referrer: &ModuleSpecifier,
|
||||||
specifier: &str,
|
specifier: &str,
|
||||||
range: &lsp::Range,
|
range: &lsp::Range,
|
||||||
|
resolved: Option<&ModuleSpecifier>,
|
||||||
jsr_search_api: &impl PackageSearchApi,
|
jsr_search_api: &impl PackageSearchApi,
|
||||||
jsr_resolver: Option<&JsrFetchResolver>,
|
jsr_resolver: Option<&JsrFetchResolver>,
|
||||||
) -> Option<Vec<lsp::CompletionItem>> {
|
) -> Option<CompletionList> {
|
||||||
// First try to match `jsr:some-package@some-version/<export-to-complete>`.
|
// First try to match `jsr:some-package@some-version/<export-to-complete>`.
|
||||||
if let Ok(req_ref) = JsrPackageReqReference::from_str(specifier) {
|
let req_ref = resolved
|
||||||
|
.and_then(|s| JsrPackageReqReference::from_specifier(s).ok())
|
||||||
|
.or_else(|| JsrPackageReqReference::from_str(specifier).ok());
|
||||||
|
if let Some(req_ref) = req_ref {
|
||||||
let sub_path = req_ref.sub_path();
|
let sub_path = req_ref.sub_path();
|
||||||
if sub_path.is_some() || specifier.ends_with('/') {
|
if sub_path.is_some() || specifier.ends_with('/') {
|
||||||
let export_prefix = sub_path.unwrap_or("");
|
let export_prefix = sub_path.unwrap_or("");
|
||||||
|
@ -543,7 +513,10 @@ async fn get_jsr_completions(
|
||||||
if !export.starts_with(export_prefix) {
|
if !export.starts_with(export_prefix) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let specifier = format!("jsr:{}/{}", req_ref.req(), export);
|
let specifier = format!(
|
||||||
|
"{}/{export}",
|
||||||
|
specifier.strip_suffix(export_prefix)?.trim_end_matches('/')
|
||||||
|
);
|
||||||
let command = Some(lsp::Command {
|
let command = Some(lsp::Command {
|
||||||
title: "".to_string(),
|
title: "".to_string(),
|
||||||
command: "deno.cache".to_string(),
|
command: "deno.cache".to_string(),
|
||||||
|
@ -571,7 +544,10 @@ async fn get_jsr_completions(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
return Some(items);
|
return Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -618,7 +594,10 @@ async fn get_jsr_completions(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
return Some(items);
|
return Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise match `jsr:<package-to-complete>`.
|
// Otherwise match `jsr:<package-to-complete>`.
|
||||||
|
@ -655,7 +634,10 @@ async fn get_jsr_completions(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Some(items)
|
Some(CompletionList {
|
||||||
|
is_incomplete: true,
|
||||||
|
items,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get completions for `npm:` specifiers.
|
/// Get completions for `npm:` specifiers.
|
||||||
|
@ -664,7 +646,7 @@ async fn get_npm_completions(
|
||||||
specifier: &str,
|
specifier: &str,
|
||||||
range: &lsp::Range,
|
range: &lsp::Range,
|
||||||
npm_search_api: &impl PackageSearchApi,
|
npm_search_api: &impl PackageSearchApi,
|
||||||
) -> Option<Vec<lsp::CompletionItem>> {
|
) -> Option<CompletionList> {
|
||||||
// First try to match `npm:some-package@<version-to-complete>`.
|
// First try to match `npm:some-package@<version-to-complete>`.
|
||||||
let bare_specifier = specifier.strip_prefix("npm:")?;
|
let bare_specifier = specifier.strip_prefix("npm:")?;
|
||||||
if let Some(v_index) = parse_bare_specifier_version_index(bare_specifier) {
|
if let Some(v_index) = parse_bare_specifier_version_index(bare_specifier) {
|
||||||
|
@ -707,7 +689,10 @@ async fn get_npm_completions(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
return Some(items);
|
return Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise match `npm:<package-to-complete>`.
|
// Otherwise match `npm:<package-to-complete>`.
|
||||||
|
@ -744,7 +729,10 @@ async fn get_npm_completions(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Some(items)
|
Some(CompletionList {
|
||||||
|
is_incomplete: true,
|
||||||
|
items,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get workspace completions that include modules in the Deno cache which match
|
/// Get workspace completions that include modules in the Deno cache which match
|
||||||
|
@ -807,6 +795,7 @@ mod tests {
|
||||||
use crate::lsp::search::tests::TestPackageSearchApi;
|
use crate::lsp::search::tests::TestPackageSearchApi;
|
||||||
use deno_core::resolve_url;
|
use deno_core::resolve_url;
|
||||||
use deno_graph::Range;
|
use deno_graph::Range;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use test_util::TempDir;
|
use test_util::TempDir;
|
||||||
|
|
||||||
|
@ -897,6 +886,7 @@ mod tests {
|
||||||
character: 22,
|
character: 22,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
&Default::default(),
|
||||||
);
|
);
|
||||||
assert!(actual.is_some());
|
assert!(actual.is_some());
|
||||||
let actual = actual.unwrap();
|
let actual = actual.unwrap();
|
||||||
|
@ -1012,13 +1002,21 @@ mod tests {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let referrer = ModuleSpecifier::parse("file:///referrer.ts").unwrap();
|
let referrer = ModuleSpecifier::parse("file:///referrer.ts").unwrap();
|
||||||
let actual =
|
let actual = get_jsr_completions(
|
||||||
get_jsr_completions(&referrer, "jsr:as", &range, &jsr_search_api, None)
|
&referrer,
|
||||||
|
"jsr:as",
|
||||||
|
&range,
|
||||||
|
None,
|
||||||
|
&jsr_search_api,
|
||||||
|
None,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual,
|
actual,
|
||||||
vec![
|
CompletionList {
|
||||||
|
is_incomplete: true,
|
||||||
|
items: vec![
|
||||||
lsp::CompletionItem {
|
lsp::CompletionItem {
|
||||||
label: "jsr:@std/assert".to_string(),
|
label: "jsr:@std/assert".to_string(),
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
@ -1065,7 +1063,8 @@ mod tests {
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1090,6 +1089,7 @@ mod tests {
|
||||||
&referrer,
|
&referrer,
|
||||||
"jsr:@std/assert@",
|
"jsr:@std/assert@",
|
||||||
&range,
|
&range,
|
||||||
|
None,
|
||||||
&jsr_search_api,
|
&jsr_search_api,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -1097,7 +1097,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual,
|
actual,
|
||||||
vec![
|
CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: vec![
|
||||||
lsp::CompletionItem {
|
lsp::CompletionItem {
|
||||||
label: "jsr:@std/assert@0.5.0".to_string(),
|
label: "jsr:@std/assert@0.5.0".to_string(),
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
@ -1167,7 +1169,8 @@ mod tests {
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1193,6 +1196,7 @@ mod tests {
|
||||||
&referrer,
|
&referrer,
|
||||||
"jsr:@std/path@0.1.0/co",
|
"jsr:@std/path@0.1.0/co",
|
||||||
&range,
|
&range,
|
||||||
|
None,
|
||||||
&jsr_search_api,
|
&jsr_search_api,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -1200,7 +1204,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual,
|
actual,
|
||||||
vec![
|
CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: vec![
|
||||||
lsp::CompletionItem {
|
lsp::CompletionItem {
|
||||||
label: "jsr:@std/path@0.1.0/common".to_string(),
|
label: "jsr:@std/path@0.1.0/common".to_string(),
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
@ -1247,7 +1253,67 @@ mod tests {
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_jsr_completions_for_exports_import_mapped() {
|
||||||
|
let jsr_search_api = TestPackageSearchApi::default().with_package_version(
|
||||||
|
"@std/path",
|
||||||
|
"0.1.0",
|
||||||
|
&[".", "./common"],
|
||||||
|
);
|
||||||
|
let range = lsp::Range {
|
||||||
|
start: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 23,
|
||||||
|
},
|
||||||
|
end: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 45,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let referrer = ModuleSpecifier::parse("file:///referrer.ts").unwrap();
|
||||||
|
let actual = get_jsr_completions(
|
||||||
|
&referrer,
|
||||||
|
"@std/path/co",
|
||||||
|
&range,
|
||||||
|
Some(&ModuleSpecifier::parse("jsr:@std/path@0.1.0/co").unwrap()),
|
||||||
|
&jsr_search_api,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
actual,
|
||||||
|
CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: vec![lsp::CompletionItem {
|
||||||
|
label: "@std/path/common".to_string(),
|
||||||
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
detail: Some("(jsr)".to_string()),
|
||||||
|
sort_text: Some("0000000002".to_string()),
|
||||||
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
|
range,
|
||||||
|
new_text: "@std/path/common".to_string(),
|
||||||
|
})),
|
||||||
|
command: Some(lsp::Command {
|
||||||
|
title: "".to_string(),
|
||||||
|
command: "deno.cache".to_string(),
|
||||||
|
arguments: Some(vec![
|
||||||
|
json!(["@std/path/common"]),
|
||||||
|
json!(&referrer),
|
||||||
|
json!({ "forceGlobalCache": true }),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
commit_characters: Some(
|
||||||
|
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect()
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1275,7 +1341,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual,
|
actual,
|
||||||
vec![
|
CompletionList {
|
||||||
|
is_incomplete: true,
|
||||||
|
items: vec![
|
||||||
lsp::CompletionItem {
|
lsp::CompletionItem {
|
||||||
label: "npm:puppeteer".to_string(),
|
label: "npm:puppeteer".to_string(),
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
@ -1368,7 +1436,8 @@ mod tests {
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1396,7 +1465,9 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual,
|
actual,
|
||||||
vec![
|
CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items: vec![
|
||||||
lsp::CompletionItem {
|
lsp::CompletionItem {
|
||||||
label: "npm:puppeteer@21.0.2".to_string(),
|
label: "npm:puppeteer@21.0.2".to_string(),
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
|
@ -1489,7 +1560,8 @@ mod tests {
|
||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2105,6 +2105,7 @@ impl Inner {
|
||||||
&self.jsr_search_api,
|
&self.jsr_search_api,
|
||||||
&self.npm_search_api,
|
&self.npm_search_api,
|
||||||
&self.documents,
|
&self.documents,
|
||||||
|
self.resolver.as_ref(),
|
||||||
self.config.tree.root_import_map().map(|i| i.as_ref()),
|
self.config.tree.root_import_map().map(|i| i.as_ref()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -1224,6 +1224,7 @@ fn lsp_import_map_import_completions() {
|
||||||
r#"{
|
r#"{
|
||||||
"imports": {
|
"imports": {
|
||||||
"/~/": "./lib/",
|
"/~/": "./lib/",
|
||||||
|
"/#/": "./src/",
|
||||||
"fs": "https://example.com/fs/index.js",
|
"fs": "https://example.com/fs/index.js",
|
||||||
"std/": "https://example.com/std@0.123.0/"
|
"std/": "https://example.com/std@0.123.0/"
|
||||||
}
|
}
|
||||||
|
@ -1296,7 +1297,14 @@ fn lsp_import_map_import_completions() {
|
||||||
"sortText": "/~",
|
"sortText": "/~",
|
||||||
"insertText": "/~",
|
"insertText": "/~",
|
||||||
"commitCharacters": ["\"", "'"],
|
"commitCharacters": ["\"", "'"],
|
||||||
}
|
}, {
|
||||||
|
"label": "/#",
|
||||||
|
"kind": 19,
|
||||||
|
"detail": "(import map)",
|
||||||
|
"sortText": "/#",
|
||||||
|
"insertText": "/#",
|
||||||
|
"commitCharacters": ["\"", "'"],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -1335,8 +1343,8 @@ fn lsp_import_map_import_completions() {
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"label": "b.ts",
|
"label": "b.ts",
|
||||||
"kind": 9,
|
"kind": 17,
|
||||||
"detail": "(import map)",
|
"detail": "(local)",
|
||||||
"sortText": "1",
|
"sortText": "1",
|
||||||
"filterText": "/~/b.ts",
|
"filterText": "/~/b.ts",
|
||||||
"textEdit": {
|
"textEdit": {
|
||||||
|
@ -7938,7 +7946,6 @@ fn lsp_completions_snippet() {
|
||||||
(5, 13),
|
(5, 13),
|
||||||
json!({ "triggerKind": 1 }),
|
json!({ "triggerKind": 1 }),
|
||||||
);
|
);
|
||||||
assert!(!list.is_incomplete);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(list),
|
json!(list),
|
||||||
json!({
|
json!({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue