Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/IPython/terminal/shortcuts/__init__.py: 32%

179 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:07 +0000

1""" 

2Module to define and register Terminal IPython shortcuts with 

3:mod:`prompt_toolkit` 

4""" 

5 

6# Copyright (c) IPython Development Team. 

7# Distributed under the terms of the Modified BSD License. 

8 

9import os 

10import signal 

11import sys 

12import warnings 

13from dataclasses import dataclass 

14from typing import Callable, Any, Optional, List 

15 

16from prompt_toolkit.application.current import get_app 

17from prompt_toolkit.key_binding import KeyBindings 

18from prompt_toolkit.key_binding.key_processor import KeyPressEvent 

19from prompt_toolkit.key_binding.bindings import named_commands as nc 

20from prompt_toolkit.key_binding.bindings.completion import ( 

21 display_completions_like_readline, 

22) 

23from prompt_toolkit.key_binding.vi_state import InputMode, ViState 

24from prompt_toolkit.filters import Condition 

25 

26from IPython.core.getipython import get_ipython 

27from IPython.terminal.shortcuts import auto_match as match 

28from IPython.terminal.shortcuts import auto_suggest 

29from IPython.terminal.shortcuts.filters import filter_from_string 

30from IPython.utils.decorators import undoc 

31 

32__all__ = ["create_ipython_shortcuts"] 

33 

34 

35@dataclass 

36class BaseBinding: 

37 command: Callable[[KeyPressEvent], Any] 

38 keys: List[str] 

39 

40 

41@dataclass 

42class RuntimeBinding(BaseBinding): 

43 filter: Condition 

44 

45 

46@dataclass 

47class Binding(BaseBinding): 

48 # while filter could be created by referencing variables directly (rather 

49 # than created from strings), by using strings we ensure that users will 

50 # be able to create filters in configuration (e.g. JSON) files too, which 

51 # also benefits the documentation by enforcing human-readable filter names. 

52 condition: Optional[str] = None 

53 

54 def __post_init__(self): 

55 if self.condition: 

56 self.filter = filter_from_string(self.condition) 

57 else: 

58 self.filter = None 

59 

60 

61def create_identifier(handler: Callable): 

62 parts = handler.__module__.split(".") 

63 name = handler.__name__ 

64 package = parts[0] 

65 if len(parts) > 1: 

66 final_module = parts[-1] 

67 return f"{package}:{final_module}.{name}" 

68 else: 

69 return f"{package}:{name}" 

70 

71 

72AUTO_MATCH_BINDINGS = [ 

73 *[ 

74 Binding( 

75 cmd, [key], "focused_insert & auto_match & followed_by_closing_paren_or_end" 

76 ) 

77 for key, cmd in match.auto_match_parens.items() 

78 ], 

79 *[ 

80 # raw string 

81 Binding(cmd, [key], "focused_insert & auto_match & preceded_by_raw_str_prefix") 

82 for key, cmd in match.auto_match_parens_raw_string.items() 

83 ], 

84 Binding( 

85 match.double_quote, 

86 ['"'], 

87 "focused_insert" 

88 " & auto_match" 

89 " & not_inside_unclosed_string" 

90 " & preceded_by_paired_double_quotes" 

91 " & followed_by_closing_paren_or_end", 

92 ), 

93 Binding( 

94 match.single_quote, 

95 ["'"], 

96 "focused_insert" 

97 " & auto_match" 

98 " & not_inside_unclosed_string" 

99 " & preceded_by_paired_single_quotes" 

100 " & followed_by_closing_paren_or_end", 

101 ), 

102 Binding( 

103 match.docstring_double_quotes, 

104 ['"'], 

105 "focused_insert" 

106 " & auto_match" 

107 " & not_inside_unclosed_string" 

108 " & preceded_by_two_double_quotes", 

109 ), 

110 Binding( 

111 match.docstring_single_quotes, 

112 ["'"], 

113 "focused_insert" 

114 " & auto_match" 

115 " & not_inside_unclosed_string" 

116 " & preceded_by_two_single_quotes", 

117 ), 

118 Binding( 

119 match.skip_over, 

120 [")"], 

121 "focused_insert & auto_match & followed_by_closing_round_paren", 

122 ), 

123 Binding( 

124 match.skip_over, 

125 ["]"], 

126 "focused_insert & auto_match & followed_by_closing_bracket", 

127 ), 

128 Binding( 

129 match.skip_over, 

130 ["}"], 

131 "focused_insert & auto_match & followed_by_closing_brace", 

132 ), 

133 Binding( 

134 match.skip_over, ['"'], "focused_insert & auto_match & followed_by_double_quote" 

135 ), 

136 Binding( 

137 match.skip_over, ["'"], "focused_insert & auto_match & followed_by_single_quote" 

138 ), 

139 Binding( 

140 match.delete_pair, 

141 ["backspace"], 

142 "focused_insert" 

143 " & preceded_by_opening_round_paren" 

144 " & auto_match" 

145 " & followed_by_closing_round_paren", 

146 ), 

147 Binding( 

148 match.delete_pair, 

149 ["backspace"], 

150 "focused_insert" 

151 " & preceded_by_opening_bracket" 

152 " & auto_match" 

153 " & followed_by_closing_bracket", 

154 ), 

155 Binding( 

156 match.delete_pair, 

157 ["backspace"], 

158 "focused_insert" 

159 " & preceded_by_opening_brace" 

160 " & auto_match" 

161 " & followed_by_closing_brace", 

162 ), 

163 Binding( 

164 match.delete_pair, 

165 ["backspace"], 

166 "focused_insert" 

167 " & preceded_by_double_quote" 

168 " & auto_match" 

169 " & followed_by_double_quote", 

170 ), 

171 Binding( 

172 match.delete_pair, 

173 ["backspace"], 

174 "focused_insert" 

175 " & preceded_by_single_quote" 

176 " & auto_match" 

177 " & followed_by_single_quote", 

178 ), 

179] 

180 

181AUTO_SUGGEST_BINDINGS = [ 

182 Binding( 

183 auto_suggest.accept_in_vi_insert_mode, 

184 ["end"], 

185 "default_buffer_focused & (ebivim | ~vi_insert_mode)", 

186 ), 

187 Binding( 

188 auto_suggest.accept_in_vi_insert_mode, 

189 ["c-e"], 

190 "vi_insert_mode & default_buffer_focused & ebivim", 

191 ), 

192 Binding(auto_suggest.accept, ["c-f"], "vi_insert_mode & default_buffer_focused"), 

193 Binding( 

194 auto_suggest.accept_word, 

195 ["escape", "f"], 

196 "vi_insert_mode & default_buffer_focused & ebivim", 

197 ), 

198 Binding( 

199 auto_suggest.accept_token, 

200 ["c-right"], 

201 "has_suggestion & default_buffer_focused", 

202 ), 

203 Binding( 

204 auto_suggest.discard, 

205 ["escape"], 

206 "has_suggestion & default_buffer_focused & emacs_insert_mode", 

207 ), 

208 Binding( 

209 auto_suggest.swap_autosuggestion_up, 

210 ["up"], 

211 "navigable_suggestions" 

212 " & ~has_line_above" 

213 " & has_suggestion" 

214 " & default_buffer_focused", 

215 ), 

216 Binding( 

217 auto_suggest.swap_autosuggestion_down, 

218 ["down"], 

219 "navigable_suggestions" 

220 " & ~has_line_below" 

221 " & has_suggestion" 

222 " & default_buffer_focused", 

223 ), 

224 Binding( 

225 auto_suggest.up_and_update_hint, 

226 ["up"], 

227 "has_line_above & navigable_suggestions & default_buffer_focused", 

228 ), 

229 Binding( 

230 auto_suggest.down_and_update_hint, 

231 ["down"], 

232 "has_line_below & navigable_suggestions & default_buffer_focused", 

233 ), 

234 Binding( 

235 auto_suggest.accept_character, 

236 ["escape", "right"], 

237 "has_suggestion & default_buffer_focused", 

238 ), 

239 Binding( 

240 auto_suggest.accept_and_move_cursor_left, 

241 ["c-left"], 

242 "has_suggestion & default_buffer_focused", 

243 ), 

244 Binding( 

245 auto_suggest.accept_and_keep_cursor, 

246 ["c-down"], 

247 "has_suggestion & default_buffer_focused", 

248 ), 

249 Binding( 

250 auto_suggest.backspace_and_resume_hint, 

251 ["backspace"], 

252 "has_suggestion & default_buffer_focused", 

253 ), 

254] 

255 

256 

257SIMPLE_CONTROL_BINDINGS = [ 

258 Binding(cmd, [key], "vi_insert_mode & default_buffer_focused & ebivim") 

259 for key, cmd in { 

260 "c-a": nc.beginning_of_line, 

261 "c-b": nc.backward_char, 

262 "c-k": nc.kill_line, 

263 "c-w": nc.backward_kill_word, 

264 "c-y": nc.yank, 

265 "c-_": nc.undo, 

266 }.items() 

267] 

268 

269 

270ALT_AND_COMOBO_CONTROL_BINDINGS = [ 

271 Binding(cmd, list(keys), "vi_insert_mode & default_buffer_focused & ebivim") 

272 for keys, cmd in { 

273 # Control Combos 

274 ("c-x", "c-e"): nc.edit_and_execute, 

275 ("c-x", "e"): nc.edit_and_execute, 

276 # Alt 

277 ("escape", "b"): nc.backward_word, 

278 ("escape", "c"): nc.capitalize_word, 

279 ("escape", "d"): nc.kill_word, 

280 ("escape", "h"): nc.backward_kill_word, 

281 ("escape", "l"): nc.downcase_word, 

282 ("escape", "u"): nc.uppercase_word, 

283 ("escape", "y"): nc.yank_pop, 

284 ("escape", "."): nc.yank_last_arg, 

285 }.items() 

286] 

287 

288 

289def add_binding(bindings: KeyBindings, binding: Binding): 

290 bindings.add( 

291 *binding.keys, 

292 **({"filter": binding.filter} if binding.filter is not None else {}), 

293 )(binding.command) 

294 

295 

296def create_ipython_shortcuts(shell, skip=None) -> KeyBindings: 

297 """Set up the prompt_toolkit keyboard shortcuts for IPython. 

298 

299 Parameters 

300 ---------- 

301 shell: InteractiveShell 

302 The current IPython shell Instance 

303 skip: List[Binding] 

304 Bindings to skip. 

305 

306 Returns 

307 ------- 

308 KeyBindings 

309 the keybinding instance for prompt toolkit. 

310 

311 """ 

312 kb = KeyBindings() 

313 skip = skip or [] 

314 for binding in KEY_BINDINGS: 

315 skip_this_one = False 

316 for to_skip in skip: 

317 if ( 

318 to_skip.command == binding.command 

319 and to_skip.filter == binding.filter 

320 and to_skip.keys == binding.keys 

321 ): 

322 skip_this_one = True 

323 break 

324 if skip_this_one: 

325 continue 

326 add_binding(kb, binding) 

327 

328 def get_input_mode(self): 

329 app = get_app() 

330 app.ttimeoutlen = shell.ttimeoutlen 

331 app.timeoutlen = shell.timeoutlen 

332 

333 return self._input_mode 

334 

335 def set_input_mode(self, mode): 

336 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6) 

337 cursor = "\x1b[{} q".format(shape) 

338 

339 sys.stdout.write(cursor) 

340 sys.stdout.flush() 

341 

342 self._input_mode = mode 

343 

344 if shell.editing_mode == "vi" and shell.modal_cursor: 

345 ViState._input_mode = InputMode.INSERT # type: ignore 

346 ViState.input_mode = property(get_input_mode, set_input_mode) # type: ignore 

347 

348 return kb 

349 

350 

351def reformat_and_execute(event): 

352 """Reformat code and execute it""" 

353 shell = get_ipython() 

354 reformat_text_before_cursor( 

355 event.current_buffer, event.current_buffer.document, shell 

356 ) 

357 event.current_buffer.validate_and_handle() 

358 

359 

360def reformat_text_before_cursor(buffer, document, shell): 

361 text = buffer.delete_before_cursor(len(document.text[: document.cursor_position])) 

362 try: 

363 formatted_text = shell.reformat_handler(text) 

364 buffer.insert_text(formatted_text) 

365 except Exception as e: 

366 buffer.insert_text(text) 

367 

368 

369def handle_return_or_newline_or_execute(event): 

370 shell = get_ipython() 

371 if getattr(shell, "handle_return", None): 

372 return shell.handle_return(shell)(event) 

373 else: 

374 return newline_or_execute_outer(shell)(event) 

375 

376 

377def newline_or_execute_outer(shell): 

378 def newline_or_execute(event): 

379 """When the user presses return, insert a newline or execute the code.""" 

380 b = event.current_buffer 

381 d = b.document 

382 

383 if b.complete_state: 

384 cc = b.complete_state.current_completion 

385 if cc: 

386 b.apply_completion(cc) 

387 else: 

388 b.cancel_completion() 

389 return 

390 

391 # If there's only one line, treat it as if the cursor is at the end. 

392 # See https://github.com/ipython/ipython/issues/10425 

393 if d.line_count == 1: 

394 check_text = d.text 

395 else: 

396 check_text = d.text[: d.cursor_position] 

397 status, indent = shell.check_complete(check_text) 

398 

399 # if all we have after the cursor is whitespace: reformat current text 

400 # before cursor 

401 after_cursor = d.text[d.cursor_position :] 

402 reformatted = False 

403 if not after_cursor.strip(): 

404 reformat_text_before_cursor(b, d, shell) 

405 reformatted = True 

406 if not ( 

407 d.on_last_line 

408 or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() 

409 ): 

410 if shell.autoindent: 

411 b.insert_text("\n" + indent) 

412 else: 

413 b.insert_text("\n") 

414 return 

415 

416 if (status != "incomplete") and b.accept_handler: 

417 if not reformatted: 

418 reformat_text_before_cursor(b, d, shell) 

419 b.validate_and_handle() 

420 else: 

421 if shell.autoindent: 

422 b.insert_text("\n" + indent) 

423 else: 

424 b.insert_text("\n") 

425 

426 return newline_or_execute 

427 

428 

429def previous_history_or_previous_completion(event): 

430 """ 

431 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit. 

432 

433 If completer is open this still select previous completion. 

434 """ 

435 event.current_buffer.auto_up() 

436 

437 

438def next_history_or_next_completion(event): 

439 """ 

440 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit. 

441 

442 If completer is open this still select next completion. 

443 """ 

444 event.current_buffer.auto_down() 

445 

446 

447def dismiss_completion(event): 

448 """Dismiss completion""" 

449 b = event.current_buffer 

450 if b.complete_state: 

451 b.cancel_completion() 

452 

453 

454def reset_buffer(event): 

455 """Reset buffer""" 

456 b = event.current_buffer 

457 if b.complete_state: 

458 b.cancel_completion() 

459 else: 

460 b.reset() 

461 

462 

463def reset_search_buffer(event): 

464 """Reset search buffer""" 

465 if event.current_buffer.document.text: 

466 event.current_buffer.reset() 

467 else: 

468 event.app.layout.focus(DEFAULT_BUFFER) 

469 

470 

471def suspend_to_bg(event): 

472 """Suspend to background""" 

473 event.app.suspend_to_background() 

474 

475 

476def quit(event): 

477 """ 

478 Quit application with ``SIGQUIT`` if supported or ``sys.exit`` otherwise. 

479 

480 On platforms that support SIGQUIT, send SIGQUIT to the current process. 

481 On other platforms, just exit the process with a message. 

482 """ 

483 sigquit = getattr(signal, "SIGQUIT", None) 

484 if sigquit is not None: 

485 os.kill(0, signal.SIGQUIT) 

486 else: 

487 sys.exit("Quit") 

488 

489 

490def indent_buffer(event): 

