mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-14 14:26:45 -04:00
Command line: a bare system variable name runs SETVAR on it (part of U10) (#799)
* Command line: a bare system variable name runs SETVAR on it (part of U10)
Typing ORTHOMODE, PICKADD and the like at the command line returned
"unknown command". Session::start now falls back to SETVAR with the name
already entered when the text is not a command but is a known variable;
dash, dot and underscore prefixes never mean a variable, and real
commands still win. Read-only variables print their value, and the
transparent form ('OSMODE) resumes the interrupted command.
Checked: fmt, clippy, test, assets, layers and wasm pass; also driven
through the control channel in the running app.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
* Tests: every variable name, command collisions and hostile input for bare SETVAR
Covers each variable SETVAR lists: a name that is not a command starts SETVAR
(or prints a read-only value), a name that is also a command keeps the command,
and odd input (lone apostrophes, NUL, wide characters, a 1 MiB name) never
panics and leaves the session usable. Disabling the fix makes all three new
and old tests fail.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
---------
Co-authored-by: Claude Sonnet 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
3 files changed
+93
-3
No files matched your search
@@ -865,7 +865,15 @@ impl Session {
|
||||
// A command-line form with an id of its own (-ATTEDIT) is not the command it prefixes.
|
||||
let dashed = cmd::resolve_alias(&name.trim_start_matches(['_', '.']).to_ascii_lowercase());
|
||||
let id = if find_command(&dashed).is_some() { dashed } else { cmd::resolve_alias(&lower) };
|
||||
let spec = find_command(&id).ok_or_else(|| EngineError::UnknownCommand(name.to_string()))?;
|
||||
let Some(spec) = find_command(&id) else {
|
||||
// A bare system variable name (ORTHOMODE, GRIDMODE…) acts as SETVAR on that variable.
|
||||
let var = lower.to_ascii_uppercase();
|
||||
if !name.starts_with(['_', '.', '-']) && sysvars::get(self, &var).is_some() {
|
||||
self.start(if transparent { "'setvar" } else { "setvar" })?;
|
||||
return self.input(Input::Text(var));
|
||||
}
|
||||
return Err(EngineError::UnknownCommand(name.to_string()));
|
||||
};
|
||||
if transparent && spec.transparent && self.running.is_some() {
|
||||
// Transparent commands (zoom, pan…) run without cancelling the active one.
|
||||
if spec.interactive.is_none() {
|
||||
|
||||
@@ -1259,3 +1259,84 @@ fn block_rotation_property_turns_attribute_text() {
|
||||
assert_eq!(t.value, "hello");
|
||||
assert!((t.height - 2.5).abs() < 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bare_variable_name_runs_setvar() {
|
||||
let mut s = Session::new();
|
||||
s.cmdline("pickadd").unwrap();
|
||||
assert!(s.current_prompt().is_some());
|
||||
s.cmdline("0").unwrap();
|
||||
assert!(s.current_prompt().is_none());
|
||||
assert_eq!(crate::sysvars::get(&s, "PICKADD"), Some(json!(0)));
|
||||
// Not a command and not a variable: still an error.
|
||||
assert!(s.cmdline("nosuchthing").is_err());
|
||||
// A dash or dot prefix never means a variable.
|
||||
assert!(s.cmdline("-pickadd").is_err());
|
||||
}
|
||||
|
||||
fn setvar_prompt_open(s: &Session) -> bool {
|
||||
s.running.as_ref().is_some_and(|r| r.id == "setvar")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_variable_name_starts_setvar_unless_it_is_a_command() {
|
||||
let mut s = Session::new();
|
||||
let names: Vec<String> = match crate::sysvars::list(&s) {
|
||||
Value::Object(m) => m.keys().cloned().collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
assert!(names.len() > 50, "expected the full variable list, got {}", names.len());
|
||||
let (mut prompted, mut read_only, mut commands) = (0, 0, 0);
|
||||
for n in &names {
|
||||
s.cancel();
|
||||
let lower = n.to_ascii_lowercase();
|
||||
let is_command = crate::cmd::find_command(&crate::cmd::resolve_alias(&lower)).is_some();
|
||||
let result = s.cmdline(&lower);
|
||||
if is_command {
|
||||
// A real command keeps its name (it may refuse to run without parameters, as a bare
|
||||
// CLAYER does); the variable is reached with SETVAR.
|
||||
assert!(!setvar_prompt_open(&s), "{n} is a command but started SETVAR");
|
||||
commands += 1;
|
||||
} else if let Err(e) = result {
|
||||
panic!("{n}: {e}");
|
||||
} else if setvar_prompt_open(&s) {
|
||||
prompted += 1;
|
||||
} else {
|
||||
// A read-only variable prints its value and ends.
|
||||
assert!(s.current_prompt().is_none(), "{n}: neither SETVAR nor finished");
|
||||
read_only += 1;
|
||||
}
|
||||
}
|
||||
s.cancel();
|
||||
assert!(prompted > 50 && read_only > 0, "prompted {prompted}, read-only {read_only}, commands {commands}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hostile_variable_like_input_never_panics() {
|
||||
let mut s = Session::new();
|
||||
let long = "a".repeat(1 << 20);
|
||||
for t in [
|
||||
"'",
|
||||
"''",
|
||||
"'''pickadd",
|
||||
"\0pickadd",
|
||||
"pickadd\0",
|
||||
" ",
|
||||
"\t",
|
||||
"pick add",
|
||||
"PICKADD\u{0}0",
|
||||
"pickadd",
|
||||
"pickadd;0",
|
||||
"ÿ",
|
||||
&long,
|
||||
"pickadd\n0",
|
||||
"'\u{202e}pickadd",
|
||||
] {
|
||||
s.cancel();
|
||||
let _ = s.cmdline(t);
|
||||
}
|
||||
s.cancel();
|
||||
// The session is still usable afterwards.
|
||||
s.cmdline("pickadd").unwrap();
|
||||
assert!(setvar_prompt_open(&s));
|
||||
}
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# Where CADCraft falls short of AutoCAD
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (Hatch and Gradient / Hatch Edit dialogs landed; F13 narrowed) · **Target:** Autodesk AutoCAD 2027
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (a bare system variable name typed as a command runs SETVAR; U10 narrowed) · **Target:** Autodesk AutoCAD 2027
|
||||
|
||||
Every known shortfall, one row each, ranked within each section by user impact. This is the work
|
||||
list: agents pick from here (top of a section first), and remove or update a row in the same PR
|
||||
@@ -57,7 +57,7 @@ Detail and evidence: [ui-parity.md](ui-parity.md).
|
||||
| U5 | Grip menus open on right-click only (no hover menu, no Ctrl to cycle the options); no multifunctional grips on dimensions, MLEADER, hatches or text; Add Vertex on an arc segment straightens it. Hot-grip options (Base point, Copy, Undo, Reference, eXit), multiple hot grips and the polyline/line/arc grip menus landed (#425) | Low-medium | 2–4 |
|
||||
| U6 | Selection: lasso; selection cycling (a list of overlapping objects, SELECTIONCYCLING); the CLass keyword; isolation saved with the drawing (OBJECTISOLATIONMODE 1) and the status-bar isolate button. Press-drag windows (#63), the W, C, F, WP and CP keywords (#226), BOX, AUto, SIngle, Multiple, Group, Add, Remove and Undo, SELECTSIMILAR, and ISOLATEOBJECTS/HIDEOBJECTS/UNISOLATEOBJECTS (#428) landed | Medium | 3–5 |
|
||||
| U7 | Right-click menus: no in-place layout tab rename or Move or Copy dialog, no Select All Layouts, no Paste to command line; palette menus are on the Layers rows and the Properties background only. The command menu (held right-click: Enter, Cancel, Recent Input, options, Snap Overrides, Pan/Zoom), the selection menu with object edits, command line, drawing tab, layout tab and palette menus landed (#512) | Low | 1–2 |
|
||||
| U10 | SETVAR covers about 150 of ~1,000 variables (UNDO options, MREDO and UNDOCTL, and header variables such as CIRCLERAD, OFFSETDIST, POLYSIDES, HPNAME and the dimension variables in SETVAR ? landed, #461); typing a variable name as a command doesn't run SETVAR | Low-medium | 3–5 |
|
||||
| U10 | SETVAR covers about 150 of ~1,000 variables (UNDO options, MREDO and UNDOCTL, and header variables such as CIRCLERAD, OFFSETDIST, POLYSIDES, HPNAME and the dimension variables in SETVAR ? landed, #461; typing a bare variable name as a command runs SETVAR on it) | Low-medium | 3–5 |
|
||||
| U11 | MVIEW Restore (a saved viewport configuration into a layout); VIEW Orthographic, UCS and background settings; view twist is kept but not shown (Zoom Realtime and Dynamic, ZOOMFACTOR, named views (VIEW) and tiled model viewports with saved configurations (VPORTS) landed in #463) | Low | 1–2 |
|
||||
| U12 | Workspaces, navigation bar, CUI customisation, alias editor, ViewCube drag (#7) | Low-medium | 15–25 |
|
||||
| U13 | `.dxf` from Finder fails to open on macOS (#158) (window controls fixed, #161) | Medium | 1–2 |
|
||||
@@ -147,6 +147,7 @@ Detail: [localization-parity.md](localization-parity.md).
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | Hatch and Gradient dialog (HATCH, GRADIENT; HPDLGMODE) and Hatch Edit dialog (HATCHEDIT, right-click, double-click a hatch): F13 narrowed to Preview, Inherit Properties and boundary add/remove in edit mode |
|
||||
| 2026-10-12 | minor | A bare system variable name typed at the command line (ORTHOMODE, GRIDMODE…) runs SETVAR on that variable, as in AutoCAD; U10 narrowed |
|
||||
| 2026-10-12 | minor | SCRIPT command reading `.scr` files landed (runs a file through the same script engine as CLI/MCP; nested scripts refused): E2 removed |
|
||||
| 2026-10-11 | minor | Refreshed for merged features: code pages (#505), tracking leftovers (#441), dynamic input/grip input (#519, #425) and SPLINEDIT Join/Add/Elevate (#559) closed their top-10 rows (top 10 now has 6 rows until the next review re-ranks); MULTILEADER (#459) and xrefs (#525) narrowed rows 1 and 4; F1 narrowed to the remaining stubs (#558, #559, #547); F5, F7, F9, F12, F14, F15, F17, F20, F22, U5, U7, U10, FF2, FF4, FF10 narrowed; F18, U2, U8, U9 removed (done); U13, S3, S4, P1 updated for merged fixes |
|
||||
| 2026-10-11 | minor | German interface catalog landed: L1 row count 936 → 989, L2 narrowed (de done; 40–60 → 35–55 h) |
|
||||
|
||||
Reference in new issue
Block a user