Repository: ipython/ipython

Issue:
Terminal title should be disabled when using IPython in a pipeline
This came up recently here: https://github.com/sharkdp/bat/issues/403

Here's an example of how IPython prints control characters even when used in a pipeline:

```
/$ ipython -c "import os; print(os.isatty(1))"
True

/$ ipython -c "import os; print(os.isatty(1))" | gcat -A
^[]0;IPython: /^GFalse$
```

It would be good if IPython checked `isatty` (as shown above) to not add terminal title control characters if `ipython` is being used in a pipeline.

To be clear, this isn't just a convenience feature, as it can actually cause unexpected behavior when used in a pipeline. For example, one would expect that a `grep` for the letter `o` in the output of "False" above would return false:

```
/$ ipython -c "import os; print(os.isatty(1))" | grep o
01;31mon: /False
```

But it returns a match! Running the same command using stock Python correctly returns nothing (and 1 return code):

```
/$ python3 -c "import os; print(os.isatty(1))" | grep o
/$:1
```

Discussion:
maciej-irl: Only semi-related but #11909 can probably be closed, it's been fixed since 2019

Patch:
diff --git a/IPython/core/completer.py b/IPython/core/completer.py
index fc3aea7b6..cffd0866f 100644
--- a/IPython/core/completer.py
+++ b/IPython/core/completer.py
@@ -100,73 +100,6 @@
 
 Be sure to update :any:`jedi` to the latest stable version or to try the
 current development version to get better completions.
-
-Matchers
-========
-
-All completions routines are implemented using unified *Matchers* API.
-The matchers API is provisional and subject to change without notice.
-
-The built-in matchers include:
-
-- :any:`IPCompleter.dict_key_matcher`:  dictionary key completions,
-- :any:`IPCompleter.magic_matcher`: completions for magics,
-- :any:`IPCompleter.unicode_name_matcher`,
-  :any:`IPCompleter.fwd_unicode_matcher`
-  and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_,
-- :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_,
-- :any:`IPCompleter.file_matcher`: paths to files and directories,
-- :any:`IPCompleter.python_func_kw_matcher` - function keywords,
-- :any:`IPCompleter.python_matches` - globals and attributes (v1 API),
-- ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
-- :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default
-  implementation in :any:`InteractiveShell` which uses IPython hooks system
-  (`complete_command`) with string dispatch (including regular expressions).
-  Differently to other matchers, ``custom_completer_matcher`` will not suppress
-  Jedi results to match behaviour in earlier IPython versions.
-
-Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
-
-Matcher API
------------
-
-Simplifying some details, the ``Matcher`` interface can described as
-
-.. code-block::
-
-    MatcherAPIv1 = Callable[[str], list[str]]
-    MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult]
-
-    Matcher = MatcherAPIv1 | MatcherAPIv2
-
-The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0
-and remains supported as a simplest way for generating completions. This is also
-currently the only API supported by the IPython hooks system `complete_command`.
-
-To distinguish between matcher versions ``matcher_api_version`` attribute is used.
-More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers,
-and requires a literal ``2`` for v2 Matchers.
-
-Once the API stabilises future versions may relax the requirement for specifying
-``matcher_api_version`` by switching to :any:`functools.singledispatch`, therefore
-please do not rely on the presence of ``matcher_api_version`` for any purposes.
-
-Suppression of competing matchers
----------------------------------
-
-By default results from all matchers are combined, in the order determined by
-their priority. Matchers can request to suppress results from subsequent
-matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
-
-When multiple matchers simultaneously request surpression, the results from of
-the matcher with higher priority will be returned.
-
-Sometimes it is desirable to suppress most but not all other matchers;
-this can be achieved by adding a list of identifiers of matchers which
-should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
-
-The suppression behaviour can is user-configurable via
-:any:`IPCompleter.suppress_competing_matchers`.
 """
 
 
@@ -176,7 +109,7 @@
 # Some of this code originated from rlcompleter in the Python standard library
 # Copyright (C) 2001 Python Software Foundation, www.python.org
 
-from __future__ import annotations
+
 import builtins as builtin_mod
 import glob
 import inspect
@@ -191,26 +124,9 @@
 import uuid
 import warnings
 from contextlib import contextmanager
-from dataclasses import dataclass
-from functools import cached_property, partial
 from importlib import import_module
 from types import SimpleNamespace
-from typing import (
-    Iterable,
-    Iterator,
-    List,
-    Tuple,
-    Union,
-    Any,
-    Sequence,
-    Dict,
-    NamedTuple,
-    Pattern,
-    Optional,
-    TYPE_CHECKING,
-    Set,
-    Literal,
-)
+from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional
 
 from IPython.core.error import TryNext
 from IPython.core.inputtransformer2 import ESC_MAGIC
@@ -218,22 +134,10 @@
 from IPython.core.oinspect import InspectColors
 from IPython.testing.skipdoctest import skip_doctest
 from IPython.utils import generics
-from IPython.utils.decorators import sphinx_options
 from IPython.utils.dir2 import dir2, get_real_method
-from IPython.utils.docs import GENERATING_DOCUMENTATION
 from IPython.utils.path import ensure_dir_exists
 from IPython.utils.process import arg_split
-from traitlets import (
-    Bool,
-    Enum,
-    Int,
-    List as ListTrait,
-    Unicode,
-    Dict as DictTrait,
-    Union as UnionTrait,
-    default,
-    observe,
-)
+from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe
 from traitlets.config.configurable import Configurable
 
 import __main__
@@ -241,7 +145,6 @@
 # skip module docstests
 __skip_doctest__ = True
 
-
 try:
     import jedi
     jedi.settings.case_insensitive_completion = False
@@ -250,26 +153,7 @@
     JEDI_INSTALLED = True
 except ImportError:
     JEDI_INSTALLED = False
-
-
-if TYPE_CHECKING or GENERATING_DOCUMENTATION:
-    from typing import cast
-    from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias
-else:
-
-    def cast(obj, type_):
-        """Workaround for `TypeError: MatcherAPIv2() takes no arguments`"""
-        return obj
-
-    # do not require on runtime
-    NotRequired = Tuple  # requires Python >=3.11
-    TypedDict = Dict  # by extension of `NotRequired` requires 3.11 too
-    Protocol = object  # requires Python >=3.8
-    TypeAlias = Any  # requires Python >=3.10
-if GENERATING_DOCUMENTATION:
-    from typing import TypedDict
-
-# -----------------------------------------------------------------------------
+#-----------------------------------------------------------------------------
 # Globals
 #-----------------------------------------------------------------------------
 
@@ -282,7 +166,7 @@ def cast(obj, type_):
 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
 
 # Public API
-__all__ = ["Completer", "IPCompleter"]
+__all__ = ['Completer','IPCompleter']
 
 if sys.platform == 'win32':
     PROTECTABLES = ' '
@@ -293,8 +177,6 @@ def cast(obj, type_):
 # may have trouble processing.
 MATCHES_LIMIT = 500
 
-# Completion type reported when no type can be inferred.
-_UNKNOWN_TYPE = "<unknown>"
 
 class ProvisionalCompleterWarning(FutureWarning):
     """
@@ -473,12 +355,9 @@ def __repr__(self):
         return '<Fake completion object jedi has crashed>'
 
 
-_JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion]
-
-
 class Completion:
     """
-    Completion object used and returned by IPython completers.
+    Completion object used and return by IPython completers.
 
     .. warning::
 
@@ -538,188 +417,6 @@ def __hash__(self):
         return hash((self.start, self.end, self.text))
 
 
-class SimpleCompletion:
-    """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
-
-    .. warning::
-
-        Provisional
-
-        This class is used to describe the currently supported attributes of
-        simple completion items, and any additional implementation details
-        should not be relied on. Additional attributes may be included in
-        future versions, and meaning of text disambiguated from the current
-        dual meaning of "text to insert" and "text to used as a label".
-    """
-
-    __slots__ = ["text", "type"]
-
-    def __init__(self, text: str, *, type: str = None):
-        self.text = text
-        self.type = type
-
-    def __repr__(self):
-        return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
-
-
-class _MatcherResultBase(TypedDict):
-    """Definition of dictionary to be returned by new-style Matcher (API v2)."""
-
-    #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
-    matched_fragment: NotRequired[str]
-
-    #: Whether to suppress results from all other matchers (True), some
-    #: matchers (set of identifiers) or none (False); default is False.
-    suppress: NotRequired[Union[bool, Set[str]]]
-
-    #: Identifiers of matchers which should NOT be suppressed when this matcher
-    #: requests to suppress all other matchers; defaults to an empty set.
-    do_not_suppress: NotRequired[Set[str]]
-
-    #: Are completions already ordered and should be left as-is? default is False.
-    ordered: NotRequired[bool]
-
-
-@sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"])
-class SimpleMatcherResult(_MatcherResultBase, TypedDict):
-    """Result of new-style completion matcher."""
-
-    # note: TypedDict is added again to the inheritance chain
-    # in order to get __orig_bases__ for documentation
-
-    #: List of candidate completions
-    completions: Sequence[SimpleCompletion]
-
-
-class _JediMatcherResult(_MatcherResultBase):
-    """Matching result returned by Jedi (will be processed differently)"""
-
-    #: list of candidate completions
-    completions: Iterable[_JediCompletionLike]
-
-
-@dataclass
-class CompletionContext:
-    """Completion context provided as an argument to matchers in the Matcher API v2."""
-
-    # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
-    # which was not explicitly visible as an argument of the matcher, making any refactor
-    # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
-    # from the completer, and make substituting them in sub-classes easier.
-
-    #: Relevant fragment of code directly preceding the cursor.
-    #: The extraction of token is implemented via splitter heuristic
-    #: (following readline behaviour for legacy reasons), which is user configurable
-    #: (by switching the greedy mode).
-    token: str
-
-    #: The full available content of the editor or buffer
-    full_text: str
-
-    #: Cursor position in the line (the same for ``full_text`` and ``text``).
-    cursor_position: int
-
-    #: Cursor line in ``full_text``.
-    cursor_line: int
-
-    #: The maximum number of completions that will be used downstream.
-    #: Matchers can use this information to abort early.
-    #: The built-in Jedi matcher is currently excepted from this limit.
-    # If not given, return all possible completions.
-    limit: Optional[int]
-
-    @cached_property
-    def text_until_cursor(self) -> str:
-        return self.line_with_cursor[: self.cursor_position]
-
-    @cached_property
-    def line_with_cursor(self) -> str:
-        return self.full_text.split("\n")[self.cursor_line]
-
-
-#: Matcher results for API v2.
-MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
-
-
-class _MatcherAPIv1Base(Protocol):
-    def __call__(self, text: str) -> list[str]:
-        """Call signature."""
-
-
-class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol):
-    #: API version
-    matcher_api_version: Optional[Literal[1]]
-
-    def __call__(self, text: str) -> list[str]:
-        """Call signature."""
-
-
-#: Protocol describing Matcher API v1.
-MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total]
-
-
-class MatcherAPIv2(Protocol):
-    """Protocol describing Matcher API v2."""
-
-    #: API version
-    matcher_api_version: Literal[2] = 2
-
-    def __call__(self, context: CompletionContext) -> MatcherResult:
-        """Call signature."""
-
-
-Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2]
-
-
-def completion_matcher(
-    *, priority: float = None, identifier: str = None, api_version: int = 1
-):
-    """Adds attributes describing the matcher.
-
-    Parameters
-    ----------
-    priority : Optional[float]
-        The priority of the matcher, determines the order of execution of matchers.
-        Higher priority means that the matcher will be executed first. Defaults to 0.
-    identifier : Optional[str]
-        identifier of the matcher allowing users to modify the behaviour via traitlets,
-        and also used to for debugging (will be passed as ``origin`` with the completions).
-        Defaults to matcher function ``__qualname__``.
-    api_version: Optional[int]
-        version of the Matcher API used by this matcher.
-        Currently supported values are 1 and 2.
-        Defaults to 1.
-    """
-
-    def wrapper(func: Matcher):
-        func.matcher_priority = priority or 0
-        func.matcher_identifier = identifier or func.__qualname__
-        func.matcher_api_version = api_version
-        if TYPE_CHECKING:
-            if api_version == 1:
-                func = cast(func, MatcherAPIv1)
-            elif api_version == 2:
-                func = cast(func, MatcherAPIv2)
-        return func
-
-    return wrapper
-
-
-def _get_matcher_priority(matcher: Matcher):
-    return getattr(matcher, "matcher_priority", 0)
-
-
-def _get_matcher_id(matcher: Matcher):
-    return getattr(matcher, "matcher_identifier", matcher.__qualname__)
-
-
-def _get_matcher_api_version(matcher):
-    return getattr(matcher, "matcher_api_version", 1)
-
-
-context_matcher = partial(completion_matcher, api_version=2)
-
-
 _IC = Iterable[Completion]
 
 
@@ -1227,20 +924,7 @@ def _safe_isinstance(obj, module, class_name):
     return (module in sys.modules and
             isinstance(obj, getattr(import_module(module), class_name)))
 
-
-@context_matcher()
-def back_unicode_name_matcher(context: CompletionContext):
-    """Match Unicode characters back to Unicode name
-
-    Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API.
-    """
-    fragment, matches = back_unicode_name_matches(context.text_until_cursor)
-    return _convert_matcher_v1_result_to_v2(
-        matches, type="unicode", fragment=fragment, suppress_if_matches=True
-    )
-
-
-def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
+def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]:
     """Match Unicode characters back to Unicode name
 
     This does  ``☃`` -> ``\\snowman``
