forked from 1b-agent/opencadstudio-upstream
fix(command-line): Enter runs the highlighted autocomplete pre-selection
This commit is contained in:
1 parent
bd1fdcc1c5
commit
631c0c97aa
1 file changed
+68
-5
+68
-5
@@ -164,8 +164,9 @@ pub struct CommandLine {
|
|||||||
pub history_open: bool,
|
pub history_open: bool,
|
||||||
/// Persisted height of the full-history editor in logical pixels.
|
/// Persisted height of the full-history editor in logical pixels.
|
||||||
pub history_height: f32,
|
pub history_height: f32,
|
||||||
/// Index of the currently-highlighted autocomplete suggestion, or
|
/// Index of the currently-highlighted autocomplete suggestion. `None`
|
||||||
/// `None` before keyboard navigation begins. Reset when input changes.
|
/// means the first match is pre-selected (highlighted) before keyboard
|
||||||
|
/// navigation begins. Reset when input changes.
|
||||||
pub autocomplete_cursor: Option<usize>,
|
pub autocomplete_cursor: Option<usize>,
|
||||||
/// Command names contributed by loaded plugins, refreshed whenever the
|
/// Command names contributed by loaded plugins, refreshed whenever the
|
||||||
/// enabled-plugin set changes. Merged into autocomplete alongside the
|
/// enabled-plugin set changes. Merged into autocomplete alongside the
|
||||||
@@ -617,11 +618,20 @@ impl CommandLine {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The command name explicitly highlighted in the autocomplete popup.
|
/// The command name highlighted in the autocomplete popup. Before any
|
||||||
|
/// arrow-key navigation the first match is pre-selected (the popup renders
|
||||||
|
/// `autocomplete_cursor.unwrap_or(0)` as highlighted), so Enter runs that
|
||||||
|
/// entry — never a different command resolved through another path.
|
||||||
pub fn selected_suggestion(&self) -> Option<String> {
|
pub fn selected_suggestion(&self) -> Option<String> {
|
||||||
let matches = self.autocomplete_matches();
|
let matches = self.autocomplete_matches();
|
||||||
self.autocomplete_cursor
|
if matches.is_empty() {
|
||||||
.and_then(|index| matches.get(index).cloned())
|
return None;
|
||||||
|
}
|
||||||
|
let index = self
|
||||||
|
.autocomplete_cursor
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(matches.len() - 1);
|
||||||
|
matches.get(index).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Autocomplete suggestions for the current input — see
|
/// Autocomplete suggestions for the current input — see
|
||||||
@@ -1230,6 +1240,59 @@ mod tests {
|
|||||||
assert_eq!(m.first().map(String::as_str), Some("LINE"), "got {m:?}");
|
assert_eq!(m.first().map(String::as_str), Some("LINE"), "got {m:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preselected_top_suggestion_is_returned_without_navigation() {
|
||||||
|
// The popup highlights the first match before any arrow-key navigation
|
||||||
|
// (`unwrap_or(0)` in `view`); Enter must run that same entry. Typing
|
||||||
|
// `LT` with no alias table highlights `LTSCALE`, so the pre-selection
|
||||||
|
// must be `LTSCALE` — not `None` (which would fall through to alias /
|
||||||
|
// closest-match resolution and could run a different command).
|
||||||
|
let mut line = CommandLine::new();
|
||||||
|
line.clear_history();
|
||||||
|
line.command_aliases = FxHashMap::default();
|
||||||
|
line.input = "LT".to_string();
|
||||||
|
line.autocomplete_cursor = None;
|
||||||
|
let matches = line.autocomplete_matches();
|
||||||
|
assert_eq!(
|
||||||
|
matches.first().map(String::as_str),
|
||||||
|
Some("LTSCALE"),
|
||||||
|
"got {matches:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
line.selected_suggestion().as_deref(),
|
||||||
|
Some("LTSCALE"),
|
||||||
|
"Enter must run the highlighted pre-selection"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preselected_alias_target_is_returned_without_navigation() {
|
||||||
|
// With the `LT` → `LINETYPE` alias, the forced top entry is `LINETYPE`
|
||||||
|
// and Enter must run it without requiring arrow-key navigation.
|
||||||
|
let mut line = CommandLine::new();
|
||||||
|
line.clear_history();
|
||||||
|
line.command_aliases = aliases(&[("LT", "LINETYPE"), ("LTS", "LTSCALE")]);
|
||||||
|
line.input = "LT".to_string();
|
||||||
|
line.autocomplete_cursor = None;
|
||||||
|
assert_eq!(
|
||||||
|
line.selected_suggestion().as_deref(),
|
||||||
|
Some("LINETYPE"),
|
||||||
|
"got {:?}",
|
||||||
|
line.autocomplete_matches()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_preselection_without_matches_or_input() {
|
||||||
|
let mut line = CommandLine::new();
|
||||||
|
line.clear_history();
|
||||||
|
line.input = String::new();
|
||||||
|
line.autocomplete_cursor = None;
|
||||||
|
assert_eq!(line.selected_suggestion(), None);
|
||||||
|
line.input = "ZZZ_NO_SUCH_COMMAND".to_string();
|
||||||
|
assert_eq!(line.selected_suggestion(), None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn history_markers_and_copy_text_are_clean() {
|
fn history_markers_and_copy_text_are_clean() {
|
||||||
let mut line = CommandLine::new();
|
let mut line = CommandLine::new();
|
||||||
|
|||||||
Reference in new issue
Block a user