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