Skip to content

Commit b325e9f

Browse files
fix: match completion prefix against unescaped names (#460)
When using `complete ... descriptions=#true`, prefix matching was done against escaped strings (e.g., `test\:integration`) instead of unescaped names (`test:integration`). This caused completions to fail when user input contained an unescaped colon. Fix by moving the filter to after the map that unescapes the names. This bug caused a problem with mise: there was no shell completion if you entered `mise test:i<tab>` even if there was a `test:integration` task. `mise run test:i<tab>` was *not* affected, that is treated differently by `mise`'s `usage` spec. ------ I feel that the test could be more readable, I might do that in a subsequent PR. I think all `complete_word` tests could use the fish syntax instead of the zsh syntax, so e.g. ```rust "test:unit\tRun unit tests\ntest:integration\tRun integration tests\nbuild\tBuild the project\n" // instead of "'test\\\\:unit'\\:'Run unit tests'\n'test\\\\:integration'\\:'Run integration tests'\n'build'\\:'Build the project'\n" ``` Let me know if you prefer I do that refactor first to make the diff here more readable (but it's not *that* bad) **Update:** Apparently, that'd also make Copilot happer. Here's a draft: ilyagr@fish-refactor . Again, I'm happy to reorder the commits so that the cleaner syntax comes first. The biggest win from the refactor is in the test added in this PR, but others are also easier to read IMO for somebody unfamiliar with either shell's syntax. ------ This was mostly debugged by AI. If the fix is not correct, it could be considered a bug report (but it seems good to me). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Fixes completion prefix matching when `complete ... descriptions=#true` by filtering after unescaping `name:description` outputs. > > - In `complete_word.rs`, process command output lines first (unescape and split) then filter by `ctoken`, so prefixes match unescaped names containing colons > - Adds test `complete_word_escaped_colons_in_completions` and example `colon-in-completions.usage.kdl` to validate colon-containing completions > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e24490f. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Ilya Grigoriev <[email protected]> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 85378c5 commit b325e9f

File tree

3 files changed

+34
-1
lines changed

3 files changed

+34
-1
lines changed

cli/src/cli/complete_word.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,6 @@ impl CompleteWord {
281281
let re = regex!(r"[^\\]:");
282282
return Ok(stdout
283283
.lines()
284-
.filter(|l| l.starts_with(ctoken))
285284
.map(|l| {
286285
if complete.descriptions {
287286
match re.find(l).map(|m| l.split_at(m.end() - 1)) {
@@ -298,6 +297,7 @@ impl CompleteWord {
298297
(l.trim().to_string(), String::new())
299298
}
300299
})
300+
.filter(|(name, _)| name.starts_with(ctoken))
301301
.collect());
302302
}
303303

cli/tests/complete_word.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,30 @@ fn complete_word_subcommands_without_shell() {
252252
cmd.assert().success().stdout(contains("install"));
253253
}
254254

255+
#[test]
256+
fn complete_word_escaped_colons_in_completions() {
257+
// When completions contain colons, they are escaped as \: in the name:description format.
258+
// Prefix matching should work against the unescaped names.
259+
260+
// Typing "test:" should match "test:unit" and "test:integration" (unescaped)
261+
assert_cmd("colon-in-completions.usage.kdl", &["--", "run", "test:"]).stdout(
262+
"'test\\\\:unit'\\:'Run unit tests'\n'test\\\\:integration'\\:'Run integration tests'\n",
263+
);
264+
265+
// Typing "test" should also match (prefix of the unescaped name)
266+
assert_cmd("colon-in-completions.usage.kdl", &["--", "run", "test"]).stdout(
267+
"'test\\\\:unit'\\:'Run unit tests'\n'test\\\\:integration'\\:'Run integration tests'\n",
268+
);
269+
270+
// Typing "build" should only match "build"
271+
assert_cmd("colon-in-completions.usage.kdl", &["--", "run", "build"])
272+
.stdout("'build'\\:'Build the project'\n");
273+
274+
// Empty input should match all completions
275+
assert_cmd("colon-in-completions.usage.kdl", &["--", "run", ""])
276+
.stdout("'test\\\\:unit'\\:'Run unit tests'\n'test\\\\:integration'\\:'Run integration tests'\n'build'\\:'Build the project'\n");
277+
}
278+
255279
fn cmd(example: &str, shell: Option<&str>) -> Command {
256280
let mut cmd = Command::new(cargo::cargo_bin!("usage"));
257281
cmd.args(["cw"]);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Test case for completions containing colons in their names
2+
// The completer outputs escaped colons (\:) per the usage spec
3+
4+
cmd "run" {
5+
arg "task"
6+
}
7+
8+
// Completer outputs escaped colons in name:description format
9+
complete "task" descriptions=#true run="echo \"test\\:unit:Run unit tests\ntest\\:integration:Run integration tests\nbuild:Build the project\""

0 commit comments

Comments
 (0)