xref: /aosp_15_r20/external/pigweed/pw_console/py/pw_console/key_bindings.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14# pylint: skip-file
15"""Console key bindings."""
16import logging
17
18from prompt_toolkit.filters import (
19    Condition,
20    has_focus,
21)
22from prompt_toolkit.key_binding import KeyBindings
23from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
24
25
26__all__ = ('create_key_bindings',)
27
28_LOG = logging.getLogger(__package__)
29
30DEFAULT_KEY_BINDINGS: dict[str, list[str]] = {
31    'global.open-user-guide': ['f1'],
32    'global.open-menu-search': ['c-p'],
33    'global.focus-previous-widget': ['c-left'],
34    'global.focus-next-widget': ['c-right', 's-tab'],
35    'global.exit-no-confirmation': ['c-x c-c'],
36    'global.exit-with-confirmation': ['c-d'],
37    'log-pane.shift-line-to-top': ['z t'],
38    'log-pane.shift-line-to-center': ['z z'],
39    'log-pane.toggle-follow': ['f'],
40    'log-pane.toggle-wrap-lines': ['w'],
41    'log-pane.toggle-table-view': ['t'],
42    'log-pane.duplicate-log-pane': ['insert'],
43    'log-pane.remove-duplicated-log-pane': ['delete'],
44    'log-pane.clear-history': ['C'],
45    'log-pane.toggle-follow': ['f'],
46    'log-pane.toggle-web-browser': ['O'],
47    'log-pane.move-cursor-up': ['up', 'k'],
48    'log-pane.move-cursor-down': ['down', 'j'],
49    'log-pane.visual-select-up': ['s-up'],
50    'log-pane.visual-select-down': ['s-down'],
51    'log-pane.visual-select-all': ['c-r'],
52    'log-pane.deselect-cancel-search': ['c-c'],
53    'log-pane.scroll-page-up': ['pageup'],
54    'log-pane.scroll-page-down': ['pagedown'],
55    'log-pane.scroll-to-top': ['g'],
56    'log-pane.scroll-to-bottom': ['G'],
57    'log-pane.save-copy': ['c-o'],
58    'log-pane.search': ['/', 'c-f'],
59    'log-pane.search-next-match': ['n', 'c-s', 'c-g'],
60    'log-pane.search-previous-match': ['N', 'c-r'],
61    'log-pane.search-apply-filter': ['escape c-f'],
62    'log-pane.clear-filters': ['escape c-r'],
63    'search-toolbar.toggle-column': ['c-t'],
64    'search-toolbar.toggle-invert': ['c-v'],
65    'search-toolbar.toggle-matcher': ['c-n'],
66    'search-toolbar.cancel': ['escape', 'c-c', 'c-d'],
67    'search-toolbar.create-filter': ['escape c-f'],
68    'window-manager.move-pane-left': ['escape c-left'],  # Alt-Ctrl-
69    'window-manager.move-pane-right': ['escape c-right'],  # Alt-Ctrl-
70    # NOTE: c-up and c-down seem swapped in prompt-toolkit
71    'window-manager.move-pane-down': ['escape c-up'],  # Alt-Ctrl-
72    'window-manager.move-pane-up': ['escape c-down'],  # Alt-Ctrl-
73    'window-manager.enlarge-pane': ['escape ='],  # Alt-= (mnemonic: Alt Plus)
74    'window-manager.shrink-pane': [
75        'escape -'
76    ],  # Alt-minus (mnemonic: Alt Minus)
77    'window-manager.shrink-split': ['escape ,'],  # Alt-, (mnemonic: Alt <)
78    'window-manager.enlarge-split': ['escape .'],  # Alt-. (mnemonic: Alt >)
79    'window-manager.focus-prev-pane': ['escape c-p'],  # Ctrl-Alt-p
80    'window-manager.focus-next-pane': ['escape c-n'],  # Ctrl-Alt-n
81    'window-manager.balance-window-panes': ['c-u'],
82    'python-repl.copy-output-selection': ['c-c'],
83    'python-repl.copy-all-output': ['escape c-c'],
84    'python-repl.copy-clear-or-cancel': ['c-c'],
85    'python-repl.paste-to-input': ['c-v'],
86    'python-repl.history-search': ['c-r'],
87    'python-repl.snippet-search': ['c-t'],
88    'save-as-dialog.cancel': ['escape', 'c-c', 'c-d'],
89    'quit-dialog.no': ['escape', 'n', 'c-c'],
90    'quit-dialog.yes': ['y', 'c-d'],
91    'command-runner.cancel': ['escape', 'c-c'],
92    'command-runner.select-previous-item': ['up', 's-tab'],
93    'command-runner.select-next-item': ['down', 'tab'],
94    'help-window.close': ['q', 'f1', 'escape'],
95    'help-window.copy-all': ['c-c'],
96}
97
98
99def create_key_bindings(console_app) -> KeyBindings:
100    """Create custom key bindings.
101
102    This starts with the key bindings, defined by `prompt-toolkit`, but adds the
103    ones which are specific for the console_app. A console_app instance
104    reference is passed in so key bind functions can access it.
105    """
106
107    key_bindings = KeyBindings()
108    register = console_app.prefs.register_keybinding
109
110    @register(
111        'global.open-user-guide',
112        key_bindings,
113        filter=Condition(lambda: not console_app.modal_window_is_open()),
114    )
115    def show_help(event):
116        """Toggle user guide window."""
117        console_app.user_guide_window.toggle_display()
118
119    # F2 is ptpython settings
120    # F3 is ptpython history
121
122    @register(
123        'global.open-menu-search',
124        key_bindings,
125        filter=Condition(lambda: not console_app.modal_window_is_open()),
126    )
127    def show_command_runner(event):
128        """Open command runner window."""
129        console_app.open_command_runner_main_menu()
130
131    @register('global.focus-previous-widget', key_bindings)
132    def app_focus_previous(event):
133        """Move focus to the previous widget."""
134        focus_previous(event)
135
136    @register('global.focus-next-widget', key_bindings)
137    def app_focus_next(event):
138        """Move focus to the next widget."""
139        focus_next(event)
140
141    # Bindings for when the ReplPane input field is in focus.
142    # These are hidden from help window global keyboard shortcuts since the
143    # method names end with `_hidden`.
144    @register(
145        'python-repl.copy-clear-or-cancel',
146        key_bindings,
147        filter=has_focus(console_app.pw_ptpython_repl),
148    )
149    def handle_ctrl_c_hidden(event):
150        """Reset the python repl on Ctrl-c"""
151        console_app.repl_pane.ctrl_c()
152
153    @register('global.exit-no-confirmation', key_bindings)
154    def quit_no_confirm(event):
155        """Quit without confirmation."""
156        event.app.exit()
157
158    @register(
159        'global.exit-with-confirmation',
160        key_bindings,
161        filter=console_app.pw_ptpython_repl.input_empty_if_in_focus_condition()
162        | has_focus(console_app.quit_dialog),
163    )
164    def quit(event):
165        """Quit with confirmation dialog."""
166        # If the python repl is in focus and has text input then Ctrl-d will
167        # delete forward characters instead.
168        console_app.quit_dialog.open_dialog()
169
170    @register(
171        'python-repl.paste-to-input',
172        key_bindings,
173        filter=has_focus(console_app.pw_ptpython_repl),
174    )
175    def paste_into_repl(event):
176        """Reset the python repl on Ctrl-c"""
177        console_app.repl_pane.paste_system_clipboard_to_input_buffer()
178
179    @register(
180        'python-repl.history-search',
181        key_bindings,
182        filter=has_focus(console_app.pw_ptpython_repl),
183    )
184    def history_search(event):
185        """Open the repl history search dialog."""
186        console_app.open_command_runner_history()
187
188    @register(
189        'python-repl.snippet-search',
190        key_bindings,
191        filter=has_focus(console_app.pw_ptpython_repl),
192    )
193    def insert_snippet(event):
194        """Open the repl snippet search dialog."""
195        console_app.open_command_runner_snippets()
196
197    @register(
198        'python-repl.copy-all-output',
199        key_bindings,
200        filter=console_app.repl_pane.input_or_output_has_focus(),
201    )
202    def copy_repl_output_text(event):
203        """Copy all Python output to the system clipboard."""
204        console_app.repl_pane.copy_all_output_text()
205
206    return key_bindings
207