nvrim/plugins/
caseconv.rs

1//! Text conversions helpers for the current Visual selection.
2//!
3//! Provides a namespaced [`Dictionary`] exposing selection conversion
4//! functionality (currently only case conversion via [`convert_case`]).
5
6use core::fmt::Display;
7
8use color_eyre::eyre::Report;
9use convert_case::Case;
10use convert_case::Casing as _;
11use nvim_oxi::Dictionary;
12
13/// Namespaced dictionary of case conversion helpers.
14///
15/// Entries:
16/// - `"convert_selection"`: wraps [`convert_selection`] and converts the active Visual selection to a user‑selected
17///   [`Case`].
18pub fn dict() -> Dictionary {
19    dict! {
20        "convert_selection": fn_from!(convert_selection),
21    }
22}
23
24/// Converts the current visual selection to a user-chosen case variant.
25///
26/// Prompts the user (via [`ytil_noxi::vim_ui_select::open`]) to select a case conversion
27/// option, then applies the conversion to all selected lines in place.
28///
29/// Returns early if:
30/// - No active Visual selection is detected.
31/// - The user cancels the prompt.
32/// - Writing the converted text back to the buffer fails (an error is reported via [`ytil_noxi::notify::error`]).
33///
34/// # Errors
35/// Errors from [`ytil_noxi::vim_ui_select::open`] are reported via [`ytil_noxi::notify::error`]
36/// using the direct display representation of [`color_eyre::Report`].
37///
38/// # Notes
39/// Blockwise selections are treated as a contiguous span (not a rectangle).
40fn convert_selection(_: ()) {
41    let Some(selection) = ytil_noxi::visual_selection::get(()) else {
42        return;
43    };
44
45    let cases = Case::all_cases();
46
47    let callback = move |choice_idx| {
48        cases.get(choice_idx).map(|case| {
49            let converted_lines = selection
50                .lines()
51                .iter()
52                .map(|line| line.as_str().to_case(*case))
53                .collect::<Vec<_>>();
54            ytil_noxi::buffer::replace_text_and_notify_if_error(&selection, converted_lines);
55            Ok::<(), Report>(())
56        });
57    };
58
59    if let Err(err) = ytil_noxi::vim_ui_select::open(
60        cases.iter().map(DisplayableCase),
61        &[("prompt", "Convert selection to case ")],
62        callback,
63        None,
64    ) {
65        ytil_noxi::notify::error(format!("error converting selection to case | error={err:#?}"));
66    }
67}
68
69/// Newtype wrapper to make [`Case`] displayable using its [`Debug`] representation.
70struct DisplayableCase<'a>(&'a Case<'a>);
71
72impl Display for DisplayableCase<'_> {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        write!(f, "{:?}", self.0)
75    }
76}