491 """Indent buffer""" 

492 event.current_buffer.insert_text(" " * 4) 

493 

494 

495def newline_autoindent(event): 

496 """Insert a newline after the cursor indented appropriately. 

497 

498 Fancier version of former ``newline_with_copy_margin`` which should 

499 compute the correct indentation of the inserted line. That is to say, indent 

500 by 4 extra space after a function definition, class definition, context 

501 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``. 

502 """ 

503 shell = get_ipython() 

504 inputsplitter = shell.input_transformer_manager 

505 b = event.current_buffer 

506 d = b.document 

507 

508 if b.complete_state: 

509 b.cancel_completion() 

510 text = d.text[: d.cursor_position] + "\n" 

511 _, indent = inputsplitter.check_complete(text) 

512 b.insert_text("\n" + (" " * (indent or 0)), move_cursor=False) 

513 

514 

515def open_input_in_editor(event): 

516 """Open code from input in external editor""" 

517 event.app.current_buffer.open_in_editor() 

518 

519 

520if sys.platform == "win32": 

521 from IPython.core.error import TryNext 

522 from IPython.lib.clipboard import ( 

523 ClipboardEmpty, 

524 tkinter_clipboard_get, 

525 win32_clipboard_get, 

526 ) 

527 

528 @undoc 

529 def win_paste(event): 

530 try: 

531 text = win32_clipboard_get() 

532 except TryNext: 

533 try: 

534 text = tkinter_clipboard_get() 

535 except (TryNext, ClipboardEmpty): 

536 return 

537 except ClipboardEmpty: 

538 return 

539 event.current_buffer.insert_text(text.replace("\t", " " * 4)) 

540 

541else: 

542 

543 @undoc 

544 def win_paste(event): 

545 """Stub used on other platforms""" 

546 pass 

547 

548 

549KEY_BINDINGS = [ 

550 Binding( 

551 handle_return_or_newline_or_execute, 

552 ["enter"], 

553 "default_buffer_focused & ~has_selection & insert_mode", 

554 ), 

555 Binding( 

556 reformat_and_execute, 

557 ["escape", "enter"], 

558 "default_buffer_focused & ~has_selection & insert_mode & ebivim", 

559 ), 

560 Binding(quit, ["c-\\"]), 

561 Binding( 

562 previous_history_or_previous_completion, 

563 ["c-p"], 

564 "vi_insert_mode & default_buffer_focused", 

565 ), 

566 Binding( 

567 next_history_or_next_completion, 

568 ["c-n"], 

569 "vi_insert_mode & default_buffer_focused", 

570 ), 

571 Binding(dismiss_completion, ["c-g"], "default_buffer_focused & has_completions"), 

572 Binding(reset_buffer, ["c-c"], "default_buffer_focused"), 

573 Binding(reset_search_buffer, ["c-c"], "search_buffer_focused"), 

574 Binding(suspend_to_bg, ["c-z"], "supports_suspend"), 

575 Binding( 

576 indent_buffer, 

577 ["tab"], # Ctrl+I == Tab 

578 "default_buffer_focused" 

579 " & ~has_selection" 

580 " & insert_mode" 

581 " & cursor_in_leading_ws", 

582 ), 

583 Binding(newline_autoindent, ["c-o"], "default_buffer_focused & emacs_insert_mode"), 

584 Binding(open_input_in_editor, ["f2"], "default_buffer_focused"), 

585 *AUTO_MATCH_BINDINGS, 

586 *AUTO_SUGGEST_BINDINGS, 

587 Binding( 

588 display_completions_like_readline, 

589 ["c-i"], 

590 "readline_like_completions" 

591 " & default_buffer_focused" 

592 " & ~has_selection" 

593 " & insert_mode" 

594 " & ~cursor_in_leading_ws", 

595 ), 

596 Binding(win_paste, ["c-v"], "default_buffer_focused & ~vi_mode & is_windows_os"), 

597 *SIMPLE_CONTROL_BINDINGS, 

598 *ALT_AND_COMOBO_CONTROL_BINDINGS, 

599]