Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/key_binding/bindings/named_commands.py: 44%
280 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1"""
2Key bindings which are also known by GNU Readline by the given names.
4See: http://www.delorie.com/gnu/docs/readline/rlman_13.html
5"""
6from __future__ import annotations
8from typing import Callable, TypeVar, Union, cast
10from prompt_toolkit.document import Document
11from prompt_toolkit.enums import EditingMode
12from prompt_toolkit.key_binding.key_bindings import Binding, key_binding
13from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent
14from prompt_toolkit.keys import Keys
15from prompt_toolkit.layout.controls import BufferControl
16from prompt_toolkit.search import SearchDirection
17from prompt_toolkit.selection import PasteMode
19from .completion import display_completions_like_readline, generate_completions
21__all__ = [
22 "get_by_name",
23]
26# Typing.
27_Handler = Callable[[KeyPressEvent], None]
28_HandlerOrBinding = Union[_Handler, Binding]
29_T = TypeVar("_T", bound=_HandlerOrBinding)
30E = KeyPressEvent
33# Registry that maps the Readline command names to their handlers.
34_readline_commands: dict[str, Binding] = {}
37def register(name: str) -> Callable[[_T], _T]:
38 """
39 Store handler in the `_readline_commands` dictionary.
40 """
42 def decorator(handler: _T) -> _T:
43 "`handler` is a callable or Binding."
44 if isinstance(handler, Binding):
45 _readline_commands[name] = handler
46 else:
47 _readline_commands[name] = key_binding()(cast(_Handler, handler))
49 return handler
51 return decorator
54def get_by_name(name: str) -> Binding:
55 """
56 Return the handler for the (Readline) command with the given name.
57 """
58 try:
59 return _readline_commands[name]
60 except KeyError as e:
61 raise KeyError("Unknown Readline command: %r" % name) from e
64#
65# Commands for moving
66# See: http://www.delorie.com/gnu/docs/readline/rlman_14.html
67#
70@register("beginning-of-buffer")
71def beginning_of_buffer(event: E) -> None:
72 """
73 Move to the start of the buffer.
74 """
75 buff = event.current_buffer
76 buff.cursor_position = 0
79@register("end-of-buffer")
80def end_of_buffer(event: E) -> None:
81 """
82 Move to the end of the buffer.
83 """
84 buff = event.current_buffer
85 buff.cursor_position = len(buff.text)
88@register("beginning-of-line")
89def beginning_of_line(event: E) -> None:
90 """
91 Move to the start of the current line.
92 """
93 buff = event.current_buffer
94 buff.cursor_position += buff.document.get_start_of_line_position(
95 after_whitespace=False
96 )
99@register("end-of-line")
100def end_of_line(event: E) -> None:
101 """
102 Move to the end of the line.
103 """
104 buff = event.current_buffer
105 buff.cursor_position += buff.document.get_end_of_line_position()
108@register("forward-char")
109def forward_char(event: E) -> None:
110 """
111 Move forward a character.
112 """
113 buff = event.current_buffer
114 buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg)
117@register("backward-char")
118def backward_char(event: E) -> None:
119 "Move back a character."
120 buff = event.current_buffer
121 buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg)
124@register("forward-word")
125def forward_word(event: E) -> None:
126 """
127 Move forward to the end of the next word. Words are composed of letters and
128 digits.
129 """
130 buff = event.current_buffer
131 pos = buff.document.find_next_word_ending(count=event.arg)
133 if pos:
134 buff.cursor_position += pos
137@register("backward-word")
138def backward_word(event: E) -> None:
139 """
140 Move back to the start of the current or previous word. Words are composed
141 of letters and digits.
142 """
143 buff = event.current_buffer
144 pos = buff.document.find_previous_word_beginning(count=event.arg)
146 if pos:
147 buff.cursor_position += pos
150@register("clear-screen")
151def clear_screen(event: E) -> None:
152 """
153 Clear the screen and redraw everything at the top of the screen.
154 """
155 event.app.renderer.clear()
158@register("redraw-current-line")
159def redraw_current_line(event: E) -> None:
160 """
161 Refresh the current line.
162 (Readline defines this command, but prompt-toolkit doesn't have it.)
163 """
164 pass
167#
168# Commands for manipulating the history.
169# See: http://www.delorie.com/gnu/docs/readline/rlman_15.html
170#
173@register("accept-line")
174def accept_line(event: E) -> None:
175 """
176 Accept the line regardless of where the cursor is.
177 """
178 event.current_buffer.validate_and_handle()
181@register("previous-history")
182def previous_history(event: E) -> None:
183 """
184 Move `back` through the history list, fetching the previous command.
185 """
186 event.current_buffer.history_backward(count=event.arg)
189@register("next-history")
190def next_history(event: E) -> None:
191 """
192 Move `forward` through the history list, fetching the next command.
193 """
194 event.current_buffer.history_forward(count=event.arg)
197@register("beginning-of-history")
198def beginning_of_history(event: E) -> None:
199 """
200 Move to the first line in the history.
201 """
202 event.current_buffer.go_to_history(0)
205@register("end-of-history")
206def end_of_history(event: E) -> None:
207 """
208 Move to the end of the input history, i.e., the line currently being entered.
209 """
210 event.current_buffer.history_forward(count=10**100)
211 buff = event.current_buffer
212 buff.go_to_history(len(buff._working_lines) - 1)
215@register("reverse-search-history")
216def reverse_search_history(event: E) -> None:
217 """
218 Search backward starting at the current line and moving `up` through
219 the history as necessary. This is an incremental search.
220 """
221 control = event.app.layout.current_control
223 if isinstance(control, BufferControl) and control.search_buffer_control:
224 event.app.current_search_state.direction = SearchDirection.BACKWARD
225 event.app.layout.current_control = control.search_buffer_control
228#
229# Commands for changing text
230#
233@register("end-of-file")
234def end_of_file(event: E) -> None:
235 """
236 Exit.
237 """
238 event.app.exit()
241@register("delete-char")
242def delete_char(event: E) -> None:
243 """
244 Delete character before the cursor.
245 """
246 deleted = event.current_buffer.delete(count=event.arg)
247 if not deleted:
248 event.app.output.bell()
251@register("backward-delete-char")
252def backward_delete_char(event: E) -> None:
253 """
254 Delete the character behind the cursor.
255 """
256 if event.arg < 0:
257 # When a negative argument has been given, this should delete in front
258 # of the cursor.
259 deleted = event.current_buffer.delete(count=-event.arg)
260 else:
261 deleted = event.current_buffer.delete_before_cursor(count=event.arg)
263 if not deleted:
264 event.app.output.bell()
267@register("self-insert")
268def self_insert(event: E) -> None:
269 """
270 Insert yourself.
271 """
272 event.current_buffer.insert_text(event.data * event.arg)
275@register("transpose-chars")
276def transpose_chars(event: E) -> None:
277 """
278 Emulate Emacs transpose-char behavior: at the beginning of the buffer,
279 do nothing. At the end of a line or buffer, swap the characters before
280 the cursor. Otherwise, move the cursor right, and then swap the
281 characters before the cursor.
282 """
283 b = event.current_buffer
284 p = b.cursor_position
285 if p == 0:
286 return
287 elif p == len(b.text) or b.text[p] == "\n":
288 b.swap_characters_before_cursor()
289 else:
290 b.cursor_position += b.document.get_cursor_right_position()
291 b.swap_characters_before_cursor()
294@register("uppercase-word")
295def uppercase_word(event: E) -> None:
296 """
297 Uppercase the current (or following) word.
298 """
299 buff = event.current_buffer
301 for i in range(event.arg):
302 pos = buff.document.find_next_word_ending()
303 words = buff.document.text_after_cursor[:pos]
304 buff.insert_text(words.upper(), overwrite=True)
307@register("downcase-word")
308def downcase_word(event: E) -> None:
309 """
310 Lowercase the current (or following) word.
311 """
312 buff = event.current_buffer
314 for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!!
315 pos = buff.document.find_next_word_ending()
316 words = buff.document.text_after_cursor[:pos]
317 buff.insert_text(words.lower(), overwrite=True)
320@register("capitalize-word")
321def capitalize_word(event: E) -> None:
322 """
323 Capitalize the current (or following) word.
324 """
325 buff = event.current_buffer
327 for i in range(event.arg):
328 pos = buff.document.find_next_word_ending()
329 words = buff.document.text_after_cursor[:pos]
330 buff.insert_text(words.title(), overwrite=True)
333@register("quoted-insert")
334def quoted_insert(event: E) -> None:
335 """
336 Add the next character typed to the line verbatim. This is how to insert
337 key sequences like C-q, for example.
338 """
339 event.app.quoted_insert = True
342#
343# Killing and yanking.
344#
347@register("kill-line")
348def kill_line(event: E) -> None:
349 """
350 Kill the text from the cursor to the end of the line.
352 If we are at the end of the line, this should remove the newline.
353 (That way, it is possible to delete multiple lines by executing this
354 command multiple times.)
355 """
356 buff = event.current_buffer
357 if event.arg < 0:
358 deleted = buff.delete_before_cursor(
359 count=-buff.document.get_start_of_line_position()
360 )
361 else:
362 if buff.document.current_char == "\n":
363 deleted = buff.delete(1)
364 else:
365 deleted = buff.delete(count=buff.document.get_end_of_line_position())
366 event.app.clipboard.set_text(deleted)
369@register("kill-word")
370def kill_word(event: E) -> None:
371 """
372 Kill from point to the end of the current word, or if between words, to the
373 end of the next word. Word boundaries are the same as forward-word.
374 """
375 buff = event.current_buffer
376 pos = buff.document.find_next_word_ending(count=event.arg)
378 if pos:
379 deleted = buff.delete(count=pos)
381 if event.is_repeat:
382 deleted = event.app.clipboard.get_data().text + deleted
384 event.app.clipboard.set_text(deleted)
387@register("unix-word-rubout")
388def unix_word_rubout(event: E, WORD: bool = True) -> None:
389 """
390 Kill the word behind point, using whitespace as a word boundary.
391 Usually bound to ControlW.
392 """
393 buff = event.current_buffer
394 pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD)
396 if pos is None:
397 # Nothing found? delete until the start of the document. (The
398 # input starts with whitespace and no words were found before the
399 # cursor.)
400 pos = -buff.cursor_position
402 if pos:
403 deleted = buff.delete_before_cursor(count=-pos)
405 # If the previous key press was also Control-W, concatenate deleted
406 # text.
407 if event.is_repeat:
408 deleted += event.app.clipboard.get_data().text
410 event.app.clipboard.set_text(deleted)
411 else:
412 # Nothing to delete. Bell.
413 event.app.output.bell()
416@register("backward-kill-word")
417def backward_kill_word(event: E) -> None:
418 """
419 Kills the word before point, using "not a letter nor a digit" as a word boundary.
420 Usually bound to M-Del or M-Backspace.
421 """
422 unix_word_rubout(event, WORD=False)
425@register("delete-horizontal-space")
426def delete_horizontal_space(event: E) -> None:
427 """
428 Delete all spaces and tabs around point.
429 """
430 buff = event.current_buffer
431 text_before_cursor = buff.document.text_before_cursor
432 text_after_cursor = buff.document.text_after_cursor
434 delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip("\t "))
435 delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip("\t "))
437 buff.delete_before_cursor(count=delete_before)
438 buff.delete(count=delete_after)
441@register("unix-line-discard")
442def unix_line_discard(event: E) -> None:
443 """
444 Kill backward from the cursor to the beginning of the current line.
445 """
446 buff = event.current_buffer
448 if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0:
449 buff.delete_before_cursor(count=1)
450 else:
451 deleted = buff.delete_before_cursor(
452 count=-buff.document.get_start_of_line_position()
453 )
454 event.app.clipboard.set_text(deleted)
457@register("yank")
458def yank(event: E) -> None:
459 """
460 Paste before cursor.
461 """
462 event.current_buffer.paste_clipboard_data(
463 event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS
464 )
467@register("yank-nth-arg")
468def yank_nth_arg(event: E) -> None:
469 """
470 Insert the first argument of the previous command. With an argument, insert
471 the nth word from the previous command (start counting at 0).
472 """
473 n = event.arg if event.arg_present else None
474 event.current_buffer.yank_nth_arg(n)
477@register("yank-last-arg")
478def yank_last_arg(event: E) -> None:
479 """
480 Like `yank_nth_arg`, but if no argument has been given, yank the last word
481 of each line.
482 """
483 n = event.arg if event.arg_present else None
484 event.current_buffer.yank_last_arg(n)
487@register("yank-pop")
488def yank_pop(event: E) -> None:
489 """
490 Rotate the kill ring, and yank the new top. Only works following yank or
491 yank-pop.
492 """
493 buff = event.current_buffer
494 doc_before_paste = buff.document_before_paste
495 clipboard = event.app.clipboard
497 if doc_before_paste is not None:
498 buff.document = doc_before_paste
499 clipboard.rotate()
500 buff.paste_clipboard_data(clipboard.get_data(), paste_mode=PasteMode.EMACS)
503#
504# Completion.
505#
508@register("complete")
509def complete(event: E) -> None:
510 """
511 Attempt to perform completion.
512 """
513 display_completions_like_readline(event)
516@register("menu-complete")
517def menu_complete(event: E) -> None:
518 """
519 Generate completions, or go to the next completion. (This is the default
520 way of completing input in prompt_toolkit.)
521 """
522 generate_completions(event)
525@register("menu-complete-backward")
526def menu_complete_backward(event: E) -> None:
527 """
528 Move backward through the list of possible completions.
529 """
530 event.current_buffer.complete_previous()
533#
534# Keyboard macros.
535#
538@register("start-kbd-macro")
539def start_kbd_macro(event: E) -> None:
540 """
541 Begin saving the characters typed into the current keyboard macro.
542 """
543 event.app.emacs_state.start_macro()
546@register("end-kbd-macro")
547def end_kbd_macro(event: E) -> None:
548 """
549 Stop saving the characters typed into the current keyboard macro and save
550 the definition.
551 """
552 event.app.emacs_state.end_macro()
555@register("call-last-kbd-macro")
556@key_binding(record_in_macro=False)
557def call_last_kbd_macro(event: E) -> None:
558 """
559 Re-execute the last keyboard macro defined, by making the characters in the
560 macro appear as if typed at the keyboard.
562 Notice that we pass `record_in_macro=False`. This ensures that the 'c-x e'
563 key sequence doesn't appear in the recording itself. This function inserts
564 the body of the called macro back into the KeyProcessor, so these keys will
565 be added later on to the macro of their handlers have `record_in_macro=True`.
566 """
567 # Insert the macro.
568 macro = event.app.emacs_state.macro
570 if macro:
571 event.app.key_processor.feed_multiple(macro, first=True)
574@register("print-last-kbd-macro")
575def print_last_kbd_macro(event: E) -> None:
576 """
577 Print the last keyboard macro.
578 """
580 # TODO: Make the format suitable for the inputrc file.
581 def print_macro() -> None:
582 macro = event.app.emacs_state.macro
583 if macro:
584 for k in macro:
585 print(k)
587 from prompt_toolkit.application.run_in_terminal import run_in_terminal
589 run_in_terminal(print_macro)
592#
593# Miscellaneous Commands.
594#
597@register("undo")
598def undo(event: E) -> None:
599 """
600 Incremental undo.
601 """
602 event.current_buffer.undo()
605@register("insert-comment")
606def insert_comment(event: E) -> None:
607 """
608 Without numeric argument, comment all lines.
609 With numeric argument, uncomment all lines.
610 In any case accept the input.
611 """
612 buff = event.current_buffer
614 # Transform all lines.
615 if event.arg != 1:
617 def change(line: str) -> str:
618 return line[1:] if line.startswith("#") else line
620 else:
622 def change(line: str) -> str:
623 return "#" + line
625 buff.document = Document(
626 text="\n".join(map(change, buff.text.splitlines())), cursor_position=0
627 )
629 # Accept input.
630 buff.validate_and_handle()
633@register("vi-editing-mode")
634def vi_editing_mode(event: E) -> None:
635 """
636 Switch to Vi editing mode.
637 """
638 event.app.editing_mode = EditingMode.VI
641@register("emacs-editing-mode")
642def emacs_editing_mode(event: E) -> None:
643 """
644 Switch to Emacs editing mode.
645 """
646 event.app.editing_mode = EditingMode.EMACS
649@register("prefix-meta")
650def prefix_meta(event: E) -> None:
651 """
652 Metafy the next character typed. This is for keyboards without a meta key.
654 Sometimes people also want to bind other keys to Meta, e.g. 'jj'::
656 key_bindings.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta)
657 """
658 # ('first' should be true, because we want to insert it at the current
659 # position in the queue.)
660 event.app.key_processor.feed(KeyPress(Keys.Escape), first=True)
663@register("operate-and-get-next")
664def operate_and_get_next(event: E) -> None:
665 """
666 Accept the current line for execution and fetch the next line relative to
667 the current line from the history for editing.
668 """
669 buff = event.current_buffer
670 new_index = buff.working_index + 1
672 # Accept the current input. (This will also redraw the interface in the
673 # 'done' state.)
674 buff.validate_and_handle()
676 # Set the new index at the start of the next run.
677 def set_working_index() -> None:
678 if new_index < len(buff._working_lines):
679 buff.working_index = new_index
681 event.app.pre_run_callables.append(set_working_index)
684@register("edit-and-execute-command")
685def edit_and_execute(event: E) -> None:
686 """
687 Invoke an editor on the current command line, and accept the result.
688 """
689 buff = event.current_buffer
690 buff.open_in_editor(validate_and_handle=True)