Merge pull request #5899 from dotCooCoo/fix/tab-stops-string-params

Reject invalid Tab Stop string parameters
This commit is contained in:
BFlat
2026-11-06 16:32:38 +08:00
committed by GitHub
2 changed files with 31 additions and 5 deletions
+27 -4
View File
@@ -13,7 +13,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Set Tab Stops",
["Window", "Tabs"],
None,
"{stops: [{position: pt, align?: left|center|right|decimal, leader?: \". \", alignOn?: \".\"}], ids?|id?, start?: byte, end?: byte} replace the tab stops of the selected text (with a range: of the paragraphs it touches)",
"{stops: [{position: pt, align?: left|center|right|decimal, leader?: \". \", alignOn?: \".\"}], ids?|id?, start?: byte, end?: byte} replace the tab stops of the selected text (with a range: of the paragraphs it touches); supplied align, leader and alignOn must be strings, and alignOn cannot be empty",
has_selection,
set
),
@@ -44,15 +44,31 @@ fn parse_stops(p: &Value) -> Result<Vec<TabStop>> {
if !(0.0..=100_000.0).contains(&position) {
return Err(bad(C, "positions must be between 0 and 100000 pt"));
}
let align = match s.get("align").and_then(Value::as_str).unwrap_or("left").to_ascii_lowercase().as_str() {
let align = match s.get("align") {
None | Some(Value::Null) => "left",
Some(Value::String(value)) => value.as_str(),
Some(_) => return Err(bad(C, "`align` must be a string")),
};
let align = match align.to_ascii_lowercase().as_str() {
"left" => TabAlign::Left,
"center" | "centre" => TabAlign::Center,
"right" => TabAlign::Right,
"decimal" => TabAlign::Decimal,
other => return Err(bad(C, format!("unknown align `{other}`"))),
};
let leader = s.get("leader").and_then(Value::as_str).unwrap_or("").chars().take(8).collect();
let align_on = s.get("alignOn").and_then(Value::as_str).and_then(|a| a.chars().next()).unwrap_or('.');
let leader = match s.get("leader") {
None | Some(Value::Null) => "",
Some(Value::String(value)) => value.as_str(),
Some(_) => return Err(bad(C, "`leader` must be a string")),
}
.chars()
.take(8)
.collect();
let align_on = match s.get("alignOn") {
None | Some(Value::Null) => '.',
Some(Value::String(value)) => value.chars().next().ok_or_else(|| bad(C, "`alignOn` cannot be empty"))?,
Some(_) => return Err(bad(C, "`alignOn` must be a string")),
};
stops.push(TabStop { position, align, leader, align_on });
}
stops.sort_by(|a, b| a.position.total_cmp(&b.position));
@@ -116,6 +132,13 @@ mod tests {
use crate::Session;
#[test]
fn supplied_tab_stop_fields_require_strings() {
for stop in [json!({"position": 40, "align": false}), json!({"position": 40, "leader": 1}), json!({"position": 40, "alignOn": ""})] {
assert!(super::parse_stops(&json!({"stops": [stop.clone()]})).is_err(), "accepted {stop}");
}
}
#[test]
fn set_get_clear_tabs() {
let mut s = Session::new();
+4 -1
View File
@@ -5718,7 +5718,10 @@ spaceBefore, spaceAfter, hyphenate, mojikumi, burasagari, direction, leadingMode
touches (a caret, `start == end`, touches its paragraph). Without a range they change every paragraph of the
targeted objects, as before. With a range, the character attributes of `text.setStyle` and `text.setFormat` style
that range of characters. When every paragraph that `text.tabs.set` or `text.tabs.clear` targets already has those
stops, the command changes nothing and records no undo step. `text.tabs.get {start?}` and `paraStyle.new` / `paraStyle.redefine {id, start?}` read the
stops, the command changes nothing and records no undo step. `text.tabs.set` rejects a supplied `align`, `leader`, or
`alignOn` value that is not a string, and an empty `alignOn` value.
`text.tabs.get {start?}` and `paraStyle.new` / `paraStyle.redefine {id, start?}` read the
paragraph at `start`; `paraStyle.list` counts paragraphs as uses, and `charStyle.list` counts runs; both count the type in
the layers, opacity masks, symbols, graph designs, pattern tiles and the art of brushes. `charStyle.unused` and
`paraStyle.unused` (Select All Unused in the Character Styles and Paragraph Styles panel menus) list the styles with no