@@ -1250,9 +934,6 @@ def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
 
     This will not either back-complete standard sequences like \\n, \\b ...
 
-    .. deprecated:: 8.6
-        You can use :meth:`back_unicode_name_matcher` instead.
-
     Returns
     =======
 
@@ -1262,6 +943,7 @@ def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
         empty string,
     - a sequence (of 1), name for the match Unicode character, preceded by
         backslash, or empty if no match.
+    
     """
     if len(text)<2:
         return '', ()
@@ -1281,26 +963,11 @@ def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
         pass
     return '', ()
 
-
-@context_matcher()
-def back_latex_name_matcher(context: CompletionContext):
-    """Match latex characters back to unicode name
-
-    Same as :any:`back_latex_name_matches`, but adopted to new Matcher API.
-    """
-    fragment, matches = back_latex_name_matches(context.text_until_cursor)
-    return _convert_matcher_v1_result_to_v2(
-        matches, type="latex", fragment=fragment, suppress_if_matches=True
-    )
-
-
-def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:
+def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] :
     """Match latex characters back to unicode name
 
     This does ``\\ℵ`` -> ``\\aleph``
 
-    .. deprecated:: 8.6
-        You can use :meth:`back_latex_name_matcher` instead.
     """
     if len(text)<2:
         return '', ()
@@ -1375,23 +1042,11 @@ def _make_signature(completion)-> str:
                                           for p in signature.defined_names()) if f])
 
 
-_CompleteResult = Dict[str, MatcherResult]
-
-
-def _convert_matcher_v1_result_to_v2(
-    matches: Sequence[str],
-    type: str,
-    fragment: str = None,
-    suppress_if_matches: bool = False,
-) -> SimpleMatcherResult:
-    """Utility to help with transition"""
-    result = {
-        "completions": [SimpleCompletion(text=match, type=type) for match in matches],
-        "suppress": (True if matches else False) if suppress_if_matches else False,
-    }
-    if fragment is not None:
-        result["matched_fragment"] = fragment
-    return result
+class _CompleteResult(NamedTuple):
+    matched_text : str
+    matches: Sequence[str]
+    matches_origin: Sequence[str]
+    jedi_matches: Any
 
 
 class IPCompleter(Completer):
@@ -1407,59 +1062,17 @@ def _greedy_changed(self, change):
         else:
             self.splitter.delims = DELIMS
 
-    dict_keys_only = Bool(
-        False,
-        help="""
-        Whether to show dict key matches only.
-        
-        (disables all matchers except for `IPCompleter.dict_key_matcher`).
-        """,
-    )
-
-    suppress_competing_matchers = UnionTrait(
-        [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))],
-        default_value=None,
-        help="""
-        Whether to suppress completions from other *Matchers*.
-
-        When set to ``None`` (default) the matchers will attempt to auto-detect
-        whether suppression of other matchers is desirable. For example, at
-        the beginning of a line followed by `%` we expect a magic completion
-        to be the only applicable option, and after ``my_dict['`` we usually
-        expect a completion with an existing dictionary key.
-
-        If you want to disable this heuristic and see completions from all matchers,
-        set ``IPCompleter.suppress_competing_matchers = False``.
-        To disable the heuristic for specific matchers provide a dictionary mapping:
-        ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``.
-
-        Set ``IPCompleter.suppress_competing_matchers = True`` to limit
-        completions to the set of matchers with the highest priority;
-        this is equivalent to ``IPCompleter.merge_completions`` and
-        can be beneficial for performance, but will sometimes omit relevant
-        candidates from matchers further down the priority list.
-        """,
-    ).tag(config=True)
+    dict_keys_only = Bool(False,
+        help="""Whether to show dict key matches only""")
 
-    merge_completions = Bool(
-        True,
+    merge_completions = Bool(True,
         help="""Whether to merge completion results into a single list
 
         If False, only the completion results from the first non-empty
         completer will be returned.
-        
-        As of version 8.6.0, setting the value to ``False`` is an alias for:
-        ``IPCompleter.suppress_competing_matchers = True.``.
-        """,
-    ).tag(config=True)
-
-    disable_matchers = ListTrait(
-        Unicode(), help="""List of matchers to disable."""
+        """
     ).tag(config=True)
-
-    omit__names = Enum(
-        (0, 1, 2),
-        default_value=2,
+    omit__names = Enum((0,1,2), default_value=2,
         help="""Instruct the completer to omit private method names
 
         Specifically, when completing on ``object.<tab>``.
@@ -1535,7 +1148,7 @@ def __init__(
             namespace=namespace,
             global_namespace=global_namespace,
             config=config,
-            **kwargs,
+            **kwargs
         )
 
         # List where completion matches will be stored
@@ -1564,8 +1177,8 @@ def __init__(
         #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
 
         self.magic_arg_matchers = [
-            self.magic_config_matcher,
-            self.magic_color_matcher,
+            self.magic_config_matches,
+            self.magic_color_matches,
         ]
 
         # This is set externally by InteractiveShell
@@ -1577,50 +1190,27 @@ def __init__(
         # attribute through the `@unicode_names` property.
         self._unicode_names = None
 
-        self._backslash_combining_matchers = [
-            self.latex_name_matcher,
-            self.unicode_name_matcher,
-            back_latex_name_matcher,
-            back_unicode_name_matcher,
-            self.fwd_unicode_matcher,
-        ]
-
-        if not self.backslash_combining_completions:
-            for matcher in self._backslash_combining_matchers:
-                self.disable_matchers.append(matcher.matcher_identifier)
-
-        if not self.merge_completions:
-            self.suppress_competing_matchers = True
-
     @property
-    def matchers(self) -> List[Matcher]:
+    def matchers(self) -> List[Any]:
         """All active matcher routines for completion"""
         if self.dict_keys_only:
-            return [self.dict_key_matcher]
+            return [self.dict_key_matches]
 
         if self.use_jedi:
             return [
                 *self.custom_matchers,
-                *self._backslash_combining_matchers,
-                *self.magic_arg_matchers,
-                self.custom_completer_matcher,
-                self.magic_matcher,
-                self._jedi_matcher,
-                self.dict_key_matcher,
-                self.file_matcher,
+                self.dict_key_matches,
+                self.file_matches,
+                self.magic_matches,
             ]
         else:
             return [
                 *self.custom_matchers,
-                *self._backslash_combining_matchers,
-                *self.magic_arg_matchers,
-                self.custom_completer_matcher,
-                self.dict_key_matcher,
-                # TODO: convert python_matches to v2 API
-                self.magic_matcher,
+                self.dict_key_matches,
                 self.python_matches,
-                self.file_matcher,
-                self.python_func_kw_matcher,
+                self.file_matches,
+                self.magic_matches,
+                self.python_func_kw_matches,
             ]
 
     def all_completions(self, text:str) -> List[str]:
@@ -1641,15 +1231,7 @@ def _clean_glob_win32(self, text:str):
         return [f.replace("\\","/")
                 for f in self.glob("%s*" % text)]
 
-    @context_matcher()
-    def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
-        """Same as :any:`file_matches`, but adopted to new Matcher API."""
-        matches = self.file_matches(context.token)
-        # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
-        #  starts with `/home/`, `C:\`, etc)
-        return _convert_matcher_v1_result_to_v2(matches, type="path")
-
-    def file_matches(self, text: str) -> List[str]:
+    def file_matches(self, text:str)->List[str]:
         """Match filenames, expanding ~USER type strings.
 
         Most of the seemingly convoluted logic in this completer is an
@@ -1661,11 +1243,7 @@ def file_matches(self, text: str) -> List[str]:
         only the parts after what's already been typed (instead of the
         full completions, as is normally done).  I don't think with the
         current (as of Python 2.3) Python readline it's possible to do
-        better.
-
-        .. deprecated:: 8.6
-            You can use :meth:`file_matcher` instead.
-        """
+        better."""
 
         # chars that require escaping with backslash - i.e. chars
         # that readline treats incorrectly as delimiters, but we
@@ -1735,22 +1313,8 @@ def file_matches(self, text: str) -> List[str]:
         # Mark directories in input list by appending '/' to their names.
         return [x+'/' if os.path.isdir(x) else x for x in matches]
 
-    @context_matcher()
-    def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
-        """Match magics."""
-        text = context.token
-        matches = self.magic_matches(text)
-        result = _convert_matcher_v1_result_to_v2(matches, type="magic")
-        is_magic_prefix = len(text) > 0 and text[0] == "%"
-        result["suppress"] = is_magic_prefix and bool(result["completions"])
-        return result
-
-    def magic_matches(self, text: str):
-        """Match magics.
-
-        .. deprecated:: 8.6
-            You can use :meth:`magic_matcher` instead.
-        """
+    def magic_matches(self, text:str):
+        """Match magics"""
         # Get all shell magics now rather than statically, so magics loaded at
         # runtime show up too.
         lsm = self.shell.magics_manager.lsmagic()
@@ -1791,19 +1355,8 @@ def matches(magic):
 
         return comp
 
-    @context_matcher()
-    def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
-        """Match class names and attributes for %config magic."""
-        # NOTE: uses `line_buffer` equivalent for compatibility
-        matches = self.magic_config_matches(context.line_with_cursor)
-        return _convert_matcher_v1_result_to_v2(matches, type="param")
-
-    def magic_config_matches(self, text: str) -> List[str]:
-        """Match class names and attributes for %config magic.
-
-        .. deprecated:: 8.6
-            You can use :meth:`magic_config_matcher` instead.
-        """
+    def magic_config_matches(self, text:str) -> List[str]:
+        """ Match class names and attributes for %config magic """
         texts = text.strip().split()
 
         if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
@@ -1837,19 +1390,8 @@ def magic_config_matches(self, text: str) -> List[str]:
                          if attr.startswith(texts[1]) ]
         return []
 
-    @context_matcher()
-    def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
-        """Match color schemes for %colors magic."""
-        # NOTE: uses `line_buffer` equivalent for compatibility
-        matches = self.magic_color_matches(context.line_with_cursor)
-        return _convert_matcher_v1_result_to_v2(matches, type="param")
-
-    def magic_color_matches(self, text: str) -> List[str]:
-        """Match color schemes for %colors magic.
-
-        .. deprecated:: 8.6
-            You can use :meth:`magic_color_matcher` instead.
-        """
+    def magic_color_matches(self, text:str) -> List[str] :
+        """ Match color schemes for %colors magic"""
         texts = text.split()
         if text.endswith(' '):
             # .split() strips off the trailing whitespace. Add '' back
@@ -1862,24 +1404,9 @@ def magic_color_matches(self, text: str) -> List[str]:
                      if color.startswith(prefix) ]
         return []
 
