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