From 08be4c9922e38c2419bb569aa9da8255d9271967 2024-10-31 19:04:23 From: M Bussonnier Date: 2024-10-31 19:04:23 Subject: [PATCH] PR: Unassigned allowed commands (#14564) From git git: > Modify binding mechanism to allow binding to one of a set of whitelisted commands, Add five such commands ("beginning_of_buffer", "end_of_buffer", "end_of_line", "forward_word", "unix_line_discard"). Currently, only commands which are assigned by default are allowed to be reassigned: ```python known_commands = { create_identifier(binding.command): binding.command for binding in KEY_BINDINGS } ... if command_id not in known_commands: ... raise ValueError( f"{command_id} is not a known shortcut command." f" Allowed commands are: \n - {allowed_commands}" ) ``` This PR adds a List UNASSIGNED_ALLOWED_COMMANDS and updates `known_commands` (now renamed to be more clear as `allowed_commands`): ```python allowed_commands.update({ create_identifier(command): command for command in UNASSIGNED_ALLOWED_COMMANDS }) ``` The five unassigned allowed commands are: ```python UNASSIGNED_ALLOWED_COMMANDS = [ nc.beginning_of_buffer, nc.end_of_buffer, nc.end_of_line, nc.forward_word, nc.unix_line_discard, ] ``` Motivation tldr: Until now, it is not possible to bind eg the common keybindng `ctrl+e` to prompt_toolkit's `end_of_line` (because it is not bound by default). Full motivation: https://github.com/ipython/ipython/issues/14369 --- diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 40e2c9a..ef4f5cd 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -52,6 +52,7 @@ from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook from .ptutils import IPythonPTCompleter, IPythonPTLexer from .shortcuts import ( KEY_BINDINGS, + UNASSIGNED_ALLOWED_COMMANDS, create_ipython_shortcuts, create_identifier, RuntimeBinding, @@ -508,19 +509,25 @@ class TerminalInteractiveShell(InteractiveShell): # rebuild the bindings list from scratch key_bindings = create_ipython_shortcuts(self) - # for now we only allow adding shortcuts for commands which are already - # registered; this is a security precaution. - known_commands = { + # for now we only allow adding shortcuts for a specific set of + # commands; this is a security precution. + allowed_commands = { create_identifier(binding.command): binding.command for binding in KEY_BINDINGS } + allowed_commands.update( + { + create_identifier(command): command + for command in UNASSIGNED_ALLOWED_COMMANDS + } + ) shortcuts_to_skip = [] shortcuts_to_add = [] for shortcut in user_shortcuts: command_id = shortcut["command"] - if command_id not in known_commands: - allowed_commands = "\n - ".join(known_commands) + if command_id not in allowed_commands: + allowed_commands = "\n - ".join(allowed_commands) raise ValueError( f"{command_id} is not a known shortcut command." f" Allowed commands are: \n - {allowed_commands}" @@ -544,7 +551,7 @@ class TerminalInteractiveShell(InteractiveShell): new_keys = shortcut.get("new_keys", None) new_filter = shortcut.get("new_filter", None) - command = known_commands[command_id] + command = allowed_commands[command_id] creating_new = shortcut.get("create", False) modifying_existing = not creating_new and ( @@ -586,12 +593,14 @@ class TerminalInteractiveShell(InteractiveShell): RuntimeBinding( command, keys=new_keys or old_keys, - filter=filter_from_string(new_filter) - if new_filter is not None - else ( - old_filter - if old_filter is not None - else filter_from_string("always") + filter=( + filter_from_string(new_filter) + if new_filter is not None + else ( + old_filter + if old_filter is not None + else filter_from_string("always") + ) ), ) ) diff --git a/IPython/terminal/shortcuts/__init__.py b/IPython/terminal/shortcuts/__init__.py index 12890f4..ba6d405 100644 --- a/IPython/terminal/shortcuts/__init__.py +++ b/IPython/terminal/shortcuts/__init__.py @@ -628,3 +628,11 @@ KEY_BINDINGS = [ *SIMPLE_CONTROL_BINDINGS, *ALT_AND_COMOBO_CONTROL_BINDINGS, ] + +UNASSIGNED_ALLOWED_COMMANDS = [ + nc.beginning_of_buffer, + nc.end_of_buffer, + nc.end_of_line, + nc.forward_word, + nc.unix_line_discard, +]