-    @context_matcher(identifier="IPCompleter.jedi_matcher")
-    def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
-        matches = self._jedi_matches(
-            cursor_column=context.cursor_position,
-            cursor_line=context.cursor_line,
-            text=context.full_text,
-        )
-        return {
-            "completions": matches,
-            # static analysis should not suppress other matchers
-            "suppress": False,
-        }
-
-    def _jedi_matches(
-        self, cursor_column: int, cursor_line: int, text: str
-    ) -> Iterable[_JediCompletionLike]:
+    def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]:
         """
-        Return a list of :any:`jedi.api.Completion`s object from a ``text`` and
+        Return a list of :any:`jedi.api.Completions` object from a ``text`` and
         cursor position.
 
         Parameters
@@ -1895,9 +1422,6 @@ def _jedi_matches(
         -----
         If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
         object containing a string with the Jedi debug information attached.
-
-        .. deprecated:: 8.6
-            You can use :meth:`_jedi_matcher` instead.
         """
         namespaces = [self.namespace]
         if self.global_namespace is not None:
@@ -2034,18 +1558,8 @@ def _default_arguments(self, obj):
 
         return list(set(ret))
 
-    @context_matcher()
-    def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
-        """Match named parameters (kwargs) of the last open function."""
-        matches = self.python_func_kw_matches(context.token)
-        return _convert_matcher_v1_result_to_v2(matches, type="param")
-
     def python_func_kw_matches(self, text):
-        """Match named parameters (kwargs) of the last open function.
-
-        .. deprecated:: 8.6
-            You can use :meth:`python_func_kw_matcher` instead.
-        """
+        """Match named parameters (kwargs) of the last open function"""
 
         if "." in text: # a parameter cannot be dotted
             return []
@@ -2140,20 +1654,9 @@ def _get_keys(obj: Any) -> List[Any]:
             return obj.dtype.names or []
         return []
 
-    @context_matcher()
-    def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
-        """Match string keys in a dictionary, after e.g. ``foo[``."""
-        matches = self.dict_key_matches(context.token)
-        return _convert_matcher_v1_result_to_v2(
-            matches, type="dict key", suppress_if_matches=True
-        )
-
-    def dict_key_matches(self, text: str) -> List[str]:
-        """Match string keys in a dictionary, after e.g. ``foo[``.
+    def dict_key_matches(self, text:str) -> List[str]:
+        "Match string keys in a dictionary, after e.g. 'foo[' "
 
-        .. deprecated:: 8.6
-            You can use :meth:`dict_key_matcher` instead.
-        """
 
         if self.__dict_key_regexps is not None:
             regexps = self.__dict_key_regexps
@@ -2255,16 +1758,8 @@ def dict_key_matches(self, text: str) -> List[str]:
 
         return [leading + k + suf for k in matches]
 
-    @context_matcher()
-    def unicode_name_matcher(self, context: CompletionContext):
-        """Same as :any:`unicode_name_matches`, but adopted to new Matcher API."""
-        fragment, matches = self.unicode_name_matches(context.text_until_cursor)
-        return _convert_matcher_v1_result_to_v2(
-            matches, type="unicode", fragment=fragment, suppress_if_matches=True
-        )
-
     @staticmethod
-    def unicode_name_matches(text: str) -> Tuple[str, List[str]]:
+    def unicode_name_matches(text:str) -> Tuple[str, List[str]] :
         """Match Latex-like syntax for unicode characters base
         on the name of the character.
 
@@ -2285,24 +1780,11 @@ def unicode_name_matches(text: str) -> Tuple[str, List[str]]:
                 pass
         return '', []
 
-    @context_matcher()
-    def latex_name_matcher(self, context: CompletionContext):
-        """Match Latex syntax for unicode characters.
 
-        This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
-        """
-        fragment, matches = self.latex_matches(context.text_until_cursor)
-        return _convert_matcher_v1_result_to_v2(
-            matches, type="latex", fragment=fragment, suppress_if_matches=True
-        )
-
-    def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]:
+    def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]:
         """Match Latex syntax for unicode characters.
 
         This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
-
-        .. deprecated:: 8.6
-            You can use :meth:`latex_name_matcher` instead.
         """
         slashpos = text.rfind('\\')
         if slashpos > -1:
@@ -2319,25 +1801,7 @@ def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]:
                     return s, matches
         return '', ()
 
-    @context_matcher()
-    def custom_completer_matcher(self, context):
-        """Dispatch custom completer.
-
-        If a match is found, suppresses all other matchers except for Jedi.
-        """
-        matches = self.dispatch_custom_completer(context.token) or []
-        result = _convert_matcher_v1_result_to_v2(
-            matches, type=_UNKNOWN_TYPE, suppress_if_matches=True
-        )
-        result["ordered"] = True
-        result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
-        return result
-
     def dispatch_custom_completer(self, text):
-        """
-        .. deprecated:: 8.6
-            You can use :meth:`custom_completer_matcher` instead.
-        """
         if not self.custom_completers:
             return
 
@@ -2491,25 +1955,12 @@ def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Com
         """
         deadline = time.monotonic() + _timeout
 
+
         before = full_text[:offset]
         cursor_line, cursor_column = position_to_cursor(full_text, offset)
 
-        jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
-
-        results = self._complete(
-            full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column
-        )
-        non_jedi_results: Dict[str, SimpleMatcherResult] = {
-            identifier: result
-            for identifier, result in results.items()
-            if identifier != jedi_matcher_id
-        }
-
-        jedi_matches = (
-            cast(results[jedi_matcher_id], _JediMatcherResult)["completions"]
-            if jedi_matcher_id in results
-            else ()
-        )
+        matched_text, matches, matches_origin, jedi_matches = self._complete(
+            full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column)
 
         iter_jm = iter(jedi_matches)
         if _timeout:
@@ -2537,57 +1988,28 @@ def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Com
 
         for jm in iter_jm:
             delta = len(jm.name_with_symbols) - len(jm.complete)
-            yield Completion(
-                start=offset - delta,
-                end=offset,
-                text=jm.name_with_symbols,
-                type=_UNKNOWN_TYPE,  # don't compute type for speed
-                _origin="jedi",
-                signature="",
-            )
+            yield Completion(start=offset - delta,
+                             end=offset,
+                             text=jm.name_with_symbols,
+                             type='<unknown>',  # don't compute type for speed
+                             _origin='jedi',
+                             signature='')
+
+
+        start_offset = before.rfind(matched_text)
 
         # TODO:
         # Suppress this, right now just for debug.
-        if jedi_matches and non_jedi_results and self.debug:
-            some_start_offset = before.rfind(
-                next(iter(non_jedi_results.values()))["matched_fragment"]
-            )
-            yield Completion(
-                start=some_start_offset,
-                end=offset,
-                text="--jedi/ipython--",
-                _origin="debug",
-                type="none",
-                signature="",
-            )
+        if jedi_matches and matches and self.debug:
+            yield Completion(start=start_offset, end=offset, text='--jedi/ipython--',
+                             _origin='debug', type='none', signature='')
 
-        ordered = []
-        sortable = []
-
-        for origin, result in non_jedi_results.items():
-            matched_text = result["matched_fragment"]
-            start_offset = before.rfind(matched_text)
-            is_ordered = result.get("ordered", False)
-            container = ordered if is_ordered else sortable
-
-            # I'm unsure if this is always true, so let's assert and see if it
-            # crash
-            assert before.endswith(matched_text)
-
-            for simple_completion in result["completions"]:
-                completion = Completion(
-                    start=start_offset,
-                    end=offset,
-                    text=simple_completion.text,
-                    _origin=origin,
-                    signature="",
-                    type=simple_completion.type or _UNKNOWN_TYPE,
-                )
-                container.append(completion)
+        # I'm unsure if this is always true, so let's assert and see if it
+        # crash
+        assert before.endswith(matched_text)
+        for m, t in zip(matches, matches_origin):
+            yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>')
 
-        yield from list(self._deduplicate(ordered + self._sort(sortable)))[
-            :MATCHES_LIMIT
-        ]
 
     def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
         """Find completions for the given text and line context.
@@ -2628,56 +2050,7 @@ def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, S
                       PendingDeprecationWarning)
         # potential todo, FOLD the 3rd throw away argument of _complete
         # into the first 2 one.
-        # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?)
-        # TODO: should we deprecate now, or does it stay?
-
-        results = self._complete(
-            line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0
-        )
-
-        jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
-
-        return self._arrange_and_extract(
-            results,
-            # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version?
-            skip_matchers={jedi_matcher_id},
-            # this API does not support different start/end positions (fragments of token).
-            abort_if_offset_changes=True,
-        )
-
-    def _arrange_and_extract(
-        self,
-        results: Dict[str, MatcherResult],
-        skip_matchers: Set[str],
-        abort_if_offset_changes: bool,
-    ):
-
-        sortable = []
-        ordered = []
-        most_recent_fragment = None
-        for identifier, result in results.items():
-            if identifier in skip_matchers:
-                continue
-            if not result["completions"]:
-                continue
-            if not most_recent_fragment:
-                most_recent_fragment = result["matched_fragment"]
-            if (
-                abort_if_offset_changes
-                and result["matched_fragment"] != most_recent_fragment
-            ):
-                break
-            if result.get("ordered", False):
-                ordered.extend(result["completions"])
-            else:
-                sortable.extend(result["completions"])
-
-        if not most_recent_fragment:
-            most_recent_fragment = ""  # to satisfy typechecker (and just in case)
-
-        return most_recent_fragment, [
-            m.text for m in self._deduplicate(ordered + self._sort(sortable))
-        ]
+        return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2]
 
     def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
                   full_text=None) -> _CompleteResult:
@@ -2712,10 +2085,14 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
 
         Returns
         -------
-        An ordered dictionary where keys are identifiers of completion
-        matchers and values are ``MatcherResult``s.
+        A tuple of N elements which are (likely):
+            matched_text: ? the text that the complete matched
+            matches: list of completions ?
+            matches_origin: ? list same length as matches, and where each completion came from
+            jedi_matches: list of Jedi matches, have it's own structure.
         """
 
+
         # if the cursor position isn't given, the only sane assumption we can
         # make is that it's at the end of the line (the common case)
         if cursor_pos is None:
@@ -2727,156 +2104,98 @@ def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
         # if text is either None or an empty string, rely on the line buffer
         if (not line_buffer) and full_text:
             line_buffer = full_text.split('\n')[cursor_line]
-        if not text:  # issue #11508: check line_buffer before calling split_line
-            text = (
-                self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ""
-            )
+        if not text: # issue #11508: check line_buffer before calling split_line
+            text = self.splitter.split_line(line_buffer, cursor_pos)  if line_buffer else ''
+
+        if self.backslash_combining_completions:
+            # allow deactivation of these on windows.
+            base_text = text if not line_buffer else line_buffer[:cursor_pos]
+
+            for meth in (self.latex_matches,
+                         self.unicode_name_matches,
+                         back_latex_name_matches,
+                         back_unicode_name_matches,
+                         self.fwd_unicode_match):
+                name_text, name_matches = meth(base_text)
+                if name_text:
+                    return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \
+                           [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ())
+
 
         # If no line buffer is given, assume the input text is all there was
         if line_buffer is None:
             line_buffer = text
 
-        # deprecated - do not use `line_buffer` in new code.
         self.line_buffer = line_buffer
         self.text_until_cursor = self.line_buffer[:cursor_pos]
 
-        if not full_text:
-            full_text = line_buffer
-
-        context = CompletionContext(
-            full_text=full_text,
-            cursor_position=cursor_pos,
-            cursor_line=cursor_line,
-            token=text,
-            limit=MATCHES_LIMIT,
-        )
+        # Do magic arg matches
+        for matcher in self.magic_arg_matchers:
+            matches = list(matcher(line_buffer))[:MATCHES_LIMIT]
+            if matches:
+                origins = [matcher.__qualname__] * len(matches)
+                return _CompleteResult(text, matches, origins, ())
 
         # Start with a clean slate of completions
