mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-08 19:42:12 +00:00
craft-proof — a run of spaces is one double-space issue over the whole run in every language, through one shared space_runs helper (#496 review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7b64b78cb0
commit
cb82e91428
9 files changed
+70
-56
No files matched your search
@@ -62,11 +62,8 @@ fn spaces(text: &str, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Mezera před interpunkcí".into(), vec![p.to_string()]));
|
||||
}
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
v.push(issue(i, i + 2, "Dvojitá mezera".into(), vec![" ".into()]));
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Dvojitá mezera".into(), vec![" ".into()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,21 +75,8 @@ fn spaces(text: &str, lang: ProofLang, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Espacio antes de un signo de puntuación".into(), vec![p.to_string()], lang));
|
||||
}
|
||||
}
|
||||
// Each run of two or more spaces once, over the whole run.
|
||||
let bytes = text.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if bytes.get(i) != Some(&b' ') {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let start = i;
|
||||
while bytes.get(i) == Some(&b' ') {
|
||||
i += 1;
|
||||
}
|
||||
if i - start >= 2 {
|
||||
v.push(issue(start, i, "Espacio doble".into(), vec![" ".into()], lang));
|
||||
}
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Espacio doble".into(), vec![" ".into()], lang));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,11 +105,8 @@ fn spaces(text: &str, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Razmak ispred interpunkcijskog znaka".into(), vec![p.to_string()]));
|
||||
}
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
v.push(issue(i, i + 2, "Dvostruki razmak".into(), vec![" ".into()]));
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Dvostruki razmak".into(), vec![" ".into()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,11 +56,8 @@ fn spaces(text: &str, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Spasi sebelum tanda baca".into(), vec![p.to_string()]));
|
||||
}
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
v.push(issue(i, i + 2, "Spasi ganda".into(), vec![" ".into()]));
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Spasi ganda".into(), vec![" ".into()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,11 +211,8 @@ fn spaces(text: &str, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Spazio prima della punteggiatura".into(), vec![p.to_string()]));
|
||||
}
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
v.push(issue(i, i + 2, "Spazio doppio".into(), vec![" ".into()]));
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Spazio doppio".into(), vec![" ".into()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-6
@@ -152,6 +152,28 @@ fn dictionary_key(w: &str) -> String {
|
||||
composed(&w.to_lowercase()).into_owned()
|
||||
}
|
||||
|
||||
/// Each run of two or more spaces in `text`, once, over the whole run (a fix replaces it with one
|
||||
/// space). A run of three is one issue over three, not a pair and a leftover.
|
||||
pub(crate) fn space_runs(text: &str) -> Vec<Range<usize>> {
|
||||
let bytes = text.as_bytes();
|
||||
let mut runs = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if bytes.get(i) != Some(&b' ') {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let start = i;
|
||||
while bytes.get(i) == Some(&b' ') {
|
||||
i += 1;
|
||||
}
|
||||
if i - start >= 2 {
|
||||
runs.push(start..i);
|
||||
}
|
||||
}
|
||||
runs
|
||||
}
|
||||
|
||||
/// Add a word to the user dictionary ("Add to Dictionary"). It applies to every language.
|
||||
pub fn add_word(w: &str) {
|
||||
if user_words().write().unwrap_or_else(std::sync::PoisonError::into_inner).insert(dictionary_key(w)) {
|
||||
@@ -1098,13 +1120,10 @@ fn grammar_en(text: &str, capital_first: bool) -> Vec<Issue> {
|
||||
lang: ProofLang::En,
|
||||
});
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
for run in space_runs(text) {
|
||||
v.push(Issue {
|
||||
start: i,
|
||||
end: i + 2,
|
||||
start: run.start,
|
||||
end: run.end,
|
||||
kind: IssueKind::Grammar,
|
||||
message: "Extra space".into(),
|
||||
suggestions: vec![" ".into()],
|
||||
@@ -2591,6 +2610,35 @@ mod tests {
|
||||
assert!(suggest_in("billabon", 5, au).iter().any(|s| s == "billabong"), "{:?}", suggest_in("billabon", 5, au));
|
||||
}
|
||||
|
||||
/// Review follow-up: a run of spaces is one issue over the whole run, in every language with a
|
||||
/// double-space rule (a run of three used to leave two after the fix, four three).
|
||||
#[test]
|
||||
fn a_run_of_spaces_is_one_issue_in_every_language() {
|
||||
assert_eq!(space_runs("a b c d "), [3..5, 6..9, 10..14]);
|
||||
assert!(space_runs("a b c").is_empty() && space_runs("").is_empty() && space_runs(" ").is_empty());
|
||||
let langs = [
|
||||
ProofLang::En,
|
||||
ProofLang::EnGb,
|
||||
ProofLang::Es,
|
||||
ProofLang::It,
|
||||
ProofLang::Pt,
|
||||
ProofLang::Cs,
|
||||
ProofLang::Hr,
|
||||
ProofLang::Id,
|
||||
ProofLang::Pl,
|
||||
ProofLang::Ru,
|
||||
];
|
||||
for lang in langs {
|
||||
for n in 2..=5 {
|
||||
let text = format!("Aaa{}bbb", " ".repeat(n));
|
||||
let hits: Vec<_> =
|
||||
check_grammar_in(&text, lang).into_iter().filter(|i| i.start <= 3 && i.end > 3 && i.suggestions == [" "]).collect();
|
||||
assert_eq!(hits.len(), 1, "{lang:?} {n}: {hits:?}");
|
||||
assert_eq!((hits[0].start, hits[0].end), (3, 3 + n), "{lang:?} {n}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// English spelling reads SCOWL 2026 through the affix engine: one dictionary per English,
|
||||
/// its notice embedded, and its ordinals (`COMPOUNDRULE`) understood.
|
||||
#[cfg_attr(not(craft_proof_data), ignore = "needs the Language data (CRAFT_I18N_DIR)")]
|
||||
|
||||
@@ -304,11 +304,8 @@ fn spaces(text: &str, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Zbędna spacja przed znakiem interpunkcyjnym".into(), vec![p.to_string()]));
|
||||
}
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
v.push(issue(i, i + 2, "Podwójna spacja".into(), vec![" ".into()]));
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Podwójna spacja".into(), vec![" ".into()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,11 +129,8 @@ fn spaces(text: &str, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Espaço antes da pontuação".into(), vec![p.to_string()]));
|
||||
}
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
v.push(issue(i, i + 2, "Espaço duplo".into(), vec![" ".into()]));
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Espaço duplo".into(), vec![" ".into()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -503,11 +503,8 @@ fn spaces(text: &str, v: &mut Vec<Issue>) {
|
||||
v.push(issue(i, i + 1 + p.len_utf8(), "Лишний пробел перед знаком препинания".into(), vec![p.to_string()]));
|
||||
}
|
||||
}
|
||||
for (i, _) in text.match_indices(" ") {
|
||||
if i > 0 && text.get(..i).is_some_and(|t| t.ends_with(' ')) {
|
||||
continue;
|
||||
}
|
||||
v.push(issue(i, i + 2, "Двойной пробел".into(), vec![" ".into()]));
|
||||
for run in crate::space_runs(text) {
|
||||
v.push(issue(run.start, run.end, "Двойной пробел".into(), vec![" ".into()]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user