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
.to_expanded_string("", config)
.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();
.to_ascii_lowercase();
let char = if let (Some(char), None) = (pos1, pos2) {
char
} else {
return Err(ShellError::UnsupportedConfigValue {
expected: "char_<CHAR: unicode codepoint>".to_string(),
value: c.to_string(),
let keycode = if let Some(rest) = keycode_str.strip_prefix("char_") {
let error = |exp: &str, value| ShellError::UnsupportedConfigValue {
expected: exp.to_string(),
value,
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)
}
} else {
match keycode_str.as_str() {
"backspace" => KeyCode::Backspace,
"enter" => KeyCode::Enter,
"space" => KeyCode::Char(' '),
"down" => KeyCode::Down,
"up" => KeyCode::Up,
@ -892,6 +903,7 @@ fn add_parsed_keybinding(
span: keybinding.keycode.span(),
})
}
}
};
if let Some(event) = parse_event(&keybinding.event, config)? {
keybindings.add_binding(modifier, keycode, event);