From eb54c560b5832ea4ca5129e13805be3c338ad2c9 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sun, 31 May 2026 17:10:34 +0800 Subject: Fix trailing space and partial match bugs in default completion --- CHANGELOG.md | 4 +++- mingling_core/src/comp.rs | 33 +++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec5cce..17e356c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ #### Fixes: -None +1. **\[core:comp\]** Fixed `default_completion` incorrectly handling multi-level subcommand suggestions when the cursor is after a trailing space. `all_words.get(1..word_index)` could go out of bounds because Zsh's `$CURRENT` (`word_index`) may exceed `all_words.len()` when trailing whitespace is present. The range end is now capped with `.min(all_words.len())`. + +2. **\[core:comp\]** Fixed `default_completion` jumping to the next subcommand level on partial input (e.g. typing `b` for `bind` would skip `bind` and directly suggest third-level commands `add`/`ls`/`rm`). Now if the last input word is only a partial match (`starts_with` but not equal), the current-level word is suggested instead of skipping ahead. #### Optimizations: diff --git a/mingling_core/src/comp.rs b/mingling_core/src/comp.rs index 9d84557..8d55c5d 100644 --- a/mingling_core/src/comp.rs +++ b/mingling_core/src/comp.rs @@ -45,7 +45,7 @@ pub struct CompletionHelper; impl CompletionHelper { pub fn exec_completion

(ctx: &ShellContext) -> Suggest where - P: ProgramCollect + Display + PartialEq + 'static, + P: ProgramCollect + Display + PartialEq + 'static + std::fmt::Debug, { only_debug! { crate::debug::init_env_logger(); @@ -80,20 +80,24 @@ impl CompletionHelper { }; #[cfg(feature = "dispatch_tree")] let suggest = if let Ok(any) = P::dispatch_args_trie(&args) { + debug!("dispatch_args_trie OK, member_id = {:?}", any.member_id); trace!("entry type: {}", any.member_id); let dispatcher_not_found = >::member_id(); if dispatcher_not_found == any.member_id { + debug!("dispatcher_not_found matched"); trace!("begin not Ok"); None } else { let result = P::do_comp(&any, ctx); + debug!("do_comp result: {:?}", result); trace!("do_comp result: {:?}", result); Some(result) } } else { + debug!("dispatch_args_trie failed, args = {:?}", args); trace!("no dispatcher matched"); None }; @@ -161,19 +165,25 @@ where }; // Get the current input path + let input_end = ctx.word_index.min(ctx.all_words.len()); + debug!( "input_path before filter: {:?}", - &ctx.all_words.get(1..ctx.word_index).unwrap_or(&[]) + &ctx.all_words.get(1..input_end).unwrap_or(&[]) ); let input_path: Vec<&str> = ctx .all_words - .get(1..ctx.word_index) + .get(1..input_end) .unwrap_or(&[]) .iter() .filter(|s| !s.is_empty()) .map(|s| s.as_str()) .collect(); + debug!( + "input_path={:?}, current_word='{}'", + input_path, ctx.current_word + ); debug!("input_path after filter: {:?}", input_path); debug!( @@ -186,6 +196,7 @@ where // Special case: if input_path is empty, return all first-level commands if input_path.is_empty() { + debug!("input_path empty, returning first-level commands"); for node in cmd_nodes { let node_parts: Vec<&str> = node.split(' ').collect(); if !node_parts.is_empty() && !suggestions.contains(&node_parts[0].to_string()) { @@ -193,6 +204,7 @@ where } } } else { + debug!("input_path NOT empty, doing next-level suggestions"); // Get the current word let current_word = input_path.last().unwrap(); @@ -252,10 +264,19 @@ where } if matches && input_path.len() <= node_parts.len() { - if input_path.len() == node_parts.len() && !ctx.current_word.is_empty() { - suggestions.push(node_parts[input_path.len() - 1].to_string()); + let last_idx = input_path.len() - 1; + let is_partial = input_path[last_idx] != node_parts[last_idx]; + + if input_path.len() == node_parts.len() { + if !ctx.current_word.is_empty() { + suggestions.push(node_parts[last_idx].to_string()); + } } else if input_path.len() < node_parts.len() { - suggestions.push(node_parts[input_path.len()].to_string()); + if is_partial { + suggestions.push(node_parts[last_idx].to_string()); + } else { + suggestions.push(node_parts[input_path.len()].to_string()); + } } } } -- cgit