Repository: nushell/nushell (language: rust)

Issue:
Reedline parser doesn't accept edit: CopyCurrentLine
### Describe the bug

I can see CopyCurrentLine in `keybindings list` for edits:
```
: keybindings list | where type == 'edits' |where name =~ '.*(CopyC|CutCurr).*'
╭───┬───────┬─────────────────╮
│ # │ type  │      name       │
├───┼───────┼─────────────────┤
│ 0 │ edits │ CopyCurrentLine │
│ 1 │ edits │ CutCurrentLine  │
╰───┴───────┴─────────────────╯
```
But `edit: CutCurrentLine` binding works, `edit: CopyCurrentLine` throws parser error.

I think it's probably [reedline_config.rs:edit_from_record()](https://github.com/nushell/nushell/blob/be508cbd7fd63020ae616e1904ca0b7d77ad832f/crates/nu-cli/src/reedline_config.rs#L1048) missing "copycurrentline"?

### How to reproduce

I created bindings for them
```
$env.config.edit_mode = 'vi'
$env.config.show_banner = false
$env.config.keybindings ++= [
  {
    name: copy_current_line
    modifier: none
    keycode: char_y
    mode: [vi_normal]
    event: { 
      edit: CopyCurrentLine
    }
  },
  {
    name: cut_current_line
    modifier: alt
    keycode: char_d
    mode: [vi_normal, vi_insert]
    event: { 
      edit: CutCurrentLine
    }
  },
  ]
```
Now the Reedline parser throws an error:
```
Error: nu::shell::invalid_value

  × Invalid value
    ╭─[/home/user/.config/nushell/config.nu:40:12]
 39 │         mode: [vi_normal]
 40 │ ╭─▶     event: { 
 41 │ │         edit: CopyCurrentLine
 42 │ ├─▶     }
    · ╰──── expected a reedline EditCommand, but got 'copycurrentline'
 43 │       },
    ╰────
```
If I remove the CopyCurrentLine binding, CutCurrentLine works as expected.


### Expected behavior

Either parser should accept `edit: CopyCurrentLine`, or it should not be listed in `keybindings list`

### Configuration

| key                | value                                                                                                                              |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
| version            | 0.102.1                                                                                                                            |
| major              | 0                                                                                                                                  |
| minor              | 102                                                                                                                                |
| patch              | 1                                                                                                                                  |
| branch             | makepkg                                                                                                                            |
| commit_hash        | 083c534948cc99307232e7373dfda59cc9c0840c                                                                                           |
| build_os           | linux-x86_64                                                                                                                       |
| build_target       | x86_64-unknown-linux-gnu                                                                                                           |
| rust_version       | rustc 1.84.1 (e71f9a9a9 2025-01-27)                                                                                                |
| rust_channel       | stable-x86_64-unknown-linux-gnu                                                                                                    |
| cargo_version      | cargo 1.84.1 (66221abde 2024-11-19)                                                                                                |
| build_time         | 2025-02-23 06:00:55 +00:00                                                                                                         |
| build_rust_channel | release                                                                                                                            |
| allocator          | mimalloc                                                                                                                           |
| features           | default, sqlite, trash                                                                                                             |
| installed_plugins  | clipboard 0.102.0, desktop_notifications 1.2.9, dns 3.0.7, highlight 1.4.3+0.102.0, nupsql 0.1.0, prometheus 0.8.0, strutils 0.9.0 |


Discussion / hints:
> think it's probably [reedline_config.rs:edit_from_record()](https://github.com/nushell/nushell/blob/be508cbd7fd63020ae616e1904ca0b7d77ad832f/crates/nu-cli/src/reedline_config.rs#L1048) missing "copycurrentline"?

Yes, that's at least part of it and what I said in discord the other day after researching it. 

