diff --git a/book/src/generated/static-cmd.md b/book/src/generated/static-cmd.md index de7763a7c..59da8f839 100644 --- a/book/src/generated/static-cmd.md +++ b/book/src/generated/static-cmd.md @@ -59,6 +59,7 @@ | `switch_to_pascal_case` | Switch to PascalCase | normal: `` `p ``, select: `` `p `` | | `switch_to_camel_case` | Switch to camelCase | normal: `` `c ``, select: `` `c `` | | `switch_to_title_case` | Switch to Title Case | normal: `` `t ``, select: `` `t `` | +| `switch_to_sentence_case` | Switch to Sentence case | normal: `` `S ``, select: `` `S `` | | `switch_to_snake_case` | Switch to snake_case | normal: `` `s ``, select: `` `s `` | | `switch_to_kebab_case` | Switch to kebab-case | normal: `` `k ``, select: `` `k `` | | `page_up` | Move page up | normal: `` ``, `` Z ``, `` z ``, `` ``, `` Z ``, `` z ``, select: `` ``, `` Z ``, `` z ``, `` ``, `` Z ``, `` z ``, insert: `` `` | diff --git a/book/src/keymap.md b/book/src/keymap.md index 82a0fb6e3..f833d19af 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -247,6 +247,7 @@ Various commands for changing the case of text in different ways. | `p` | Switch text to Pascal Case | `switch_to_pascal_case` | | `c` | Switch text to camelCase | `switch_to_camel_case` | | `t` | Switch text to Title Case | `switch_to_title_case` | +| `S` | Switch text to Sentence case | `switch_to_sentence_case` | | `s` | Switch text to snake_case | `switch_to_snake_case` | | `k` | Switch text to kebab-case | `switch_to_kebab_case` | | `a` | Switch text to aLTERNATE cASE | `switch_to_alternate_case` | diff --git a/helix-core/src/case_conversion.rs b/helix-core/src/case_conversion.rs index e4a4927b3..f3ea6c73c 100644 --- a/helix-core/src/case_conversion.rs +++ b/helix-core/src/case_conversion.rs @@ -9,13 +9,20 @@ fn has_camel_transition(prev: Option, current: char) -> bool { current.is_uppercase() && prev.is_some_and(|ch| ch.is_lowercase()) } -pub fn smart_case_conversion( +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +enum CapitalizeWords { + AllButFirst, + All, + First, +} + +fn smart_case_conversion( chars: impl Iterator, buf: &mut Tendril, - capitalize_first: bool, + capitalize: CapitalizeWords, separator: Option, ) { - let mut should_capitalize_current = capitalize_first; + let mut should_capitalize_current = capitalize != CapitalizeWords::AllButFirst; let mut prev = None; let add_separator_if_needed = |prev: Option, buf: &mut Tendril| { @@ -30,7 +37,7 @@ pub fn smart_case_conversion( for current in chars.skip_while(|ch| ch.is_whitespace()) { if !current.is_alphanumeric() { - should_capitalize_current = true; + should_capitalize_current = capitalize != CapitalizeWords::First; add_separator_if_needed(prev, buf); prev = Some(current); continue; @@ -38,7 +45,7 @@ pub fn smart_case_conversion( if has_camel_transition(prev, current) { add_separator_if_needed(prev, buf); - should_capitalize_current = true; + should_capitalize_current = capitalize != CapitalizeWords::First; } if should_capitalize_current { @@ -54,7 +61,7 @@ pub fn smart_case_conversion( *buf = buf.trim_end().into(); } -pub fn separator_case_conversion( +fn separator_case_conversion( chars: impl Iterator, buf: &mut Tendril, separator: char, @@ -143,15 +150,19 @@ pub fn into_snake_case(chars: impl Iterator, buf: &mut Tendril) { } pub fn into_title_case(chars: impl Iterator, buf: &mut Tendril) { - smart_case_conversion(chars, buf, true, Some(' ')); + smart_case_conversion(chars, buf, CapitalizeWords::All, Some(' ')); +} + +pub fn into_sentence_case(chars: impl Iterator, buf: &mut Tendril) { + smart_case_conversion(chars, buf, CapitalizeWords::First, Some(' ')); } pub fn into_camel_case(chars: impl Iterator, buf: &mut Tendril) { - smart_case_conversion(chars, buf, false, None); + smart_case_conversion(chars, buf, CapitalizeWords::AllButFirst, None); } pub fn into_pascal_case(chars: impl Iterator, buf: &mut Tendril) { - smart_case_conversion(chars, buf, true, None); + smart_case_conversion(chars, buf, CapitalizeWords::All, None); } /// Create functional versions of the "into_*" case functions that take a `&mut Tendril` @@ -176,6 +187,7 @@ to_case! { into_title_case => to_title_case into_kebab_case => to_kebab_case into_snake_case => to_snake_case + into_sentence_case => to_sentence_case } #[cfg(test)] @@ -296,6 +308,7 @@ mod tests { fn test_title_case_conversion() { let tests = [ ("hello world", "Hello World"), + ("hello world again", "Hello World Again"), ("Hello World", "Hello World"), ("hello_world", "Hello World"), ("HELLO_WORLD", "Hello World"), @@ -315,6 +328,30 @@ mod tests { } } + #[test] + fn test_sentence_case_conversion() { + let tests = [ + ("hello world", "Hello world"), + ("hello world again", "Hello world again"), + ("Hello World", "Hello world"), + ("hello_world", "Hello world"), + ("HELLO_WORLD", "Hello world"), + ("hello-world", "Hello world"), + ("hello world", "Hello world"), + (" hello world", "Hello world"), + ("hello\tworld", "Hello world"), + ("HELLO WORLD", "Hello world"), + ("HELLO-world", "Hello world"), + ("hello WORLD ", "Hello world"), + ("helloWorld", "Hello world"), + ]; + + for (input, expected) in tests { + dbg!(input); + assert_eq!(to_sentence_case(input.chars()), expected) + } + } + #[test] fn test_kebab_case_conversion() { let tests = [ diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 07de5adee..c209306c7 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -18,11 +18,7 @@ use tui::{ pub use typed::*; use helix_core::{ - case_conversion::{ - to_alternate_case, to_camel_case, to_kebab_case, to_lowercase, to_pascal_case, - to_snake_case, to_title_case, to_uppercase, - }, - char_idx_at_visual_offset, + case_conversion, char_idx_at_visual_offset, chars::char_is_word, command_line, comment, doc_formatter::TextFormat, @@ -361,6 +357,7 @@ impl MappableCommand { switch_to_pascal_case, "Switch to PascalCase", switch_to_camel_case, "Switch to camelCase", switch_to_title_case, "Switch to Title Case", + switch_to_sentence_case, "Switch to Sentence case", switch_to_snake_case, "Switch to snake_case", switch_to_kebab_case, "Switch to kebab-case", page_up, "Move page up", @@ -1717,6 +1714,7 @@ fn replace(cx: &mut Context) { }) } +#[inline] fn switch_case_impl(cx: &mut Context, change_fn: F) where F: for<'a> Fn(&mut (dyn Iterator + 'a)) -> Tendril, @@ -1739,35 +1737,39 @@ where } fn switch_to_pascal_case(cx: &mut Context) { - switch_case_impl(cx, |chars| to_pascal_case(chars)) + switch_case_impl(cx, |chars| case_conversion::to_pascal_case(chars)) } fn switch_to_camel_case(cx: &mut Context) { - switch_case_impl(cx, |chars| to_camel_case(chars)) + switch_case_impl(cx, |chars| case_conversion::to_camel_case(chars)) } fn switch_to_lowercase(cx: &mut Context) { - switch_case_impl(cx, |chars| to_lowercase(chars)) + switch_case_impl(cx, |chars| case_conversion::to_lowercase(chars)) } fn switch_to_uppercase(cx: &mut Context) { - switch_case_impl(cx, |chars| to_uppercase(chars)) + switch_case_impl(cx, |chars| case_conversion::to_uppercase(chars)) } fn switch_to_alternate_case(cx: &mut Context) { - switch_case_impl(cx, |chars| to_alternate_case(chars)) + switch_case_impl(cx, |chars| case_conversion::to_alternate_case(chars)) } fn switch_to_title_case(cx: &mut Context) { - switch_case_impl(cx, |chars| to_title_case(chars)) + switch_case_impl(cx, |chars| case_conversion::to_title_case(chars)) +} + +fn switch_to_sentence_case(cx: &mut Context) { + switch_case_impl(cx, |chars| case_conversion::to_sentence_case(chars)) } fn switch_to_snake_case(cx: &mut Context) { - switch_case_impl(cx, |chars| to_snake_case(chars)) + switch_case_impl(cx, |chars| case_conversion::to_snake_case(chars)) } fn switch_to_kebab_case(cx: &mut Context) { - switch_case_impl(cx, |chars| to_kebab_case(chars)) + switch_case_impl(cx, |chars| case_conversion::to_kebab_case(chars)) } pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor: bool) { diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index f801dbbd1..256aa6c2c 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -27,6 +27,7 @@ pub fn default() -> HashMap { "p" => switch_to_pascal_case, "c" => switch_to_camel_case, "t" => switch_to_title_case, + "S" => switch_to_sentence_case, "s" => switch_to_snake_case, "k" => switch_to_kebab_case, },