1# pylint: disable=function-redefined 2from __future__ import unicode_literals 3 4from prompt_toolkit.enums import DEFAULT_BUFFER 5from prompt_toolkit.filters import HasSelection, Condition, EmacsInsertMode, ViInsertMode 6from prompt_toolkit.keys import Keys 7from prompt_toolkit.layout.screen import Point 8from prompt_toolkit.mouse_events import MouseEventType, MouseEvent 9from prompt_toolkit.renderer import HeightIsUnknownError 10from prompt_toolkit.utils import suspend_to_background_supported, is_windows 11 12from .named_commands import get_by_name 13from ..registry import Registry 14 15 16__all__ = ( 17 'load_basic_bindings', 18 'load_abort_and_exit_bindings', 19 'load_basic_system_bindings', 20 'load_auto_suggestion_bindings', 21) 22 23def if_no_repeat(event): 24 """ Callable that returns True when the previous event was delivered to 25 another handler. """ 26 return not event.is_repeat 27 28 29def load_basic_bindings(): 30 registry = Registry() 31 insert_mode = ViInsertMode() | EmacsInsertMode() 32 handle = registry.add_binding 33 has_selection = HasSelection() 34 35 @handle(Keys.ControlA) 36 @handle(Keys.ControlB) 37 @handle(Keys.ControlC) 38 @handle(Keys.ControlD) 39 @handle(Keys.ControlE) 40 @handle(Keys.ControlF) 41 @handle(Keys.ControlG) 42 @handle(Keys.ControlH) 43 @handle(Keys.ControlI) 44 @handle(Keys.ControlJ) 45 @handle(Keys.ControlK) 46 @handle(Keys.ControlL) 47 @handle(Keys.ControlM) 48 @handle(Keys.ControlN) 49 @handle(Keys.ControlO) 50 @handle(Keys.ControlP) 51 @handle(Keys.ControlQ) 52 @handle(Keys.ControlR) 53 @handle(Keys.ControlS) 54 @handle(Keys.ControlT) 55 @handle(Keys.ControlU) 56 @handle(Keys.ControlV) 57 @handle(Keys.ControlW) 58 @handle(Keys.ControlX) 59 @handle(Keys.ControlY) 60 @handle(Keys.ControlZ) 61 @handle(Keys.F1) 62 @handle(Keys.F2) 63 @handle(Keys.F3) 64 @handle(Keys.F4) 65 @handle(Keys.F5) 66 @handle(Keys.F6) 67 @handle(Keys.F7) 68 @handle(Keys.F8) 69 @handle(Keys.F9) 70 @handle(Keys.F10) 71 @handle(Keys.F11) 72 @handle(Keys.F12) 73 @handle(Keys.F13) 74 @handle(Keys.F14) 75 @handle(Keys.F15) 76 @handle(Keys.F16) 77 @handle(Keys.F17) 78 @handle(Keys.F18) 79 @handle(Keys.F19) 80 @handle(Keys.F20) 81 @handle(Keys.ControlSpace) 82 @handle(Keys.ControlBackslash) 83 @handle(Keys.ControlSquareClose) 84 @handle(Keys.ControlCircumflex) 85 @handle(Keys.ControlUnderscore) 86 @handle(Keys.Backspace) 87 @handle(Keys.Up) 88 @handle(Keys.Down) 89 @handle(Keys.Right) 90 @handle(Keys.Left) 91 @handle(Keys.ShiftUp) 92 @handle(Keys.ShiftDown) 93 @handle(Keys.ShiftRight) 94 @handle(Keys.ShiftLeft) 95 @handle(Keys.Home) 96 @handle(Keys.End) 97 @handle(Keys.Delete) 98 @handle(Keys.ShiftDelete) 99 @handle(Keys.ControlDelete) 100 @handle(Keys.PageUp) 101 @handle(Keys.PageDown) 102 @handle(Keys.BackTab) 103 @handle(Keys.Tab) 104 @handle(Keys.ControlLeft) 105 @handle(Keys.ControlRight) 106 @handle(Keys.ControlUp) 107 @handle(Keys.ControlDown) 108 @handle(Keys.Insert) 109 @handle(Keys.Ignore) 110 def _(event): 111 """ 112 First, for any of these keys, Don't do anything by default. Also don't 113 catch them in the 'Any' handler which will insert them as data. 114 115 If people want to insert these characters as a literal, they can always 116 do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi 117 mode.) 118 """ 119 pass 120 121 # Readline-style bindings. 122 handle(Keys.Home)(get_by_name('beginning-of-line')) 123 handle(Keys.End)(get_by_name('end-of-line')) 124 handle(Keys.Left)(get_by_name('backward-char')) 125 handle(Keys.Right)(get_by_name('forward-char')) 126 handle(Keys.ControlUp)(get_by_name('previous-history')) 127 handle(Keys.ControlDown)(get_by_name('next-history')) 128 handle(Keys.ControlL)(get_by_name('clear-screen')) 129 130 handle(Keys.ControlK, filter=insert_mode)(get_by_name('kill-line')) 131 handle(Keys.ControlU, filter=insert_mode)(get_by_name('unix-line-discard')) 132 handle(Keys.ControlH, filter=insert_mode, save_before=if_no_repeat)( 133 get_by_name('backward-delete-char')) 134 handle(Keys.Backspace, filter=insert_mode, save_before=if_no_repeat)( 135 get_by_name('backward-delete-char')) 136 handle(Keys.Delete, filter=insert_mode, save_before=if_no_repeat)( 137 get_by_name('delete-char')) 138 handle(Keys.ShiftDelete, filter=insert_mode, save_before=if_no_repeat)( 139 get_by_name('delete-char')) 140 handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)( 141 get_by_name('self-insert')) 142 handle(Keys.ControlT, filter=insert_mode)(get_by_name('transpose-chars')) 143 handle(Keys.ControlW, filter=insert_mode)(get_by_name('unix-word-rubout')) 144 handle(Keys.ControlI, filter=insert_mode)(get_by_name('menu-complete')) 145 handle(Keys.BackTab, filter=insert_mode)(get_by_name('menu-complete-backward')) 146 147 handle(Keys.PageUp, filter= ~has_selection)(get_by_name('previous-history')) 148 handle(Keys.PageDown, filter= ~has_selection)(get_by_name('next-history')) 149 150 # CTRL keys. 151 152 text_before_cursor = Condition(lambda cli: cli.current_buffer.text) 153 handle(Keys.ControlD, filter=text_before_cursor & insert_mode)(get_by_name('delete-char')) 154 155 is_multiline = Condition(lambda cli: cli.current_buffer.is_multiline()) 156 is_returnable = Condition(lambda cli: cli.current_buffer.accept_action.is_returnable) 157 158 @handle(Keys.ControlJ, filter=is_multiline & insert_mode) 159 def _(event): 160 " Newline (in case of multiline input. " 161 event.current_buffer.newline(copy_margin=not event.cli.in_paste_mode) 162 163 @handle(Keys.ControlJ, filter=~is_multiline & is_returnable) 164 def _(event): 165 " Enter, accept input. " 166 buff = event.current_buffer 167 buff.accept_action.validate_and_handle(event.cli, buff) 168 169 # Delete the word before the cursor. 170 171 @handle(Keys.Up) 172 def _(event): 173 event.current_buffer.auto_up(count=event.arg) 174 175 @handle(Keys.Down) 176 def _(event): 177 event.current_buffer.auto_down(count=event.arg) 178 179 @handle(Keys.Delete, filter=has_selection) 180 def _(event): 181 data = event.current_buffer.cut_selection() 182 event.cli.clipboard.set_data(data) 183 184 # Global bindings. 185 186 @handle(Keys.ControlZ) 187 def _(event): 188 """ 189 By default, control-Z should literally insert Ctrl-Z. 190 (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File. 191 In a Python REPL for instance, it's possible to type 192 Control-Z followed by enter to quit.) 193 194 When the system bindings are loaded and suspend-to-background is 195 supported, that will override this binding. 196 """ 197 event.current_buffer.insert_text(event.data) 198 199 @handle(Keys.CPRResponse, save_before=lambda e: False) 200 def _(event): 201 """ 202 Handle incoming Cursor-Position-Request response. 203 """ 204 # The incoming data looks like u'\x1b[35;1R' 205 # Parse row/col information. 206 row, col = map(int, event.data[2:-1].split(';')) 207 208 # Report absolute cursor position to the renderer. 209 event.cli.renderer.report_absolute_cursor_row(row) 210 211 @handle(Keys.BracketedPaste) 212 def _(event): 213 " Pasting from clipboard. " 214 data = event.data 215 216 # Be sure to use \n as line ending. 217 # Some terminals (Like iTerm2) seem to paste \r\n line endings in a 218 # bracketed paste. See: https://github.com/ipython/ipython/issues/9737 219 data = data.replace('\r\n', '\n') 220 data = data.replace('\r', '\n') 221 222 event.current_buffer.insert_text(data) 223 224 @handle(Keys.Any, filter=Condition(lambda cli: cli.quoted_insert), eager=True) 225 def _(event): 226 """ 227 Handle quoted insert. 228 """ 229 event.current_buffer.insert_text(event.data, overwrite=False) 230 event.cli.quoted_insert = False 231 232 return registry 233 234 235def load_mouse_bindings(): 236 """ 237 Key bindings, required for mouse support. 238 (Mouse events enter through the key binding system.) 239 """ 240 registry = Registry() 241 242 @registry.add_binding(Keys.Vt100MouseEvent) 243 def _(event): 244 """ 245 Handling of incoming mouse event. 246 """ 247 # Typical: "Esc[MaB*" 248 # Urxvt: "Esc[96;14;13M" 249 # Xterm SGR: "Esc[<64;85;12M" 250 251 # Parse incoming packet. 252 if event.data[2] == 'M': 253 # Typical. 254 mouse_event, x, y = map(ord, event.data[3:]) 255 mouse_event = { 256 32: MouseEventType.MOUSE_DOWN, 257 35: MouseEventType.MOUSE_UP, 258 96: MouseEventType.SCROLL_UP, 259 97: MouseEventType.SCROLL_DOWN, 260 }.get(mouse_event) 261 262 # Handle situations where `PosixStdinReader` used surrogateescapes. 263 if x >= 0xdc00: x-= 0xdc00 264 if y >= 0xdc00: y-= 0xdc00 265 266 x -= 32 267 y -= 32 268 else: 269 # Urxvt and Xterm SGR. 270 # When the '<' is not present, we are not using the Xterm SGR mode, 271 # but Urxvt instead. 272 data = event.data[2:] 273 if data[:1] == '<': 274 sgr = True 275 data = data[1:] 276 else: 277 sgr = False 278 279 # Extract coordinates. 280 mouse_event, x, y = map(int, data[:-1].split(';')) 281 m = data[-1] 282 283 # Parse event type. 284 if sgr: 285 mouse_event = { 286 (0, 'M'): MouseEventType.MOUSE_DOWN, 287 (0, 'm'): MouseEventType.MOUSE_UP, 288 (64, 'M'): MouseEventType.SCROLL_UP, 289 (65, 'M'): MouseEventType.SCROLL_DOWN, 290 }.get((mouse_event, m)) 291 else: 292 mouse_event = { 293 32: MouseEventType.MOUSE_DOWN, 294 35: MouseEventType.MOUSE_UP, 295 96: MouseEventType.SCROLL_UP, 296 97: MouseEventType.SCROLL_DOWN, 297 }.get(mouse_event) 298 299 x -= 1 300 y -= 1 301 302 # Only handle mouse events when we know the window height. 303 if event.cli.renderer.height_is_known and mouse_event is not None: 304 # Take region above the layout into account. The reported 305 # coordinates are absolute to the visible part of the terminal. 306 try: 307 y -= event.cli.renderer.rows_above_layout 308 except HeightIsUnknownError: 309 return 310 311 # Call the mouse handler from the renderer. 312 handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] 313 handler(event.cli, MouseEvent(position=Point(x=x, y=y), 314 event_type=mouse_event)) 315 316 @registry.add_binding(Keys.WindowsMouseEvent) 317 def _(event): 318 """ 319 Handling of mouse events for Windows. 320 """ 321 assert is_windows() # This key binding should only exist for Windows. 322 323 # Parse data. 324 event_type, x, y = event.data.split(';') 325 x = int(x) 326 y = int(y) 327 328 # Make coordinates absolute to the visible part of the terminal. 329 screen_buffer_info = event.cli.renderer.output.get_win32_screen_buffer_info() 330 rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.cli.renderer._cursor_pos.y 331 y -= rows_above_cursor 332 333 # Call the mouse event handler. 334 handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] 335 handler(event.cli, MouseEvent(position=Point(x=x, y=y), 336 event_type=event_type)) 337 338 return registry 339 340 341def load_abort_and_exit_bindings(): 342 """ 343 Basic bindings for abort (Ctrl-C) and exit (Ctrl-D). 344 """ 345 registry = Registry() 346 handle = registry.add_binding 347 348 @handle(Keys.ControlC) 349 def _(event): 350 " Abort when Control-C has been pressed. " 351 event.cli.abort() 352 353 @Condition 354 def ctrl_d_condition(cli): 355 """ Ctrl-D binding is only active when the default buffer is selected 356 and empty. """ 357 return (cli.current_buffer_name == DEFAULT_BUFFER and 358 not cli.current_buffer.text) 359 360 handle(Keys.ControlD, filter=ctrl_d_condition)(get_by_name('end-of-file')) 361 362 return registry 363 364 365def load_basic_system_bindings(): 366 """ 367 Basic system bindings (For both Emacs and Vi mode.) 368 """ 369 registry = Registry() 370 371 suspend_supported = Condition( 372 lambda cli: suspend_to_background_supported()) 373 374 @registry.add_binding(Keys.ControlZ, filter=suspend_supported) 375 def _(event): 376 """ 377 Suspend process to background. 378 """ 379 event.cli.suspend_to_background() 380 381 return registry 382 383 384def load_auto_suggestion_bindings(): 385 """ 386 Key bindings for accepting auto suggestion text. 387 """ 388 registry = Registry() 389 handle = registry.add_binding 390 391 suggestion_available = Condition( 392 lambda cli: 393 cli.current_buffer.suggestion is not None and 394 cli.current_buffer.document.is_cursor_at_the_end) 395 396 @handle(Keys.ControlF, filter=suggestion_available) 397 @handle(Keys.ControlE, filter=suggestion_available) 398 @handle(Keys.Right, filter=suggestion_available) 399 def _(event): 400 " Accept suggestion. " 401 b = event.current_buffer 402 suggestion = b.suggestion 403 404 if suggestion: 405 b.insert_text(suggestion.text) 406 407 return registry 408