Skip to content

Commit f72ccdc

Browse files
committed
优化不可改写区判定逻辑
1 parent cf5e923 commit f72ccdc

14 files changed

Lines changed: 64 additions & 307 deletions

File tree

src-tauri/src/adapters/docx/simple_extract_runs.rs

Lines changed: 2 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -409,162 +409,9 @@ pub(super) fn push_writeback_editable_text_regions(
409409

410410
pub(super) fn split_editable_text_segments<'a>(
411411
text: &'a str,
412-
presentation: &Option<TextPresentation>,
412+
_presentation: &Option<TextPresentation>,
413413
) -> Vec<(&'a str, bool)> {
414-
let mut segments = Vec::new();
415-
for (segment_text, allow_rewrite) in split_structured_text_segments(text, presentation) {
416-
if !allow_rewrite {
417-
segments.push((segment_text, false));
418-
continue;
419-
}
420-
extend_url_locked_segments(&mut segments, segment_text);
421-
}
422-
segments
423-
}
424-
425-
pub(super) fn split_structured_text_segments<'a>(
426-
text: &'a str,
427-
presentation: &Option<TextPresentation>,
428-
) -> Vec<(&'a str, bool)> {
429-
if !presentation.as_ref().is_some_and(|item| item.underline) || !text_has_visible_content(text)
430-
{
431-
return vec![(text, text_has_visible_content(text))];
432-
}
433-
let Some((content_start, content_end)) = text_content_bounds(text) else {
434-
return vec![(text, false)];
435-
};
436-
if content_start == 0 && content_end == text.len() {
437-
return vec![(text, true)];
438-
}
439-
let mut segments = Vec::with_capacity(3);
440-
if content_start > 0 {
441-
segments.push((&text[..content_start], false));
442-
}
443-
segments.push((&text[content_start..content_end], true));
444-
if content_end < text.len() {
445-
segments.push((&text[content_end..], false));
446-
}
447-
segments
448-
}
449-
450-
pub(super) fn text_content_bounds(text: &str) -> Option<(usize, usize)> {
451-
let start = text.char_indices().find(|(_, ch)| !ch.is_whitespace())?.0;
452-
let (end_start, end_ch) = text
453-
.char_indices()
454-
.rev()
455-
.find(|(_, ch)| !ch.is_whitespace())?;
456-
Some((start, end_start + end_ch.len_utf8()))
457-
}
458-
459-
pub(super) fn extend_url_locked_segments<'a>(segments: &mut Vec<(&'a str, bool)>, text: &'a str) {
460-
let spans = bare_url_spans(text);
461-
if spans.is_empty() {
462-
segments.push((text, true));
463-
return;
464-
}
465-
466-
let mut cursor = 0usize;
467-
for (start, end) in spans {
468-
if cursor < start {
469-
let prefix = &text[cursor..start];
470-
segments.push((prefix, text_has_visible_content(prefix)));
471-
}
472-
segments.push((&text[start..end], false));
473-
cursor = end;
474-
}
475-
if cursor < text.len() {
476-
let suffix = &text[cursor..];
477-
segments.push((suffix, text_has_visible_content(suffix)));
478-
}
479-
}
480-
481-
pub(super) fn bare_url_spans(text: &str) -> Vec<(usize, usize)> {
482-
let mut spans = Vec::new();
483-
let mut index = 0usize;
484-
while index < text.len() {
485-
let slice = &text[index..];
486-
let prefix_len = if slice.starts_with("https://") {
487-
Some("https://".len())
488-
} else if slice.starts_with("http://") {
489-
Some("http://".len())
490-
} else if slice.starts_with("www.") {
491-
Some("www.".len())
492-
} else {
493-
None
494-
};
495-
let Some(prefix_len) = prefix_len else {
496-
index += text[index..]
497-
.chars()
498-
.next()
499-
.map(|ch| ch.len_utf8())
500-
.unwrap_or(1);
501-
continue;
502-
};
503-
if !url_start_allowed(text, index) {
504-
index += prefix_len;
505-
continue;
506-
}
507-
let end = find_bare_url_end(text, index, prefix_len);
508-
if end > index + prefix_len {
509-
spans.push((index, end));
510-
index = end;
511-
} else {
512-
index += prefix_len;
513-
}
514-
}
515-
spans
516-
}
517-
518-
pub(super) fn url_start_allowed(text: &str, start: usize) -> bool {
519-
let Some(prev) = text[..start].chars().next_back() else {
520-
return true;
521-
};
522-
!(prev.is_ascii_alphanumeric() || matches!(prev, '/' | '.' | '_' | '-' | '@'))
523-
}
524-
525-
pub(super) fn find_bare_url_end(text: &str, start: usize, prefix_len: usize) -> usize {
526-
let bytes = text.as_bytes();
527-
let mut end = start;
528-
while end < bytes.len() && !bytes[end].is_ascii_whitespace() {
529-
end += 1;
530-
}
531-
while end > start + prefix_len && url_trailing_punctuation(text[..end].chars().next_back()) {
532-
end -= text[..end]
533-
.chars()
534-
.next_back()
535-
.map(|ch| ch.len_utf8())
536-
.unwrap_or(1);
537-
}
538-
end
539-
}
540-
541-
pub(super) fn url_trailing_punctuation(ch: Option<char>) -> bool {
542-
matches!(
543-
ch,
544-
Some(
545-
'.' | ','
546-
| ';'
547-
| ':'
548-
| '!'
549-
| '?'
550-
| ')'
551-
| ']'
552-
| '}'
553-
| '"'
554-
| '\''
555-
| '。'
556-
| ','
557-
| ';'
558-
| ':'
559-
| '!'
560-
| '?'
561-
| ')'
562-
| '】'
563-
| '」'
564-
| '』'
565-
| '、'
566-
)
567-
)
414+
vec![(text, true)]
568415
}
569416

