feat: `switch_to_sentence_case` (`` `S ``)

_
pull/12043/head
Nik Revenco 2025-03-25 16:21:40 +00:00
parent 2cc5222226
commit b0251ef715
5 changed files with 64 additions and 22 deletions

View File

@ -59,6 +59,7 @@
| `switch_to_pascal_case` | Switch to PascalCase | normal: `` `p ``, select: `` `p `` | | `switch_to_pascal_case` | Switch to PascalCase | normal: `` `p ``, select: `` `p `` |
| `switch_to_camel_case` | Switch to camelCase | normal: `` `c ``, select: `` `c `` | | `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_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_snake_case` | Switch to snake_case | normal: `` `s ``, select: `` `s `` |
| `switch_to_kebab_case` | Switch to kebab-case | normal: `` `k ``, select: `` `k `` | | `switch_to_kebab_case` | Switch to kebab-case | normal: `` `k ``, select: `` `k `` |
| `page_up` | Move page up | normal: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, select: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, insert: `` <pageup> `` | | `page_up` | Move page up | normal: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, select: `` <C-b> ``, `` Z<C-b> ``, `` z<C-b> ``, `` <pageup> ``, `` Z<pageup> ``, `` z<pageup> ``, insert: `` <pageup> `` |

View File

@ -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` | | `p` | Switch text to Pascal Case | `switch_to_pascal_case` |
| `c` | Switch text to camelCase | `switch_to_camel_case` | | `c` | Switch text to camelCase | `switch_to_camel_case` |
| `t` | Switch text to Title Case | `switch_to_title_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` | | `s` | Switch text to snake_case | `switch_to_snake_case` |
| `k` | Switch text to kebab-case | `switch_to_kebab_case` | | `k` | Switch text to kebab-case | `switch_to_kebab_case` |
| `a` | Switch text to aLTERNATE cASE | `switch_to_alternate_case` | | `a` | Switch text to aLTERNATE cASE | `switch_to_alternate_case` |

View File

@ -9,13 +9,20 @@ fn has_camel_transition(prev: Option<char>, current: char) -> bool {
current.is_uppercase() && prev.is_some_and(|ch| ch.is_lowercase()) 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<Item = char>, chars: impl Iterator<Item = char>,
buf: &mut Tendril, buf: &mut Tendril,
capitalize_first: bool, capitalize: CapitalizeWords,
separator: Option<char>, separator: Option<char>,
) { ) {
let mut should_capitalize_current = capitalize_first; let mut should_capitalize_current = capitalize != CapitalizeWords::AllButFirst;
let mut prev = None; let mut prev = None;
let add_separator_if_needed = |prev: Option<char>, buf: &mut Tendril| { let add_separator_if_needed = |prev: Option<char>, buf: &mut Tendril| {
@ -30,7 +37,7 @@ pub fn smart_case_conversion(
for current in chars.skip_while(|ch| ch.is_whitespace()) { for current in chars.skip_while(|ch| ch.is_whitespace()) {
if !current.is_alphanumeric() { if !current.is_alphanumeric() {
should_capitalize_current = true; should_capitalize_current = capitalize != CapitalizeWords::First;
add_separator_if_needed(prev, buf); add_separator_if_needed(prev, buf);
prev = Some(current); prev = Some(current);
continue; continue;
@ -38,7 +45,7 @@ pub fn smart_case_conversion(
if has_camel_transition(prev, current) { if has_camel_transition(prev, current) {
add_separator_if_needed(prev, buf); add_separator_if_needed(prev, buf);
should_capitalize_current = true; should_capitalize_current = capitalize != CapitalizeWords::First;
} }
if should_capitalize_current { if should_capitalize_current {
@ -54,7 +61,7 @@ pub fn smart_case_conversion(
*buf = buf.trim_end().into(); *buf = buf.trim_end().into();
} }
pub fn separator_case_conversion( fn separator_case_conversion(
chars: impl Iterator<Item = char>, chars: impl Iterator<Item = char>,
buf: &mut Tendril, buf: &mut Tendril,
separator: char, separator: char,
@ -143,15 +150,19 @@ pub fn into_snake_case(chars: impl Iterator<Item = char>, buf: &mut Tendril) {
} }
pub fn into_title_case(chars: impl Iterator<Item = char>, buf: &mut Tendril) { pub fn into_title_case(chars: impl Iterator<Item = char>, 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<Item = char>, buf: &mut Tendril) {
smart_case_conversion(chars, buf, CapitalizeWords::First, Some(' '));
} }
pub fn into_camel_case(chars: impl Iterator<Item = char>, buf: &mut Tendril) { pub fn into_camel_case(chars: impl Iterator<Item = char>, 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<Item = char>, buf: &mut Tendril) { pub fn into_pascal_case(chars: impl Iterator<Item = char>, 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` /// 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_title_case => to_title_case
into_kebab_case => to_kebab_case into_kebab_case => to_kebab_case
into_snake_case => to_snake_case into_snake_case => to_snake_case
into_sentence_case => to_sentence_case
} }
#[cfg(test)] #[cfg(test)]
@ -296,6 +308,7 @@ mod tests {
fn test_title_case_conversion() { fn test_title_case_conversion() {
let tests = [ let tests = [
("hello world", "Hello World"), ("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"),
@ -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] #[test]
fn test_kebab_case_conversion() { fn test_kebab_case_conversion() {
let tests = [ let tests = [

View File

@ -18,11 +18,7 @@ use tui::{
pub use typed::*; pub use typed::*;
use helix_core::{ use helix_core::{
case_conversion::{ case_conversion, char_idx_at_visual_offset,
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,
chars::char_is_word, chars::char_is_word,
command_line, comment, command_line, comment,
doc_formatter::TextFormat, doc_formatter::TextFormat,
@ -361,6 +357,7 @@ impl MappableCommand {
switch_to_pascal_case, "Switch to PascalCase", switch_to_pascal_case, "Switch to PascalCase",
switch_to_camel_case, "Switch to camelCase", switch_to_camel_case, "Switch to camelCase",
switch_to_title_case, "Switch to Title Case", 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_snake_case, "Switch to snake_case",
switch_to_kebab_case, "Switch to kebab-case", switch_to_kebab_case, "Switch to kebab-case",
page_up, "Move page up", page_up, "Move page up",
@ -1717,6 +1714,7 @@ fn replace(cx: &mut Context) {
}) })
} }
#[inline]
fn switch_case_impl<F>(cx: &mut Context, change_fn: F) fn switch_case_impl<F>(cx: &mut Context, change_fn: F)
where where
F: for<'a> Fn(&mut (dyn Iterator<Item = char> + 'a)) -> Tendril, F: for<'a> Fn(&mut (dyn Iterator<Item = char> + 'a)) -> Tendril,
@ -1739,35 +1737,39 @@ where
} }
fn switch_to_pascal_case(cx: &mut Context) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { 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) { pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor: bool) {

View File

@ -27,6 +27,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"p" => switch_to_pascal_case, "p" => switch_to_pascal_case,
"c" => switch_to_camel_case, "c" => switch_to_camel_case,
"t" => switch_to_title_case, "t" => switch_to_title_case,
"S" => switch_to_sentence_case,
"s" => switch_to_snake_case, "s" => switch_to_snake_case,
"k" => switch_to_kebab_case, "k" => switch_to_kebab_case,
}, },