Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/prompt_toolkit/key_binding/bindings/named_commands.py: 44%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

281 statements  

1""" 

2Key bindings which are also known by GNU Readline by the given names. 

3 

4See: http://www.delorie.com/gnu/docs/readline/rlman_13.html 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Callable, TypeVar, Union, cast 

10 

11from prompt_toolkit.document import Document 

12from prompt_toolkit.enums import EditingMode 

13from prompt_toolkit.key_binding.key_bindings import Binding, key_binding 

14from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent 

15from prompt_toolkit.keys import Keys 

16from prompt_toolkit.layout.controls import BufferControl 

17from prompt_toolkit.search import SearchDirection 

18from prompt_toolkit.selection import PasteMode 

19 

20from .completion import display_completions_like_readline, generate_completions 

21 

22__all__ = [ 

23 "get_by_name", 

24] 

25 

26 

27# Typing. 

28_Handler = Callable[[KeyPressEvent], None] 

29_HandlerOrBinding = Union[_Handler, Binding] 

30_T = TypeVar("_T", bound=_HandlerOrBinding) 

31E = KeyPressEvent 

32 

33 

34# Registry that maps the Readline command names to their handlers. 

35_readline_commands: dict[str, Binding] = {} 

36 

37 

38def register(name: str) -> Callable[[_T], _T]: 

39 """ 

40 Store handler in the `_readline_commands` dictionary. 

41 """ 

42 

43 def decorator(handler: _T) -> _T: 

44 "`handler` is a callable or Binding." 

45 if isinstance(handler, Binding): 

46 _readline_commands[name] = handler 

47 else: 

48 _readline_commands[name] = key_binding()(cast(_Handler, handler)) 

49 

50 return handler 

51 

52 return decorator 

53 

54 

55def get_by_name(name: str) -> Binding: 

56 """ 

57 Return the handler for the (Readline) command with the given name. 

58 """ 

59 try: 

60 return _readline_commands[name] 

61 except KeyError as e: 

62 raise KeyError(f"Unknown Readline command: {name!r}") from e 

63 

64 

65# 

66# Commands for moving 

67# See: http://www.delorie.com/gnu/docs/readline/rlman_14.html 

68# 

69 

70 

71@register("beginning-of-buffer") 

72def beginning_of_buffer(event: E) -> None: 

73 """ 

74 Move to the start of the buffer. 

75 """ 

76 buff = event.current_buffer 

77 buff.cursor_position = 0 

78 

79 

80@register("end-of-buffer") 

81def end_of_buffer(event: E) -> None: 

82 """ 

83 Move to the end of the buffer. 

84 """ 

85 buff = event.current_buffer 

86 buff.cursor_position = len(buff.text) 

87 

88 

89@register("beginning-of-line") 

90def beginning_of_line(event: E) -> None: 

91 """ 

92 Move to the start of the current line. 

93 """ 

94 buff = event.current_buffer 

95 buff.cursor_position += buff.document.get_start_of_line_position( 

96 after_whitespace=False 

97 ) 

98 

99 

100@register("end-of-line") 

101def end_of_line(event: E) -> None: 

102 """ 

103 Move to the end of the line. 

104 """ 

105 buff = event.current_buffer 

106 buff.cursor_position += buff.document.get_end_of_line_position() 

107 

108 

109@register("forward-char") 

110def forward_char(event: E) -> None: 

111 """ 

112 Move forward a character. 

113 """ 

114 buff = event.current_buffer 

115 buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg) 

116 

117 

118@register("backward-char") 

119def backward_char(event: E) -> None: 

120 "Move back a character." 

121 buff = event.current_buffer 

122 buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg) 

123 

124 

125@register("forward-word") 

126def forward_word(event: E) -> None: 

127 """ 

128 Move forward to the end of the next word. Words are composed of letters and 

129 digits. 

130 """ 

131 buff = event.current_buffer 

132 pos = buff.document.find_next_word_ending(count=event.arg) 

133 

134 if pos: 

135 buff.cursor_position += pos 

136 

137 

138@register("backward-word") 

139def backward_word(event: E) -> None: 

140 """ 

141 Move back to the start of the current or previous word. Words are composed 

142 of letters and digits. 

143 """ 

144 buff = event.current_buffer 

145 pos = buff.document.find_previous_word_beginning(count=event.arg) 

146 

147 if pos: 

148 buff.cursor_position += pos 

149 

150 

151@register("clear-screen") 

152def clear_screen(event: E) -> None: 

153 """ 

154 Clear the screen and redraw everything at the top of the screen. 

155 """ 

156 event.app.renderer.clear() 

157 

158 

159@register("redraw-current-line") 

160def redraw_current_line(event: E) -> None: 

161 """ 

162 Refresh the current line. 

163 (Readline defines this command, but prompt-toolkit doesn't have it.) 

164 """ 

165 pass 

166 

167 

168# 

169# Commands for manipulating the history. 

170# See: http://www.delorie.com/gnu/docs/readline/rlman_15.html 

171# 

172 

173 

174@register("accept-line") 

175def accept_line(event: E) -> None: 

176 """ 

177 Accept the line regardless of where the cursor is. 

178 """ 

179 event.current_buffer.validate_and_handle() 

180 

181 

182@register("previous-history") 

183def previous_history(event: E) -> None: 

184 """ 

185 Move `back` through the history list, fetching the previous command. 

186 """ 

187 event.current_buffer.history_backward(count=event.arg) 

188 

189 

190@register("next-history") 

191def next_history(event: E) -> None: 

192 """ 

193 Move `forward` through the history list, fetching the next command. 

194 """ 

195 event.current_buffer.history_forward(count=event.arg) 

196 

197 

198@register("beginning-of-history") 

199def beginning_of_history(event: E) -> None: 

200 """ 

201 Move to the first line in the history. 

202 """ 

203 event.current_buffer.go_to_history(0) 

204 

205 

206@register("end-of-history") 

207def end_of_history(event: E) -> None: 

208 """ 

209 Move to the end of the input history, i.e., the line currently being entered. 

210 """ 

211 event.current_buffer.history_forward(count=10**100) 

212 buff = event.current_buffer 

213 buff.go_to_history(len(buff._working_lines) - 1) 

214 

215 

216@register("reverse-search-history") 

217def reverse_search_history(event: E) -> None: 

218 """ 

219 Search backward starting at the current line and moving `up` through 

220 the history as necessary. This is an incremental search. 

221 """ 

222 control = event.app.layout.current_control 

223 

224 if isinstance(control, BufferControl) and control.search_buffer_control: 

225 event.app.current_search_state.direction = SearchDirection.BACKWARD 

226 event.app.layout.current_control = control.search_buffer_control 

227 

228 

229# 

230# Commands for changing text 

231# 

232 

233 

234@register("end-of-file") 

235def end_of_file(event: E) -> None: 

236 """ 

237 Exit. 

238 """ 

239 event.app.exit() 

240 

241 

242@register("delete-char") 

243def delete_char(event: E) -> None: 

244 """ 

245 Delete character before the cursor. 

246 """ 

247 deleted = event.current_buffer.delete(count=event.arg) 

248 if not deleted: 

249 event.app.output.bell() 

250 

251 

252@register("backward-delete-char") 

253def backward_delete_char(event: E) -> None: 

254 """ 

255 Delete the character behind the cursor. 

256 """ 

257 if event.arg < 0: 

258 # When a negative argument has been given, this should delete in front 

259 # of the cursor. 

260 deleted = event.current_buffer.delete(count=-event.arg) 

261 else: 

262 deleted = event.current_buffer.delete_before_cursor(count=event.arg) 

263 

264 if not deleted: 

265 event.app.output.bell() 

266 

267 

268@register("self-insert") 

269def self_insert(event: E) -> None: 

270 """ 

271 Insert yourself. 

272 """ 

273 event.current_buffer.insert_text(event.data * event.arg) 

274 

275 

276@register("transpose-chars") 

277def transpose_chars(event: E) -> None: 

278 """ 

279 Emulate Emacs transpose-char behavior: at the beginning of the buffer, 

280 do nothing. At the end of a line or buffer, swap the characters before 

281 the cursor. Otherwise, move the cursor right, and then swap the 

282 characters before the cursor. 

283 """ 

284 b = event.current_buffer 

285 p = b.cursor_position 

286 if p == 0: 

287 return 

288 elif p == len(b.text) or b.text[p] == "\n": 

289 b.swap_characters_before_cursor() 

290 else: 

291 b.cursor_position += b.document.get_cursor_right_position() 

292 b.swap_characters_before_cursor() 

293 

294 

295@register("uppercase-word") 

296def uppercase_word(event: E) -> None: 

297 """ 

298 Uppercase the current (or following) word. 

299 """ 

300 buff = event.current_buffer 

301 

302 for i in range(event.arg): 

303 pos = buff.document.find_next_word_ending() 

304 words = buff.document.text_after_cursor[:pos] 

305 buff.insert_text(words.upper(), overwrite=True) 

306 

307 

308@register("downcase-word") 

309def downcase_word(event: E) -> None: 

310 """ 

311 Lowercase the current (or following) word. 

312 """ 

313 buff = event.current_buffer 

314 

315 for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!! 

316 pos = buff.document.find_next_word_ending() 

317 words = buff.document.text_after_cursor[:pos] 

318 buff.insert_text(words.lower(), overwrite=True) 

319 

320 

321@register("capitalize-word") 

322def capitalize_word(event: E) -> None: 

323 """ 

324 Capitalize the current (or following) word. 

325 """ 

326 buff = event.current_buffer 

327 

328 for i in range(event.arg): 

329 pos = buff.document.find_next_word_ending() 

330 words = buff.document.text_after_cursor[:pos] 

331 buff.insert_text(words.title(), overwrite=True) 

332 

333 

334@register("quoted-insert") 

335def quoted_insert(event: E) -> None: 

336 """ 

337 Add the next character typed to the line verbatim. This is how to insert 

338 key sequences like C-q, for example. 

339 """ 

340 event.app.quoted_insert = True 

341 

342 

343# 

344# Killing and yanking. 

345# 

346 

347 

348@register("kill-line") 

349def kill_line(event: E) -> None: 

350 """ 

351 Kill the text from the cursor to the end of the line. 

352 

353 If we are at the end of the line, this should remove the newline. 

354 (That way, it is possible to delete multiple lines by executing this 

355 command multiple times.) 

356 """ 

357 buff = event.current_buffer 

358 if event.arg < 0: 

359 deleted = buff.delete_before_cursor( 

360 count=-buff.document.get_start_of_line_position() 

361 ) 

362 else: 

363 if buff.document.current_char == "\n": 

364 deleted = buff.delete(1) 

365 else: 

366 deleted = buff.delete(count=buff.document.get_end_of_line_position()) 

367 event.app.clipboard.set_text(deleted) 

368 

369 

370@register("kill-word") 

371def kill_word(event: E) -> None: 

372 """ 

373 Kill from point to the end of the current word, or if between words, to the 

374 end of the next word. Word boundaries are the same as forward-word. 

375 """ 

376 buff = event.current_buffer 

377 pos = buff.document.find_next_word_ending(count=event.arg) 

378 

379 if pos: 

380 deleted = buff.delete(count=pos) 

381 

382 if event.is_repeat: 

383 deleted = event.app.clipboard.get_data().text + deleted 

384 

385 event.app.clipboard.set_text(deleted) 

386 

387 

388@register("unix-word-rubout") 

389def unix_word_rubout(event: E, WORD: bool = True) -> None: 

390 """ 

391 Kill the word behind point, using whitespace as a word boundary. 

392 Usually bound to ControlW. 

393 """ 

394 buff = event.current_buffer 

395 pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD) 

396 

397 if pos is None: 

398 # Nothing found? delete until the start of the document. (The 

399 # input starts with whitespace and no words were found before the 

400 # cursor.) 

401 pos = -buff.cursor_position 

402 

403 if pos: 

404 deleted = buff.delete_before_cursor(count=-pos) 

405 

406 # If the previous key press was also Control-W, concatenate deleted 

407 # text. 

408 if event.is_repeat: 

409 deleted += event.app.clipboard.get_data().text 

410 

411 event.app.clipboard.set_text(deleted) 

412 else: 

413 # Nothing to delete. Bell. 

414 event.app.output.bell() 

415 

416 

417@register("backward-kill-word") 

418def backward_kill_word(event: E) -> None: 

419 """ 

420 Kills the word before point, using "not a letter nor a digit" as a word boundary. 

421 Usually bound to M-Del or M-Backspace. 

422 """ 

423 unix_word_rubout(event, WORD=False) 

424 

425 

426@register("delete-horizontal-space") 

427def delete_horizontal_space(event: E) -> None: 

428 """ 

429 Delete all spaces and tabs around point. 

430 """ 

431 buff = event.current_buffer 

432 text_before_cursor = buff.document.text_before_cursor 

433 text_after_cursor = buff.document.text_after_cursor 

434 

435 delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip("\t ")) 

436 delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip("\t ")) 

437 

438 buff.delete_before_cursor(count=delete_before) 

439 buff.delete(count=delete_after) 

440 

441 

442@register("unix-line-discard") 

443def unix_line_discard(event: E) -> None: 

444 """ 

445 Kill backward from the cursor to the beginning of the current line. 

446 """ 

447 buff = event.current_buffer 

448 

449 if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0: 

450 buff.delete_before_cursor(count=1) 

451 else: 

452 deleted = buff.delete_before_cursor( 

453 count=-buff.document.get_start_of_line_position() 

454 ) 

455 event.app.clipboard.set_text(deleted) 

456 

457 

458@register("yank") 

459def yank(event: E) -> None: 

460 """ 

461 Paste before cursor. 

462 """ 

463 event.current_buffer.paste_clipboard_data( 

464 event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS 

465 ) 

466 

467 

468@register("yank-nth-arg") 

469def yank_nth_arg(event: E) -> None: 

470 """ 

471 Insert the first argument of the previous command. With an argument, insert 

472 the nth word from the previous command (start counting at 0). 

473 """ 

474 n = event.arg if event.arg_present else None 

475 event.current_buffer.yank_nth_arg(n) 

476 

477 

478@register("yank-last-arg") 

479def yank_last_arg(event: E) -> None: 

480 """ 

481 Like `yank_nth_arg`, but if no argument has been given, yank the last word 

482 of each line. 

483 """ 

484 n = event.arg if event.arg_present else None 

485 event.current_buffer.yank_last_arg(n) 

486 

487 

488@register("yank-pop") 

489def yank_pop(event: E) -> None: 

490 """ 

491 Rotate the kill ring, and yank the new top. Only works following yank or 

492 yank-pop. 

493 """ 

494 buff = event.current_buffer 

495 doc_before_paste = buff.document_before_paste 

496 clipboard = event.app.clipboard 

497 

498 if doc_before_paste is not None: 

499 buff.document = doc_before_paste 

500 clipboard.rotate() 

501 buff.paste_clipboard_data(clipboard.get_data(), paste_mode=PasteMode.EMACS) 

502 

503 

504# 

505# Completion. 

506# 

507 

508 

509@register("complete") 

510def complete(event: E) -> None: 

511 """ 

512 Attempt to perform completion. 

513 """ 

514 display_completions_like_readline(event) 

515 

516 

517@register("menu-complete") 

518def menu_complete(event: E) -> None: 

519 """ 

520 Generate completions, or go to the next completion. (This is the default 

521 way of completing input in prompt_toolkit.) 

522 """ 

523 generate_completions(event) 

524 

525 

526@register("menu-complete-backward") 

527def menu_complete_backward(event: E) -> None: 

528 """ 

529 Move backward through the list of possible completions. 

530 """ 

531 event.current_buffer.complete_previous() 

532 

533 

534# 

535# Keyboard macros. 

536# 

537 

538 

539@register("start-kbd-macro") 

540def start_kbd_macro(event: E) -> None: 

541 """ 

542 Begin saving the characters typed into the current keyboard macro. 

543 """ 

544 event.app.emacs_state.start_macro() 

545 

546 

547@register("end-kbd-macro") 

548def end_kbd_macro(event: E) -> None: 

549 """ 

550 Stop saving the characters typed into the current keyboard macro and save 

551 the definition. 

552 """ 

553 event.app.emacs_state.end_macro() 

554 

555 

556@register("call-last-kbd-macro") 

557@key_binding(record_in_macro=False) 

558def call_last_kbd_macro(event: E) -> None: 

559 """ 

560 Re-execute the last keyboard macro defined, by making the characters in the 

561 macro appear as if typed at the keyboard. 

562 

563 Notice that we pass `record_in_macro=False`. This ensures that the 'c-x e' 

564 key sequence doesn't appear in the recording itself. This function inserts 

565 the body of the called macro back into the KeyProcessor, so these keys will 

566 be added later on to the macro of their handlers have `record_in_macro=True`. 

567 """ 

568 # Insert the macro. 

569 macro = event.app.emacs_state.macro 

570 

571 if macro: 

572 event.app.key_processor.feed_multiple(macro, first=True) 

573 

574 

575@register("print-last-kbd-macro") 

576def print_last_kbd_macro(event: E) -> None: 

577 """ 

578 Print the last keyboard macro. 

579 """ 

580 

581 # TODO: Make the format suitable for the inputrc file. 

582 def print_macro() -> None: 

583 macro = event.app.emacs_state.macro 

584 if macro: 

585 for k in macro: 

586 print(k) 

587 

588 from prompt_toolkit.application.run_in_terminal import run_in_terminal 

589 

590 run_in_terminal(print_macro) 

591 

592 

593# 

594# Miscellaneous Commands. 

595# 

596 

597 

598@register("undo") 

599def undo(event: E) -> None: 

600 """ 

601 Incremental undo. 

602 """ 

603 event.current_buffer.undo() 

604 

605 

606@register("insert-comment") 

607def insert_comment(event: E) -> None: 

608 """ 

609 Without numeric argument, comment all lines. 

610 With numeric argument, uncomment all lines. 

611 In any case accept the input. 

612 """ 

613 buff = event.current_buffer 

614 

615 # Transform all lines. 

616 if event.arg != 1: 

617 

618 def change(line: str) -> str: 

619 return line[1:] if line.startswith("#") else line 

620 

621 else: 

622 

623 def change(line: str) -> str: 

624 return "#" + line 

625 

626 buff.document = Document( 

627 text="\n".join(map(change, buff.text.splitlines())), cursor_position=0 

628 ) 

629 

630 # Accept input. 

631 buff.validate_and_handle() 

632 

633 

634@register("vi-editing-mode") 

635def vi_editing_mode(event: E) -> None: 

636 """ 

637 Switch to Vi editing mode. 

638 """ 

639 event.app.editing_mode = EditingMode.VI 

640 

641 

642@register("emacs-editing-mode") 

643def emacs_editing_mode(event: E) -> None: 

644 """ 

645 Switch to Emacs editing mode. 

646 """ 

647 event.app.editing_mode = EditingMode.EMACS 

648 

649 

650@register("prefix-meta") 

651def prefix_meta(event: E) -> None: 

652 """ 

653 Metafy the next character typed. This is for keyboards without a meta key. 

654 

655 Sometimes people also want to bind other keys to Meta, e.g. 'jj':: 

656 

657 key_bindings.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta) 

658 """ 

659 # ('first' should be true, because we want to insert it at the current 

660 # position in the queue.) 

661 event.app.key_processor.feed(KeyPress(Keys.Escape), first=True) 

662 

663 

664@register("operate-and-get-next") 

665def operate_and_get_next(event: E) -> None: 

666 """ 

667 Accept the current line for execution and fetch the next line relative to 

668 the current line from the history for editing. 

669 """ 

670 buff = event.current_buffer 

671 new_index = buff.working_index + 1 

672 

673 # Accept the current input. (This will also redraw the interface in the 

674 # 'done' state.) 

675 buff.validate_and_handle() 

676 

677 # Set the new index at the start of the next run. 

678 def set_working_index() -> None: 

679 if new_index < len(buff._working_lines): 

680 buff.working_index = new_index 

681 

682 event.app.pre_run_callables.append(set_working_index) 

683 

684 

685@register("edit-and-execute-command") 

686def edit_and_execute(event: E) -> None: 

687 """ 

688 Invoke an editor on the current command line, and accept the result. 

689 """ 

690 buff = event.current_buffer 

691 buff.open_in_editor(validate_and_handle=True)