-        results = {}
-
-        jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
-
-        suppressed_matchers = set()
-
-        matchers = {
-            _get_matcher_id(matcher): matcher
-            for matcher in sorted(
-                self.matchers, key=_get_matcher_priority, reverse=True
-            )
-        }
-
-        for matcher_id, matcher in matchers.items():
-            api_version = _get_matcher_api_version(matcher)
-            matcher_id = _get_matcher_id(matcher)
-
-            if matcher_id in self.disable_matchers:
-                continue
-
-            if matcher_id in results:
-                warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
-
-            if matcher_id in suppressed_matchers:
-                continue
-
-            try:
-                if api_version == 1:
-                    result = _convert_matcher_v1_result_to_v2(
-                        matcher(text), type=_UNKNOWN_TYPE
-                    )
-                elif api_version == 2:
-                    result = cast(matcher, MatcherAPIv2)(context)
-                else:
-                    raise ValueError(f"Unsupported API version {api_version}")
-            except:
-                # Show the ugly traceback if the matcher causes an
-                # exception, but do NOT crash the kernel!
-                sys.excepthook(*sys.exc_info())
-                continue
-
-            # set default value for matched fragment if suffix was not selected.
-            result["matched_fragment"] = result.get("matched_fragment", context.token)
-
-            if not suppressed_matchers:
-                suppression_recommended = result.get("suppress", False)
-
-                suppression_config = (
-                    self.suppress_competing_matchers.get(matcher_id, None)
-                    if isinstance(self.suppress_competing_matchers, dict)
-                    else self.suppress_competing_matchers
-                )
-                should_suppress = (
-                    (suppression_config is True)
-                    or (suppression_recommended and (suppression_config is not False))
-                ) and len(result["completions"])
-
-                if should_suppress:
-                    suppression_exceptions = result.get("do_not_suppress", set())
-                    try:
-                        to_suppress = set(suppression_recommended)
-                    except TypeError:
-                        to_suppress = set(matchers)
-                    suppressed_matchers = to_suppress - suppression_exceptions
-
-                    new_results = {}
-                    for previous_matcher_id, previous_result in results.items():
-                        if previous_matcher_id not in suppressed_matchers:
-                            new_results[previous_matcher_id] = previous_result
-                    results = new_results
-
-            results[matcher_id] = result
-
-        _, matches = self._arrange_and_extract(
-            results,
-            # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission?
-            #  if it was omission, we can remove the filtering step, otherwise remove this comment.
-            skip_matchers={jedi_matcher_id},
-            abort_if_offset_changes=False,
-        )
+        matches = []
 
-        # populate legacy stateful API
-        self.matches = matches
+        # FIXME: we should extend our api to return a dict with completions for
+        # different types of objects.  The rlcomplete() method could then
+        # simply collapse the dict into a list for readline, but we'd have
+        # richer completion semantics in other environments.
+        is_magic_prefix = len(text) > 0 and text[0] == "%"
+        completions: Iterable[Any] = []
+        if self.use_jedi and not is_magic_prefix:
+            if not full_text:
+                full_text = line_buffer
+            completions = self._jedi_matches(
+                cursor_pos, cursor_line, full_text)
+
+        if self.merge_completions:
+            matches = []
+            for matcher in self.matchers:
+                try:
+                    matches.extend([(m, matcher.__qualname__)
+                                    for m in matcher(text)])
+                except:
+                    # Show the ugly traceback if the matcher causes an
+                    # exception, but do NOT crash the kernel!
+                    sys.excepthook(*sys.exc_info())
+        else:
+            for matcher in self.matchers:
+                matches = [(m, matcher.__qualname__)
+                            for m in matcher(text)]
+                if matches:
+                    break
+                    
+        seen = set()
+        filtered_matches = set()
+        for m in matches:
+            t, c = m
+            if t not in seen:
+                filtered_matches.add(m)
+                seen.add(t)
 
-        return results
+        _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0]))
 
-    @staticmethod
-    def _deduplicate(
-        matches: Sequence[SimpleCompletion],
-    ) -> Iterable[SimpleCompletion]:
-        filtered_matches = {}
-        for match in matches:
-            text = match.text
-            if (
-                text not in filtered_matches
-                or filtered_matches[text].type == _UNKNOWN_TYPE
-            ):
-                filtered_matches[text] = match
-
-        return filtered_matches.values()
+        custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []]
+        
+        _filtered_matches = custom_res or _filtered_matches
+        
+        _filtered_matches = _filtered_matches[:MATCHES_LIMIT]
+        _matches = [m[0] for m in _filtered_matches]
+        origins = [m[1] for m in _filtered_matches]
 
-    @staticmethod
-    def _sort(matches: Sequence[SimpleCompletion]):
-        return sorted(matches, key=lambda x: completions_sorting_key(x.text))
-
-    @context_matcher()
-    def fwd_unicode_matcher(self, context: CompletionContext):
-        """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API."""
-        # TODO: use `context.limit` to terminate early once we matched the maximum
-        #  number that will be used downstream; can be added as an optional to
-        #  `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here.
-        fragment, matches = self.fwd_unicode_match(context.text_until_cursor)
-        return _convert_matcher_v1_result_to_v2(
-            matches, type="unicode", fragment=fragment, suppress_if_matches=True
-        )
+        self.matches = _matches
 
-    def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]:
+        return _CompleteResult(text, _matches, origins, completions)
+        
+    def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]:
         """
         Forward match a string starting with a backslash with a list of
         potential Unicode completions.
 
-        Will compute list of Unicode character names on first call and cache it.
-
-        .. deprecated:: 8.6
-            You can use :meth:`fwd_unicode_matcher` instead.
+        Will compute list list of Unicode character names on first call and cache it.
 
         Returns
         -------
diff --git a/IPython/core/extensions.py b/IPython/core/extensions.py
index 21fba40ea..ce419e1bd 100644
--- a/IPython/core/extensions.py
+++ b/IPython/core/extensions.py
@@ -88,7 +88,13 @@ def _load_extension(self, module_str: str):
 
         with self.shell.builtin_trap:
             if module_str not in sys.modules:
-                mod = import_module(module_str)
+                with prepended_to_syspath(self.ipython_extension_dir):
+                    mod = import_module(module_str)
+                    if mod.__file__.startswith(self.ipython_extension_dir):
+                        print(("Loading extensions from {dir} is deprecated. "
+                               "We recommend managing extensions like any "
+                               "other Python packages, in site-packages.").format(
+                              dir=compress_user(self.ipython_extension_dir)))
             mod = sys.modules[module_str]
             if self._call_load_ipython_extension(mod):
                 self.loaded.add(module_str)
@@ -149,3 +155,13 @@ def _call_unload_ipython_extension(self, mod):
         if hasattr(mod, 'unload_ipython_extension'):
             mod.unload_ipython_extension(self.shell)
             return True
+
+    @undoc
+    def install_extension(self, url, filename=None):
+        """
+        Deprecated.
+        """
+        # Ensure the extension directory exists
+        raise DeprecationWarning(
+            '`install_extension` and the `install_ext` magic have been deprecated since IPython 4.0'
+            'Use pip or other package managers to manage ipython extensions.')
diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py
index 37f0e7699..e4e385a95 100644
--- a/IPython/core/inputtransformer2.py
+++ b/IPython/core/inputtransformer2.py
@@ -429,17 +429,13 @@ def transform(self, lines):
 
         return lines_before + [new_line] + lines_after
 
-
-_help_end_re = re.compile(
-    r"""(%{0,2}
-    (?!\d)[\w*]+            # Variable name
-    (\.(?!\d)[\w*]+|\[-?[0-9]+\])*       # .etc.etc or [0], we only support literal integers.
-    )
-    (\?\??)$                # ? or ??
-    """,
-    re.VERBOSE,
-)
-
+_help_end_re = re.compile(r"""(%{0,2}
+                              (?!\d)[\w*]+            # Variable name
+                              (\.(?!\d)[\w*]+)*       # .etc.etc
+                              )
+                              (\?\??)$                # ? or ??
+                              """,
+                              re.VERBOSE)
 
 class HelpEnd(TokenTransformBase):
     """Transformer for help syntax: obj? and obj??"""
@@ -468,11 +464,10 @@ def find(cls, tokens_by_line):
     def transform(self, lines):
         """Transform a help command found by the ``find()`` classmethod.
         """
-
-        piece = "".join(lines[self.start_line : self.q_line + 1])
-        indent, content = piece[: self.start_col], piece[self.start_col :]
-        lines_before = lines[: self.start_line]
-        lines_after = lines[self.q_line + 1 :]
+        piece = ''.join(lines[self.start_line:self.q_line+1])
+        indent, content = piece[:self.start_col], piece[self.start_col:]
+        lines_before = lines[:self.start_line]
+        lines_after = lines[self.q_line + 1:]
 
         m = _help_end_re.search(content)
         if not m:
@@ -548,13 +543,8 @@ def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):
 
 def show_linewise_tokens(s: str):
     """For investigation and debugging"""
-    warnings.warn(
-        "show_linewise_tokens is deprecated since IPython 8.6",
-        DeprecationWarning,
-        stacklevel=2,
-    )
-    if not s.endswith("\n"):
-        s += "\n"
+    if not s.endswith('\n'):
+        s += '\n'
     lines = s.splitlines(keepends=True)
     for line in make_tokens_by_line(lines):
         print("Line -------")
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
index 649ceba90..069126406 100644
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -147,19 +147,6 @@ class ProvisionalWarning(DeprecationWarning):
 # Utilities
 #-----------------------------------------------------------------------------
 
-
-def is_integer_string(s: str):
-    """
-    Variant of "str.isnumeric()" that allow negative values and other ints.
-    """
-    try:
-        int(s)
-        return True
-    except ValueError:
-        return False
-    raise ValueError("Unexpected error")
-
-
 @undoc
 def softspace(file, newvalue):
     """Copied from code.py, to remove the dependency"""
@@ -1550,33 +1537,10 @@ def _ofind(self, oname, namespaces=None):
         Has special code to detect magic functions.
         """
         oname = oname.strip()
-        raw_parts = oname.split(".")
-        parts = []
-        parts_ok = True
-        for p in raw_parts:
-            if p.endswith("]"):
-                var, *indices = p.split("[")
-                if not var.isidentifier():
-                    parts_ok = False
-                    break
-                parts.append(var)
-                for ind in indices:
-                    if ind[-1] != "]" and not is_integer_string(ind[:-1]):
-                        parts_ok = False
-                        break
-                    parts.append(ind[:-1])
-                continue
-
-            if not p.isidentifier():
-                parts_ok = False
-            parts.append(p)
-
-        if (
-            not oname.startswith(ESC_MAGIC)
-            and not oname.startswith(ESC_MAGIC2)
-            and not parts_ok
-        ):
-            return {"found": False}
+        if not oname.startswith(ESC_MAGIC) and \
+                not oname.startswith(ESC_MAGIC2) and \
+                not all(a.isidentifier() for a in oname.split(".")):
+            return {'found': False}
 
         if namespaces is None:
             # Namespaces to search in:
@@ -1598,7 +1562,7 @@ def _ofind(self, oname, namespaces=None):
         # Look for the given name by splitting it in parts.  If the head is
         # found, then we look for all the remaining parts as members, and only
         # declare success if we can find them all.
-        oname_parts = parts
+        oname_parts = oname.split('.')
         oname_head, oname_rest = oname_parts[0],oname_parts[1:]
         for nsname,ns in namespaces:
             try:
@@ -1615,10 +1579,7 @@ def _ofind(self, oname, namespaces=None):
                         if idx == len(oname_rest) - 1:
                             obj = self._getattr_property(obj, part)
                         else:
-                            if is_integer_string(part):
-                                obj = obj[int(part)]
-                            else:
-                                obj = getattr(obj, part)
+                            obj = getattr(obj, part)
                     except:
                         # Blanket except b/c some badly implemented objects
                         # allow __getattr__ to raise exceptions other than
@@ -1682,10 +1643,7 @@ def _getattr_property(obj, attrname):
                 #
                 # The universal alternative is to traverse the mro manually
                 # searching for attrname in class dicts.
-                if is_integer_string(attrname):
-                    return obj[int(attrname)]
-                else:
-                    attr = getattr(type(obj), attrname)
+                attr = getattr(type(obj), attrname)
             except AttributeError:
                 pass
             else:
diff --git a/IPython/core/magics/config.py b/IPython/core/magics/config.py
index f442ba152..c1387b601 100644
--- a/IPython/core/magics/config.py
+++ b/IPython/core/magics/config.py
@@ -80,9 +80,6 @@ def config(self, s):
                 Enable debug for the Completer. Mostly print extra information for
                 experimental jedi integration.
                 Current: False
-            IPCompleter.disable_matchers=<list-item-1>...
-                List of matchers to disable.
-                Current: []
             IPCompleter.greedy=<Bool>
                 Activate greedy completion
                         PENDING DEPRECATION. this is now mostly taken care of with Jedi.
@@ -105,8 +102,6 @@ def config(self, s):
                 Whether to merge completion results into a single list
                         If False, only the completion results from the first non-empty
                         completer will be returned.
-                        As of version 8.6.0, setting the value to ``False`` is an alias for:
-                        ``IPCompleter.suppress_competing_matchers = True.``.
                 Current: True
             IPCompleter.omit__names=<Enum>
                 Instruct the completer to omit private method names
@@ -122,24 +117,6 @@ def config(self, s):
             IPCompleter.profiler_output_dir=<Unicode>
                 Template for path at which to output profile data for completions.
                 Current: '.completion_profiles'
-            IPCompleter.suppress_competing_matchers=<Union>
-                Whether to suppress completions from other *Matchers*.
-                When set to ``None`` (default) the matchers will attempt to auto-detect
-                whether suppression of other matchers is desirable. For example, at the
-                beginning of a line followed by `%` we expect a magic completion to be the
-                only applicable option, and after ``my_dict['`` we usually expect a
-                completion with an existing dictionary key.
-                If you want to disable this heuristic and see completions from all matchers,
-                set ``IPCompleter.suppress_competing_matchers = False``. To disable the
-                heuristic for specific matchers provide a dictionary mapping:
-                ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher':
-                False}``.
-                Set ``IPCompleter.suppress_competing_matchers = True`` to limit completions
-                to the set of matchers with the highest priority; this is equivalent to
-                ``IPCompleter.merge_completions`` and can be beneficial for performance, but
-                will sometimes omit relevant candidates from matchers further down the
-                priority list.
-                Current: None
             IPCompleter.use_jedi=<Bool>
                 Experimental: Use Jedi to generate autocompletions. Default to True if jedi
                 is installed.
diff --git a/IPython/core/magics/namespace.py b/IPython/core/magics/namespace.py
index 5da8f7161..c86d3de9b 100644
--- a/IPython/core/magics/namespace.py
+++ b/IPython/core/magics/namespace.py
@@ -492,7 +492,7 @@ def reset(self, parameter_s=''):
         --aggressive
             Try to aggressively remove modules from sys.modules ; this
             may allow you to reimport Python modules that have been updated and
-            pick up changes, but can have unintended consequences.
+            pick up changes, but can have unattended consequences.
 
         in
             reset input history
diff --git a/IPython/core/ultratb.py b/IPython/core/ultratb.py
index 8f40c63b7..125ee9a78 100644
--- a/IPython/core/ultratb.py
+++ b/IPython/core/ultratb.py
@@ -610,8 +610,6 @@ class VerboseTB(TBTools):
     traceback, to be used with alternate interpreters (because their own code
     would appear in the traceback)."""
 
-    _tb_highlight = "bg:ansiyellow"
-
     def __init__(
         self,
         color_scheme: str = "Linux",
@@ -838,7 +836,7 @@ def get_records(
         before = context - after
         if self.has_colors:
             style = get_style_by_name("default")
-            style = stack_data.style_with_executing_node(style, self._tb_highlight)
+            style = stack_data.style_with_executing_node(style, "bg:ansiyellow")
             formatter = Terminal256Formatter(style=style)
         else:
             formatter = None
diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py
index b7739c885..cf2ab4277 100644
--- a/IPython/terminal/interactiveshell.py
+++ b/IPython/terminal/interactiveshell.py
@@ -405,14 +405,14 @@ def _autosuggestions_provider_changed(self, change):
     @observe('term_title')
     def init_term_title(self, change=None):
         # Enable or disable the terminal title.
-        if self.term_title:
+        if self.term_title and _is_tty:
             toggle_set_term_title(True)
             set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
         else:
             toggle_set_term_title(False)
 
     def restore_term_title(self):
-        if self.term_title:
+        if self.term_title and _is_tty:
             restore_term_title()
 
     def init_display_formatter(self):
diff --git a/IPython/terminal/ipapp.py b/IPython/terminal/ipapp.py
index a87eb2f44..df4648b89 100755
--- a/IPython/terminal/ipapp.py
+++ b/IPython/terminal/ipapp.py
@@ -318,6 +318,7 @@ def start(self):
             self.shell.mainloop()
         else:
             self.log.debug("IPython not interactive...")
+            self.shell.restore_term_title()
             if not self.shell.last_execution_succeeded:
                 sys.exit(1)
 
diff --git a/IPython/utils/decorators.py b/IPython/utils/decorators.py
index bc7589cd3..47791d7ca 100644
--- a/IPython/utils/decorators.py
+++ b/IPython/utils/decorators.py
@@ -2,7 +2,7 @@
 """Decorators that don't go anywhere else.
 
 This module contains misc. decorators that don't really go with another module
-in :mod:`IPython.utils`. Before putting something here please see if it should
+in :mod:`IPython.utils`. Beore putting something here please see if it should
 go into another topical module in :mod:`IPython.utils`.
 """
 
@@ -16,10 +16,6 @@
 #-----------------------------------------------------------------------------
 # Imports
 #-----------------------------------------------------------------------------
-from typing import Sequence
-
-from IPython.utils.docs import GENERATING_DOCUMENTATION
-
 
 #-----------------------------------------------------------------------------
 # Code
@@ -52,7 +48,6 @@ def wrapper(*args,**kw):
     wrapper.__doc__ = func.__doc__
     return wrapper
 
-
 def undoc(func):
     """Mark a function or class as undocumented.
 
@@ -61,23 +56,3 @@ def undoc(func):
     """
     return func
 
-
-def sphinx_options(
-    show_inheritance: bool = True,
-    show_inherited_members: bool = False,
-    exclude_inherited_from: Sequence[str] = tuple(),
-):
-    """Set sphinx options"""
-
-    def wrapper(func):
-        if not GENERATING_DOCUMENTATION:
-            return func
-
-        func._sphinx_options = dict(
-            show_inheritance=show_inheritance,
-            show_inherited_members=show_inherited_members,
-            exclude_inherited_from=exclude_inherited_from,
-        )
-        return func
-
-    return wrapper
diff --git a/IPython/utils/docs.py b/IPython/utils/docs.py
deleted file mode 100644
index 6a97815cd..000000000
--- a/IPython/utils/docs.py
+++ /dev/null
@@ -1,3 +0,0 @@
-import os
-
-GENERATING_DOCUMENTATION = os.environ.get("IN_SPHINX_RUN", None) == "True"
diff --git a/IPython/utils/terminal.py b/IPython/utils/terminal.py
index 49fd3fe17..161a9ae60 100644
--- a/IPython/utils/terminal.py
+++ b/IPython/utils/terminal.py
@@ -62,15 +62,27 @@ def _restore_term_title():
     pass
 
 
+_xterm_term_title_saved = False
+
+
 def _set_term_title_xterm(title):
     """ Change virtual terminal title in xterm-workalikes """
-    # save the current title to the xterm "stack"
-    sys.stdout.write('\033[22;0t') 
+    global _xterm_term_title_saved
+    # Only save the title the first time we set, otherwise restore will only
+    # go back one title (probably undoing a %cd title change).
+    if not _xterm_term_title_saved:
+        # save the current title to the xterm "stack"
+        sys.stdout.write("\033[22;0t")
+        _xterm_term_title_saved = True
     sys.stdout.write('\033]0;%s\007' % title)
 
 
 def _restore_term_title_xterm():
+    # Make sure the restore has at least one accompanying set.
+    global _xterm_term_title_saved
+    assert _xterm_term_title_saved
     sys.stdout.write('\033[23;0t') 
+    _xterm_term_title_saved = False
 
 
 if os.name == 'posix':
diff --git a/docs/source/conf.py b/docs/source/conf.py
index d04d4637b..29212af8b 100755
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -41,14 +41,6 @@
     html_theme = "sphinx_rtd_theme"
     html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
 
-# Allow Python scripts to change behaviour during sphinx run
-os.environ["IN_SPHINX_RUN"] = "True"
-
-autodoc_type_aliases = {
-    "Matcher": " IPython.core.completer.Matcher",
-    "MatcherAPIv1": " IPython.core.completer.MatcherAPIv1",
-}
-
 # If your extensions are in another directory, add it here. If the directory
 # is relative to the documentation root, use os.path.abspath to make it
 # absolute, like shown here.
diff --git a/docs/source/config/extensions/index.rst b/docs/source/config/extensions/index.rst
index 4b0a2222a..e3c9cab42 100644
--- a/docs/source/config/extensions/index.rst
+++ b/docs/source/config/extensions/index.rst
@@ -6,7 +6,8 @@ IPython extensions
 
 A level above configuration are IPython extensions, Python modules which modify
 the behaviour of the shell. They are referred to by an importable module name,
-and can be placed anywhere you'd normally import from.
+and can be placed anywhere you'd normally import from, or in
+``.ipython/extensions/``.
 
 Getting extensions
 ==================
@@ -70,7 +71,10 @@ Useful :class:`InteractiveShell` methods include :meth:`~IPython.core.interactiv
    :ref:`defining_magics`
 
 You can put your extension modules anywhere you want, as long as they can be
-imported by Python's standard import mechanism.
+imported by Python's standard import mechanism. However, to make it easy to
+write extensions, you can also put your extensions in :file:`extensions/`
+within the :ref:`IPython directory <ipythondir>`. This directory is
+added to :data:`sys.path` automatically.
 
 When your extension is ready for general use, please add it to the `extensions
 index <https://github.com/ipython/ipython/wiki/Extensions-Index>`_. We also
diff --git a/docs/sphinxext/apigen.py b/docs/sphinxext/apigen.py
index 47dc11019..e58493b17 100644
--- a/docs/sphinxext/apigen.py
+++ b/docs/sphinxext/apigen.py
@@ -24,9 +24,14 @@
 import os
 import re
 from importlib import import_module
-from types import SimpleNamespace as Obj
 
 
+class Obj(object):
+    '''Namespace to hold arbitrary information.'''
+    def __init__(self, **kwargs):
+        for k, v in kwargs.items():
+            setattr(self, k, v)
+                                        
 class FuncClsScanner(ast.NodeVisitor):
     """Scan a module for top-level functions and classes.
     
@@ -37,7 +42,7 @@ def __init__(self):
         self.classes = []
         self.classes_seen = set()
         self.functions = []
