diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 5c79a17..98e5eb8 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -188,6 +188,15 @@ class TerminalInteractiveShell(InteractiveShell): allow_none=True ).tag(config=True) + auto_match = Bool( + False, + help=""" + Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted. + Brackets: (), [], {} + Quotes: '', \"\" + """, + ).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) diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index 75faaa5..6aab3d2 100644 --- a/IPython/terminal/shortcuts.py +++ b/IPython/terminal/shortcuts.py @@ -85,12 +85,169 @@ def create_ipython_shortcuts(shell): kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor) - if shell.display_completions == 'readlinelike': - kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER) - & ~has_selection - & insert_mode - & ~cursor_in_leading_ws - ))(display_completions_like_readline) + @Condition + def auto_match(): + return shell.auto_match + + focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER) + _preceding_text_cache = {} + _following_text_cache = {} + + def preceding_text(pattern): + try: + return _preceding_text_cache[pattern] + except KeyError: + pass + m = re.compile(pattern) + + def _preceding_text(): + app = get_app() + return bool(m.match(app.current_buffer.document.current_line_before_cursor)) + + condition = Condition(_preceding_text) + _preceding_text_cache[pattern] = condition + return condition + + def following_text(pattern): + try: + return _following_text_cache[pattern] + except KeyError: + pass + m = re.compile(pattern) + + def _following_text(): + app = get_app() + return bool(m.match(app.current_buffer.document.current_line_after_cursor)) + + condition = Condition(_following_text) + _following_text_cache[pattern] = condition + return condition + + # auto match + @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$")) + def _(event): + event.current_buffer.insert_text("()") + event.current_buffer.cursor_left() + + @kb.add("[", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$")) + def _(event): + event.current_buffer.insert_text("[]") + event.current_buffer.cursor_left() + + @kb.add("{", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$")) + def _(event): + event.current_buffer.insert_text("{}") + event.current_buffer.cursor_left() + + @kb.add('"', filter=focused_insert & auto_match & following_text(r"[,)}\]]|$")) + def _(event): + event.current_buffer.insert_text('""') + event.current_buffer.cursor_left() + + @kb.add("'", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$")) + def _(event): + event.current_buffer.insert_text("''") + event.current_buffer.cursor_left() + + # raw string + @kb.add("(", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$")) + def _(event): + matches = re.match( + r".*(r|R)[\"'](-*)", + event.current_buffer.document.current_line_before_cursor, + ) + dashes = matches.group(2) or "" + event.current_buffer.insert_text("()" + dashes) + event.current_buffer.cursor_left(len(dashes) + 1) + + @kb.add("[", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$")) + def _(event): + matches = re.match( + r".*(r|R)[\"'](-*)", + event.current_buffer.document.current_line_before_cursor, + ) + dashes = matches.group(2) or "" + event.current_buffer.insert_text("[]" + dashes) + event.current_buffer.cursor_left(len(dashes) + 1) + + @kb.add("{", filter=focused_insert & preceding_text(r".*(r|R)[\"'](-*)$")) + def _(event): + matches = re.match( + r".*(r|R)[\"'](-*)", + event.current_buffer.document.current_line_before_cursor, + ) + dashes = matches.group(2) or "" + event.current_buffer.insert_text("{}" + dashes) + event.current_buffer.cursor_left(len(dashes) + 1) + + @kb.add('"', filter=focused_insert & preceding_text(r".*(r|R)$")) + def _(event): + event.current_buffer.insert_text('""') + event.current_buffer.cursor_left() + + @kb.add("'", filter=focused_insert & preceding_text(r".*(r|R)$")) + def _(event): + event.current_buffer.insert_text("''") + event.current_buffer.cursor_left() + + # just move cursor + @kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)")) + @kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]")) + @kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}")) + @kb.add('"', filter=focused_insert & auto_match & following_text('^"')) + @kb.add("'", filter=focused_insert & auto_match & following_text("^'")) + def _(event): + event.current_buffer.cursor_right() + + @kb.add( + "backspace", + filter=focused_insert + & preceding_text(r".*\($") + & auto_match + & following_text(r"^\)"), + ) + @kb.add( + "backspace", + filter=focused_insert + & preceding_text(r".*\[$") + & auto_match + & following_text(r"^\]"), + ) + @kb.add( + "backspace", + filter=focused_insert + & preceding_text(r".*\{$") + & auto_match + & following_text(r"^\}"), + ) + @kb.add( + "backspace", + filter=focused_insert + & preceding_text('.*"$') + & auto_match + & following_text('^"'), + ) + @kb.add( + "backspace", + filter=focused_insert + & preceding_text(r".*'$") + & auto_match + & following_text(r"^'"), + ) + def _(event): + event.current_buffer.delete() + event.current_buffer.delete_before_cursor() + + if shell.display_completions == "readlinelike": + kb.add( + "c-i", + filter=( + has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + & ~cursor_in_leading_ws + ), + )(display_completions_like_readline) if sys.platform == "win32": kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)