mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
fix(lsp): relative completions for bare import-mapped specifiers (#26137)
This commit is contained in:
parent
ccdbeb433b
commit
94b588ce66
2 changed files with 117 additions and 105 deletions
|
@ -200,15 +200,11 @@ pub async fn get_import_completions(
|
||||||
{
|
{
|
||||||
// completions for import map specifiers
|
// completions for import map specifiers
|
||||||
Some(lsp::CompletionResponse::List(completion_list))
|
Some(lsp::CompletionResponse::List(completion_list))
|
||||||
} else if text.starts_with("./")
|
} else if let Some(completion_list) =
|
||||||
|| text.starts_with("../")
|
get_local_completions(specifier, &text, &range, resolver)
|
||||||
|| text.starts_with('/')
|
|
||||||
{
|
{
|
||||||
// completions for local relative modules
|
// completions for local relative modules
|
||||||
Some(lsp::CompletionResponse::List(CompletionList {
|
Some(lsp::CompletionResponse::List(completion_list))
|
||||||
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
|
||||||
check_auto_config_registry(
|
check_auto_config_registry(
|
||||||
|
@ -363,15 +359,15 @@ fn get_local_completions(
|
||||||
text: &str,
|
text: &str,
|
||||||
range: &lsp::Range,
|
range: &lsp::Range,
|
||||||
resolver: &LspResolver,
|
resolver: &LspResolver,
|
||||||
) -> Option<Vec<lsp::CompletionItem>> {
|
) -> Option<CompletionList> {
|
||||||
if base.scheme() != "file" {
|
if base.scheme() != "file" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let parent = base.join(text).ok()?.join(".").ok()?;
|
let parent = &text[..text.char_indices().rfind(|(_, c)| *c == '/')?.0 + 1];
|
||||||
let resolved_parent = resolver
|
let resolved_parent = resolver
|
||||||
.as_graph_resolver(Some(base))
|
.as_graph_resolver(Some(base))
|
||||||
.resolve(
|
.resolve(
|
||||||
parent.as_str(),
|
parent,
|
||||||
&Range {
|
&Range {
|
||||||
specifier: base.clone(),
|
specifier: base.clone(),
|
||||||
start: deno_graph::Position::zeroed(),
|
start: deno_graph::Position::zeroed(),
|
||||||
|
@ -381,62 +377,62 @@ fn get_local_completions(
|
||||||
)
|
)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let resolved_parent_path = url_to_file_path(&resolved_parent).ok()?;
|
let resolved_parent_path = url_to_file_path(&resolved_parent).ok()?;
|
||||||
let raw_parent =
|
|
||||||
&text[..text.char_indices().rfind(|(_, c)| *c == '/')?.0 + 1];
|
|
||||||
if resolved_parent_path.is_dir() {
|
if resolved_parent_path.is_dir() {
|
||||||
let cwd = std::env::current_dir().ok()?;
|
let cwd = std::env::current_dir().ok()?;
|
||||||
let items = std::fs::read_dir(resolved_parent_path).ok()?;
|
let entries = std::fs::read_dir(resolved_parent_path).ok()?;
|
||||||
Some(
|
let items = entries
|
||||||
items
|
.filter_map(|de| {
|
||||||
.filter_map(|de| {
|
let de = de.ok()?;
|
||||||
let de = de.ok()?;
|
let label = de.path().file_name()?.to_string_lossy().to_string();
|
||||||
let label = de.path().file_name()?.to_string_lossy().to_string();
|
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
|
||||||
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
|
if entry_specifier == *base {
|
||||||
if entry_specifier == *base {
|
return None;
|
||||||
return None;
|
}
|
||||||
}
|
let full_text = format!("{parent}{label}");
|
||||||
let full_text = format!("{raw_parent}{label}");
|
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 = Some(full_text);
|
||||||
let filter_text = Some(full_text);
|
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()),
|
||||||
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,
|
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()
|
}),
|
||||||
}),
|
Ok(file_type) if file_type.is_file() => {
|
||||||
Ok(file_type) if file_type.is_file() => {
|
if is_importable_ext(&de.path()) {
|
||||||
if is_importable_ext(&de.path()) {
|
Some(lsp::CompletionItem {
|
||||||
Some(lsp::CompletionItem {
|
label,
|
||||||
label,
|
kind: Some(lsp::CompletionItemKind::FILE),
|
||||||
kind: Some(lsp::CompletionItemKind::FILE),
|
detail: Some("(local)".to_string()),
|
||||||
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,
|
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 {
|
||||||
} else {
|
None
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
})
|
_ => None,
|
||||||
.collect(),
|
}
|
||||||
)
|
})
|
||||||
|
.collect();
|
||||||
|
Some(CompletionList {
|
||||||
|
is_incomplete: false,
|
||||||
|
items,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -921,11 +917,11 @@ mod tests {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
);
|
)
|
||||||
assert!(actual.is_some());
|
.unwrap();
|
||||||
let actual = actual.unwrap();
|
assert!(!actual.is_incomplete);
|
||||||
assert_eq!(actual.len(), 3);
|
assert_eq!(actual.items.len(), 3);
|
||||||
for item in actual {
|
for item in actual.items {
|
||||||
match item.text_edit {
|
match item.text_edit {
|
||||||
Some(lsp::CompletionTextEdit::Edit(text_edit)) => {
|
Some(lsp::CompletionTextEdit::Edit(text_edit)) => {
|
||||||
assert!(["./b", "./f.mjs", "./g.json"]
|
assert!(["./b", "./f.mjs", "./g.json"]
|
||||||
|
|
|
@ -1342,6 +1342,7 @@ fn lsp_import_map_import_completions() {
|
||||||
"/#/": "./src/",
|
"/#/": "./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/",
|
||||||
|
"lib/": "./lib/",
|
||||||
},
|
},
|
||||||
"scopes": {
|
"scopes": {
|
||||||
"file:///": {
|
"file:///": {
|
||||||
|
@ -1364,17 +1365,18 @@ fn lsp_import_map_import_completions() {
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
"languageId": "typescript",
|
"languageId": "typescript",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"text": "import * as a from \"/~/b.ts\";\nimport * as b from \"\""
|
"text": r#"
|
||||||
}
|
import * as b from "";
|
||||||
|
import * as b from "/~/";
|
||||||
|
import * as b from "lib/";
|
||||||
|
"#,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let res = client.get_completion(
|
let res = client.get_completion(
|
||||||
&uri,
|
&uri,
|
||||||
(1, 20),
|
(1, 28),
|
||||||
json!({
|
json!({ "triggerKind": 2, "triggerCharacter": "\"" }),
|
||||||
"triggerKind": 2,
|
|
||||||
"triggerCharacter": "\""
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(res),
|
json!(res),
|
||||||
|
@ -1409,6 +1411,13 @@ fn lsp_import_map_import_completions() {
|
||||||
"sortText": "std",
|
"sortText": "std",
|
||||||
"insertText": "std",
|
"insertText": "std",
|
||||||
"commitCharacters": ["\"", "'"],
|
"commitCharacters": ["\"", "'"],
|
||||||
|
}, {
|
||||||
|
"label": "lib",
|
||||||
|
"kind": 19,
|
||||||
|
"detail": "(import map)",
|
||||||
|
"sortText": "lib",
|
||||||
|
"insertText": "lib",
|
||||||
|
"commitCharacters": ["\"", "'"],
|
||||||
}, {
|
}, {
|
||||||
"label": "fs",
|
"label": "fs",
|
||||||
"kind": 17,
|
"kind": 17,
|
||||||
|
@ -1435,32 +1444,10 @@ fn lsp_import_map_import_completions() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
client.write_notification(
|
|
||||||
"textDocument/didChange",
|
|
||||||
json!({
|
|
||||||
"textDocument": {
|
|
||||||
"uri": uri,
|
|
||||||
"version": 2
|
|
||||||
},
|
|
||||||
"contentChanges": [
|
|
||||||
{
|
|
||||||
"range": {
|
|
||||||
"start": { "line": 1, "character": 20 },
|
|
||||||
"end": { "line": 1, "character": 20 }
|
|
||||||
},
|
|
||||||
"text": "/~/"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let res = client.get_completion(
|
let res = client.get_completion(
|
||||||
uri,
|
&uri,
|
||||||
(1, 23),
|
(2, 31),
|
||||||
json!({
|
json!({ "triggerKind": 2, "triggerCharacter": "/" }),
|
||||||
"triggerKind": 2,
|
|
||||||
"triggerCharacter": "/"
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(res),
|
json!(res),
|
||||||
|
@ -1475,15 +1462,44 @@ fn lsp_import_map_import_completions() {
|
||||||
"filterText": "/~/b.ts",
|
"filterText": "/~/b.ts",
|
||||||
"textEdit": {
|
"textEdit": {
|
||||||
"range": {
|
"range": {
|
||||||
"start": { "line": 1, "character": 20 },
|
"start": { "line": 2, "character": 28 },
|
||||||
"end": { "line": 1, "character": 23 }
|
"end": { "line": 2, "character": 31 },
|
||||||
},
|
},
|
||||||
"newText": "/~/b.ts"
|
"newText": "/~/b.ts",
|
||||||
},
|
},
|
||||||
"commitCharacters": ["\"", "'"],
|
"commitCharacters": ["\"", "'"],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
})
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let res = client.get_completion(
|
||||||
|
&uri,
|
||||||
|
(3, 32),
|
||||||
|
json!({ "triggerKind": 2, "triggerCharacter": "/" }),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
json!(res),
|
||||||
|
json!({
|
||||||
|
"isIncomplete": false,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"label": "b.ts",
|
||||||
|
"kind": 17,
|
||||||
|
"detail": "(local)",
|
||||||
|
"sortText": "1",
|
||||||
|
"filterText": "lib/b.ts",
|
||||||
|
"textEdit": {
|
||||||
|
"range": {
|
||||||
|
"start": { "line": 3, "character": 28 },
|
||||||
|
"end": { "line": 3, "character": 32 },
|
||||||
|
},
|
||||||
|
"newText": "lib/b.ts",
|
||||||
|
},
|
||||||
|
"commitCharacters": ["\"", "'"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
client.shutdown();
|
client.shutdown();
|
||||||
|
|
Loading…
Reference in a new issue