Type: typed +, # and > start lists with a hollow circle, square and dash

Typed list detection gave every bulleted trigger the filled dot. Now
`*` and a space start a list with the filled dot (•), `+` with the
hollow circle (◦), `#` with the filled square (▪) and `>` with the dash
(–), each one of the existing Bullets presets. `1.`, `1)`, `A.` and `A)`
start the same numbered lists as before.

Only the list a typed trigger starts changes. Lists already in a
document keep their bullets, pasted triggers start no list, and Undo
and Redo of a typed list work as before.

Docs: docs/mcp.md (typed list paragraph), docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-11-08 23:50:35 +07:00
committed by bflatastic
parent 1e6c67a374
commit 246b8ed8cd
5 changed files with 45 additions and 24 deletions
+6 -3
View File
@@ -233,13 +233,16 @@ impl ParaList {
/// The list that the Type tool starts when `typed` and a space are typed at the start of a
/// paragraph: "1." or "1)" starts a numbered list in 1, 2, 3, "A." or "A)" starts one in A, B,
/// C, and "*", "#", ">" or "+" starts a bulleted list with the filled dot. Any other text starts
/// none.
/// C, and "*" starts a bulleted list with the filled dot, "+" one with the hollow circle, "#"
/// one with the filled square and ">" one with the dash. Any other text starts none.
pub fn typed(typed: &str) -> Option<ParaList> {
match typed {
"1." | "1)" => Some(Self::numbered(NumberStyle::Arabic)),
"A." | "A)" => Some(Self::numbered(NumberStyle::UpperLetters)),
"*" | "#" | ">" | "+" => Some(Self::bulleted(Bullet::Dot)),
"*" => Some(Self::bulleted(Bullet::Dot)),
"+" => Some(Self::bulleted(Bullet::Circle)),
"#" => Some(Self::bulleted(Bullet::Square)),
">" => Some(Self::bulleted(Bullet::Dash)),
_ => None,
}
}
+7 -6
View File
@@ -212,22 +212,23 @@ fn number_styles_write_their_numbers_and_roll_over() {
}
/// Typed list detection: "1.", "1)", "A." and "A)" and a space at the start of a paragraph
/// start a numbered list, and "*", "#", ">" and "+" and a space start a bulleted list with the
/// filled dot. Other text, the same characters later in a paragraph or without the space, and
/// a paragraph already in a list start nothing.
/// start a numbered list, and "*", "+", "#" and ">" and a space start a bulleted list with the
/// filled dot, the hollow circle, the filled square and the dash. Other text, the same characters
/// later in a paragraph or without the space, and a paragraph already in a list start nothing.
#[test]
fn typed_list_starts_are_found_at_a_paragraphs_start() {
let (arabic, letters, dot) =
(ParaList::numbered(NumberStyle::Arabic), ParaList::numbered(NumberStyle::UpperLetters), ParaList::bulleted(Bullet::Dot));
let typed = ["1.", "1)", "A.", "A)", "*", "#", ">", "+"].map(ParaList::typed);
assert_eq!(typed, [arabic, arabic, letters, letters, dot, dot, dot, dot].map(Some));
let (circle, square, dash) = (ParaList::bulleted(Bullet::Circle), ParaList::bulleted(Bullet::Square), ParaList::bulleted(Bullet::Dash));
let typed = ["1.", "1)", "A.", "A)", "*", "+", "#", ">"].map(ParaList::typed);
assert_eq!(typed, [arabic, arabic, letters, letters, dot, circle, square, dash].map(Some));
for other in ["", "2.", "a.", "a)", "I.", "1", "A", "1:", "-", "•", "1.)", " 1.", "*1", "1."] {
assert_eq!(ParaList::typed(other), None, "{other:?}");
}
let mut t = TextObject::point(Point::ZERO, "Fruit\n1. Apples\nA) x * y\n+ ", CharStyle::default());
assert_eq!(t.typed_list_at(9), Some((6, arabic)));
assert_eq!(t.typed_list_at(19), Some((16, letters)));
assert_eq!(t.typed_list_at(27), Some((25, dot)));
assert_eq!(t.typed_list_at(27), Some((25, circle)));
// Not at the paragraph's start, not right after the space, or past the end.
for at in [0, 5, 8, 10, 15, 23, 25, 26, 99] {
assert_eq!(t.typed_list_at(at), None, "{at}");
+29 -13
View File
@@ -1047,18 +1047,20 @@ fn list_markers(s: &Session, id: NodeId) -> Vec<String> {
}
/// Typed list detection: at the start of a paragraph, "1.", "1)", "A." or "A)" and a space start
/// a numbered list, and "*", "#", ">" or "+" and a space start a bulleted list with the filled dot.
/// The typed characters go, the paragraph takes the hanging indent, and the text typed next is the
/// list item's. One Undo puts the typed characters back with no list, and typing goes on after
/// them, also after a Backspace that deleted nothing.
/// a numbered list, and "*", "+", "#" or ">" and a space start a bulleted list with the filled dot,
/// the hollow circle, the filled square or the dash. The typed characters go, the paragraph takes
/// the hanging indent, and the text typed next is the list item's. One Undo puts the typed
/// characters back with no list, and typing goes on after them, also after a Backspace that
/// deleted nothing.
#[test]
fn typing_a_list_start_and_a_space_starts_a_list() {
use vectorcraft_doc::text::{Bullet, NumberStyle, ParaList};
let v = ViewInfo::default();
let (arabic, letters, dot) =
(ParaList::numbered(NumberStyle::Arabic), ParaList::numbered(NumberStyle::UpperLetters), ParaList::bulleted(Bullet::Dot));
let (arabic, letters) = (ParaList::numbered(NumberStyle::Arabic), ParaList::numbered(NumberStyle::UpperLetters));
let cases = [("1.", arabic, "1."), ("1)", arabic, "1."), ("A.", letters, "A."), ("A)", letters, "A.")];
let bullets = ["*", "#", ">", "+"].map(|b| (b, dot, "\u{2022}"));
let bullets =
[("*", Bullet::Dot, "\u{2022}"), ("+", Bullet::Circle, "\u{25E6}"), ("#", Bullet::Square, "\u{25AA}"), (">", Bullet::Dash, "\u{2013}")]
.map(|(b, bullet, marker)| (b, ParaList::bulleted(bullet), marker));
for (typed, list, marker) in cases.into_iter().chain(bullets) {
let mut s = session();
s.select_tool("type", v).unwrap();
@@ -1190,7 +1192,7 @@ fn typed_lists_is_a_type_preference_on_by_default() {
/// (a paste), or at the start of a paragraph that is already in a list start nothing.
#[test]
fn list_starts_typed_elsewhere_or_pasted_start_no_list() {
use vectorcraft_doc::text::{NumberStyle, ParaList};
use vectorcraft_doc::text::{Bullet, NumberStyle, ParaList};
let v = ViewInfo::default();
let mut s = session();
s.select_tool("type", v).unwrap();
@@ -1205,12 +1207,26 @@ fn list_starts_typed_elsewhere_or_pasted_start_no_list() {
for c in "1.Pears".chars() {
s.tool_text(&c.to_string(), v).unwrap();
}
key(&mut s, ToolKey::Enter, Mods::default());
s.tool_text("* ", v).unwrap();
for pasted in ["* ", "+ ", "# ", "> "] {
key(&mut s, ToolKey::Enter, Mods::default());
s.tool_text(pasted, v).unwrap();
}
let t = obj(&s, id);
assert_eq!(t.plain_text(), "Fruit 1. * A) x\n1. Apples\n1.Pears\n* ");
assert_eq!((0..4).map(|i| t.para_at(i).list).collect::<Vec<_>>(), [None, None, None, None]);
assert_eq!(t.plain_text(), "Fruit 1. * A) x\n1. Apples\n1.Pears\n* \n+ \n# \n> ");
assert!((0..7).all(|i| t.para_at(i).list.is_none()));
assert!(!undo_labels(&s).iter().any(|l| l == "Bullets and Numbering"));
// A paragraph in a bulleted list keeps its bullet when another list start is typed before it.
let id = text(&mut s, "Item");
s.execute("text.setFormat", &json!({"ids": [id.0], "list": "bullet", "bullet": "dot"})).unwrap();
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
click(&mut s, 101.0, 95.0);
s.set_tool_option("select", &json!({"start": 0, "end": 0}));
for c in "+ ".chars() {
s.tool_text(&c.to_string(), v).unwrap();
}
let t = obj(&s, id);
assert_eq!((t.plain_text().as_str(), t.para_at(0).list), ("+ Item", Some(ParaList::bulleted(Bullet::Dot))));
assert_eq!(list_markers(&s, id), ["\u{2022}"]);
// A paragraph in a list keeps its list, and the typed characters stay as text.
let id = text(&mut s, "Item");
s.execute("text.setFormat", &json!({"ids": [id.0], "list": "number"})).unwrap();
@@ -1245,7 +1261,7 @@ fn start_list_command_removes_the_typed_characters_and_starts_the_list() {
let id = text(&mut s, "x\n# ");
let r = s.execute("text.startList", &json!({"id": id.0})).unwrap();
assert_eq!((r["started"].clone(), obj(&s, id).plain_text()), (json!(true), "x\n".to_string()));
assert_eq!(obj(&s, id).para_at(1).list, Some(ParaList::bulleted(Bullet::Dot)));
assert_eq!(obj(&s, id).para_at(1).list, Some(ParaList::bulleted(Bullet::Square)));
let steps = undo_labels(&s).len();
let id = text(&mut s, "1.x");
assert_eq!(s.execute("text.startList", &json!({"id": id.0, "at": 3})).unwrap()["started"], json!(false));
+2 -1
View File
@@ -5755,7 +5755,8 @@ next to the same character, and that frame becomes the selection when it was not
Typing starts a list too. When the Type tool types a space on its own (one key press, or `type_text {text: " "}`) right
after `1.` or `1)`, `A.` or `A)`, or one of `*`, `#`, `>` and `+` at the start of a paragraph that is in no list, the
typed characters and the space are removed and the paragraph becomes a numbered list in 1, 2, 3 or A, B, C, or a
bulleted list with the filled dot. A list started with `1)` or `A)` also writes its numbers with a period. A paragraph
bulleted list: `*` gives the filled dot (•), `+` the hollow circle (◦), `#` the filled square (▪) and `>` the dash
(–). A list started with `1)` or `A)` also writes its numbers with a period. A paragraph
whose left and first-line indents are both 0 gets 18 pt and -18 pt. The tool runs `text.startList {id, at}`, with `at`
the byte after the space, as an undo step of its own after the typing: one Undo puts the characters back as text with no
list, and typing goes on after them; Redo removes them again, and typing goes on before the paragraph's text. The same
+1 -1
View File
@@ -1048,7 +1048,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
The Paragraph panel's Numbered List button, Type › Bullets and Numbering › Apply Numbers and the context menu's Apply Numbers (`text.setFormat {list, number}`, `type.applyNumbers`) make paragraphs a numbered list with the documented presets (1, A, a, I, i, 01, 001, and None, which turns the list off; [Bullets and numbering](https://web.archive.org/web/20250807202418/https://helpx.adobe.com/illustrator/using/bullets-numbering.html), [Create bulleted or numbered lists](https://web.archive.org/web/20260417034638/https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/create-bulleted-or-numbered-lists.html)): each paragraph starts with its number and a period, counted through the list paragraphs of the story and its threaded frames, with the text and indents as in a bulleted list; letters go on from Z to AA, and roman numerals go on from III to IV, an empty paragraph is not numbered, and a paragraph in no list starts the count again at 1.
Type › Bullets and Numbering › Convert to Text (`type.convertListToText`, `text.convertListToText`) makes the bullets and numbers of the list paragraphs editable characters of their text ([Create bulleted or numbered lists](https://web.archive.org/web/20260417034638/https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/create-bulleted-or-numbered-lists.html), [December 2022 release notes](https://web.archive.org/web/20260203094741/https://helpx.adobe.com/illustrator/using/whats-new/2023-1.html)): each marker becomes its characters and a tab at the start of its paragraph, in the marker's font, size and color, the list is turned off, and a left tab stop at the left indent keeps the text after the tab where it started; threaded type keeps the numbers its story counted.
While no type is being edited, a press with Shift held makes new type with the horizontal or vertical counterpart of the Type, Area Type or Type on a Path tool chosen, and a press with Alt (Option) held makes it with Type on a Path in place of Area Type or the reverse, in the chosen tool's writing direction; with both keys held, both switch ([Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html)). The pointer shows the tool the press acts as, and the tool chosen stays selected.
The Type tool starts a list from the characters [Create bulleted or numbered lists](https://web.archive.org/web/20260417034638/https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/create-bulleted-or-numbered-lists.html) names (`text.startList`): a space typed on its own after `1.`, `1)`, `A.` or `A)` at the start of a paragraph in no list makes the paragraph a numbered list in 1, 2, 3 or A, B, C, and a space typed after `*`, `#`, `>` or `+` makes it a bulleted list with the filled dot. The typed characters are removed, and one Undo puts them back as text with no list. The same characters later in a paragraph, and pasted list characters, start nothing.
The Type tool starts a list from the characters [Create bulleted or numbered lists](https://web.archive.org/web/20260417034638/https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/create-bulleted-or-numbered-lists.html) names (`text.startList`): a space typed on its own after `1.`, `1)`, `A.` or `A)` at the start of a paragraph in no list makes the paragraph a numbered list in 1, 2, 3 or A, B, C, and a space typed after `*`, `+`, `#` or `>` makes it a bulleted list with the filled dot, the hollow circle, the filled square or the dash (a VectorCraft choice: the page does not say which bullet each character gives). The typed characters are removed, and one Undo puts them back as text with no list. The same characters later in a paragraph, and pasted list characters, start nothing.
PDF export writes bullets and numbers as text, in the glyphs and at the places the canvas draws them, in area type, type on a path, right-to-left paragraphs and threaded frames, and a PDF viewer selects, copies and finds them; a marker glyph that stands for no character is drawn as a path.
The Character panel menu's Character Direction (with the Indic options; `text.setFormat {charDirection}`, `text.setRangeStyle {charDirection}`) sets the selected characters left to right or right to left inside their paragraph, as a Unicode bidi embedding: a date set Left to Right keeps its order in right-to-left text, and a word set Right to Left keeps the punctuation after it on its left. Default (the default) leaves the characters to the bidi algorithm; character styles and the native file keep the setting.
In vertical type, the Tabs panel names its alignment buttons Top-Justified Tab, Center-Justified Tab, Bottom-Justified Tab and Decimal-Justified Tab, in their tool tips and for screen readers, and labels the position field Y. In horizontal type the buttons keep the names Left-Justified Tab and Right-Justified Tab, and the field keeps the label X.