forked from 1b-agent/opencadstudio-upstream
feat(commandline): autocomplete suggestions + inventory-based registry
Type-as-you-go suggestion popup above the input that filters known command names by substring (so `LEADER` surfaces both LEADER and MLEADER). Up / Down navigates the list, Enter dispatches the highlighted entry, clicking a row dispatches directly. Esc on a non-empty input clears the buffer instead of escalating to command-cancel. The name corpus comes from `inventory::submit!` blocks placed next to each `CadCommand` impl rather than a hand-maintained static list. Adding a new interactive command means dropping one `inventory::submit!` line in its own module file — the autocomplete picks it up at the next compile. 98 submissions seeded across 60 module files; `command::all_registered_command_names()` flattens them at runtime. One-shot dispatch arms in `app/commands.rs` (NEW, OPEN, SAVE, UNDO, …) don't have a `CadCommand` impl and are therefore absent from autocomplete by design. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
4124cb2689
commit
05ea31aa2a
67 files changed
+507
-2
No files matched your search
@@ -357,6 +357,9 @@ pub enum Message {
|
||||
CommandHistoryNext,
|
||||
/// Toggle the dropdown listing the full command-line history.
|
||||
CommandHistoryToggle,
|
||||
/// User clicked an autocomplete suggestion — fill the input with
|
||||
/// the chosen command name and dispatch it.
|
||||
CommandSuggestionPick(String),
|
||||
ToggleLayers,
|
||||
LayerToggleVisible(usize),
|
||||
LayerToggleLock(usize),
|
||||
|
||||
@@ -824,6 +824,9 @@ impl OpenCADStudio {
|
||||
return Task::done(Message::CommandSubmit);
|
||||
}
|
||||
self.command_line.input = s;
|
||||
// Typing invalidates the previous arrow-key cursor —
|
||||
// the matches list has likely changed.
|
||||
self.command_line.autocomplete_cursor = None;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@@ -833,20 +836,36 @@ impl OpenCADStudio {
|
||||
if s.chars().all(|c| !c.is_control()) {
|
||||
self.command_line.input.push_str(&s);
|
||||
}
|
||||
self.command_line.autocomplete_cursor = None;
|
||||
self.focus_cmd_input()
|
||||
}
|
||||
|
||||
Message::CommandBackspace => {
|
||||
self.command_line.input.pop();
|
||||
self.command_line.autocomplete_cursor = None;
|
||||
self.focus_cmd_input()
|
||||
}
|
||||
|
||||
Message::CommandHistoryPrev => {
|
||||
// While autocomplete is showing suggestions, ↑ walks up
|
||||
// that list. Otherwise it falls back to recall history.
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].active_cmd.is_none()
|
||||
&& self.command_line.autocomplete_prev()
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
self.command_line.history_prev();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CommandHistoryNext => {
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].active_cmd.is_none()
|
||||
&& self.command_line.autocomplete_next()
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
self.command_line.history_next();
|
||||
Task::none()
|
||||
}
|
||||
@@ -856,11 +875,29 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::CommandSuggestionPick(cmd) => {
|
||||
self.command_line.input.clear();
|
||||
self.command_line.close_history();
|
||||
self.dispatch_command(&cmd)
|
||||
}
|
||||
|
||||
Message::CommandSubmit => {
|
||||
// Submitting a command implicitly dismisses the history
|
||||
// dropdown so the dispatched command's new prompt is
|
||||
// immediately visible on the overlay.
|
||||
self.command_line.close_history();
|
||||
// If the user navigated the autocomplete list with the
|
||||
// arrow keys, Enter dispatches the highlighted command
|
||||
// rather than the partial text actually in the buffer.
|
||||
let i_tab = self.active_tab;
|
||||
if self.tabs[i_tab].active_cmd.is_none() {
|
||||
if let Some(picked) = self.command_line.selected_suggestion() {
|
||||
let cmd = picked.to_string();
|
||||
self.command_line.input.clear();
|
||||
self.command_line.autocomplete_cursor = None;
|
||||
return self.dispatch_command(&cmd);
|
||||
}
|
||||
}
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].active_cmd.is_some() {
|
||||
let text = self.command_line.input.trim().to_string();
|
||||
@@ -971,6 +1008,16 @@ impl OpenCADStudio {
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
// Typed text on the command line cancels first — one
|
||||
// Esc empties the buffer, a second Esc then escalates
|
||||
// to whatever the current mode would otherwise do
|
||||
// (cancel command / exit viewport / deselect).
|
||||
if !self.command_line.input.is_empty() {
|
||||
self.command_line.input.clear();
|
||||
self.command_line.autocomplete_cursor = None;
|
||||
self.command_line.close_history();
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].active_cmd.is_some() {
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_escape());
|
||||
|
||||
+4
-1
@@ -508,7 +508,10 @@ impl OpenCADStudio {
|
||||
// viewport stack rather than as a separate row in the main
|
||||
// column — frees up vertical space when no command is active
|
||||
// and keeps the input close to where the cursor is drawing.
|
||||
let command_line_overlay = iced::widget::container(self.command_line.view())
|
||||
// Autocomplete shows only when no command is collecting its
|
||||
// own input (otherwise typed prefixes are coordinates / values).
|
||||
let allow_autocomplete = tab.active_cmd.is_none();
|
||||
let command_line_overlay = iced::widget::container(self.command_line.view(allow_autocomplete))
|
||||
.width(Fill)
|
||||
.height(Fill)
|
||||
.align_x(iced::alignment::Horizontal::Center)
|
||||
|
||||
Reference in new issue
Block a user