570417
pub(super) fn parse_writeback_formula_region(

src-tauri/src/adapters/docx/slots.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn build_paragraph_slots(
101101

102102
let split_count = fragments.len();
103103
for (fragment_index, fragment) in fragments.into_iter().enumerate() {
104-
let slot_editable = editable && text_has_visible_content(&fragment.text);
104+
let slot_editable = editable && !fragment.text.is_empty();
105105
slots.push(WritebackSlot {
106106
id: slot_id(&anchor, fragment_index, split_count),
107107
order: start_order + slots.len(),

src-tauri/src/adapters/docx/tests/import_basic.rs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ fn report_template_keeps_first_heading_numbered_as_chapter_one() {
411411
}
412412

413413
#[test]
414-
fn imports_underlined_blank_runs_as_locked_underlined_text() {
414+
fn imports_underlined_blank_runs_as_editable_underlined_text() {
415415
let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
416416
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
417417
<w:body>
@@ -438,13 +438,13 @@ fn imports_underlined_blank_runs_as_locked_underlined_text() {
438438
.expect("underlined blank presentation");
439439

440440
assert_eq!(rebuilt, "填写日期:    ");
441-
assert!(blank_region.skip_rewrite);
441+
assert!(!blank_region.skip_rewrite);
442442
assert!(presentation.underline);
443443
assert_eq!(presentation.protect_kind.as_deref(), None);
444444
}
445445

446446
#[test]
447-
fn splits_underlined_run_edge_whitespace_into_locked_regions() {
447+
fn keeps_underlined_run_edge_whitespace_editable() {
448448
let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
449449
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
450450
<w:body>
@@ -473,14 +473,11 @@ fn splits_underlined_run_edge_whitespace_into_locked_regions() {
473473
.collect::<Vec<_>>();
474474

475475
assert_eq!(rebuilt, "作品编号:  ABC123   ");
476-
assert_eq!(
477-
underlined_regions,
478-
vec![("  ", true), ("ABC123", false), ("   ", true)]
479-
);
476+
assert_eq!(underlined_regions, vec![("  ABC123   ", false)]);
480477
}
481478

482479
#[test]
483-
fn writes_back_underlined_run_with_locked_edge_whitespace() {
480+
fn writes_back_underlined_run_with_editable_edge_whitespace() {
484481
let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
485482
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
486483
<w:body>
@@ -498,9 +495,9 @@ fn writes_back_underlined_run_with_locked_edge_whitespace() {
498495
let mut regions = DocxAdapter::extract_writeback_regions(&bytes).expect("extract regions");
499496
let editable_region = regions
500497
.iter_mut()
501-
.find(|region| !region.skip_rewrite && region.body == "ABC123")
498+
.find(|region| !region.skip_rewrite && region.body == "  ABC123   ")
502499
.expect("editable fill content");
503-
editable_region.body = "ZX-9".to_string();
500+
editable_region.body = "  ZX-9   ".to_string();
504501

505502
let rewritten = DocxAdapter::write_updated_regions(&bytes, &source, &regions)
506503
.expect("write updated regions");
@@ -516,9 +513,5 @@ fn writes_back_underlined_run_with_locked_edge_whitespace() {
516513
.map(|region| (region.body.as_str(), region.skip_rewrite))
517514
.collect::<Vec<_>>();
518515

519-
assert_eq!(
520-
underlined_regions,
521-
vec![("  ", true), ("ZX-9", false), ("   ", true)]
522-
);
516+
assert_eq!(underlined_regions, vec![("  ZX-9   ", false)]);
523517
}
524-

src-tauri/src/adapters/docx/tests/regression_misc.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ fn extracts_hyperlink_display_text_with_target_presentation() {
129129
}
130130

131131
#[test]
132-
fn locks_bare_urls_inside_plain_docx_runs() {
132+
fn keeps_bare_urls_inside_plain_docx_runs_editable() {
133133
let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
134134
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
135135
<w:body>
@@ -150,16 +150,15 @@ fn locks_bare_urls_inside_plain_docx_runs() {
150150

151151
assert_eq!(
152152
parts,
153-
vec![
154-
("访问 ", false),
155-
("https://chat.deepseek.com/share/lzlvnjcj3o5uees841", true),
156-
(" 查看答案", false),
157-
]
153+
vec![(
154+
"访问 https://chat.deepseek.com/share/lzlvnjcj3o5uees841 查看答案",
155+
false
156+
)]
158157
);
159158
}
160159

161160
#[test]
162-
fn keeps_url_with_trailing_space_as_one_locked_region() {
161+
fn keeps_url_with_trailing_space_as_one_editable_region() {
163162
let xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
164163
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
165164
<w:body>
@@ -180,7 +179,7 @@ fn keeps_url_with_trailing_space_as_one_locked_region() {
180179

181180
assert_eq!(
182181
parts,
183-
vec![("https://chat.deepseek.com/share/lzlvnjcj3o5uees841 ", true)]
182+
vec![("https://chat.deepseek.com/share/lzlvnjcj3o5uees841 ", false)]
184183
);
185184
}
186185

src-tauri/src/adapters/markdown/inline_lines.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,6 @@ fn protected_markdown_region(text: &str) -> TextRegion {
188188
|| text.starts_with('$')
189189
|| text.starts_with("![")
190190
|| text.starts_with('[')
191-
|| text.starts_with("http://")
192-
|| text.starts_with("https://")
193-
|| text.starts_with("www.")
194191
{
195192
return TextRegion::inline_object(text);
196193
}

0 commit comments

Comments
 (0)