From 5e286478f7f3be9bd996be678ba269030de5ae72 Mon Sep 17 00:00:00 2001
From: Grant Lemons <grantlemons@aol.com>
Date: Thu, 16 Jan 2025 09:17:09 -0700
Subject: [PATCH] fix(title-case): retain all characters from input

---
 harper-core/src/title_case.rs | 34 +++++++++++++++++++++++++++-------
 1 file changed, 27 insertions(+), 7 deletions(-)

diff --git a/harper-core/src/title_case.rs b/harper-core/src/title_case.rs
index 7485017c..cad47f6c 100644
--- a/harper-core/src/title_case.rs
+++ b/harper-core/src/title_case.rs
@@ -28,10 +28,8 @@ pub fn make_title_case(toks: &[Token], source: &[char], dict: &impl Dictionary)
         return Vec::new();
     }
 
-    let start_index = toks.first().unwrap().span.start;
-
     let mut words = toks.iter_word_likes().enumerate().peekable();
-    let mut output = toks.span().unwrap().get_content(source).to_vec();
+    let mut output = source.to_vec();
 
     // Only specific conjunctions are not capitalized.
     lazy_static! {
@@ -62,17 +60,16 @@ pub fn make_title_case(toks: &[Token], source: &[char], dict: &impl Dictionary)
             || words.peek().is_none();
 
         if should_capitalize {
-            output[word.span.start - start_index] =
-                output[word.span.start - start_index].to_ascii_uppercase();
+            output[word.span.start] = output[word.span.start].to_ascii_uppercase();
 
             // The rest of the word should be lowercase.
-            for v in &mut output[word.span.start + 1 - start_index..word.span.end - start_index] {
+            for v in &mut output[word.span.start + 1..word.span.end] {
                 *v = v.to_ascii_lowercase();
             }
         } else {
             // The whole word should be lowercase.
             for i in word.span {
-                output[i - start_index] = output[i].to_ascii_lowercase();
+                output[i] = output[i].to_ascii_lowercase();
             }
         }
     }
@@ -120,6 +117,29 @@ mod tests {
         )
     }
 
+    #[test]
+    fn span_breaks_in_source() {
+        use super::make_title_case;
+        use crate::{CharStringExt, Span, Token, TokenKind, WordMetadata};
+        use itertools::Itertools;
+
+        let source = "// linted
+// linted";
+        let source = source.chars().collect_vec();
+
+        let toks = &[
+            Token::new(Span::new(3, 9), TokenKind::Word(WordMetadata::default())),
+            Token::new(Span::new(9, 10), TokenKind::Newline(1)),
+            Token::new(Span::new(13, 19), TokenKind::Word(WordMetadata::default())),
+        ];
+
+        assert_eq!(
+            make_title_case(toks, &source, &FstDictionary::curated()).to_string(),
+            "// Linted
+// Linted"
+        )
+    }
+
     #[derive(Debug, Clone)]
     struct Word(String);