Thanks for adding the issue.
Yup, the conversion from reedline enum to string used by some of the `keybinding` commands is autogenerated by `reedline` using the `strum` macro but the parsing has to be handrolled for every addition. Not super convenient (and in some parts necessary as some variants take extra arguments or don't make that much sense as a static configuratoin)

Fix patch:
diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs
index 69be71eaf72cb..eb85d76de2ce4 100644
--- a/crates/nu-cli/src/reedline_config.rs
+++ b/crates/nu-cli/src/reedline_config.rs
@@ -998,41 +998,54 @@ fn event_from_record(
 ) -> Result<ReedlineEvent, ShellError> {
     let event = match name {
         "none" => ReedlineEvent::None,
-        "clearscreen" => ReedlineEvent::ClearScreen,
-        "clearscrollback" => ReedlineEvent::ClearScrollback,
         "historyhintcomplete" => ReedlineEvent::HistoryHintComplete,
         "historyhintwordcomplete" => ReedlineEvent::HistoryHintWordComplete,
         "ctrld" => ReedlineEvent::CtrlD,
         "ctrlc" => ReedlineEvent::CtrlC,
+        "clearscreen" => ReedlineEvent::ClearScreen,
+        "clearscrollback" => ReedlineEvent::ClearScrollback,
         "enter" => ReedlineEvent::Enter,
         "submit" => ReedlineEvent::Submit,
         "submitornewline" => ReedlineEvent::SubmitOrNewline,
         "esc" | "escape" => ReedlineEvent::Esc,
+        // Non-sensical for user configuration:
+        //
+        // `ReedlineEvent::Mouse` - itself a no-op
+        // `ReedlineEvent::Resize` - requires size info specifically from the ANSI resize
+        // event
+        //
+        // Handled above in `parse_event`:
+        //
+        // `ReedlineEvent::Edit`
+        "repaint" => ReedlineEvent::Repaint,
+        "previoushistory" => ReedlineEvent::PreviousHistory,
         "up" => ReedlineEvent::Up,
         "down" => ReedlineEvent::Down,
         "right" => ReedlineEvent::Right,
         "left" => ReedlineEvent::Left,
-        "searchhistory" => ReedlineEvent::SearchHistory,
         "nexthistory" => ReedlineEvent::NextHistory,
-        "previoushistory" => ReedlineEvent::PreviousHistory,
-        "repaint" => ReedlineEvent::Repaint,
-        "menudown" => ReedlineEvent::MenuDown,
+        "searchhistory" => ReedlineEvent::SearchHistory,
+        // Handled above in `parse_event`:
+        //
+        // `ReedlineEvent::Multiple`
+        // `ReedlineEvent::UntilFound`
+        "menu" => {
+            let menu = extract_value("name", record, span)?;
+            ReedlineEvent::Menu(menu.to_expanded_string("", config))
+        }
+        "menunext" => ReedlineEvent::MenuNext,
+        "menuprevious" => ReedlineEvent::MenuPrevious,
         "menuup" => ReedlineEvent::MenuUp,
+        "menudown" => ReedlineEvent::MenuDown,
         "menuleft" => ReedlineEvent::MenuLeft,
         "menuright" => ReedlineEvent::MenuRight,
-        "menunext" => ReedlineEvent::MenuNext,
-        "menuprevious" => ReedlineEvent::MenuPrevious,
         "menupagenext" => ReedlineEvent::MenuPageNext,
         "menupageprevious" => ReedlineEvent::MenuPagePrevious,
-        "openeditor" => ReedlineEvent::OpenEditor,
-        "menu" => {
-            let menu = extract_value("name", record, span)?;
-            ReedlineEvent::Menu(menu.to_expanded_string("", config))
-        }
         "executehostcommand" => {
             let cmd = extract_value("cmd", record, span)?;
             ReedlineEvent::ExecuteHostCommand(cmd.to_expanded_string("", config))
         }
+        "openeditor" => ReedlineEvent::OpenEditor,
         str => {
             return Err(ShellError::InvalidValue {
                 valid: "a reedline event".into(),
@@ -1062,7 +1075,6 @@ fn edit_from_record(
                 .and_then(|value| value.as_bool())
                 .unwrap_or(false),
         },
-
         "movetoend" => EditCommand::MoveToEnd {
             select: extract_value("select", record, span)
                 .and_then(|value| value.as_bool())
@@ -1098,22 +1110,22 @@ fn edit_from_record(
                 .and_then(|value| value.as_bool())
                 .unwrap_or(false),
         },
-        "movewordrightend" => EditCommand::MoveWordRightEnd {
+        "movewordrightstart" => EditCommand::MoveWordRightStart {
             select: extract_value("select", record, span)
                 .and_then(|value| value.as_bool())
                 .unwrap_or(false),
         },
-        "movebigwordrightend" => EditCommand::MoveBigWordRightEnd {
+        "movebigwordrightstart" => EditCommand::MoveBigWordRightStart {
             select: extract_value("select", record, span)
                 .and_then(|value| value.as_bool())
                 .unwrap_or(false),
         },
-        "movewordrightstart" => EditCommand::MoveWordRightStart {
+        "movewordrightend" => EditCommand::MoveWordRightEnd {
             select: extract_value("select", record, span)
                 .and_then(|value| value.as_bool())
                 .unwrap_or(false),
         },
-        "movebigwordrightstart" => EditCommand::MoveBigWordRightStart {
+        "movebigwordrightend" => EditCommand::MoveBigWordRightEnd {
             select: extract_value("select", record, span)
                 .and_then(|value| value.as_bool())
                 .unwrap_or(false),
@@ -1139,6 +1151,13 @@ fn edit_from_record(
             EditCommand::InsertString(value.to_expanded_string("", config))
         }
         "insertnewline" => EditCommand::InsertNewline,
+        "replacechar" => {
+            let value = extract_value("value", record, span)?;
+            let char = extract_char(value)?;
+            EditCommand::ReplaceChar(char)
+        }
+        // `EditCommand::ReplaceChars` - Internal hack not sanely implementable as a
+        // standalone binding
         "backspace" => EditCommand::Backspace,
         "delete" => EditCommand::Delete,
         "cutchar" => EditCommand::CutChar,
@@ -1146,6 +1165,7 @@ fn edit_from_record(
         "deleteword" => EditCommand::DeleteWord,
         "clear" => EditCommand::Clear,
         "cleartolineend" => EditCommand::ClearToLineEnd,
+        "complete" => EditCommand::Complete,
         "cutcurrentline" => EditCommand::CutCurrentLine,
         "cutfromstart" => EditCommand::CutFromStart,
         "cutfromlinestart" => EditCommand::CutFromLineStart,
@@ -1162,6 +1182,7 @@ fn edit_from_record(
         "uppercaseword" => EditCommand::UppercaseWord,
         "lowercaseword" => EditCommand::LowercaseWord,
         "capitalizechar" => EditCommand::CapitalizeChar,
+        "switchcasechar" => EditCommand::SwitchcaseChar,
         "swapwords" => EditCommand::SwapWords,
         "swapgraphemes" => EditCommand::SwapGraphemes,
         "undo" => EditCommand::Undo,
@@ -1218,17 +1239,64 @@ fn edit_from_record(
                 .unwrap_or(false);
             EditCommand::MoveLeftBefore { c: char, select }
         }
-        "complete" => EditCommand::Complete,
+        "selectall" => EditCommand::SelectAll,
         "cutselection" => EditCommand::CutSelection,
+        "copyselection" => EditCommand::CopySelection,
+        "paste" => EditCommand::Paste,
+        "copyfromstart" => EditCommand::CopyFromStart,
+        "copyfromlinestart" => EditCommand::CopyFromLineStart,
+        "copytoend" => EditCommand::CopyToEnd,
+        "copytolineend" => EditCommand::CopyToLineEnd,
+        "copycurrentline" => EditCommand::CopyCurrentLine,
+        "copywordleft" => EditCommand::CopyWordLeft,
+        "copybigwordleft" => EditCommand::CopyBigWordLeft,
+        "copywordright" => EditCommand::CopyWordRight,
+        "copybigwordright" => EditCommand::CopyBigWordRight,
+        "copywordrighttonext" => EditCommand::CopyWordRightToNext,
+        "copybigwordrighttonext" => EditCommand::CopyBigWordRightToNext,
+        "copyleft" => EditCommand::CopyLeft,
+        "copyright" => EditCommand::CopyRight,
+        "copyrightuntil" => {
+            let value = extract_value("value", record, span)?;
+            let char = extract_char(value)?;
+            EditCommand::CopyRightUntil(char)
+        }
+        "copyrightbefore" => {
+            let value = extract_value("value", record, span)?;
+            let char = extract_char(value)?;
+            EditCommand::CopyRightBefore(char)
+        }
+        "copyleftuntil" => {
+            let value = extract_value("value", record, span)?;
+            let char = extract_char(value)?;
+            EditCommand::CopyLeftUntil(char)
+        }
+        "copyleftbefore" => {
+            let value = extract_value("value", record, span)?;
+            let char = extract_char(value)?;
+            EditCommand::CopyLeftBefore(char)
+        }
+        "swapcursorandanchor" => EditCommand::SwapCursorAndAnchor,
         #[cfg(feature = "system-clipboard")]
         "cutselectionsystem" => EditCommand::CutSelectionSystem,
-        "copyselection" => EditCommand::CopySelection,
         #[cfg(feature = "system-clipboard")]
         "copyselectionsystem" => EditCommand::CopySelectionSystem,
-        "paste" => EditCommand::Paste,
         #[cfg(feature = "system-clipboard")]
         "pastesystem" => EditCommand::PasteSystem,
-        "selectall" => EditCommand::SelectAll,
+        "cutinside" => {
+            let value = extract_value("left", record, span)?;
+            let left = extract_char(value)?;
+            let value = extract_value("right", record, span)?;
+            let right = extract_char(value)?;
+            EditCommand::CutInside { left, right }
+        }
+        "yankinside" => {
+            let value = extract_value("left", record, span)?;
+            let left = extract_char(value)?;
+            let value = extract_value("right", record, span)?;
+            let right = extract_char(value)?;
+            EditCommand::YankInside { left, right }
+        }
         str => {
             return Err(ShellError::InvalidValue {
                 valid: "a reedline EditCommand".into(),


Test patch: