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

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""" 

6from __future__ import annotations 

7 

8from typing import Callable, TypeVar, Union, cast 

9 

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 

18 

19from .completion import display_completions_like_readline, generate_completions 

20 

21__all__ = [ 

22 "get_by_name", 

23] 

24 

25 

26# Typing. 

27_Handler = Callable[[KeyPressEvent], None] 

28_HandlerOrBinding = Union[_Handler, Binding] 

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

30E = KeyPressEvent 

31 

32 

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

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

35 

36 

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

38 """ 

39 Store handler in the `_readline_commands` dictionary. 

40 """ 

41 

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)) 

48 

49 return handler 

50 

51 return decorator 

52 

53 

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 

62 

63 

64# 

65# Commands for moving 

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

67# 

68 

69 

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 

77 

78 

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) 

86 

87 

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 ) 

97 

98 

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() 

106 

107 

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) 

115 

116 

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) 

122 

123 

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) 

132 

133 if pos: 

134 buff.cursor_position += pos 

135 

136 

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) 

145 

146 if pos: 

147 buff.cursor_position += pos 

148 

149 

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() 

156 

157 

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 

165 

166 

167# 

168# Commands for manipulating the history. 

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

170# 

171 

172 

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() 

179 

180 

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) 

187 

188 

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) 

195 

196 

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) 

203 

204 

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) 

213 

214 

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 

222 

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 

226 

227 

228# 

229# Commands for changing text 

230# 

231 

232 

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

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

235 """ 

236 Exit. 

237 """ 

238 event.app.exit() 

239 

240 

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() 

249 

250 

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) 

262 

263 if not deleted: 

264 event.app.output.bell() 

265 

266 

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) 

273 

274 

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() 

292 

293 

294@register("uppercase-word") 

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

296 """ 

297 Uppercase the current (or following) word. 

298 """ 

299 buff = event.current_buffer 

300 

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) 

305 

306 

307@register("downcase-word") 

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

309 """ 

310 Lowercase the current (or following) word. 

311 """ 

312 buff = event.current_buffer 

313 

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) 

318 

319 

320@register("capitalize-word") 

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

322 """ 

323 Capitalize the current (or following) word. 

324 """ 

325 buff = event.current_buffer 

326 

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) 

331 

332 

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 

340 

341 

342# 

343# Killing and yanking. 

344# 

345 

346 

347@register("kill-line") 

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

349 """ 

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

351 

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) 

367 

368 

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) 

377 

378 if pos: 

379 deleted = buff.delete(count=pos) 

380 

381 if event.is_repeat: 

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

383 

384 event.app.clipboard.set_text(deleted) 

385 

386 

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) 

395 

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 

401 

402 if pos: 

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

404 

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 

409 

410 event.app.clipboard.set_text(deleted) 

411 else: 

412 # Nothing to delete. Bell. 

413 event.app.output.bell() 

414 

415 

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) 

423 

424 

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 

433 

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 ")) 

436 

437 buff.delete_before_cursor(count=delete_before) 

438 buff.delete(count=delete_after) 

439 

440 

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 

447 

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) 

455 

456 

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 ) 

465 

466 

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) 

475 

476 

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) 

485 

486 

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 

496 

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) 

501 

502 

503# 

504# Completion. 

505# 

506 

507 

508@register("complete") 

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

510 """ 

511 Attempt to perform completion. 

512 """ 

513 display_completions_like_readline(event) 

514 

515 

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) 

523 

524 

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() 

531 

532 

533# 

534# Keyboard macros. 

535# 

536 

537 

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() 

544 

545 

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() 

553 

554 

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. 

561 

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 

569 

570 if macro: 

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

572 

573 

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

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

576 """ 

577 Print the last keyboard macro. 

578 """ 

579 

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) 

586 

587 from prompt_toolkit.application.run_in_terminal import run_in_terminal 

588 

589 run_in_terminal(print_macro) 

590 

591 

592# 

593# Miscellaneous Commands. 

594# 

595 

596 

597@register("undo") 

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

599 """ 

600 Incremental undo. 

601 """ 

602 event.current_buffer.undo() 

603 

604 

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 

613 

614 # Transform all lines. 

615 if event.arg != 1: 

616 

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

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

619 

620 else: 

621 

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

623 return "#" + line 

624 

625 buff.document = Document( 

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

627 ) 

628 

629 # Accept input. 

630 buff.validate_and_handle() 

631 

632 

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 

639 

640 

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 

647 

648 

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. 

653 

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

655 

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) 

661 

662 

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 

671 

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

673 # 'done' state.) 

674 buff.validate_and_handle() 

675 

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 

680 

681 event.app.pre_run_callables.append(set_working_index) 

682 

683 

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)