-
+    
     @staticmethod
     def has_undoc_decorator(node):
         return any(isinstance(d, ast.Name) and d.id == 'undoc' \
@@ -57,15 +62,11 @@ def visit_FunctionDef(self, node):
             self.functions.append(node.name)
     
     def visit_ClassDef(self, node):
-        if (
-            not (node.name.startswith("_") or self.has_undoc_decorator(node))
-            and node.name not in self.classes_seen
-        ):
-            cls = Obj(name=node.name, sphinx_options={})
-            cls.has_init = any(
-                isinstance(n, ast.FunctionDef) and n.name == "__init__"
-                for n in node.body
-            )
+        if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \
+                and node.name not in self.classes_seen:
+            cls = Obj(name=node.name)
+            cls.has_init = any(isinstance(n, ast.FunctionDef) and \
+                                n.name=='__init__' for n in node.body)
             self.classes.append(cls)
             self.classes_seen.add(node.name)
     
@@ -220,11 +221,7 @@ def _import_funcs_classes(self, uri):
         funcs, classes = [], []
         for name, obj in ns.items():
             if inspect.isclass(obj):
-                cls = Obj(
-                    name=name,
-                    has_init="__init__" in obj.__dict__,
-                    sphinx_options=getattr(obj, "_sphinx_options", {}),
-                )
+                cls = Obj(name=name, has_init='__init__' in obj.__dict__)
                 classes.append(cls)
             elif inspect.isfunction(obj):
                 funcs.append(name)
@@ -282,18 +279,10 @@ def generate_api_doc(self, uri):
                   self.rst_section_levels[2] * len(subhead) + '\n'
 
         for c in classes:
-            opts = c.sphinx_options
-            ad += "\n.. autoclass:: " + c.name + "\n"
+            ad += '\n.. autoclass:: ' + c.name + '\n'
             # must NOT exclude from index to keep cross-refs working
-            ad += "  :members:\n"
-            if opts.get("show_inheritance", True):
-                ad += "  :show-inheritance:\n"
-            if opts.get("show_inherited_members", False):
-                exclusions_list = opts.get("exclude_inherited_from", [])
-                exclusions = (
-                    (" " + " ".join(exclusions_list)) if exclusions_list else ""
-                )
-                ad += f"  :inherited-members:{exclusions}\n"
+            ad += '  :members:\n' \
+                  '  :show-inheritance:\n'
             if c.has_init:
                   ad += '\n  .. automethod:: __init__\n'
         
diff --git a/setup.cfg b/setup.cfg
index b3a26586c..6004f3a4d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -54,7 +54,6 @@ doc =
     matplotlib
     stack_data
     pytest<7
-    typing_extensions
     %(test)s
 kernel =
     ipykernel
@@ -79,7 +78,7 @@ test_extra =
     curio
     matplotlib!=3.2.0
     nbformat
-    numpy>=1.20
+    numpy>=1.19
     pandas
     trio
 all =


Test patch:
diff --git a/IPython/core/tests/test_completer.py b/IPython/core/tests/test_completer.py
index fd72cf7d5..746a1e682 100644
--- a/IPython/core/tests/test_completer.py
+++ b/IPython/core/tests/test_completer.py
@@ -24,9 +24,6 @@
     provisionalcompleter,
     match_dict_keys,
     _deduplicate_completions,
-    completion_matcher,
-    SimpleCompletion,
-    CompletionContext,
 )
 
 # -----------------------------------------------------------------------------
@@ -112,16 +109,6 @@ def greedy_completion():
         ip.Completer.greedy = greedy_original
 
 
-@contextmanager
-def custom_matchers(matchers):
-    ip = get_ipython()
-    try:
-        ip.Completer.custom_matchers.extend(matchers)
-        yield
-    finally:
-        ip.Completer.custom_matchers.clear()
-
-
 def test_protect_filename():
     if sys.platform == "win32":
         pairs = [
@@ -311,7 +298,7 @@ def test_back_unicode_completion(self):
         ip = get_ipython()
 
         name, matches = ip.complete("\\Ⅴ")
-        self.assertEqual(matches, ["\\ROMAN NUMERAL FIVE"])
+        self.assertEqual(matches, ("\\ROMAN NUMERAL FIVE",))
 
     def test_forward_unicode_completion(self):
         ip = get_ipython()
@@ -392,12 +379,6 @@ def test_local_file_completions(self):
 
     def test_quoted_file_completions(self):
         ip = get_ipython()
-
-        def _(text):
-            return ip.Completer._complete(
-                cursor_line=0, cursor_pos=len(text), full_text=text
-            )["IPCompleter.file_matcher"]["completions"]
-
         with TemporaryWorkingDirectory():
             name = "foo'bar"
             open(name, "w", encoding="utf-8").close()
@@ -406,16 +387,25 @@ def _(text):
             escaped = name if sys.platform == "win32" else "foo\\'bar"
 
             # Single quote matches embedded single quote
-            c = _("open('foo")[0]
-            self.assertEqual(c.text, escaped)
+            text = "open('foo"
+            c = ip.Completer._complete(
+                cursor_line=0, cursor_pos=len(text), full_text=text
+            )[1]
+            self.assertEqual(c, [escaped])
 
             # Double quote requires no escape
-            c = _('open("foo')[0]
-            self.assertEqual(c.text, name)
+            text = 'open("foo'
+            c = ip.Completer._complete(
+                cursor_line=0, cursor_pos=len(text), full_text=text
+            )[1]
+            self.assertEqual(c, [name])
 
             # No quote requires an escape
-            c = _("%ls foo")[0]
-            self.assertEqual(c.text, escaped)
+            text = "%ls foo"
+            c = ip.Completer._complete(
+                cursor_line=0, cursor_pos=len(text), full_text=text
+            )[1]
+            self.assertEqual(c, [escaped])
 
     def test_all_completions_dups(self):
         """
@@ -485,17 +475,6 @@ def test_completion_have_signature(self):
             "encoding" in c.signature
         ), "Signature of function was not found by completer"
 
-    def test_completions_have_type(self):
-        """
-        Lets make sure matchers provide completion type.
-        """
-        ip = get_ipython()
-        with provisionalcompleter():
-            ip.Completer.use_jedi = False
-            completions = ip.Completer.completions("%tim", 3)
-            c = next(completions)  # should be `%time` or similar
-        assert c.type == "magic", "Type of magic was not assigned by completer"
-
     @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0")
     def test_deduplicate_completions(self):
         """
@@ -1294,153 +1273,3 @@ def test_percent_symbol_restrict_to_magic_completions(self):
             completions = completer.completions(text, len(text))
             for c in completions:
                 self.assertEqual(c.text[0], "%")
-
-    def test_fwd_unicode_restricts(self):
-        ip = get_ipython()
-        completer = ip.Completer
-        text = "\\ROMAN NUMERAL FIVE"
-
-        with provisionalcompleter():
-            completer.use_jedi = True
-            completions = [
-                completion.text for completion in completer.completions(text, len(text))
-            ]
-            self.assertEqual(completions, ["\u2164"])
-
-    def test_dict_key_restrict_to_dicts(self):
-        """Test that dict key suppresses non-dict completion items"""
-        ip = get_ipython()
-        c = ip.Completer
-        d = {"abc": None}
-        ip.user_ns["d"] = d
-
-        text = 'd["a'
-
-        def _():
-            with provisionalcompleter():
-                c.use_jedi = True
-                return [
-                    completion.text for completion in c.completions(text, len(text))
-                ]
-
-        completions = _()
-        self.assertEqual(completions, ["abc"])
-
-        # check that it can be disabled in granular manner:
-        cfg = Config()
-        cfg.IPCompleter.suppress_competing_matchers = {
-            "IPCompleter.dict_key_matcher": False
-        }
-        c.update_config(cfg)
-
-        completions = _()
-        self.assertIn("abc", completions)
-        self.assertGreater(len(completions), 1)
-
-    def test_matcher_suppression(self):
-        @completion_matcher(identifier="a_matcher")
-        def a_matcher(text):
-            return ["completion_a"]
-
-        @completion_matcher(identifier="b_matcher", api_version=2)
-        def b_matcher(context: CompletionContext):
-            text = context.token
-            result = {"completions": [SimpleCompletion("completion_b")]}
-
-            if text == "suppress c":
-                result["suppress"] = {"c_matcher"}
-
-            if text.startswith("suppress all"):
-                result["suppress"] = True
-                if text == "suppress all but c":
-                    result["do_not_suppress"] = {"c_matcher"}
-                if text == "suppress all but a":
-                    result["do_not_suppress"] = {"a_matcher"}
-
-            return result
-
-        @completion_matcher(identifier="c_matcher")
-        def c_matcher(text):
-            return ["completion_c"]
-
-        with custom_matchers([a_matcher, b_matcher, c_matcher]):
-            ip = get_ipython()
-            c = ip.Completer
-
-            def _(text, expected):
-                c.use_jedi = False
-                s, matches = c.complete(text)
-                self.assertEqual(expected, matches)
-
-            _("do not suppress", ["completion_a", "completion_b", "completion_c"])
-            _("suppress all", ["completion_b"])
-            _("suppress all but a", ["completion_a", "completion_b"])
-            _("suppress all but c", ["completion_b", "completion_c"])
-
-            def configure(suppression_config):
-                cfg = Config()
-                cfg.IPCompleter.suppress_competing_matchers = suppression_config
-                c.update_config(cfg)
-
-            # test that configuration takes priority over the run-time decisions
-
-            configure(False)
-            _("suppress all", ["completion_a", "completion_b", "completion_c"])
-
-            configure({"b_matcher": False})
-            _("suppress all", ["completion_a", "completion_b", "completion_c"])
-
-            configure({"a_matcher": False})
-            _("suppress all", ["completion_b"])
-
-            configure({"b_matcher": True})
-            _("do not suppress", ["completion_b"])
-
-    def test_matcher_disabling(self):
-        @completion_matcher(identifier="a_matcher")
-        def a_matcher(text):
-            return ["completion_a"]
-
-        @completion_matcher(identifier="b_matcher")
-        def b_matcher(text):
-            return ["completion_b"]
-
-        def _(expected):
-            s, matches = c.complete("completion_")
-            self.assertEqual(expected, matches)
-
-        with custom_matchers([a_matcher, b_matcher]):
-            ip = get_ipython()
-            c = ip.Completer
-
-            _(["completion_a", "completion_b"])
-
-            cfg = Config()
-            cfg.IPCompleter.disable_matchers = ["b_matcher"]
-            c.update_config(cfg)
-
-            _(["completion_a"])
-
-            cfg.IPCompleter.disable_matchers = []
-            c.update_config(cfg)
-
-    def test_matcher_priority(self):
-        @completion_matcher(identifier="a_matcher", priority=0, api_version=2)
-        def a_matcher(text):
-            return {"completions": [SimpleCompletion("completion_a")], "suppress": True}
-
-        @completion_matcher(identifier="b_matcher", priority=2, api_version=2)
-        def b_matcher(text):
-            return {"completions": [SimpleCompletion("completion_b")], "suppress": True}
-
-        def _(expected):
-            s, matches = c.complete("completion_")
-            self.assertEqual(expected, matches)
-
-        with custom_matchers([a_matcher, b_matcher]):
-            ip = get_ipython()
-            c = ip.Completer
-
-            _(["completion_b"])
-            a_matcher.matcher_priority = 3
-            _(["completion_a"])
diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py
index 509dd66dd..1c793ca29 100644
--- a/IPython/core/tests/test_magic.py
+++ b/IPython/core/tests/test_magic.py
@@ -84,7 +84,7 @@ def test_extract_symbols_raises_exception_with_non_python_code():
 def test_magic_not_found():
     # magic not found raises UsageError
     with pytest.raises(UsageError):
-        _ip.run_line_magic("doesntexist", "")
+        _ip.magic('doesntexist')
 
     # ensure result isn't success when a magic isn't found
     result = _ip.run_cell('%doesntexist')
@@ -116,14 +116,13 @@ def test_config():
     magic.
     """
     ## should not raise.
-    _ip.run_line_magic("config", "")
-
+    _ip.magic('config')
 
 def test_config_available_configs():
     """ test that config magic prints available configs in unique and
     sorted order. """
     with capture_output() as captured:
-        _ip.run_line_magic("config", "")
+        _ip.magic('config')
 
     stdout = captured.stdout
     config_classes = stdout.strip().split('\n')[1:]
@@ -132,7 +131,7 @@ def test_config_available_configs():
 def test_config_print_class():
     """ test that config with a classname prints the class's options. """
     with capture_output() as captured:
-        _ip.run_line_magic("config", "TerminalInteractiveShell")
+        _ip.magic('config TerminalInteractiveShell')
 
     stdout = captured.stdout
     assert re.match(
@@ -145,7 +144,7 @@ def test_rehashx():
     _ip.alias_manager.clear_aliases()
     del _ip.db['syscmdlist']
 
-    _ip.run_line_magic("rehashx", "")
+    _ip.magic('rehashx')
     # Practically ALL ipython development systems will have more than 10 aliases
 
     assert len(_ip.alias_manager.aliases) > 10
@@ -278,11 +277,11 @@ def test_macro():
     cmds = ["a=1", "def b():\n  return a**2", "print(a,b())"]
     for i, cmd in enumerate(cmds, start=1):
         ip.history_manager.store_inputs(i, cmd)
-    ip.run_line_magic("macro", "test 1-3")
+    ip.magic("macro test 1-3")
     assert ip.user_ns["test"].value == "\n".join(cmds) + "\n"
 
     # List macros
-    assert "test" in ip.run_line_magic("macro", "")
+    assert "test" in ip.magic("macro")
 
 
 def test_macro_run():
@@ -303,7 +302,7 @@ def test_magic_magic():
     """Test %magic"""
     ip = get_ipython()
     with capture_output() as captured:
-        ip.run_line_magic("magic", "")
+        ip.magic("magic")
 
     stdout = captured.stdout
     assert "%magic" in stdout
@@ -317,7 +316,7 @@ def test_numpy_reset_array_undec():
     _ip.ex("import numpy as np")
     _ip.ex("a = np.empty(2)")
     assert "a" in _ip.user_ns
-    _ip.run_line_magic("reset", "-f array")
+    _ip.magic("reset -f array")
     assert "a" not in _ip.user_ns
 
 
@@ -327,7 +326,7 @@ def test_reset_out():
     # test '%reset -f out', make an Out prompt
     _ip.run_cell("parrot", store_history=True)
     assert "dead" in [_ip.user_ns[x] for x in ("_", "__", "___")]
-    _ip.run_line_magic("reset", "-f out")
+    _ip.magic("reset -f out")
     assert "dead" not in [_ip.user_ns[x] for x in ("_", "__", "___")]
     assert len(_ip.user_ns["Out"]) == 0
 
@@ -337,7 +336,7 @@ def test_reset_in():
     # test '%reset -f in'
     _ip.run_cell("parrot", store_history=True)
     assert "parrot" in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
-    _ip.run_line_magic("reset", "-f in")
+    _ip.magic("%reset -f in")
     assert "parrot" not in [_ip.user_ns[x] for x in ("_i", "_ii", "_iii")]
     assert len(set(_ip.user_ns["In"])) == 1
 
@@ -345,10 +344,10 @@ def test_reset_in():
 def test_reset_dhist():
     "Test '%reset dhist' magic"
     _ip.run_cell("tmp = [d for d in _dh]")  # copy before clearing
-    _ip.run_line_magic("cd", os.path.dirname(pytest.__file__))
-    _ip.run_line_magic("cd", "-")
+    _ip.magic("cd " + os.path.dirname(pytest.__file__))
+    _ip.magic("cd -")
     assert len(_ip.user_ns["_dh"]) > 0
-    _ip.run_line_magic("reset", "-f dhist")
+    _ip.magic("reset -f dhist")
     assert len(_ip.user_ns["_dh"]) == 0
     _ip.run_cell("_dh = [d for d in tmp]")  # restore
 
@@ -473,8 +472,8 @@ def test_time_local_ns():
 
 def test_doctest_mode():
     "Toggle doctest_mode twice, it should be a no-op and run without error"
-    _ip.run_line_magic("doctest_mode", "")
-    _ip.run_line_magic("doctest_mode", "")
+    _ip.magic('doctest_mode')
+    _ip.magic('doctest_mode')
 
 
 def test_parse_options():
@@ -499,9 +498,7 @@ def test_parse_options_preserve_non_option_string():
 def test_run_magic_preserve_code_block():
     """Test to assert preservation of non-option part of magic-block, while running magic."""
     _ip.user_ns["spaces"] = []
-    _ip.run_line_magic(
-        "timeit", "-n1 -r1 spaces.append([s.count(' ') for s in ['document']])"
-    )
+    _ip.magic("timeit -n1 -r1 spaces.append([s.count(' ') for s in ['document']])")
     assert _ip.user_ns["spaces"] == [[0]]
 
 
@@ -512,13 +509,13 @@ def test_dirops():
     startdir = os.getcwd()
     ipdir = os.path.realpath(_ip.ipython_dir)
     try:
-        _ip.run_line_magic("cd", '"%s"' % ipdir)
+        _ip.magic('cd "%s"' % ipdir)
         assert curpath() == ipdir
-        _ip.run_line_magic("cd", "-")
+        _ip.magic('cd -')
         assert curpath() == startdir
-        _ip.run_line_magic("pushd", '"%s"' % ipdir)
+        _ip.magic('pushd "%s"' % ipdir)
         assert curpath() == ipdir
-        _ip.run_line_magic("popd", "")
+        _ip.magic('popd')
         assert curpath() == startdir
     finally:
         os.chdir(startdir)
@@ -545,7 +542,7 @@ def test_xmode():
     # Calling xmode three times should be a no-op
     xmode = _ip.InteractiveTB.mode
     for i in range(4):
-        _ip.run_line_magic("xmode", "")
+        _ip.magic("xmode")
     assert _ip.InteractiveTB.mode == xmode
 
 def test_reset_hard():
@@ -560,7 +557,7 @@ def __repr__(self):
     _ip.run_cell("a")
 
     assert monitor == []
-    _ip.run_line_magic("reset", "-f")
+    _ip.magic("reset -f")
     assert monitor == [1]
 
 class TestXdel(tt.TempFileMixin):
@@ -573,14 +570,14 @@ def test_xdel(self):
                "a = A()\n")
         self.mktmp(src)
         # %run creates some hidden references...
-        _ip.run_line_magic("run", "%s" % self.fname)
+        _ip.magic("run %s" % self.fname)
         # ... as does the displayhook.
         _ip.run_cell("a")
 
         monitor = _ip.user_ns["A"].monitor
         assert monitor == []
 
-        _ip.run_line_magic("xdel", "a")
+        _ip.magic("xdel a")
 
         # Check that a's __del__ method has been called.
         gc.collect(0)
@@ -617,7 +614,7 @@ class A(object):
         def __repr__(self):
             raise Exception()
     _ip.user_ns['a'] = A()
-    _ip.run_line_magic("whos", "")
+    _ip.magic("whos")
 
 def doctest_precision():
     """doctest for %precision
@@ -658,12 +655,12 @@ def test_psearch():
 def test_timeit_shlex():
     """test shlex issues with timeit (#1109)"""
     _ip.ex("def f(*a,**kw): pass")
-    _ip.run_line_magic("timeit", '-n1 "this is a bug".count(" ")')
-    _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1)')
-    _ip.run_line_magic("timeit", '-r1 -n1 f(" ", 1, " ", 2, " ")')
-    _ip.run_line_magic("timeit", '-r1 -n1 ("a " + "b")')
-    _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b")')
-    _ip.run_line_magic("timeit", '-r1 -n1 f("a " + "b ")')
+    _ip.magic('timeit -n1 "this is a bug".count(" ")')
+    _ip.magic('timeit -r1 -n1 f(" ", 1)')
+    _ip.magic('timeit -r1 -n1 f(" ", 1, " ", 2, " ")')
+    _ip.magic('timeit -r1 -n1 ("a " + "b")')
+    _ip.magic('timeit -r1 -n1 f("a " + "b")')
+    _ip.magic('timeit -r1 -n1 f("a " + "b ")')
 
 
 def test_timeit_special_syntax():
@@ -741,9 +738,9 @@ def test_extension():
     try:
         _ip.user_ns.pop('arq', None)
         invalidate_caches()   # Clear import caches
-        _ip.run_line_magic("load_ext", "daft_extension")
+        _ip.magic("load_ext daft_extension")
         assert _ip.user_ns["arq"] == 185
-        _ip.run_line_magic("unload_ext", "daft_extension")
+        _ip.magic("unload_ext daft_extension")
         assert 'arq' not in _ip.user_ns
     finally:
         sys.path.remove(daft_path)
@@ -758,17 +755,17 @@ def test_notebook_export_json():
         _ip.history_manager.store_inputs(i, cmd)
     with TemporaryDirectory() as td:
         outfile = os.path.join(td, "nb.ipynb")
-        _ip.run_line_magic("notebook", "%s" % outfile)
+        _ip.magic("notebook %s" % outfile)
 
 
 class TestEnv(TestCase):
 
     def test_env(self):
-        env = _ip.run_line_magic("env", "")
+        env = _ip.magic("env")
         self.assertTrue(isinstance(env, dict))
 
     def test_env_secret(self):
-        env = _ip.run_line_magic("env", "")
+        env = _ip.magic("env")
         hidden = "<hidden>"
         with mock.patch.dict(
             os.environ,
@@ -779,35 +776,35 @@ def test_env_secret(self):
                 "VAR": "abc"
             }
         ):
