Report DWGPREFIX, DWGTITLED, FILEDIA, CMDECHO and more from GETVAR (#1715) (#1742)

* Report DWGPREFIX, DWGTITLED, FILEDIA, CMDECHO and more from GETVAR (#1715)

* Keep CMDECHO and FILEDIA in session settings so they survive drawing switches (#1715)

* Leave LOGINNAME out of GETVAR so scripts and agents can't read the user's login name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MarekSpodBiedry
2026-10-24 01:49:28 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent f119610287
commit f8f13c3f4e
3 changed files with 70 additions and 2 deletions
+6
View File
@@ -400,6 +400,10 @@ pub struct Settings {
pub pickfirst: bool,
/// ACADLSPASDOC: acad.lsp loads into every drawing, not once per session.
pub acadlspasdoc: bool,
/// CMDECHO: kept for routines that switch command echo around their commands; no command reads it yet.
pub cmdecho: bool,
/// FILEDIA: kept for routines that switch file dialogs off around a script; no command reads it yet.
pub filedia: bool,
pub pickadd: bool,
/// PICKDRAG: false = two-click windows (0), true = clicks or dragging (2).
/// Drag-only mode (1) is not supported yet.
@@ -491,6 +495,8 @@ impl Default for Settings {
aperture: 10.0,
pickfirst: true,
acadlspasdoc: false,
cmdecho: true,
filedia: true,
pickadd: true,
pickdrag: true,
pickstyle: 1,
+18
View File
@@ -437,6 +437,24 @@ fn tables_and_variables() {
assert_eq!(ok(&mut s, "(getvar \"NOSUCHVAR\")"), "nil");
}
#[test]
fn getvar_drawing_and_session_variables() {
let mut s = session();
assert_eq!(ok(&mut s, "(list (getvar \"DWGPREFIX\") (getvar \"DWGTITLED\"))"), "(\"\" 0)");
assert_eq!(ok(&mut s, "(list (getvar \"FILEDIA\") (getvar \"CMDECHO\") (getvar \"TILEMODE\"))"), "(1 1 1)");
assert_eq!(ok(&mut s, "(setvar \"FILEDIA\" 0) (setvar \"CMDECHO\" 0) (getvar \"FILEDIA\")"), "0");
// Both belong to the session, so they survive a switch to another drawing.
s.new_drawing(true);
assert_eq!(ok(&mut s, "(list (getvar \"FILEDIA\") (getvar \"CMDECHO\"))"), "(0 0)");
assert_eq!(ok(&mut s, "(getvar \"CMDACTIVE\")"), "0");
let dir = std::env::temp_dir();
let file = dir.join("lisp_getvar_test.dxf");
s.state_mut().unwrap().path = Some(file.to_string_lossy().into_owned());
let want = format!("{}{}", dir.to_string_lossy().trim_end_matches(std::path::MAIN_SEPARATOR), std::path::MAIN_SEPARATOR);
assert_eq!(crate::sysvars::get(&s, "DWGPREFIX"), Some(serde_json::json!(want)));
assert_eq!(ok(&mut s, "(getvar \"DWGTITLED\")"), "1");
}
#[test]
fn input_functions_take_supplied_answers() {
let mut s = session();
+46 -2
View File
@@ -55,6 +55,8 @@ const SESSION_VARS: &[&str] = &[
"APERTURE",
"PICKFIRST",
"ACADLSPASDOC",
"CMDECHO",
"FILEDIA",
"PICKADD",
"PICKDRAG",
"PICKSTYLE",
@@ -129,6 +131,8 @@ pub fn get(s: &Session, name: &str) -> Option<Value> {
"APERTURE" => json!(st.aperture),
"PICKFIRST" => json!(i32::from(st.pickfirst)),
"ACADLSPASDOC" => json!(i32::from(st.acadlspasdoc)),
"CMDECHO" => json!(i32::from(st.cmdecho)),
"FILEDIA" => json!(i32::from(st.filedia)),
"PICKADD" => json!(i32::from(st.pickadd)),
"PICKDRAG" => json!(i32::from(st.pickdrag) * 2),
"PICKSTYLE" => json!(st.pickstyle),
@@ -149,6 +153,12 @@ pub fn get(s: &Session, name: &str) -> Option<Value> {
"CMDNAMES" => json!(s.running.as_ref().map(|r| r.id.to_ascii_uppercase()).unwrap_or_default()),
"DWGNAME" => json!(s.state().map(|d| d.title.clone()).unwrap_or_default()),
"DBMOD" => json!(s.state().map(|d| i32::from(d.is_dirty())).unwrap_or(0)),
// The drawing's folder with a trailing separator ("" while it has no file); DWGTITLED is 1 once it has one.
"DWGPREFIX" => json!(s.state().ok().and_then(|d| d.path.as_deref()).map(dwg_prefix).unwrap_or_default()),
"DWGTITLED" => json!(s.state().map(|d| i32::from(d.path.is_some())).unwrap_or(0)),
"CMDACTIVE" => json!(i32::from(s.running.is_some())),
"TILEMODE" => json!(i32::from(s.state().is_ok_and(|d| d.space.layout().is_none()))),
"PLATFORM" => json!(std::env::consts::OS),
"BLOCKEDITOR" => json!(i32::from(s.state().is_ok_and(|d| d.block_edit.is_some()))),
// The template NEW/QNEW start from (a profile setting, like FONTALT; "" = none).
"QNEWTEMPLATE" => json!(crate::cmd::file::qnew_template()),
@@ -186,6 +196,21 @@ pub fn get(s: &Session, name: &str) -> Option<Value> {
Some(v)
}
/// DWGPREFIX: the folder of the drawing file, made absolute, with a trailing separator.
fn dwg_prefix(path: &str) -> String {
let full = std::path::absolute(path).unwrap_or_else(|_| std::path::PathBuf::from(path));
match full.parent() {
Some(dir) => {
let mut text = dir.to_string_lossy().into_owned();
if !text.ends_with(std::path::MAIN_SEPARATOR) {
text.push(std::path::MAIN_SEPARATOR);
}
text
}
None => String::new(),
}
}
fn as_bool(v: &Value) -> Option<bool> {
v.as_bool().or_else(|| v.as_i64().map(|i| i != 0)).or_else(|| v.as_str().and_then(|s| s.trim().parse::<i64>().ok()).map(|i| i != 0))
}
@@ -205,8 +230,25 @@ fn as_pt(v: &Value) -> Option<cadcraft_geom::Vec2> {
}
/// Variables `get` reports from session or drawing state that `set` can't change.
const READ_ONLY: &[&str] =
&["CMDNAMES", "DWGNAME", "DBMOD", "BLOCKEDITOR", "CTAB", "LASTPOINT", "VIEWCTR", "VIEWSIZE", "EXTMIN", "EXTMAX", "UNDOCTL", "UNDOMARKS"];
const READ_ONLY: &[&str] = &[
"CMDNAMES",
"DWGNAME",
"DWGPREFIX",
"DWGTITLED",
"CMDACTIVE",
"TILEMODE",
"PLATFORM",
"DBMOD",
"BLOCKEDITOR",
"CTAB",
"LASTPOINT",
"VIEWCTR",
"VIEWSIZE",
"EXTMIN",
"EXTMAX",
"UNDOCTL",
"UNDOMARKS",
];
pub fn is_read_only(name: &str) -> bool {
READ_ONLY.iter().any(|r| r.eq_ignore_ascii_case(name.trim())) || table::is_read_only(&name.trim().to_ascii_uppercase())
@@ -302,6 +344,8 @@ pub fn set(s: &mut Session, name: &str, v: &Value) -> Result<()> {
"APERTURE" => st.aperture = as_f64(v).ok_or_else(bad)?.clamp(1.0, 50.0),
"PICKFIRST" => st.pickfirst = as_bool(v).ok_or_else(bad)?,
"ACADLSPASDOC" => st.acadlspasdoc = as_bool(v).ok_or_else(bad)?,
"CMDECHO" => st.cmdecho = small(v, 1).ok_or_else(bad)? == 1,
"FILEDIA" => st.filedia = small(v, 1).ok_or_else(bad)? == 1,
"PICKADD" => st.pickadd = as_bool(v).ok_or_else(bad)?,
"PICKDRAG" => st.pickdrag = as_i(v).filter(|b| matches!(b, 0 | 2)).map(|b| b == 2).ok_or_else(bad)?,
"PICKSTYLE" => st.pickstyle = as_i(v).filter(|b| (0..=3).contains(b)).and_then(|b| u8::try_from(b).ok()).ok_or_else(bad)?,