diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 5a0aac8..c1355dd 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): diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index 28aa5b4..e44e342 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,7 +95,17 @@ 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): + def newline_or_execute(event): """When the user presses return, insert a newline or execute the code.""" b = event.current_buffer @@ -107,7 +126,12 @@ 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 + after_cursor = d.text[d.cursor_position:] + if not after_cursor.strip(): + 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() ): @@ -118,6 +142,7 @@ def newline_or_execute_outer(shell): return if (status != 'incomplete') and b.accept_handler: + reformat_text_before_cursor(b, d, shell) b.validate_and_handle() else: if shell.autoindent: diff --git a/docs/source/whatsnew/pr/autoformatting.rst b/docs/source/whatsnew/pr/autoformatting.rst new file mode 100644 index 0000000..dff810e --- /dev/null +++ b/docs/source/whatsnew/pr/autoformatting.rst @@ -0,0 +1,24 @@ +Code autoformatting +=================== + +The IPython terminal can now auto format your code just before entering a new +line or executing a command. To do so use the +``--TerminalInteractiveShell.autoformatter`` option and set it to ``'black'``; +if black is installed IPython will use black to format your code when possible. + +IPython cannot always properly format your code; in particular it will +auto formatting with *black* will only work if: + + - Your code does not contains magics or special python syntax. + + - There is no code after your cursor. + +The Black API is also still in motion; so this may not work with all versions of +black. + +It should be possible to register custom reformatter, though the API is till in +flux. + + + +