Skip to content

Commit 94b588c

Browse files
authored
fix(lsp): relative completions for bare import-mapped specifiers (denoland#26137)
1 parent ccdbeb4 commit 94b588c

File tree

2 files changed

+112
-100
lines changed

2 files changed

+112
-100
lines changed

cli/lsp/completions.rs

Lines changed: 63 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -200,15 +200,11 @@ pub async fn get_import_completions(
200200
{
201201
// completions for import map specifiers
202202
Some(lsp::CompletionResponse::List(completion_list))
203-
} else if text.starts_with("./")
204-
|| text.starts_with("../")
205-
|| text.starts_with('/')
203+
} else if let Some(completion_list) =
204+
get_local_completions(specifier, &text, &range, resolver)
206205
{
207206
// completions for local relative modules
208-
Some(lsp::CompletionResponse::List(CompletionList {
209-
is_incomplete: false,
210-
items: get_local_completions(specifier, &text, &range, resolver)?,
211-
}))
207+
Some(lsp::CompletionResponse::List(completion_list))
212208
} else if !text.is_empty() {
213209
// completion of modules from a module registry or cache
214210
check_auto_config_registry(
@@ -363,15 +359,15 @@ fn get_local_completions(
363359
text: &str,
364360
range: &lsp::Range,
365361
resolver: &LspResolver,
366-
) -> Option<Vec<lsp::CompletionItem>> {
362+
) -> Option<CompletionList> {
367363
if base.scheme() != "file" {
368364
return None;
369365
}
370-
let parent = base.join(text).ok()?.join(".").ok()?;
366+
let parent = &text[..text.char_indices().rfind(|(_, c)| *c == '/')?.0 + 1];
371367
let resolved_parent = resolver
372368
.as_graph_resolver(Some(base))
373369
.resolve(
374-
parent.as_str(),
370+
parent,
375371
&Range {
376372
specifier: base.clone(),
377373
start: deno_graph::Position::zeroed(),
@@ -381,62 +377,62 @@ fn get_local_completions(
381377
)
382378
.ok()?;
383379
let resolved_parent_path = url_to_file_path(&resolved_parent).ok()?;
384-
let raw_parent =
385-
&text[..text.char_indices().rfind(|(_, c)| *c == '/')?.0 + 1];
386380
if resolved_parent_path.is_dir() {
387381
let cwd = std::env::current_dir().ok()?;
388-
let items = std::fs::read_dir(resolved_parent_path).ok()?;
389-
Some(
390-
items
391-
.filter_map(|de| {
392-
let de = de.ok()?;
393-
let label = de.path().file_name()?.to_string_lossy().to_string();
394-
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
395-
if entry_specifier == *base {
396-
return None;
397-
}
398-
let full_text = format!("{raw_parent}{label}");
399-
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
400-
range: *range,
401-
new_text: full_text.clone(),
402-
}));
403-
let filter_text = Some(full_text);
404-
match de.file_type() {
405-
Ok(file_type) if file_type.is_dir() => Some(lsp::CompletionItem {
406-
label,
407-
kind: Some(lsp::CompletionItemKind::FOLDER),
408-
detail: Some("(local)".to_string()),
409-
filter_text,
410-
sort_text: Some("1".to_string()),
411-
text_edit,
412-
commit_characters: Some(
413-
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
414-
),
415-
..Default::default()
416-
}),
417-
Ok(file_type) if file_type.is_file() => {
418-
if is_importable_ext(&de.path()) {
419-
Some(lsp::CompletionItem {
420-
label,
421-
kind: Some(lsp::CompletionItemKind::FILE),
422-
detail: Some("(local)".to_string()),
423-
filter_text,
424-
sort_text: Some("1".to_string()),
425-
text_edit,
426-
commit_characters: Some(
427-
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
428-
),
429-
..Default::default()
430-
})
431-
} else {
432-
None
433-
}
382+
let entries = std::fs::read_dir(resolved_parent_path).ok()?;
383+
let items = entries
384+
.filter_map(|de| {
385+
let de = de.ok()?;
386+
let label = de.path().file_name()?.to_string_lossy().to_string();
387+
let entry_specifier = resolve_path(de.path().to_str()?, &cwd).ok()?;
388+
if entry_specifier == *base {
389+
return None;
390+
}
391+
let full_text = format!("{parent}{label}");
392+
let text_edit = Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
393+
range: *range,
394+
new_text: full_text.clone(),
395+
}));
396+
let filter_text = Some(full_text);
397+
match de.file_type() {
398+
Ok(file_type) if file_type.is_dir() => Some(lsp::CompletionItem {
399+
label,
400+
kind: Some(lsp::CompletionItemKind::FOLDER),
401+
detail: Some("(local)".to_string()),
402+
filter_text,
403+
sort_text: Some("1".to_string()),
404+
text_edit,
405+
commit_characters: Some(
406+
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
407+
),
408+
..Default::default()
409+
}),
410+
Ok(file_type) if file_type.is_file() => {
411+
if is_importable_ext(&de.path()) {
412+
Some(lsp::CompletionItem {
413+
label,
414+
kind: Some(lsp::CompletionItemKind::FILE),
415+
detail: Some("(local)".to_string()),
416+
filter_text,
417+
sort_text: Some("1".to_string()),
418+
text_edit,
419+
commit_characters: Some(
420+
IMPORT_COMMIT_CHARS.iter().map(|&c| c.into()).collect(),
421+
),
422+
..Default::default()
423+
})
424+
} else {
425+
None
434426
}
435-
_ => None,
436427
}
437-
})
438-
.collect(),
439-
)
428+
_ => None,
429+
}
430+
})
431+
.collect();
432+
Some(CompletionList {
433+
is_incomplete: false,
434+
items,
435+
})
440436
} else {
441437
None
442438
}
@@ -921,11 +917,11 @@ mod tests {
921917
},
922918
},
923919
&Default::default(),
924-
);
925-
assert!(actual.is_some());
926-
let actual = actual.unwrap();
927-
assert_eq!(actual.len(), 3);
928-
for item in actual {
920+
)
921+
.unwrap();
922+
assert!(!actual.is_incomplete);
923+
assert_eq!(actual.items.len(), 3);
924+
for item in actual.items {
929925
match item.text_edit {
930926
Some(lsp::CompletionTextEdit::Edit(text_edit)) => {
931927
assert!(["./b", "./f.mjs", "./g.json"]

tests/integration/lsp_tests.rs

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,7 @@ fn lsp_import_map_import_completions() {
13421342
"/#/": "./src/",
13431343
"fs": "https://example.com/fs/index.js",
13441344
"std/": "https://example.com/[email protected]/",
1345+
"lib/": "./lib/",
13451346
},
13461347
"scopes": {
13471348
"file:///": {
@@ -1364,17 +1365,18 @@ fn lsp_import_map_import_completions() {
13641365
"uri": uri,
13651366
"languageId": "typescript",
13661367
"version": 1,
1367-
"text": "import * as a from \"/~/b.ts\";\nimport * as b from \"\""
1368-
}
1368+
"text": r#"
1369+
import * as b from "";
1370+
import * as b from "/~/";
1371+
import * as b from "lib/";
1372+
"#,
1373+
},
13691374
}));
13701375

13711376
let res = client.get_completion(
13721377
&uri,
1373-
(1, 20),
1374-
json!({
1375-
"triggerKind": 2,
1376-
"triggerCharacter": "\""
1377-
}),
1378+
(1, 28),
1379+
json!({ "triggerKind": 2, "triggerCharacter": "\"" }),
13781380
);
13791381
assert_eq!(
13801382
json!(res),
@@ -1409,6 +1411,13 @@ fn lsp_import_map_import_completions() {
14091411
"sortText": "std",
14101412
"insertText": "std",
14111413
"commitCharacters": ["\"", "'"],
1414+
}, {
1415+
"label": "lib",
1416+
"kind": 19,
1417+
"detail": "(import map)",
1418+
"sortText": "lib",
1419+
"insertText": "lib",
1420+
"commitCharacters": ["\"", "'"],
14121421
}, {
14131422
"label": "fs",
14141423
"kind": 17,
@@ -1435,32 +1444,39 @@ fn lsp_import_map_import_completions() {
14351444
})
14361445
);
14371446

1438-
client.write_notification(
1439-
"textDocument/didChange",
1447+
let res = client.get_completion(
1448+
&uri,
1449+
(2, 31),
1450+
json!({ "triggerKind": 2, "triggerCharacter": "/" }),
1451+
);
1452+
assert_eq!(
1453+
json!(res),
14401454
json!({
1441-
"textDocument": {
1442-
"uri": uri,
1443-
"version": 2
1444-
},
1445-
"contentChanges": [
1455+
"isIncomplete": false,
1456+
"items": [
14461457
{
1447-
"range": {
1448-
"start": { "line": 1, "character": 20 },
1449-
"end": { "line": 1, "character": 20 }
1458+
"label": "b.ts",
1459+
"kind": 17,
1460+
"detail": "(local)",
1461+
"sortText": "1",
1462+
"filterText": "/~/b.ts",
1463+
"textEdit": {
1464+
"range": {
1465+
"start": { "line": 2, "character": 28 },
1466+
"end": { "line": 2, "character": 31 },
1467+
},
1468+
"newText": "/~/b.ts",
14501469
},
1451-
"text": "/~/"
1452-
}
1453-
]
1470+
"commitCharacters": ["\"", "'"],
1471+
},
1472+
],
14541473
}),
14551474
);
14561475

14571476
let res = client.get_completion(
1458-
uri,
1459-
(1, 23),
1460-
json!({
1461-
"triggerKind": 2,
1462-
"triggerCharacter": "/"
1463-
}),
1477+
&uri,
1478+
(3, 32),
1479+
json!({ "triggerKind": 2, "triggerCharacter": "/" }),
14641480
);
14651481
assert_eq!(
14661482
json!(res),
@@ -1472,18 +1488,18 @@ fn lsp_import_map_import_completions() {
14721488
"kind": 17,
14731489
"detail": "(local)",
14741490
"sortText": "1",
1475-
"filterText": "/~/b.ts",
1491+
"filterText": "lib/b.ts",
14761492
"textEdit": {
14771493
"range": {
1478-
"start": { "line": 1, "character": 20 },
1479-
"end": { "line": 1, "character": 23 }
1494+
"start": { "line": 3, "character": 28 },
1495+
"end": { "line": 3, "character": 32 },
14801496
},
1481-
"newText": "/~/b.ts"
1497+
"newText": "lib/b.ts",
14821498
},
14831499
"commitCharacters": ["\"", "'"],
1484-
}
1485-
]
1486-
})
1500+
},
1501+
],
1502+
}),
14871503
);
14881504

14891505
client.shutdown();

0 commit comments

Comments
 (0)