mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-06 02:21:51 +00:00
Spelling: "we", "if", "email" and curly-apostrophe contractions are known
The spell checker uses the Moby Hyphenator II word list, which lacks some very common short words (we, us, if, Mr, pm, vs) and most computer and web vocabulary (email, online, website, database, desktop, download...). Contractions typed with a typographer's apostrophe (don’t, we’re), which Use Typographer's Quotes produces by default, were looked up with the curly apostrophe and flagged too. Dynamic Spelling underlined most words of an ordinary sentence. `known` now reads U+2019 as a straight apostrophe (also in the user dictionary) and accepts a short sorted list of such common words, with the existing inflection rules applied to them. Found in the Learn Skills Daily course, chapter 26. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3ce3a24f08
commit
c0e38160d6
2 files changed
+131
-5
No files matched your search
@@ -348,6 +348,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m
|
||||
|
||||
Newest first.
|
||||
|
||||
- **2026-10-21** · Spelling (Check Spelling and Dynamic Spelling) no longer flags everyday words the bundled hyphenation word list lacks: "we", "us", "if", titles such as "Mr", and words such as email, online, website, database, desktop and download, with their plurals and other forms. Contractions typed with a typographer's apostrophe (don’t, we’re), which Use Typographer's Quotes produces by default, count like their straight-apostrophe forms. Dynamic Spelling used to underline most words of an ordinary sentence. Found in the Learn Skills Daily course, chapter 26. No readiness percentage change.
|
||||
- **2026-10-16** · Type › Reload Fonts (`font.reload`): fonts installed, edited or removed while DesignCraft runs are read again without a restart, and the open documents recompose with them (#328, #547). An edited font file is noticed by its length and modification time; bundled, added and document fonts stay as they are. InDesign does this by itself, so noticing changes automatically is still open (gap 6). No readiness percentage change.
|
||||
- **2026-10-16** · Parents can be deleted from their Pages-panel context menu or `layout.parents.delete {id}`. Applied-page and based-on references are cleared, local override copies survive, and Undo restores the parent. Addresses the deletion portion of #956; no readiness percentage change.
|
||||
- **2026-10-17** · File › Package writes the document's and the IDML's links relative to the package (`Links/logo.png`, `file:Links/logo.png`), so a package holds no path from the machine that made it; opening a document resolves them to the files beside it. Links › Copy Link(s) To still links by full path. (#340). No readiness percentage change.
|
||||
|
||||
@@ -81,13 +81,91 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
]
|
||||
}
|
||||
|
||||
/// Common words the hyphenation word list (from the early 1990s) lacks: everyday short words
|
||||
/// such as "we", "us" and "if", titles, and words that came with computers and the web. Sorted,
|
||||
/// lowercase, base forms (the inflections below reach "emails", "downloaded", "bloggers").
|
||||
const COMMON: &[&str] = &[
|
||||
"ah",
|
||||
"bitmap",
|
||||
"blog",
|
||||
"bluetooth",
|
||||
"chatbot",
|
||||
"checkbox",
|
||||
"cybersecurity",
|
||||
"database",
|
||||
"dataset",
|
||||
"desktop",
|
||||
"download",
|
||||
"dr",
|
||||
"dropdown",
|
||||
"ebook",
|
||||
"ecommerce",
|
||||
"eh",
|
||||
"email",
|
||||
"emoji",
|
||||
"firewall",
|
||||
"gigabyte",
|
||||
"hashtag",
|
||||
"hm",
|
||||
"hmm",
|
||||
"homepage",
|
||||
"hyperlink",
|
||||
"if",
|
||||
"inbox",
|
||||
"internet",
|
||||
"jr",
|
||||
"kilobyte",
|
||||
"laptop",
|
||||
"livestream",
|
||||
"login",
|
||||
"logout",
|
||||
"malware",
|
||||
"megabyte",
|
||||
"metadata",
|
||||
"mr",
|
||||
"offline",
|
||||
"ok",
|
||||
"online",
|
||||
"ow",
|
||||
"pm",
|
||||
"podcast",
|
||||
"popup",
|
||||
"printout",
|
||||
"screenshot",
|
||||
"selfie",
|
||||
"smartphone",
|
||||
"smartwatch",
|
||||
"spreadsheet",
|
||||
"sr",
|
||||
"startup",
|
||||
"terabyte",
|
||||
"touchscreen",
|
||||
"um",
|
||||
"upload",
|
||||
"us",
|
||||
"username",
|
||||
"voicemail",
|
||||
"vs",
|
||||
"we",
|
||||
"webinar",
|
||||
"webmaster",
|
||||
"webpage",
|
||||
"website",
|
||||
"wifi",
|
||||
"workflow",
|
||||
"yay",
|
||||
];
|
||||
|
||||
/// Is `w` (lowercase) a known word, allowing common inflections of dictionary stems?
|
||||
pub fn known(dict: &Dictionary, user: &[String], w: &str) -> bool {
|
||||
if w.len() <= 1 || dict.get(w).is_some() || user.iter().any(|u| u == w) {
|
||||
// A typographer's apostrophe (don’t, we’re) is the word list's straight one.
|
||||
let w = &w.replace('’', "'");
|
||||
let has = |w: &str| dict.get(w).is_some() || COMMON.binary_search(&w).is_ok();
|
||||
if w.len() <= 1 || has(w) || user.iter().any(|u| u.replace('’', "'") == *w) {
|
||||
return true;
|
||||
}
|
||||
let w = w.trim_end_matches("'s").trim_end_matches("’s");
|
||||
if dict.get(w).is_some() {
|
||||
let w = w.trim_end_matches("'s");
|
||||
if has(w) {
|
||||
return true;
|
||||
}
|
||||
let stems: [(&str, &[&str]); 9] = [
|
||||
@@ -105,14 +183,14 @@ pub fn known(dict: &Dictionary, user: &[String], w: &str) -> bool {
|
||||
if let Some(base) = w.strip_suffix(suf) {
|
||||
for add in adds {
|
||||
let cand = format!("{base}{add}");
|
||||
if cand.len() > 1 && dict.get(&cand).is_some() {
|
||||
if cand.len() > 1 && has(&cand) {
|
||||
return true;
|
||||
}
|
||||
// Doubled consonant: "stopped" → "stop".
|
||||
if let Some(c) = base.chars().last()
|
||||
&& base.len() > 2
|
||||
&& base.ends_with(&format!("{c}{c}"))
|
||||
&& dict.get(&base[..base.len() - c.len_utf8()]).is_some()
|
||||
&& has(&base[..base.len() - c.len_utf8()])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -253,6 +331,53 @@ mod tests {
|
||||
assert_eq!(bad, vec![6..10]);
|
||||
}
|
||||
|
||||
/// Everyday words the hyphenation list lacks aren't misspelled ("we", "us", "if", "email"…),
|
||||
/// with their inflections, and neither are contractions typed with a typographer's
|
||||
/// apostrophe, which Use Typographer's Quotes turns ' into.
|
||||
#[test]
|
||||
fn common_words_and_curly_apostrophes_are_known() {
|
||||
let d = Dictionary::en_us();
|
||||
if d.is_empty() {
|
||||
return; // data not bundled in this build
|
||||
}
|
||||
assert!(COMMON.is_sorted(), "COMMON is binary-searched");
|
||||
for w in [
|
||||
"we",
|
||||
"us",
|
||||
"if",
|
||||
"ok",
|
||||
"mr",
|
||||
"email",
|
||||
"emails",
|
||||
"emailed",
|
||||
"online",
|
||||
"website",
|
||||
"websites",
|
||||
"downloaded",
|
||||
"database",
|
||||
"databases",
|
||||
"blogger",
|
||||
"don’t",
|
||||
"can’t",
|
||||
"i’m",
|
||||
"we’re",
|
||||
"they’ve",
|
||||
"it’s",
|
||||
"don't",
|
||||
] {
|
||||
assert!(known(d, &[], w), "{w}");
|
||||
}
|
||||
for w in ["dont", "wee’re", "emial"] {
|
||||
assert!(!known(d, &[], w), "{w}");
|
||||
}
|
||||
// A user word added with a typographer's apostrophe matches either form.
|
||||
assert!(known(d, &["zorbo’s".into()], "zorbo's") && known(d, &["zorbo's".into()], "zorbo’s"));
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
s.execute("frame.create", &json!({"rect": [36, 36, 300, 200], "content": "text", "text": "We don’t email us if we’re online."})).unwrap();
|
||||
assert_eq!(s.execute("spelling.check", &json!({})).unwrap(), json!([]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_finds_misspellings() {
|
||||
let mut s = Session::new();
|
||||
|
||||
Reference in new issue
Block a user