Improve keybinding parsing for Unicode support (#14020)

# Description

This pull request enhances the `add_parsed_keybinding` function to
provide greater flexibility in specifying keycodes for keybindings in
Nushell. Previously, the function only supported specifying keycodes
directly through character notation (e.g., `char_e` for the character
`e`). This limited users to a small set of keybindings, especially in
scenarios where specific non-English characters were needed.

With this new version, users can also specify characters using their
Unicode codes, such as `char_u003B` for the semicolon (`;`), providing a
more flexible approach to customization, for example like this:

```nushell
{
    name: move_to_line_end_or_take_history_hint
    modifier: shift
    keycode: char_u003B # char_;
    mode: vi_normal
    event: {
        until: [
            { send: historyhintcomplete }
            { edit: movetolineend }
        ]
    }
}
```

# User-Facing Changes

Added support for specifying keycodes using Unicode codes, e.g.,
char_u002C (comma - `,`):

```nushell
{
    name: <command_name>, # name of the command
    modifier: none,       # key modifier
    keycode: char_u002C,  # Unicode code for the comma (',')
    mode: vi_normal,      # mode in which this binding should work
    event: {
        send: <action>    # action to be performed
    }
}
```
This commit is contained in:
JustForFun88 2024-10-08 17:42:15 +05:00 committed by GitHub
parent 2830ec008c
commit 55c3fc9141
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -833,31 +833,42 @@ fn add_parsed_keybinding(
} }
}; };
let keycode = match keybinding let keycode_str = keybinding
.keycode .keycode
.to_expanded_string("", config) .to_expanded_string("", config)
.to_ascii_lowercase() .to_ascii_lowercase();
.as_str()
{
"backspace" => KeyCode::Backspace,
"enter" => KeyCode::Enter,
c if c.starts_with("char_") => {
let mut char_iter = c.chars().skip(5);
let pos1 = char_iter.next();
let pos2 = char_iter.next();
let char = if let (Some(char), None) = (pos1, pos2) { let keycode = if let Some(rest) = keycode_str.strip_prefix("char_") {
char let error = |exp: &str, value| ShellError::UnsupportedConfigValue {
} else { expected: exp.to_string(),
return Err(ShellError::UnsupportedConfigValue { value,
expected: "char_<CHAR: unicode codepoint>".to_string(),
value: c.to_string(),
span: keybinding.keycode.span(), span: keybinding.keycode.span(),
}); };
let mut char_iter = rest.chars();
let char = match (char_iter.next(), char_iter.next()) {
(Some(char), None) => char,
(Some('u'), Some(_)) => {
// This will never panic as we know there are at least two symbols
let Ok(code_point) = u32::from_str_radix(&rest[1..], 16) else {
return Err(error("valid hex code in keycode", keycode_str));
};
char::from_u32(code_point).ok_or(error("valid Unicode code point", keycode_str))?
}
_ => {
return Err(error(
"format 'char_<char>' or 'char_u<hex code>'",
keycode_str,
))
}
}; };
KeyCode::Char(char) KeyCode::Char(char)
} } else {
match keycode_str.as_str() {
"backspace" => KeyCode::Backspace,
"enter" => KeyCode::Enter,
"space" => KeyCode::Char(' '), "space" => KeyCode::Char(' '),
"down" => KeyCode::Down, "down" => KeyCode::Down,
"up" => KeyCode::Up, "up" => KeyCode::Up,
@ -892,6 +903,7 @@ fn add_parsed_keybinding(
span: keybinding.keycode.span(), span: keybinding.keycode.span(),
}) })
} }
}
}; };
if let Some(event) = parse_event(&keybinding.event, config)? { if let Some(event) = parse_event(&keybinding.event, config)? {
keybindings.add_binding(modifier, keycode, event); keybindings.add_binding(modifier, keycode, event);