-            env = _ip.run_line_magic("env", "")
+            env = _ip.magic("env")
         assert env["API_KEY"] == hidden
         assert env["SECRET_THING"] == hidden
         assert env["JUPYTER_TOKEN"] == hidden
         assert env["VAR"] == "abc"
 
     def test_env_get_set_simple(self):
-        env = _ip.run_line_magic("env", "var val1")
+        env = _ip.magic("env var val1")
         self.assertEqual(env, None)
-        self.assertEqual(os.environ["var"], "val1")
-        self.assertEqual(_ip.run_line_magic("env", "var"), "val1")
-        env = _ip.run_line_magic("env", "var=val2")
+        self.assertEqual(os.environ['var'], 'val1')
+        self.assertEqual(_ip.magic("env var"), 'val1')
+        env = _ip.magic("env var=val2")
         self.assertEqual(env, None)
         self.assertEqual(os.environ['var'], 'val2')
 
     def test_env_get_set_complex(self):
-        env = _ip.run_line_magic("env", "var 'val1 '' 'val2")
+        env = _ip.magic("env var 'val1 '' 'val2")
         self.assertEqual(env, None)
         self.assertEqual(os.environ['var'], "'val1 '' 'val2")
-        self.assertEqual(_ip.run_line_magic("env", "var"), "'val1 '' 'val2")
-        env = _ip.run_line_magic("env", 'var=val2 val3="val4')
+        self.assertEqual(_ip.magic("env var"), "'val1 '' 'val2")
+        env = _ip.magic('env var=val2 val3="val4')
         self.assertEqual(env, None)
         self.assertEqual(os.environ['var'], 'val2 val3="val4')
 
     def test_env_set_bad_input(self):
-        self.assertRaises(UsageError, lambda: _ip.run_line_magic("set_env", "var"))
+        self.assertRaises(UsageError, lambda: _ip.magic("set_env var"))
 
     def test_env_set_whitespace(self):
-        self.assertRaises(UsageError, lambda: _ip.run_line_magic("env", "var A=B"))
+        self.assertRaises(UsageError, lambda: _ip.magic("env var A=B"))
 
 
 class CellMagicTestCase(TestCase):
@@ -1311,7 +1308,7 @@ def test_ls_magic():
     ip = get_ipython()
     json_formatter = ip.display_formatter.formatters['application/json']
     json_formatter.enabled = True
-    lsmagic = ip.run_line_magic("lsmagic", "")
+    lsmagic = ip.magic('lsmagic')
     with warnings.catch_warnings(record=True) as w:
         j = json_formatter(lsmagic)
     assert sorted(j) == ["cell", "line"]
@@ -1361,16 +1358,16 @@ def test_logging_magic_not_quiet():
 
 
 def test_time_no_var_expand():
-    _ip.user_ns["a"] = 5
-    _ip.user_ns["b"] = []
-    _ip.run_line_magic("time", 'b.append("{a}")')
-    assert _ip.user_ns["b"] == ["{a}"]
+    _ip.user_ns['a'] = 5
+    _ip.user_ns['b'] = []
+    _ip.magic('time b.append("{a}")')
+    assert _ip.user_ns['b'] == ['{a}']
 
 
 # this is slow, put at the end for local testing.
 def test_timeit_arguments():
     "Test valid timeit arguments, should not cause SyntaxError (GH #1269)"
