@@ -12,23 +12,23 @@ use unicode_segmentation::UnicodeSegmentation;
1212#[ derive( Debug , Clone , Copy , Default , PartialEq , Eq , Hash , dyn_any:: DynAny , node_macro:: ChoiceType , serde:: Serialize , serde:: Deserialize ) ]
1313#[ widget( Dropdown ) ]
1414pub enum StringCapitalization {
15- /// "a tale of two cities " — Converts all letters to lower case.
15+ /// "on the origin of species " — Converts all letters to lower case.
1616 #[ default]
1717 #[ label( "lower case" ) ]
1818 LowerCase ,
19- /// "A TALE OF TWO CITIES " — Converts all letters to upper case.
19+ /// "ON THE ORIGIN OF SPECIES " — Converts all letters to upper case.
2020 #[ label( "UPPER CASE" ) ]
2121 UpperCase ,
22- /// "A Tale Of Two Cities " — Converts the first letter of every word to upper case.
22+ /// "On The Origin Of Species " — Converts the first letter of every word to upper case.
2323 #[ label( "Capital Case" ) ]
2424 CapitalCase ,
25- /// "A Tale of Two Cities " — Converts the first letter of significant words to upper case.
25+ /// "On the Origin of Species " — Converts the first letter of significant words to upper case.
2626 #[ label( "Headline Case" ) ]
2727 HeadlineCase ,
28- /// "A tale of two cities " — Converts the first letter of every word to lower case, except the first which is made upper case.
28+ /// "On the origin of species " — Converts the first letter of every word to lower case, except the initial word which is made upper case.
2929 #[ label( "Sentence case" ) ]
3030 SentenceCase ,
31- /// "a Tale Of Two Cities " — Converts the first letter of every word to upper case, except the first which is made lower case.
31+ /// "on The Origin Of Species " — Converts the first letter of every word to upper case, except the initial word which is made lower case.
3232 #[ label( "camel Case" ) ]
3333 CamelCase ,
3434}
@@ -359,11 +359,34 @@ fn string_capitalization(
359359 joiner : String ,
360360) -> String {
361361 // When the joiner is disabled, apply only character-level casing while preserving the string's existing structure
362- if !use_joiner {
363- return match capitalization {
362+ if use_joiner {
363+ match capitalization {
364+ // Simple case mappings that preserve the string's existing structure
364365 StringCapitalization :: LowerCase => string. to_lowercase ( ) ,
365366 StringCapitalization :: UpperCase => string. to_uppercase ( ) ,
366- StringCapitalization :: CapitalCase | StringCapitalization :: HeadlineCase => {
367+
368+ // Word-aware capitalizations that split on word boundaries and rejoin with the joiner
369+ StringCapitalization :: CapitalCase => Converter :: new ( ) . set_boundaries ( & Boundary :: defaults ( ) ) . set_pattern ( pattern:: capital) . set_delim ( & joiner) . convert ( & string) ,
370+ StringCapitalization :: HeadlineCase => {
371+ // First split into words with convert_case so word boundaries like "AlphaNumeric" are detected consistently with other modes,
372+ // then apply the titlecase crate for smart capitalization (lowercasing short words like "of", "the", etc.),
373+ // then rejoin with the custom joiner without mangling the capitalization
374+ let spaced = Converter :: new ( ) . set_boundaries ( & Boundary :: defaults ( ) ) . set_pattern ( pattern:: capital) . set_delim ( " " ) . convert ( & string) ;
375+ let headline = titlecase:: titlecase ( & spaced) ;
376+ Converter :: new ( ) . set_boundaries ( & [ Boundary :: SPACE ] ) . set_pattern ( pattern:: noop) . set_delim ( & joiner) . convert ( & headline)
377+ }
378+ StringCapitalization :: SentenceCase => Converter :: new ( )
379+ . set_boundaries ( & Boundary :: defaults ( ) )
380+ . set_pattern ( pattern:: sentence)
381+ . set_delim ( & joiner)
382+ . convert ( & string) ,
383+ StringCapitalization :: CamelCase => Converter :: new ( ) . set_boundaries ( & Boundary :: defaults ( ) ) . set_pattern ( pattern:: camel) . set_delim ( & joiner) . convert ( & string) ,
384+ }
385+ } else {
386+ match capitalization {
387+ StringCapitalization :: LowerCase => string. to_lowercase ( ) ,
388+ StringCapitalization :: UpperCase => string. to_uppercase ( ) ,
389+ StringCapitalization :: CapitalCase => {
367390 let mut capitalize_next = true ;
368391 string
369392 . chars ( )
@@ -380,6 +403,7 @@ fn string_capitalization(
380403 } )
381404 . collect ( )
382405 }
406+ StringCapitalization :: HeadlineCase => titlecase:: titlecase ( & string) ,
383407 StringCapitalization :: SentenceCase => {
384408 let mut chars = string. chars ( ) ;
385409 match chars. next ( ) {
@@ -404,28 +428,7 @@ fn string_capitalization(
404428 } )
405429 . collect ( )
406430 }
407- } ;
408- }
409-
410- match capitalization {
411- // Simple case mappings that preserve the string's existing structure
412- StringCapitalization :: LowerCase => string. to_lowercase ( ) ,
413- StringCapitalization :: UpperCase => string. to_uppercase ( ) ,
414-
415- // Word-aware capitalizations that split on word boundaries and rejoin with the joiner
416- StringCapitalization :: CapitalCase => Converter :: new ( ) . set_boundaries ( & Boundary :: defaults ( ) ) . set_pattern ( pattern:: capital) . set_delim ( & joiner) . convert ( & string) ,
417- StringCapitalization :: HeadlineCase => {
418- // Headline case uses the `titlecase` crate for smart capitalization (lowercasing short words like "of", "the", etc.),
419- // then a second pass rejoins with the custom joiner without mangling the capitalization
420- let headline = titlecase:: titlecase ( & string) ;
421- Converter :: new ( ) . set_boundaries ( & [ Boundary :: SPACE ] ) . set_pattern ( pattern:: noop) . set_delim ( & joiner) . convert ( & headline)
422431 }
423- StringCapitalization :: SentenceCase => Converter :: new ( )
424- . set_boundaries ( & Boundary :: defaults ( ) )
425- . set_pattern ( pattern:: sentence)
426- . set_delim ( & joiner)
427- . convert ( & string) ,
428- StringCapitalization :: CamelCase => Converter :: new ( ) . set_boundaries ( & Boundary :: defaults ( ) ) . set_pattern ( pattern:: camel) . set_delim ( & joiner) . convert ( & string) ,
429432 }
430433}
431434
0 commit comments