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_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: `` <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` |
| `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` |

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())
}
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>,
buf: &mut Tendril,
capitalize_first: bool,
capitalize: CapitalizeWords,
separator: Option<char>,
) {
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<char>, 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<Item = char>,
buf: &mut Tendril,
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) {
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) {
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) {
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 = [

View File

@ -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<F>(cx: &mut Context, change_fn: F)
where
F: for<'a> Fn(&mut (dyn Iterator<Item = char> + '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) {

View File

@ -27,6 +27,7 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"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,
},