-    _ip.run_line_magic("timeit", "-n1 -r1 a=('#')")
+    _ip.magic("timeit -n1 -r1 a=('#')")
 
 
 MINIMAL_LAZY_MAGIC = """
@@ -1445,7 +1442,7 @@ def get_data(self, path):
         sys.meta_path.insert(0, MyTempImporter())
 
         with capture_output() as captured:
-            _ip.run_line_magic("run", "-m my_tmp")
+            _ip.magic("run -m my_tmp")
             _ip.run_cell("import my_tmp")
 
         output = "Loaded my_tmp\nI just ran a script\nLoaded my_tmp\n"
diff --git a/IPython/core/tests/test_oinspect.py b/IPython/core/tests/test_oinspect.py
index 8ae146fa4..94deb356a 100644
--- a/IPython/core/tests/test_oinspect.py
+++ b/IPython/core/tests/test_oinspect.py
@@ -5,7 +5,6 @@
 # Distributed under the terms of the Modified BSD License.
 
 
-from contextlib import contextmanager
 from inspect import signature, Signature, Parameter
 import inspect
 import os
@@ -44,7 +43,7 @@ class SourceModuleMainTest:
 # defined, if any code is inserted above, the following line will need to be
 # updated.  Do NOT insert any whitespace between the next line and the function
 # definition below.
-THIS_LINE_NUMBER = 47  # Put here the actual number of this line
+THIS_LINE_NUMBER = 46  # Put here the actual number of this line
 
 
 def test_find_source_lines():
@@ -346,70 +345,6 @@ def foo(): pass
     inspector.pdef(foo, 'foo')
 
 
-@contextmanager
-def cleanup_user_ns(**kwargs):
-    """
-    On exit delete all the keys that were not in user_ns before entering.
-
-    It does not restore old values !
-
-    Parameters
-    ----------
-
-    **kwargs
-        used to update ip.user_ns
-
-    """
-    try:
-        known = set(ip.user_ns.keys())
-        ip.user_ns.update(kwargs)
-        yield
-    finally:
-        added = set(ip.user_ns.keys()) - known
-        for k in added:
-            del ip.user_ns[k]
-
-
-def test_pinfo_getindex():
-    def dummy():
-        """
-        MARKER
-        """
-
-    container = [dummy]
-    with cleanup_user_ns(container=container):
-        with AssertPrints("MARKER"):
-            ip._inspect("pinfo", "container[0]", detail_level=0)
-    assert "container" not in ip.user_ns.keys()
-
-
-def test_qmark_getindex():
-    def dummy():
-        """
-        MARKER 2
-        """
-
-    container = [dummy]
-    with cleanup_user_ns(container=container):
-        with AssertPrints("MARKER 2"):
-            ip.run_cell("container[0]?")
-    assert "container" not in ip.user_ns.keys()
-
-
-def test_qmark_getindex_negatif():
-    def dummy():
-        """
-        MARKER 3
-        """
-
-    container = [dummy]
-    with cleanup_user_ns(container=container):
-        with AssertPrints("MARKER 3"):
-            ip.run_cell("container[-1]?")
-    assert "container" not in ip.user_ns.keys()
-
-
-
 def test_pinfo_nonascii():
     # See gh-1177
     from . import nonascii2
diff --git a/IPython/core/tests/test_run.py b/IPython/core/tests/test_run.py
index 9687786b4..ae20ce609 100644
--- a/IPython/core/tests/test_run.py
+++ b/IPython/core/tests/test_run.py
@@ -180,13 +180,13 @@ def run_tmpfile(self):
         _ip = get_ipython()
         # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
         # See below and ticket https://bugs.launchpad.net/bugs/366353
-        _ip.run_line_magic("run", self.fname)
+        _ip.magic('run %s' % self.fname)
 
     def run_tmpfile_p(self):
         _ip = get_ipython()
         # This fails on Windows if self.tmpfile.name has spaces or "~" in it.
         # See below and ticket https://bugs.launchpad.net/bugs/366353
-        _ip.run_line_magic("run", "-p %s" % self.fname)
+        _ip.magic('run -p %s' % self.fname)
 
     def test_builtins_id(self):
         """Check that %run doesn't damage __builtins__ """
@@ -216,20 +216,20 @@ def test_run_profile(self):
     def test_run_debug_twice(self):
         # https://github.com/ipython/ipython/issues/10028
         _ip = get_ipython()
-        with tt.fake_input(["c"]):
-            _ip.run_line_magic("run", "-d %s" % self.fname)
-        with tt.fake_input(["c"]):
-            _ip.run_line_magic("run", "-d %s" % self.fname)
+        with tt.fake_input(['c']):
+            _ip.magic('run -d %s' % self.fname)
+        with tt.fake_input(['c']):
+            _ip.magic('run -d %s' % self.fname)
 
     def test_run_debug_twice_with_breakpoint(self):
         """Make a valid python temp file."""
         _ip = get_ipython()
-        with tt.fake_input(["b 2", "c", "c"]):
-            _ip.run_line_magic("run", "-d %s" % self.fname)
+        with tt.fake_input(['b 2', 'c', 'c']):
+            _ip.magic('run -d %s' % self.fname)
 
-        with tt.fake_input(["c"]):
-            with tt.AssertNotPrints("KeyError"):
-                _ip.run_line_magic("run", "-d %s" % self.fname)
+        with tt.fake_input(['c']):
+            with tt.AssertNotPrints('KeyError'):
+                _ip.magic('run -d %s' % self.fname)
 
 
 class TestMagicRunSimple(tt.TempFileMixin):
@@ -239,7 +239,7 @@ def test_simpledef(self):
         src = ("class foo: pass\n"
                "def f(): return foo()")
         self.mktmp(src)
-        _ip.run_line_magic("run", str(self.fname))
+        _ip.magic("run %s" % self.fname)
         _ip.run_cell("t = isinstance(f(), foo)")
         assert _ip.user_ns["t"] is True
 
@@ -277,7 +277,7 @@ def test_aggressive_namespace_cleanup(self):
                 "       break\n" % ("run " + empty.fname)
             )
             self.mktmp(src)
-            _ip.run_line_magic("run", str(self.fname))
+            _ip.magic("run %s" % self.fname)
             _ip.run_cell("ip == get_ipython()")
             assert _ip.user_ns["i"] == 4
 
@@ -288,8 +288,8 @@ def test_run_second(self):
         with tt.TempFileMixin() as empty:
             empty.mktmp("")
 
-            _ip.run_line_magic("run", self.fname)
-            _ip.run_line_magic("run", empty.fname)
+            _ip.magic("run %s" % self.fname)
+            _ip.magic("run %s" % empty.fname)
             assert _ip.user_ns["afunc"]() == 1
 
     def test_tclass(self):
@@ -323,22 +323,22 @@ def test_run_i_after_reset(self):
         self.mktmp(src)
         _ip.run_cell("zz = 23")
         try:
-            _ip.run_line_magic("run", "-i %s" % self.fname)
+            _ip.magic("run -i %s" % self.fname)
             assert _ip.user_ns["yy"] == 23
         finally:
-            _ip.run_line_magic("reset", "-f")
+            _ip.magic('reset -f')
 
         _ip.run_cell("zz = 23")
         try:
-            _ip.run_line_magic("run", "-i %s" % self.fname)
+            _ip.magic("run -i %s" % self.fname)
             assert _ip.user_ns["yy"] == 23
         finally:
-            _ip.run_line_magic("reset", "-f")
+            _ip.magic('reset -f')
 
     def test_unicode(self):
         """Check that files in odd encodings are accepted."""
         mydir = os.path.dirname(__file__)
-        na = os.path.join(mydir, "nonascii.py")
+        na = os.path.join(mydir, 'nonascii.py')
         _ip.magic('run "%s"' % na)
         assert _ip.user_ns["u"] == "Ўт№Ф"
 
@@ -347,9 +347,9 @@ def test_run_py_file_attribute(self):
         src = "t = __file__\n"
         self.mktmp(src)
         _missing = object()
-        file1 = _ip.user_ns.get("__file__", _missing)
-        _ip.run_line_magic("run", self.fname)
-        file2 = _ip.user_ns.get("__file__", _missing)
+        file1 = _ip.user_ns.get('__file__', _missing)
+        _ip.magic('run %s' % self.fname)
+        file2 = _ip.user_ns.get('__file__', _missing)
 
         # Check that __file__ was equal to the filename in the script's
         # namespace.
@@ -363,9 +363,9 @@ def test_run_ipy_file_attribute(self):
         src = "t = __file__\n"
         self.mktmp(src, ext='.ipy')
         _missing = object()
-        file1 = _ip.user_ns.get("__file__", _missing)
-        _ip.run_line_magic("run", self.fname)
-        file2 = _ip.user_ns.get("__file__", _missing)
+        file1 = _ip.user_ns.get('__file__', _missing)
+        _ip.magic('run %s' % self.fname)
+        file2 = _ip.user_ns.get('__file__', _missing)
 
         # Check that __file__ was equal to the filename in the script's
         # namespace.
@@ -378,18 +378,18 @@ def test_run_formatting(self):
         """ Test that %run -t -N<N> does not raise a TypeError for N > 1."""
         src = "pass"
         self.mktmp(src)
-        _ip.run_line_magic("run", "-t -N 1 %s" % self.fname)
-        _ip.run_line_magic("run", "-t -N 10 %s" % self.fname)
+        _ip.magic('run -t -N 1 %s' % self.fname)
+        _ip.magic('run -t -N 10 %s' % self.fname)
 
     def test_ignore_sys_exit(self):
         """Test the -e option to ignore sys.exit()"""
         src = "import sys; sys.exit(1)"
         self.mktmp(src)
-        with tt.AssertPrints("SystemExit"):
-            _ip.run_line_magic("run", self.fname)
+        with tt.AssertPrints('SystemExit'):
+            _ip.magic('run %s' % self.fname)
 
-        with tt.AssertNotPrints("SystemExit"):
-            _ip.run_line_magic("run", "-e %s" % self.fname)
+        with tt.AssertNotPrints('SystemExit'):
+            _ip.magic('run -e %s' % self.fname)
 
     def test_run_nb(self):
         """Test %run notebook.ipynb"""
@@ -404,7 +404,7 @@ def test_run_nb(self):
         src = writes(nb, version=4)
         self.mktmp(src, ext='.ipynb')
 
-        _ip.run_line_magic("run", self.fname)
+        _ip.magic("run %s" % self.fname)
 
         assert _ip.user_ns["answer"] == 42
 
@@ -478,16 +478,12 @@ def tearDown(self):
         sys.path[:] = [p for p in sys.path if p != self.tempdir.name]
         self.tempdir.cleanup()
 
-    def check_run_submodule(self, submodule, opts=""):
-        _ip.user_ns.pop("x", None)
-        _ip.run_line_magic(
-            "run", "{2} -m {0}.{1}".format(self.package, submodule, opts)
-        )
-        self.assertEqual(
-            _ip.user_ns["x"],
-            self.value,
-            "Variable `x` is not loaded from module `{0}`.".format(submodule),
-        )
+    def check_run_submodule(self, submodule, opts=''):
+        _ip.user_ns.pop('x', None)
+        _ip.magic('run {2} -m {0}.{1}'.format(self.package, submodule, opts))
+        self.assertEqual(_ip.user_ns['x'], self.value,
+                         'Variable `x` is not loaded from module `{0}`.'
+                         .format(submodule))
 
     def test_run_submodule_with_absolute_import(self):
         self.check_run_submodule('absolute')
@@ -537,17 +533,17 @@ def test_run__name__():
             f.write("q = __name__")
 
         _ip.user_ns.pop("q", None)
-        _ip.run_line_magic("run", "{}".format(path))
+        _ip.magic("run {}".format(path))
         assert _ip.user_ns.pop("q") == "__main__"
 
-        _ip.run_line_magic("run", "-n {}".format(path))
+        _ip.magic("run -n {}".format(path))
         assert _ip.user_ns.pop("q") == "foo"
 
         try:
-            _ip.run_line_magic("run", "-i -n {}".format(path))
+            _ip.magic("run -i -n {}".format(path))
             assert _ip.user_ns.pop("q") == "foo"
         finally:
-            _ip.run_line_magic("reset", "-f")
+            _ip.magic('reset -f')
 
 
 def test_run_tb():
@@ -567,7 +563,7 @@ def test_run_tb():
                 )
             )
         with capture_output() as io:
-            _ip.run_line_magic("run", "{}".format(path))
+            _ip.magic('run {}'.format(path))
         out = io.stdout
         assert "execfile" not in out
         assert "RuntimeError" in out