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 <noreply@anthropic.com>
This commit is contained in:
parent
4124cb2689
commit
05ea31aa2a
67 changed files with 507 additions and 2 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
|
@ -12,6 +12,7 @@ dependencies = [
|
||||||
"glam 0.33.0",
|
"glam 0.33.0",
|
||||||
"iced",
|
"iced",
|
||||||
"image",
|
"image",
|
||||||
|
"inventory",
|
||||||
"open",
|
"open",
|
||||||
"printpdf",
|
"printpdf",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
|
@ -2311,6 +2312,15 @@ dependencies = [
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inventory"
|
||||||
|
version = "0.3.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b"
|
||||||
|
dependencies = [
|
||||||
|
"rustversion",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-docker"
|
name = "is-docker"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ flate2 = "1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||||
rayon = "1"
|
rayon = "1"
|
||||||
ureq = { version = "3", default-features = false, features = ["rustls"] }
|
ureq = { version = "3", default-features = false, features = ["rustls"] }
|
||||||
|
inventory = "0.3"
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,9 @@ pub enum Message {
|
||||||
CommandHistoryNext,
|
CommandHistoryNext,
|
||||||
/// Toggle the dropdown listing the full command-line history.
|
/// Toggle the dropdown listing the full command-line history.
|
||||||
CommandHistoryToggle,
|
CommandHistoryToggle,
|
||||||
|
/// User clicked an autocomplete suggestion — fill the input with
|
||||||
|
/// the chosen command name and dispatch it.
|
||||||
|
CommandSuggestionPick(String),
|
||||||
ToggleLayers,
|
ToggleLayers,
|
||||||
LayerToggleVisible(usize),
|
LayerToggleVisible(usize),
|
||||||
LayerToggleLock(usize),
|
LayerToggleLock(usize),
|
||||||
|
|
|
||||||
|
|
@ -824,6 +824,9 @@ impl OpenCADStudio {
|
||||||
return Task::done(Message::CommandSubmit);
|
return Task::done(Message::CommandSubmit);
|
||||||
}
|
}
|
||||||
self.command_line.input = s;
|
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()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -833,20 +836,36 @@ impl OpenCADStudio {
|
||||||
if s.chars().all(|c| !c.is_control()) {
|
if s.chars().all(|c| !c.is_control()) {
|
||||||
self.command_line.input.push_str(&s);
|
self.command_line.input.push_str(&s);
|
||||||
}
|
}
|
||||||
|
self.command_line.autocomplete_cursor = None;
|
||||||
self.focus_cmd_input()
|
self.focus_cmd_input()
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::CommandBackspace => {
|
Message::CommandBackspace => {
|
||||||
self.command_line.input.pop();
|
self.command_line.input.pop();
|
||||||
|
self.command_line.autocomplete_cursor = None;
|
||||||
self.focus_cmd_input()
|
self.focus_cmd_input()
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::CommandHistoryPrev => {
|
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();
|
self.command_line.history_prev();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::CommandHistoryNext => {
|
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();
|
self.command_line.history_next();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -856,11 +875,29 @@ impl OpenCADStudio {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::CommandSuggestionPick(cmd) => {
|
||||||
|
self.command_line.input.clear();
|
||||||
|
self.command_line.close_history();
|
||||||
|
self.dispatch_command(&cmd)
|
||||||
|
}
|
||||||
|
|
||||||
Message::CommandSubmit => {
|
Message::CommandSubmit => {
|
||||||
// Submitting a command implicitly dismisses the history
|
// Submitting a command implicitly dismisses the history
|
||||||
// dropdown so the dispatched command's new prompt is
|
// dropdown so the dispatched command's new prompt is
|
||||||
// immediately visible on the overlay.
|
// immediately visible on the overlay.
|
||||||
self.command_line.close_history();
|
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;
|
let i = self.active_tab;
|
||||||
if self.tabs[i].active_cmd.is_some() {
|
if self.tabs[i].active_cmd.is_some() {
|
||||||
let text = self.command_line.input.trim().to_string();
|
let text = self.command_line.input.trim().to_string();
|
||||||
|
|
@ -971,6 +1008,16 @@ impl OpenCADStudio {
|
||||||
{
|
{
|
||||||
return Task::none();
|
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;
|
let i = self.active_tab;
|
||||||
if self.tabs[i].active_cmd.is_some() {
|
if self.tabs[i].active_cmd.is_some() {
|
||||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_escape());
|
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_escape());
|
||||||
|
|
|
||||||
|
|
@ -508,7 +508,10 @@ impl OpenCADStudio {
|
||||||
// viewport stack rather than as a separate row in the main
|
// viewport stack rather than as a separate row in the main
|
||||||
// column — frees up vertical space when no command is active
|
// column — frees up vertical space when no command is active
|
||||||
// and keeps the input close to where the cursor is drawing.
|
// 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)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
.align_x(iced::alignment::Horizontal::Center)
|
.align_x(iced::alignment::Horizontal::Center)
|
||||||
|
|
|
||||||
|
|
@ -317,3 +317,29 @@ pub trait CadCommand: Send {
|
||||||
/// Default: no-op.
|
/// Default: no-op.
|
||||||
fn inject_picked_entity(&mut self, _entity: acadrust::EntityType) {}
|
fn inject_picked_entity(&mut self, _entity: acadrust::EntityType) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Every `impl CadCommand for Foo` module submits the names it answers to
|
||||||
|
// at compile time via `inventory::submit!`. The command-line autocomplete
|
||||||
|
// then iterates the resulting collection at runtime — no central list to
|
||||||
|
// keep in sync.
|
||||||
|
//
|
||||||
|
// Non-interactive one-shot dispatch arms (NEW, OPEN, SAVE, …) live in
|
||||||
|
// `app/commands.rs` and don't have a `CadCommand` impl; they're absent
|
||||||
|
// from autocomplete by design. Add an explicit `inventory::submit!` next
|
||||||
|
// to their dispatch arm if you want them surfaced.
|
||||||
|
|
||||||
|
pub struct CommandRegistration {
|
||||||
|
pub names: &'static [&'static str],
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory::collect!(CommandRegistration);
|
||||||
|
|
||||||
|
/// All registered command names, including aliases.
|
||||||
|
pub fn all_registered_command_names() -> Vec<&'static str> {
|
||||||
|
inventory::iter::<CommandRegistration>
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|r| r.names.iter().copied())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,3 +149,7 @@ fn preview_aligned(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> WireModel {
|
||||||
fill_tris: vec![],
|
fill_tris: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DAL", "DIMALIGNED"] }); // AlignedDimensionCommand
|
||||||
|
|
|
||||||
|
|
@ -173,3 +173,7 @@ fn angular_preview(vertex: Vec3, first: Vec3, second: Vec3, arc_pt: Vec3) -> Vec
|
||||||
}
|
}
|
||||||
points
|
points
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIMANGULAR"] }); // AngularDimensionCommand
|
||||||
|
|
|
||||||
|
|
@ -112,3 +112,7 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
|
||||||
fill_tris: vec![],
|
fill_tris: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DDI", "DIMDIAMETER"] }); // DiameterDimensionCommand
|
||||||
|
|
|
||||||
|
|
@ -177,3 +177,7 @@ impl CadCommand for DimBaselineCommand {
|
||||||
fn v3(p: Vec3) -> Vector3 {
|
fn v3(p: Vec3) -> Vector3 {
|
||||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DBA", "DIMBASELINE"] }); // DimBaselineCommand
|
||||||
|
|
|
||||||
|
|
@ -158,3 +158,7 @@ impl CadCommand for DimContinueCommand {
|
||||||
fn v3(p: Vec3) -> Vector3 {
|
fn v3(p: Vec3) -> Vector3 {
|
||||||
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
Vector3::new(p.x as f64, p.y as f64, p.z as f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DCO", "DIMCONTINUE"] }); // DimContinueCommand
|
||||||
|
|
|
||||||
|
|
@ -97,3 +97,7 @@ impl CadCommand for DimBreakCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DBR", "DIMBREAK"] }); // DimBreakCommand
|
||||||
|
|
|
||||||
|
|
@ -85,3 +85,7 @@ impl CadCommand for DimEditCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DED", "DIMEDIT"] }); // DimEditCommand
|
||||||
|
|
|
||||||
|
|
@ -108,3 +108,7 @@ impl CadCommand for DimJogLineCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIMJOGLINE", "DJL"] }); // DimJogLineCommand
|
||||||
|
|
|
||||||
|
|
@ -125,3 +125,7 @@ impl CadCommand for DimSpaceCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIMSPACE", "DSPACE"] }); // DimSpaceCommand
|
||||||
|
|
|
||||||
|
|
@ -128,3 +128,7 @@ impl CadCommand for DimTeditCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIMTED", "DIMTEDIT"] }); // DimTeditCommand
|
||||||
|
|
|
||||||
|
|
@ -252,3 +252,7 @@ pub fn arrowhead_wings(tip: Vec3, next: Vec3, size: f32) -> [Vec3; 2] {
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["LE", "LEADER"] }); // LeaderCommand
|
||||||
|
|
|
||||||
|
|
@ -163,3 +163,7 @@ fn linear_text_pos(first: Vec3, second: Vec3, def: Vec3) -> Vec3 {
|
||||||
let d2 = second + perp * offset;
|
let d2 = second + perp * offset;
|
||||||
(d1 + d2) * 0.5 + perp * 0.15
|
(d1 + d2) * 0.5 + perp * 0.15
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIMLINEAR"] }); // LinearDimensionCommand
|
||||||
|
|
|
||||||
|
|
@ -199,3 +199,7 @@ fn arrowhead_wings(tip: Vec3, next: Vec3, size: f32) -> [Vec3; 2] {
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MLD", "MLEADER"] }); // MLeaderCommand
|
||||||
|
|
|
||||||
|
|
@ -476,3 +476,10 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
|
||||||
|
|
||||||
// Silence unused-import warning for MultiLeader and LeaderLine if not used in all paths
|
// Silence unused-import warning for MultiLeader and LeaderLine if not used in all paths
|
||||||
fn _uses_ml_types(_ml: &MultiLeader, _ll: &LeaderLine) {}
|
fn _uses_ml_types(_ml: &MultiLeader, _ll: &LeaderLine) {}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MLA", "MLEADERADD"] }); // MLeaderAddCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MLAL", "MLEADERALIGN"] }); // MLeaderAlignCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MLC", "MLEADERCOLLECT"] }); // MLeaderCollectCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MLEADERREMOVE", "MLR"] }); // MLeaderRemoveCommand
|
||||||
|
|
|
||||||
|
|
@ -91,3 +91,7 @@ impl CadCommand for MTextCommand {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MT", "MTEXT"] }); // MTextCommand
|
||||||
|
|
|
||||||
|
|
@ -86,3 +86,7 @@ impl CadCommand for OrdinateDimCommand {
|
||||||
fn v3(p: Vec3) -> Vector3 {
|
fn v3(p: Vec3) -> Vector3 {
|
||||||
Vector3::new(p.x as f64, 0.0, p.z as f64)
|
Vector3::new(p.x as f64, 0.0, p.z as f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIMORDINATE", "DOR"] }); // OrdinateDimCommand
|
||||||
|
|
|
||||||
|
|
@ -128,3 +128,7 @@ pub fn make_linear_dim(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> EntityType {
|
||||||
dim.base.actual_measurement = dim.measurement();
|
dim.base.actual_measurement = dim.measurement();
|
||||||
EntityType::Dimension(Dimension::Linear(dim))
|
EntityType::Dimension(Dimension::Linear(dim))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["QDIM"] }); // QdimCommand
|
||||||
|
|
|
||||||
|
|
@ -117,3 +117,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||||
fill_tris: vec![],
|
fill_tris: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIMRADIUS"] }); // RadiusDimensionCommand
|
||||||
|
|
|
||||||
|
|
@ -169,3 +169,7 @@ impl CadCommand for TableCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["TABLE"] }); // TableCommand
|
||||||
|
|
|
||||||
|
|
@ -86,3 +86,7 @@ impl CadCommand for TextCommand {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DT", "T", "TEXT"] }); // TextCommand
|
||||||
|
|
|
||||||
|
|
@ -108,3 +108,7 @@ impl CadCommand for ToleranceCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["TOL", "TOLERANCE"] }); // ToleranceCommand
|
||||||
|
|
|
||||||
|
|
@ -58,3 +58,7 @@ impl CadCommand for PasteCommand {
|
||||||
self.wires.iter().map(|w| w.translated(delta)).collect()
|
self.wires.iter().map(|w| w.translated(delta)).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["PASTECLIP", "PC"] }); // PasteCommand
|
||||||
|
|
|
||||||
|
|
@ -1102,3 +1102,16 @@ impl CadCommand for ArcCSLCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_3P"] }); // Arc3PCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_CSA"] }); // ArcCSACommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_CSL"] }); // ArcCSLCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["A", "ARC"] }); // ArcCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_SCA"] }); // ArcSCACommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_SCE"] }); // ArcSCECommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_SCL"] }); // ArcSCLCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_SEA"] }); // ArcSEACommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_SED"] }); // ArcSEDCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ARC_SER"] }); // ArcSERCommand
|
||||||
|
|
|
||||||
|
|
@ -184,3 +184,7 @@ impl CadCommand for AttdefCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ATTDEF"] }); // AttdefCommand
|
||||||
|
|
|
||||||
|
|
@ -910,3 +910,12 @@ impl CadCommand for CircleTTTCommand {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CIRCLE_2P"] }); // Circle2PCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CIRCLE_3P"] }); // Circle3PCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CIRCLE_CD"] }); // CircleCDCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["C", "CIRCLE"] }); // CircleCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CIRCLE_TTR"] }); // CircleTTRCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CIRCLE_TTT"] }); // CircleTTTCommand
|
||||||
|
|
|
||||||
|
|
@ -132,3 +132,7 @@ fn make_donut(cx: f64, cy: f64, inner_r: f64, outer_r: f64) -> EntityType {
|
||||||
p.vertices = vec![v0, v1];
|
p.vertices = vec![v0, v1];
|
||||||
EntityType::LwPolyline(p)
|
EntityType::LwPolyline(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DO", "DONUT"] }); // DonutCommand
|
||||||
|
|
|
||||||
|
|
@ -532,3 +532,9 @@ fn line_wire(from: Vec3, to: Vec3) -> WireModel {
|
||||||
fill_tris: vec![],
|
fill_tris: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ELLIPSE_ARC"] }); // EllipseArcCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ELLIPSE_AXIS"] }); // EllipseAxisCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["EL", "ELLIPSE"] }); // EllipseCommand
|
||||||
|
|
|
||||||
|
|
@ -423,3 +423,9 @@ impl CadCommand for BoundaryCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["BOUNDARY"] }); // BoundaryCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["GRADIENT"] }); // GradientCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["H", "HATCH"] }); // HatchCommand
|
||||||
|
|
|
||||||
|
|
@ -161,3 +161,7 @@ impl CadCommand for HatcheditCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["HATCHEDIT", "HE"] }); // HatcheditCommand
|
||||||
|
|
|
||||||
|
|
@ -94,3 +94,7 @@ impl CadCommand for LineCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["L", "LINE"] }); // LineCommand
|
||||||
|
|
|
||||||
|
|
@ -157,3 +157,7 @@ fn build_mline(pts: &[Vec3], scale: f64, closed: bool, style_name: &str) -> Enti
|
||||||
mline.style_name = style_name.to_string();
|
mline.style_name = style_name.to_string();
|
||||||
EntityType::MLine(mline)
|
EntityType::MLine(mline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ML", "MLINE"] }); // MlineCommand
|
||||||
|
|
|
||||||
|
|
@ -55,3 +55,7 @@ impl CadCommand for PointCommand {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["PO", "POINT"] }); // PointCommand
|
||||||
|
|
|
||||||
|
|
@ -321,3 +321,7 @@ impl CadCommand for PlineCommand {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["PL", "PLINE"] }); // PlineCommand
|
||||||
|
|
|
||||||
|
|
@ -173,3 +173,8 @@ impl CadCommand for XLineCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["RAY"] }); // RayCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CONSTRUCTIONLINE", "XL", "XLINE"] }); // XLineCommand
|
||||||
|
|
|
||||||
|
|
@ -133,3 +133,7 @@ fn make_revcloud(pts: &[Vec3], arc_len: f64) -> EntityType {
|
||||||
p.vertices = vertices;
|
p.vertices = vertices;
|
||||||
EntityType::LwPolyline(p)
|
EntityType::LwPolyline(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["REVCLOUD"] }); // RevCloudCommand
|
||||||
|
|
|
||||||
|
|
@ -633,3 +633,12 @@ fn edge_poly_params(a: Vec3, b: Vec3, sides: u32) -> Option<(Vec3, f32, f32)> {
|
||||||
let sa = angle_xy(center, a);
|
let sa = angle_xy(center, a);
|
||||||
Some((center, vr, sa))
|
Some((center, vr, sa))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["POLY_C"] }); // PolyCCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["POL", "POLY", "POLYGON"] }); // PolyCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["POLY_E"] }); // PolyECommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["RECT_CEN"] }); // RectCenCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["REC", "RECT", "RECTANG"] }); // RectCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["RECT_ROT"] }); // RectRotCommand
|
||||||
|
|
|
||||||
|
|
@ -118,3 +118,7 @@ impl CadCommand for SplineCommand {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["SPL", "SPLINE"] }); // SplineCommand
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,7 @@ impl CadCommand for UngroupCommand {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["UG", "UNGROUP"] }); // UngroupCommand
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,7 @@ impl CadCommand for AreaCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["AREA"] }); // AreaCommand
|
||||||
|
|
|
||||||
|
|
@ -77,3 +77,7 @@ impl CadCommand for DistCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DI", "DIST"] }); // DistCommand
|
||||||
|
|
|
||||||
|
|
@ -295,3 +295,8 @@ fn arc_span_rad(start: f64, end: f64) -> f64 {
|
||||||
span
|
span
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["DIV", "DIVIDE"] }); // DivideCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ME", "MEASURE"] }); // MeasureCommand
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,7 @@ impl CadCommand for IdCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["ID"] }); // IdCommand
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,7 @@ impl CadCommand for LayMatchCommand {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["LAYMATCH", "LAYMCH"] }); // LayMatchCommand
|
||||||
|
|
|
||||||
|
|
@ -189,3 +189,7 @@ impl AlignCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["AL", "ALIGN"] }); // AlignCommand
|
||||||
|
|
|
||||||
|
|
@ -467,3 +467,8 @@ impl CadCommand for BreakAtPointCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["BAP", "BREAKATPOINT"] }); // BreakAtPointCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["BR", "BREAK"] }); // BreakInteractiveCommand
|
||||||
|
|
|
||||||
|
|
@ -1706,3 +1706,8 @@ impl CadCommand for ChamferCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CHA", "CHAMFER"] }); // ChamferCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["F", "FILLET"] }); // FilletCommand
|
||||||
|
|
|
||||||
|
|
@ -237,3 +237,7 @@ fn try_join_arcs(arcs: &[&(Handle, &EntityType)]) -> Option<(Vec<Handle>, Vec<En
|
||||||
fn vec3_xz_to_v3(v: Vec3, z: f64) -> Vector3 {
|
fn vec3_xz_to_v3(v: Vec3, z: f64) -> Vector3 {
|
||||||
Vector3::new(v.x as f64, v.y as f64, z)
|
Vector3::new(v.x as f64, v.y as f64, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["J", "JOIN"] }); // JoinCommand
|
||||||
|
|
|
||||||
|
|
@ -500,3 +500,7 @@ fn lengthen_spline(spl: &SplineEnt, pick_pt: Vec3, mode: &LenMode) -> Option<Ent
|
||||||
Some(EntityType::Spline(bspline_to_spline(&right, spl)))
|
Some(EntityType::Spline(bspline_to_spline(&right, spl)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["LEN", "LENGTHEN"] }); // LengthenCommand
|
||||||
|
|
|
||||||
|
|
@ -714,3 +714,7 @@ impl CadCommand for OffsetCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["O", "OFFSET"] }); // OffsetCommand
|
||||||
|
|
|
||||||
|
|
@ -138,3 +138,7 @@ pub fn apply_pedit(entity: &mut EntityType, op: &PeditOp) -> bool {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["PE", "PEDIT"] }); // PeditCommand
|
||||||
|
|
|
||||||
|
|
@ -206,3 +206,8 @@ pub fn apply_insert_inverse_transform(entity: &mut EntityType, session: &RefEdit
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["REFCLOSE"] }); // RefCloseCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["REFEDIT"] }); // RefEditPickCommand
|
||||||
|
|
|
||||||
|
|
@ -200,3 +200,7 @@ pub fn apply_spline_op(doc: &mut acadrust::CadDocument, handle: acadrust::Handle
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["SPE", "SPLINEDIT"] }); // SplineditCommand
|
||||||
|
|
|
||||||
|
|
@ -2457,3 +2457,8 @@ impl CadCommand for ExtendCommand {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["EX", "EXTEND"] }); // ExtendCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["TR", "TRIM"] }); // TrimCommand
|
||||||
|
|
|
||||||
|
|
@ -89,3 +89,7 @@ impl CadCommand for MatchPropCommand {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MA", "MATCHPROP"] }); // MatchPropCommand
|
||||||
|
|
|
||||||
|
|
@ -60,3 +60,7 @@ impl CadCommand for SelectObjectsCommand {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["AR", "ARRAY", "ARRAYPATH", "ARRAYPOLAR", "ARRAYRECT", "BLOCK", "CC", "CO", "COPY", "COPYCLIP", "CUTCLIP", "CX", "E", "ERASE", "EXPLODE", "G", "GROUP", "LAYFRZ", "LAYLCK", "LAYMCUR", "LAYOFF", "LAYULK", "M", "MI", "MIRROR", "MOVE", "RO", "ROTATE", "SC", "SCALE", "SS", "STRETCH", "X"] }); // SelectObjectsCommand
|
||||||
|
|
|
||||||
|
|
@ -210,3 +210,7 @@ impl InsertBlockCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["INSERT"] }); // InsertBlockCommand
|
||||||
|
|
|
||||||
|
|
@ -658,3 +658,13 @@ impl CadCommand for LoftCommand {
|
||||||
pub fn empty_solid3d() -> EntityType {
|
pub fn empty_solid3d() -> EntityType {
|
||||||
EntityType::Solid3D(Solid3D::new())
|
EntityType::Solid3D(Solid3D::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["BOX"] }); // BoxCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["CYLINDER"] }); // CylinderCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["EXT", "EXTRUDE"] }); // ExtrudeCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["LOFT"] }); // LoftCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["REV", "REVOLVE"] }); // RevolveCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["SPHERE"] }); // SphereCommand
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["SWEEP"] }); // SweepCommand
|
||||||
|
|
|
||||||
|
|
@ -104,3 +104,7 @@ impl CadCommand for MviewCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["MV", "MVIEW"] }); // MviewCommand
|
||||||
|
|
|
||||||
|
|
@ -106,3 +106,7 @@ impl CadCommand for VplayerCommand {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["VPLAYER"] }); // VplayerCommand
|
||||||
|
|
|
||||||
|
|
@ -75,3 +75,7 @@ impl CadCommand for PlotWindowCommand {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ── Autocomplete registry ─────────────────────────────────
|
||||||
|
inventory::submit!(crate::command::CommandRegistration { names: &["PLOTWINDOW", "PW"] }); // PlotWindowCommand
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ pub const CMD_INPUT_ID: &str = "cmd_input";
|
||||||
/// out. Picking the full archive happens through the dropdown button.
|
/// out. Picking the full archive happens through the dropdown button.
|
||||||
const HISTORY_VISIBLE_SECS: f32 = 3.0;
|
const HISTORY_VISIBLE_SECS: f32 = 3.0;
|
||||||
|
|
||||||
|
/// How many autocomplete matches the suggestion popup shows at once.
|
||||||
|
const AUTOCOMPLETE_LIMIT: usize = 8;
|
||||||
|
|
||||||
fn cmd_input_id() -> iced::widget::Id {
|
fn cmd_input_id() -> iced::widget::Id {
|
||||||
iced::widget::Id::new(CMD_INPUT_ID)
|
iced::widget::Id::new(CMD_INPUT_ID)
|
||||||
}
|
}
|
||||||
|
|
@ -30,6 +33,10 @@ pub struct CommandLine {
|
||||||
recall_draft: String,
|
recall_draft: String,
|
||||||
/// When `true`, the dropdown showing the full backlog is open.
|
/// When `true`, the dropdown showing the full backlog is open.
|
||||||
pub history_open: bool,
|
pub history_open: bool,
|
||||||
|
/// Index of the currently-highlighted autocomplete suggestion, or
|
||||||
|
/// `None` when the user hasn't yet started navigating with the
|
||||||
|
/// arrow keys. Reset on every keystroke.
|
||||||
|
pub autocomplete_cursor: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
@ -152,7 +159,74 @@ impl CommandLine {
|
||||||
self.history_open = false;
|
self.history_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> Element<'_, Message> {
|
/// Move the autocomplete highlight up one entry. Wraps to the last
|
||||||
|
/// match. Returns `true` when there was a list to navigate.
|
||||||
|
pub fn autocomplete_prev(&mut self) -> bool {
|
||||||
|
let len = self.autocomplete_matches().len();
|
||||||
|
if len == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let next = match self.autocomplete_cursor {
|
||||||
|
None => len - 1,
|
||||||
|
Some(0) => len - 1,
|
||||||
|
Some(i) => i - 1,
|
||||||
|
};
|
||||||
|
self.autocomplete_cursor = Some(next);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the autocomplete highlight down one entry. Wraps to the
|
||||||
|
/// first match. Returns `true` when there was a list to navigate.
|
||||||
|
pub fn autocomplete_next(&mut self) -> bool {
|
||||||
|
let len = self.autocomplete_matches().len();
|
||||||
|
if len == 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let next = match self.autocomplete_cursor {
|
||||||
|
None => 0,
|
||||||
|
Some(i) if i + 1 < len => i + 1,
|
||||||
|
Some(_) => 0,
|
||||||
|
};
|
||||||
|
self.autocomplete_cursor = Some(next);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The command name the user has currently highlighted in the
|
||||||
|
/// autocomplete popup, if any.
|
||||||
|
pub fn selected_suggestion(&self) -> Option<&'static str> {
|
||||||
|
let matches = self.autocomplete_matches();
|
||||||
|
self.autocomplete_cursor
|
||||||
|
.and_then(|i| matches.get(i).copied())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Autocomplete suggestions for the current input, capped at
|
||||||
|
/// [`AUTOCOMPLETE_LIMIT`]. Match is substring-anywhere (typing
|
||||||
|
/// `LEADER` surfaces `LEADER` and `MLEADER` and `QLEADER`); the
|
||||||
|
/// list is sorted so prefix matches come first.
|
||||||
|
///
|
||||||
|
/// Names come from `crate::command::all_registered_command_names()`
|
||||||
|
/// which collects every `inventory::submit!` block placed next to a
|
||||||
|
/// `CadCommand` impl — no central list to maintain.
|
||||||
|
pub fn autocomplete_matches(&self) -> Vec<&'static str> {
|
||||||
|
let typed = self.input.trim();
|
||||||
|
if typed.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let needle = typed.to_uppercase();
|
||||||
|
let mut matches: Vec<&'static str> = crate::command::all_registered_command_names()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|cmd| cmd.contains(&needle))
|
||||||
|
.collect();
|
||||||
|
matches.sort();
|
||||||
|
matches.dedup();
|
||||||
|
// Prefix matches rank above mid-string ones, then alphabetical
|
||||||
|
// so the order is stable as the user keeps typing.
|
||||||
|
matches.sort_by_key(|cmd| (!cmd.starts_with(&needle), *cmd));
|
||||||
|
matches.truncate(AUTOCOMPLETE_LIMIT);
|
||||||
|
matches
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self, show_autocomplete: bool) -> Element<'_, Message> {
|
||||||
// Only the most recent entries pushed within the last few
|
// Only the most recent entries pushed within the last few
|
||||||
// seconds show on the overlay. The dropdown button keeps the
|
// seconds show on the overlay. The dropdown button keeps the
|
||||||
// full backlog reachable when the user actually wants it.
|
// full backlog reachable when the user actually wants it.
|
||||||
|
|
@ -207,6 +281,67 @@ impl CommandLine {
|
||||||
})
|
})
|
||||||
.size(11)
|
.size(11)
|
||||||
.padding([4, 6]);
|
.padding([4, 6]);
|
||||||
|
// Autocomplete suggestions panel, shown above the input row
|
||||||
|
// when the user has typed a prefix that matches at least one
|
||||||
|
// command. Each row is a button — clicking it dispatches the
|
||||||
|
// command directly.
|
||||||
|
let autocomplete: Element<'_, Message> = if show_autocomplete {
|
||||||
|
let matches = self.autocomplete_matches();
|
||||||
|
if matches.is_empty() {
|
||||||
|
container(column![]).height(0).into()
|
||||||
|
} else {
|
||||||
|
let cursor = self.autocomplete_cursor;
|
||||||
|
let mut col = column![].spacing(0).width(Length::Fill);
|
||||||
|
for (idx, cmd) in matches.iter().enumerate() {
|
||||||
|
let is_selected = cursor == Some(idx);
|
||||||
|
let row = button(text(*cmd).size(11).color(CMD_COLOR))
|
||||||
|
.on_press(Message::CommandSuggestionPick(cmd.to_string()))
|
||||||
|
.width(Length::Fill)
|
||||||
|
.padding([2, 8])
|
||||||
|
.style(move |_: &Theme, status| {
|
||||||
|
let bg = if is_selected {
|
||||||
|
Color {
|
||||||
|
r: 0.28,
|
||||||
|
g: 0.40,
|
||||||
|
b: 0.56,
|
||||||
|
a: 1.0,
|
||||||
|
}
|
||||||
|
} else if matches!(status, button::Status::Hovered) {
|
||||||
|
Color {
|
||||||
|
r: 0.22,
|
||||||
|
g: 0.30,
|
||||||
|
b: 0.42,
|
||||||
|
a: 1.0,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PANEL_BG
|
||||||
|
};
|
||||||
|
button::Style {
|
||||||
|
background: Some(Background::Color(bg)),
|
||||||
|
text_color: Color::WHITE,
|
||||||
|
border: Border::default(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
col = col.push(row);
|
||||||
|
}
|
||||||
|
container(col)
|
||||||
|
.style(|_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(PANEL_BG)),
|
||||||
|
border: Border {
|
||||||
|
color: BORDER_COLOR,
|
||||||
|
width: 1.0,
|
||||||
|
radius: 4.0.into(),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Length::Fill)
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
container(column![]).height(0).into()
|
||||||
|
};
|
||||||
|
|
||||||
// Dropdown trigger next to the input. Clicking it pops up the
|
// Dropdown trigger next to the input. Clicking it pops up the
|
||||||
// full backlog (everything pushed since the app started) so the
|
// full backlog (everything pushed since the app started) so the
|
||||||
// user can recover anything that has already faded off the
|
// user can recover anything that has already faded off the
|
||||||
|
|
@ -275,6 +410,7 @@ impl CommandLine {
|
||||||
};
|
};
|
||||||
|
|
||||||
container(column![
|
container(column![
|
||||||
|
autocomplete,
|
||||||
dropdown,
|
dropdown,
|
||||||
container(history_rows)
|
container(history_rows)
|
||||||
.style(|_: &Theme| container::Style {
|
.style(|_: &Theme| container::Style {
|
||||||
|
|
@ -369,3 +505,4 @@ const INFO_COLOR: Color = Color {
|
||||||
b: 0.90,
|
b: 0.90,
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue