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

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

282 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 collections.abc import Callable 

10from typing import TypeVar, cast 

11 

12from prompt_toolkit.document import Document 

13from prompt_toolkit.enums import EditingMode 

14from prompt_toolkit.key_binding.key_bindings import Binding, key_binding 

15from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent 

16from prompt_toolkit.keys import Keys 

17from prompt_toolkit.layout.controls import BufferControl 

18from prompt_toolkit.search import SearchDirection 

19from prompt_toolkit.selection import PasteMode 

20 

21from .completion import display_completions_like_readline, generate_completions 

22 

23__all__ = [ 

24 "get_by_name", 

25] 

26 

27 

28# Typing. 

29_Handler = Callable[[KeyPressEvent], None] 

30_HandlerOrBinding = _Handler | Binding 

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

32E = KeyPressEvent 

33 

34 

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

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

37 

38 

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

40 """ 

41 Store handler in the `_readline_commands` dictionary. 

42 """ 

43 

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

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

46 if isinstance(handler, Binding): 

47 _readline_commands[name] = handler 

48 else: 

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

50 

51 return handler 

52 

53 return decorator 

54 

55 

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

57 """ 

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

59 """ 

60 try: 

61 return _readline_commands[name] 

62 except KeyError as e: 

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

64 

65 

66# 

67# Commands for moving 

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

69# 

70 

71 

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

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

74 """ 

75 Move to the start of the buffer. 

76 """ 

77 buff = event.current_buffer 

78 buff.cursor_position = 0 

79 

80 

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

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

83 """ 

84 Move to the end of the buffer. 

85 """ 

86 buff = event.current_buffer 

87 buff.cursor_position = len(buff.text) 

88 

89 

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

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

92 """ 

93 Move to the start of the current line. 

94 """ 

95 buff = event.current_buffer 

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

97 after_whitespace=False 

98 ) 

99 

100 

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

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

103 """ 

104 Move to the end of the line. 

105 """ 

106 buff = event.current_buffer 

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

108 

109 

110@register("forward-char") 

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

112 """ 

113 Move forward a character. 

114 """ 

115 buff = event.current_buffer 

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

117 

118 

119@register("backward-char") 

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

121 "Move back a character." 

122 buff = event.current_buffer 

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

124 

125 

126@register("forward-word") 

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

128 """ 

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

130 digits. 

131 """ 

132 buff = event.current_buffer 

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

134 

135 if pos: 

136 buff.cursor_position += pos 

137 

138 

139@register("backward-word") 

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

141 """ 

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

143 of letters and digits. 

144 """ 

145 buff = event.current_buffer 

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

147 

148 if pos: 

149 buff.cursor_position += pos 

150 

151 

152@register("clear-screen") 

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

154 """ 

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

156 """ 

157 event.app.renderer.clear() 

158 

159 

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

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

162 """ 

163 Refresh the current line. 

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

165 """ 

166 pass 

167 

168 

169# 

170# Commands for manipulating the history. 

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

172# 

173 

174 

175@register("accept-line") 

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

177 """ 

178 Accept the line regardless of where the cursor is. 

179 """ 

180 event.current_buffer.validate_and_handle() 

181 

182 

183@register("previous-history") 

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

185 """ 

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

187 """ 

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

189 

190 

191@register("next-history") 

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

193 """ 

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

195 """ 

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

197 

198 

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

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

201 """ 

202 Move to the first line in the history. 

203 """ 

204 event.current_buffer.go_to_history(0) 

205 

206 

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

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

209 """ 

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

211 """ 

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

213 buff = event.current_buffer 

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

215 

216 

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

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

219 """ 

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

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

222 """ 

223 control = event.app.layout.current_control 

224 

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

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

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

228 

229 

230# 

231# Commands for changing text 

232# 

233 

234 

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

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

237 """ 

238 Exit. 

239 """ 

240 event.app.exit() 

241 

242 

243@register("delete-char") 

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

245 """ 

246 Delete character before the cursor. 

247 """ 

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

249 if not deleted: 

250 event.app.output.bell() 

251 

252 

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

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

255 """ 

256 Delete the character behind the cursor. 

257 """ 

258 if event.arg < 0: 

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

260 # of the cursor. 

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

262 else: 

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

264 

265 if not deleted: 

266 event.app.output.bell() 

267 

268 

269@register("self-insert") 

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

271 """ 

272 Insert yourself. 

273 """ 

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

275 

276 

277@register("transpose-chars") 

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

279 """ 

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

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

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

283 characters before the cursor. 

284 """ 

285 b = event.current_buffer 

286 p = b.cursor_position 

287 if p == 0: 

288 return 

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

290 b.swap_characters_before_cursor() 

291 else: 

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

293 b.swap_characters_before_cursor() 

294 

295 

296@register("uppercase-word") 

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

298 """ 

299 Uppercase the current (or following) word. 

300 """ 

301 buff = event.current_buffer 

302 

303 for i in range(event.arg): 

304 pos = buff.document.find_next_word_ending() 

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

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

307 

308 

309@register("downcase-word") 

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

311 """ 

312 Lowercase the current (or following) word. 

313 """ 

314 buff = event.current_buffer 

315 

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

317 pos = buff.document.find_next_word_ending() 

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

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

320 

321 

322@register("capitalize-word") 

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

324 """ 

325 Capitalize the current (or following) word. 

326 """ 

327 buff = event.current_buffer 

328 

329 for i in range(event.arg): 

330 pos = buff.document.find_next_word_ending() 

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

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

333 

334 

335@register("quoted-insert") 

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

337 """ 

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

339 key sequences like C-q, for example. 

340 """ 

341 event.app.quoted_insert = True 

342 

343 

344# 

345# Killing and yanking. 

346# 

347 

348 

349@register("kill-line") 

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

351 """ 

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

353 

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

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

356 command multiple times.) 

357 """ 

358 buff = event.current_buffer 

359 if event.arg < 0: 

360 deleted = buff.delete_before_cursor( 

361 count=-buff.document.get_start_of_line_position() 

362 ) 

363 else: 

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

365 deleted = buff.delete(1) 

366 else: 

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

368 event.app.clipboard.set_text(deleted) 

369 

370 

371@register("kill-word") 

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

373 """ 

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

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

376 """ 

377 buff = event.current_buffer 

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

379 

380 if pos: 

381 deleted = buff.delete(count=pos) 

382 

383 if event.is_repeat: 

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

385 

386 event.app.clipboard.set_text(deleted) 

387 

388 

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

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

391 """ 

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

393 Usually bound to ControlW. 

394 """ 

395 buff = event.current_buffer 

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

397 

398 if pos is None: 

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

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

401 # cursor.) 

402 pos = -buff.cursor_position 

403 

404 if pos: 

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

406 

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

408 # text. 

409 if event.is_repeat: 

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

411 

412 event.app.clipboard.set_text(deleted) 

413 else: 

414 # Nothing to delete. Bell. 

415 event.app.output.bell() 

416 

417 

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

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

420 """ 

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

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

423 """ 

424 unix_word_rubout(event, WORD=False) 

425 

426 

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

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

429 """ 

430 Delete all spaces and tabs around point. 

431 """ 

432 buff = event.current_buffer 

433 text_before_cursor = buff.document.text_before_cursor 

434 text_after_cursor = buff.document.text_after_cursor 

435 

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

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

438 

439 buff.delete_before_cursor(count=delete_before) 

440 buff.delete(count=delete_after) 

441 

442 

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

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

445 """ 

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

447 """ 

448 buff = event.current_buffer 

449 

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

451 buff.delete_before_cursor(count=1) 

452 else: 

453 deleted = buff.delete_before_cursor( 

454 count=-buff.document.get_start_of_line_position() 

455 ) 

456 event.app.clipboard.set_text(deleted) 

457 

458 

459@register("yank") 

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

461 """ 

462 Paste before cursor. 

463 """ 

