diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index be738f8..1a4d562 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -87,6 +87,14 @@ else: _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) +def black_reformat_handler(text_before_cursor): + import black + formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) + if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'): + formatted_text = formatted_text[:-1] + return formatted_text + + class TerminalInteractiveShell(InteractiveShell): space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' 'to reserve for the completion menu' @@ -120,6 +128,11 @@ class TerminalInteractiveShell(InteractiveShell): help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", ).tag(config=True) + autoformatter = Unicode(None, + help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`", + allow_none=True + ).tag(config=True) + mouse_support = Bool(False, help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" ).tag(config=True) @@ -150,6 +163,16 @@ class TerminalInteractiveShell(InteractiveShell): if self.pt_app: self.pt_app.editing_mode = u_mode + @observe('autoformatter') + def _autoformatter_changed(self, change): + formatter = change.new + if formatter is None: + self.reformat_handler = lambda x:x + elif formatter == 'black': + self.reformat_handler = black_reformat_handler + else: + raise ValueError + @observe('highlighting_style') @observe('colors') def _highlighting_style_changed(self, change): @@ -246,6 +269,7 @@ class TerminalInteractiveShell(InteractiveShell): self.display_formatter.ipython_display_formatter.enabled = False def init_prompt_toolkit_cli(self): + self.reformat_handler = lambda x:x if self.simple_prompt: # Fall back to plain non-interactive output for tests. # This is very limited. diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index 11bd448..ef0f65c 100644 --- a/IPython/terminal/shortcuts.py +++ b/IPython/terminal/shortcuts.py @@ -44,6 +44,15 @@ def create_ipython_shortcuts(shell): & insert_mode ))(return_handler) + def reformat_and_execute(event): + reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell) + event.current_buffer.validate_and_handle() + + kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + ))(reformat_and_execute) + kb.add('c-\\')(force_exit) kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) @@ -86,24 +95,21 @@ def create_ipython_shortcuts(shell): return kb +def reformat_text_before_cursor(buffer, document, shell): + text = buffer.delete_before_cursor(len(document.text[:document.cursor_position])) + try: + formatted_text = shell.reformat_handler(text) + buffer.insert_text(formatted_text) + except Exception as e: + buffer.insert_text(text) + + def newline_or_execute_outer(shell): - import black def newline_or_execute(event): """When the user presses return, insert a newline or execute the code.""" b = event.current_buffer d = b.document - def try_reformat(): - try: - tbc = b.delete_before_cursor(len(d.text[:d.cursor_position])) - fmt= black.format_str(tbc, mode=black.FileMode()) - if not tbc.endswith('\n') and fmt.endswith('\n'): - fmt = fmt[:-1] - b.insert_text(fmt) - #print(f'no eexc |{tbc[-1]}|,|{d.text[-1]}|, |{fmt[-3:-1]}|') - except Exception as e: - b.insert_text(tbc) - if b.complete_state: cc = b.complete_state.current_completion @@ -120,7 +126,11 @@ def newline_or_execute_outer(shell): else: check_text = d.text[:d.cursor_position] status, indent = shell.check_complete(check_text) - + + # if all we have after the cursor is whitespace: reformat current text + # before cursor + if d.text[d.cursor_position:].isspace(): + reformat_text_before_cursor(b, d, shell) if not (d.on_last_line or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() ): @@ -131,11 +141,10 @@ def newline_or_execute_outer(shell): return if (status != 'incomplete') and b.accept_handler: - try_reformat() + reformat_text_before_cursor(b, d, shell) b.validate_and_handle() else: if shell.autoindent: - try_reformat() b.insert_text('\n' + indent) else: b.insert_text('\n')