Skip to content

Allow to customise shortcuts using a traitlet#13928

Merged
Carreau merged 9 commits intoipython:mainfrom
krassowski:customise-shortcuts
Feb 13, 2023
Merged

Allow to customise shortcuts using a traitlet#13928
Carreau merged 9 commits intoipython:mainfrom
krassowski:customise-shortcuts

Conversation

@krassowski
Copy link
Copy Markdown
Member

This is a refactor of keybindings code aiming to enable users to modify, disable, and add new shortcuts.

Closes #13878, relates to #13879.

Code changes

  • The filters are no longer defined as Python condition expression but as strings. This ensures that all shortcuts that we define can be unambiguously overridden by users from JSON config files.
  • All filters were moved to a new filters.py module
  • All commands previously defined in closure of create_ipython_shortcuts(shell) were moved to globals (which ensures nice identifier names and makes unit-testing easier)
  • All bindings are now collected in KEY_BINDINGS global variable; in future one could consider further splitting them up and moving bindings definition to respective modules (e.g. AUTO_MATCH_BINDINGS to auto_match.py).

User-facing changes

  • New configuration traitlet: c.TerminalInteractiveShell.shortcuts
  • Accept single character in autosuggestion shortcut now uses alt + right instead of right (which is accepting the entire suggestion as in versions 8.8 and before).

After a few iterations I arrived to a specification that separates the existing key/filter from the new key/filter and has a separate "create" flag used to indicate that a new shortcut should be created (rather than modifying an existing one):

Each entry on the list should be a dictionary with command key identifying the target function executed by the shortcut and at least one of the following:

  • match_keys: list of keys used to match an existing shortcut,
  • match_filter: shortcut filter used to match an existing shortcut,
  • new_keys: list of keys to set,
  • new_filter: a new shortcut filter to set

The filters have to be composed of pre-defined verbs and joined by one of the following conjunctions: & (and), | (or), ~ (not). The pre-defined verbs are: .....

To disable a shortcut set new_keys to an empty list.
To add a shortcut add key create with value True. When modifying/disabling shortcuts, match_keys/match_filter can
be omitted if the provided specification uniquely identifies a shortcut to be overridden/disabled.

When modifying a shortcut new_filter or new_keys can be omitted which will result in reuse of the existing filter/keys.

Only shortcuts defined in IPython (and not default prompt toolkit shortcuts) can be modified or disabled.

@krassowski krassowski added the autosuggestions Related to fish-like autosuggestion feature (as opposed to the tab-completions) label Feb 5, 2023
@Carreau Carreau self-requested a review February 6, 2023 09:09
Comment thread IPython/terminal/interactiveshell.py Outdated
""".format(
"\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
),
).tag(config=True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok.

(i'm just going to write some Ok to know where I am in the review).

final_module = parts[-1]
return f"{package}:{final_module}.{name}"
else:
return f"{package}:{name}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the opposite convention of i believe pytest (and a few other things), where is <fully qualified path>:<object potentially with dot access to attribute>.
Any particular reasons to use this schema ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I have mixed feelings on the implementation I chose because as you say fully qualified path feels more standard. The reason why I did not go for it is that (a) some are really really long, see below for examples (b) it was initially written to use this in documentation and only later repurposed for customisation (this PR). Examples of worst offenders would be:

prompt_toolkit.key_binding.bindings.auto_suggest.load_auto_suggest_bindings.<locals>._accept
prompt_toolkit.shortcuts.prompt.PromptSession._create_prompt_bindings.<locals>._complete_like_readline
prompt_toolkit.key_binding.bindings.completion.display_completions_like_readline

In IPython it is not as bad but there is a number of long ones:

IPython.terminal.shortcuts.previous_history_or_previous_completion
IPython.terminal.shortcuts.auto_suggest.accept_and_move_cursor_left
IPython.terminal.shortcuts.auto_suggest.accept_in_vi_insert_mode

I am happy to go either way.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with this for now, you already wrote it.

Comment thread IPython/terminal/interactiveshell.py Outdated
key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
for binding in shortcuts_to_add:
add_binding(key_bindings, binding)
self.pt_app.key_bindings = key_bindings
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Copy Markdown
Member

@Carreau Carreau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks good to me, I'm going to delay the merging a bit as this is a big change I I want to make a small release before as there is a CVE that need fixing/release first.

So I'm going to do a 8.10, and this can go in 8.11.

color_depth=self.color_depth,
tempfile_suffix=".py",
**self._extra_prompt_options()
**self._extra_prompt_options(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

final_module = parts[-1]
return f"{package}:{final_module}.{name}"
else:
return f"{package}:{name}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with this for now, you already wrote it.

"c-_": nc.undo,
}
}.items()
]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

"""Skip over automatically added parenthesis/quote.

(rather than adding another parenthesis)"""
(rather than adding another parenthesis/quote)"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok.

assert up(event) is None
assert down(event) is None
assert swap_autosuggestion_up(event) is None
assert swap_autosuggestion_down(event) is None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

@Carreau Carreau added this pull request to the merge queue Feb 13, 2023
Merged via the queue into ipython:main with commit 442c33c Feb 13, 2023
@Carreau Carreau added this to the 8.11 milestone Feb 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosuggestions Related to fish-like autosuggestion feature (as opposed to the tab-completions)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve autocomplete UX

2 participants