464 event.current_buffer.paste_clipboard_data( 

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

466 ) 

467 

468 

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

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

471 """ 

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

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

474 """ 

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

476 event.current_buffer.yank_nth_arg(n) 

477 

478 

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

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

481 """ 

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

483 of each line. 

484 """ 

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

486 event.current_buffer.yank_last_arg(n) 

487 

488 

489@register("yank-pop") 

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

491 """ 

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

493 yank-pop. 

494 """ 

495 buff = event.current_buffer 

496 doc_before_paste = buff.document_before_paste 

497 clipboard = event.app.clipboard 

498 

499 if doc_before_paste is not None: 

500 buff.document = doc_before_paste 

501 clipboard.rotate() 

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

503 

504 

505# 

506# Completion. 

507# 

508 

509 

510@register("complete") 

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

512 """ 

513 Attempt to perform completion. 

514 """ 

515 display_completions_like_readline(event) 

516 

517 

518@register("menu-complete") 

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

520 """ 

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

522 way of completing input in prompt_toolkit.) 

523 """ 

524 generate_completions(event) 

525 

526 

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

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

529 """ 

530 Move backward through the list of possible completions. 

531 """ 

532 event.current_buffer.complete_previous() 

533 

534 

535# 

536# Keyboard macros. 

537# 

538 

539 

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

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

542 """ 

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

544 """ 

545 event.app.emacs_state.start_macro() 

546 

547 

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

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

550 """ 

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

552 the definition. 

553 """ 

554 event.app.emacs_state.end_macro() 

555 

556 

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

558@key_binding(record_in_macro=False) 

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

560 """ 

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

562 macro appear as if typed at the keyboard. 

563 

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

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

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

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

568 """ 

569 # Insert the macro. 

570 macro = event.app.emacs_state.macro 

571 

572 if macro: 

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

574 

575 

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

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

578 """ 

579 Print the last keyboard macro. 

580 """ 

581 

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

583 def print_macro() -> None: 

584 macro = event.app.emacs_state.macro 

585 if macro: 

586 for k in macro: 

587 print(k) 

588 

589 from prompt_toolkit.application.run_in_terminal import run_in_terminal 

590 

591 run_in_terminal(print_macro) 

592 

593 

594# 

595# Miscellaneous Commands. 

596# 

597 

598 

599@register("undo") 

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

601 """ 

602 Incremental undo. 

603 """ 

604 event.current_buffer.undo() 

605 

606 

607@register("insert-comment") 

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

609 """ 

610 Without numeric argument, comment all lines. 

611 With numeric argument, uncomment all lines. 

612 In any case accept the input. 

613 """ 

614 buff = event.current_buffer 

615 

616 # Transform all lines. 

617 if event.arg != 1: 

618 

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

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

621 

622 else: 

623 

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

625 return "#" + line 

626 

627 buff.document = Document( 

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

629 ) 

630 

631 # Accept input. 

632 buff.validate_and_handle() 

633 

634 

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

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

637 """ 

638 Switch to Vi editing mode. 

639 """ 

640 event.app.editing_mode = EditingMode.VI 

641 

642 

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

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

645 """ 

646 Switch to Emacs editing mode. 

647 """ 

648 event.app.editing_mode = EditingMode.EMACS 

649 

650 

651@register("prefix-meta") 

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

653 """ 

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

655 

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

657 

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

659 """ 

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

661 # position in the queue.) 

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

663 

664 

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

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

667 """ 

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

669 the current line from the history for editing. 

670 """ 

671 buff = event.current_buffer 

672 new_index = buff.working_index + 1 

673 

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

675 # 'done' state.) 

676 buff.validate_and_handle() 

677 

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

679 def set_working_index() -> None: 

680 if new_index < len(buff._working_lines): 

681 buff.working_index = new_index 

682 

683 event.app.pre_run_callables.append(set_working_index) 

684 

685 

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

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

688 """ 

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

690 """ 

691 buff = event.current_buffer 

692 buff.open_in_editor(validate_and_handle=True)