From 1e81146bd6fa04201515f9b6cc1acd698f85d8f4 Mon Sep 17 00:00:00 2001 From: Italo Cunha Date: Sun, 5 Jun 2022 12:08:06 -0300 Subject: [PATCH] Support deletion in whole_word_jumping xontrib (#4788) This binds Alt/Control+Delete/Backspace to support deletion matching the motion bindings in `whole_word_jumping`. Should work mostly out of the box on terminals that generate `\x7f` when `backspace` is pressed and `\x08` when `control+backspace` is pressed. Tested to work on Microsoft Terminal (WSL2). For incompatible terminals, users can set `$XONSH_WHOLE_WORD_CTRL_BKSP = False` to avoid configuration of the `control+backspace` binding. --- news/extend-whole-word-jumping.rst | 26 ++++++++++++ xontrib/whole_word_jumping.py | 64 +++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 news/extend-whole-word-jumping.rst diff --git a/news/extend-whole-word-jumping.rst b/news/extend-whole-word-jumping.rst new file mode 100644 index 000000000..9eefcc9bd --- /dev/null +++ b/news/extend-whole-word-jumping.rst @@ -0,0 +1,26 @@ +**Added:** + +* + +**Changed:** + +* Extended `whole_word_jumping` xontrib with matching bindings for + `delete` and `backspace`. The `XONSH_WHOLE_WORD_CTRL_BKSP` environment + variable can be set to `False` to avoid binding `control+backspace` in + incompatible terminals. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/xontrib/whole_word_jumping.py b/xontrib/whole_word_jumping.py index 7f9538146..94de46ec6 100644 --- a/xontrib/whole_word_jumping.py +++ b/xontrib/whole_word_jumping.py @@ -1,14 +1,33 @@ -"""Jumping across whole words (non-whitespace) with Ctrl+Left/Right. +"""Jump/delete across whole (non-whitespace) words with Ctrl+Left/Right/Delete/Backspace. -Alt+Left/Right remains unmodified to jump over smaller word segments. -Shift+Delete removes the whole word. +Control+left/right: Jump to previous/next whole word +Control+backspace: Delete to beginning of whole word +Control+delete: Delete to end of whole word +Shift+delete: Delete whole word + +Alt+Left/Right/Delete/Backspace remain unmodified: + +Alt+left/right: Jump to previous/next token +Alt+backspace: Delete to beginning of token +Alt+delete: Delete to end of token + +Some terminals cannot differentiate between Backspace and Control+Backspace. +In this case, users can set `$XONSH_WHOLE_WORD_CTRL_BKSP = False` to skip +configuration of the Control+Backspace key binding. """ + +import prompt_toolkit.input.ansi_escape_sequences as ansiseq +import prompt_toolkit.input.win32 as ptk_win32 +from prompt_toolkit.filters import EmacsInsertMode, ViInsertMode +from prompt_toolkit.key_binding.bindings.named_commands import get_by_name from prompt_toolkit.keys import Keys -from xonsh.built_ins import XonshSession +from xonsh.built_ins import XSH, XonshSession +from xonsh.platform import ON_WINDOWS def custom_keybindings(bindings, **kw): + insert_mode = ViInsertMode() | EmacsInsertMode() # Key bindings for jumping over whole words (everything that's not # white space) using Ctrl+Left and Ctrl+Right; @@ -29,8 +48,8 @@ def custom_keybindings(bindings, **kw): if pos: buff.cursor_position += pos - @bindings.add(Keys.ShiftDelete) - def shift_delete(event): + @bindings.add(Keys.ShiftDelete, filter=insert_mode) + def delete_surrounding_big_word(event): buff = event.current_buffer startpos, endpos = buff.document.find_boundaries_of_current_word(WORD=True) startpos = buff.cursor_position + startpos - 1 @@ -40,6 +59,39 @@ def custom_keybindings(bindings, **kw): buff.text = buff.text[:startpos] + buff.text[endpos:] buff.cursor_position = startpos + @bindings.add(Keys.ControlDelete, filter=insert_mode) + def delete_big_word(event): + buff = event.current_buffer + pos = buff.document.find_next_word_ending(count=event.arg, WORD=True) + if pos: + buff.delete(count=pos) + + @bindings.add(Keys.Escape, Keys.Delete, filter=insert_mode) + def delete_small_word(event): + get_by_name("kill-word").call(event) + + # PTK sets both "\x7f" (^?) and "\x08" (^H) to the same behavior. Refs: + # https://github.com/prompt-toolkit/python-prompt-toolkit/blob/65c3d0607c69c19d80abb052a18569a2546280e5/src/prompt_toolkit/input/ansi_escape_sequences.py#L65 + # https://github.com/prompt-toolkit/python-prompt-toolkit/issues/257#issuecomment-190328366 + # We patch the ANSI sequences used by PTK. This requires a terminal + # that sends different codes for and . + # PTK sets Keys.Backspace = Keys.ControlH, so we hardcode the code. + # Windows has the codes reversed, see https://github.com/xonsh/xonsh/commit/406d20f78f18af39d9bbaf9580b0a763df78a0db + if XSH.env.get("XONSH_WHOLE_WORD_CTRL_BKSP", True): + CONTROL_BKSP = "\x08" + if ON_WINDOWS: + # On windows BKSP is "\x08" and CTRL-BKSP is "\x7f" + CONTROL_BKSP = "\x7f" + ptk_win32.ConsoleInputReader.mappings[b"\x7f"] = CONTROL_BKSP + ansiseq.ANSI_SEQUENCES[CONTROL_BKSP] = CONTROL_BKSP + ansiseq.REVERSE_ANSI_SEQUENCES[CONTROL_BKSP] = CONTROL_BKSP + + @bindings.add(CONTROL_BKSP, filter=insert_mode) + def backward_delete_big_word(event): + get_by_name("unix-word-rubout").call(event) + + # backward_delete_small_word works on Alt+Backspace by default + def _load_xontrib_(xsh: XonshSession, **_): xsh.builtins.events.on_ptk_create(custom_keybindings)