##// END OF EJS Templates
Merge branch 'main' into completion-matcher
Michał Krassowski -
r27777:b0daec19 merge
parent child Browse files
Show More
@@ -1,36 +1,36 b''
1 name: Build docs
1 name: Build docs
2
2
3 on: [push, pull_request]
3 on: [push, pull_request]
4
4
5 jobs:
5 jobs:
6 build:
6 build:
7 runs-on: ubuntu-latest
7 runs-on: ubuntu-latest
8
8
9 steps:
9 steps:
10 - uses: actions/checkout@v2
10 - uses: actions/checkout@v3
11 - name: Set up Python 3.8
11 - name: Set up Python
12 uses: actions/setup-python@v2
12 uses: actions/setup-python@v4
13 with:
13 with:
14 python-version: 3.8
14 python-version: 3.x
15 - name: Install Graphviz
15 - name: Install Graphviz
16 run: |
16 run: |
17 sudo apt-get update
17 sudo apt-get update
18 sudo apt-get install graphviz
18 sudo apt-get install graphviz
19 - name: Install Python dependencies
19 - name: Install Python dependencies
20 run: |
20 run: |
21 python -m pip install --upgrade pip setuptools coverage rstvalidator
21 python -m pip install --upgrade pip setuptools coverage rstvalidator
22 pip install -r docs/requirements.txt
22 pip install -r docs/requirements.txt
23 - name: Build docs
23 - name: Build docs
24 run: |
24 run: |
25 python -m rstvalidator long_description.rst
25 python -m rstvalidator long_description.rst
26 python tools/fixup_whats_new_pr.py
26 python tools/fixup_whats_new_pr.py
27 make -C docs/ html SPHINXOPTS="-W" \
27 make -C docs/ html SPHINXOPTS="-W" \
28 PYTHON="coverage run -a" \
28 PYTHON="coverage run -a" \
29 SPHINXBUILD="coverage run -a -m sphinx.cmd.build"
29 SPHINXBUILD="coverage run -a -m sphinx.cmd.build"
30 - name: Generate coverage xml
30 - name: Generate coverage xml
31 run: |
31 run: |
32 coverage combine `find . -name .coverage\*` && coverage xml
32 coverage combine `find . -name .coverage\*` && coverage xml
33 - name: Upload coverage to Codecov
33 - name: Upload coverage to Codecov
34 uses: codecov/codecov-action@v2
34 uses: codecov/codecov-action@v2
35 with:
35 with:
36 name: Docs
36 name: Docs
@@ -1,50 +1,50 b''
1 name: Run Downstream tests
1 name: Run Downstream tests
2
2
3 on:
3 on:
4 push:
4 push:
5 pull_request:
5 pull_request:
6 # Run weekly on Monday at 1:23 UTC
6 # Run weekly on Monday at 1:23 UTC
7 schedule:
7 schedule:
8 - cron: '23 1 * * 1'
8 - cron: '23 1 * * 1'
9 workflow_dispatch:
9 workflow_dispatch:
10
10
11
11
12 jobs:
12 jobs:
13 test:
13 test:
14 runs-on: ${{ matrix.os }}
14 runs-on: ${{ matrix.os }}
15 strategy:
15 strategy:
16 matrix:
16 matrix:
17 os: [ubuntu-latest]
17 os: [ubuntu-latest]
18 python-version: ["3.9"]
18 python-version: ["3.9"]
19 include:
19 include:
20 - os: macos-latest
20 - os: macos-latest
21 python-version: "3.9"
21 python-version: "3.9"
22
22
23 steps:
23 steps:
24 - uses: actions/checkout@v2
24 - uses: actions/checkout@v3
25 - name: Set up Python ${{ matrix.python-version }}
25 - name: Set up Python ${{ matrix.python-version }}
26 uses: actions/setup-python@v2
26 uses: actions/setup-python@v4
27 with:
27 with:
28 python-version: ${{ matrix.python-version }}
28 python-version: ${{ matrix.python-version }}
29 - name: Update Python installer
29 - name: Update Python installer
30 run: |
30 run: |
31 python -m pip install --upgrade pip setuptools wheel
31 python -m pip install --upgrade pip setuptools wheel
32 - name: Install ipykernel
32 - name: Install ipykernel
33 run: |
33 run: |
34 cd ..
34 cd ..
35 git clone https://github.com/ipython/ipykernel
35 git clone https://github.com/ipython/ipykernel
36 cd ipykernel
36 cd ipykernel
37 pip install -e .[test]
37 pip install -e .[test]
38 cd ..
38 cd ..
39 - name: Install and update Python dependencies
39 - name: Install and update Python dependencies
40 run: |
40 run: |
41 python -m pip install --upgrade -e file://$PWD#egg=ipython[test]
41 python -m pip install --upgrade -e file://$PWD#egg=ipython[test]
42 # we must install IPython after ipykernel to get the right versions.
42 # we must install IPython after ipykernel to get the right versions.
43 python -m pip install --upgrade --upgrade-strategy eager flaky ipyparallel
43 python -m pip install --upgrade --upgrade-strategy eager flaky ipyparallel
44 python -m pip install --upgrade 'pytest<7'
44 python -m pip install --upgrade 'pytest<7'
45 - name: pytest
45 - name: pytest
46 env:
46 env:
47 COLUMNS: 120
47 COLUMNS: 120
48 run: |
48 run: |
49 cd ../ipykernel
49 cd ../ipykernel
50 pytest
50 pytest
@@ -1,34 +1,34 b''
1 name: Run MyPy
1 name: Run MyPy
2
2
3 on:
3 on:
4 push:
4 push:
5 branches: [ main, 7.x]
5 branches: [ main, 7.x]
6 pull_request:
6 pull_request:
7 branches: [ main, 7.x]
7 branches: [ main, 7.x]
8
8
9 jobs:
9 jobs:
10 build:
10 build:
11
11
12 runs-on: ubuntu-latest
12 runs-on: ubuntu-latest
13 strategy:
13 strategy:
14 matrix:
14 matrix:
15 python-version: [3.8]
15 python-version: [3.8]
16
16
17 steps:
17 steps:
18 - uses: actions/checkout@v2
18 - uses: actions/checkout@v3
19 - name: Set up Python ${{ matrix.python-version }}
19 - name: Set up Python ${{ matrix.python-version }}
20 uses: actions/setup-python@v2
20 uses: actions/setup-python@v4
21 with:
21 with:
22 python-version: ${{ matrix.python-version }}
22 python-version: ${{ matrix.python-version }}
23 - name: Install dependencies
23 - name: Install dependencies
24 run: |
24 run: |
25 python -m pip install --upgrade pip
25 python -m pip install --upgrade pip
26 pip install mypy pyflakes flake8
26 pip install mypy pyflakes flake8
27 - name: Lint with mypy
27 - name: Lint with mypy
28 run: |
28 run: |
29 mypy -p IPython.terminal
29 mypy -p IPython.terminal
30 mypy -p IPython.core.magics
30 mypy -p IPython.core.magics
31 - name: Lint with pyflakes
31 - name: Lint with pyflakes
32 run: |
32 run: |
33 flake8 IPython/core/magics/script.py
33 flake8 IPython/core/magics/script.py
34 flake8 IPython/core/magics/packaging.py
34 flake8 IPython/core/magics/packaging.py
@@ -1,40 +1,36 b''
1 # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
1 # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
2 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3
3
4 name: Python package
4 name: Python package
5
5
6 on:
6 on:
7 push:
7 push:
8 branches: [ main, 7.x ]
8 branches: [ main, 7.x ]
9 pull_request:
9 pull_request:
10 branches: [ main, 7.x ]
10 branches: [ main, 7.x ]
11
11
12 jobs:
12 jobs:
13 formatting:
13 formatting:
14
14
15 runs-on: ubuntu-latest
15 runs-on: ubuntu-latest
16 timeout-minutes: 5
16 timeout-minutes: 5
17 strategy:
18 matrix:
19 python-version: [3.8]
20
21 steps:
17 steps:
22 - uses: actions/checkout@v2
18 - uses: actions/checkout@v3
23 with:
19 with:
24 fetch-depth: 0
20 fetch-depth: 0
25 - name: Set up Python ${{ matrix.python-version }}
21 - name: Set up Python
26 uses: actions/setup-python@v2
22 uses: actions/setup-python@v4
27 with:
23 with:
28 python-version: ${{ matrix.python-version }}
24 python-version: 3.x
29 - name: Install dependencies
25 - name: Install dependencies
30 run: |
26 run: |
31 python -m pip install --upgrade pip
27 python -m pip install --upgrade pip
32 pip install darker black==21.12b0
28 pip install darker black==21.12b0
33 - name: Lint with darker
29 - name: Lint with darker
34 run: |
30 run: |
35 darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || (
31 darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || (
36 echo "Changes need auto-formatting. Run:"
32 echo "Changes need auto-formatting. Run:"
37 echo " darker -r 60625f241f298b5039cb2debc365db38aa7bb522"
33 echo " darker -r 60625f241f298b5039cb2debc365db38aa7bb522"
38 echo "then commit and push changes to fix."
34 echo "then commit and push changes to fix."
39 exit 1
35 exit 1
40 )
36 )
@@ -1,80 +1,80 b''
1 name: Run tests
1 name: Run tests
2
2
3 on:
3 on:
4 push:
4 push:
5 branches:
5 branches:
6 - main
6 - main
7 - '*.x'
7 - '*.x'
8 pull_request:
8 pull_request:
9 # Run weekly on Monday at 1:23 UTC
9 # Run weekly on Monday at 1:23 UTC
10 schedule:
10 schedule:
11 - cron: '23 1 * * 1'
11 - cron: '23 1 * * 1'
12 workflow_dispatch:
12 workflow_dispatch:
13
13
14
14
15 jobs:
15 jobs:
16 test:
16 test:
17 runs-on: ${{ matrix.os }}
17 runs-on: ${{ matrix.os }}
18 strategy:
18 strategy:
19 fail-fast: false
19 fail-fast: false
20 matrix:
20 matrix:
21 os: [ubuntu-latest, windows-latest]
21 os: [ubuntu-latest, windows-latest]
22 python-version: ["3.8", "3.9", "3.10"]
22 python-version: ["3.8", "3.9", "3.10"]
23 deps: [test_extra]
23 deps: [test_extra]
24 # Test all on ubuntu, test ends on macos
24 # Test all on ubuntu, test ends on macos
25 include:
25 include:
26 - os: macos-latest
26 - os: macos-latest
27 python-version: "3.8"
27 python-version: "3.8"
28 deps: test_extra
28 deps: test_extra
29 - os: macos-latest
29 - os: macos-latest
30 python-version: "3.10"
30 python-version: "3.10"
31 deps: test_extra
31 deps: test_extra
32 # Tests minimal dependencies set
32 # Tests minimal dependencies set
33 - os: ubuntu-latest
33 - os: ubuntu-latest
34 python-version: "3.10"
34 python-version: "3.10"
35 deps: test
35 deps: test
36 # Tests latest development Python version
36 # Tests latest development Python version
37 - os: ubuntu-latest
37 - os: ubuntu-latest
38 python-version: "3.11-dev"
38 python-version: "3.11-dev"
39 deps: test
39 deps: test
40 # Installing optional dependencies stuff takes ages on PyPy
40 # Installing optional dependencies stuff takes ages on PyPy
41 - os: ubuntu-latest
41 - os: ubuntu-latest
42 python-version: "pypy-3.8"
42 python-version: "pypy-3.8"
43 deps: test
43 deps: test
44 - os: windows-latest
44 - os: windows-latest
45 python-version: "pypy-3.8"
45 python-version: "pypy-3.8"
46 deps: test
46 deps: test
47 - os: macos-latest
47 - os: macos-latest
48 python-version: "pypy-3.8"
48 python-version: "pypy-3.8"
49 deps: test
49 deps: test
50
50
51 steps:
51 steps:
52 - uses: actions/checkout@v2
52 - uses: actions/checkout@v3
53 - name: Set up Python ${{ matrix.python-version }}
53 - name: Set up Python ${{ matrix.python-version }}
54 uses: actions/setup-python@v2
54 uses: actions/setup-python@v4
55 with:
55 with:
56 python-version: ${{ matrix.python-version }}
56 python-version: ${{ matrix.python-version }}
57 cache: pip
57 cache: pip
58 - name: Install latex
58 - name: Install latex
59 if: runner.os == 'Linux' && matrix.deps == 'test_extra'
59 if: runner.os == 'Linux' && matrix.deps == 'test_extra'
60 run: echo "disable latex for now, issues in mirros" #sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng
60 run: echo "disable latex for now, issues in mirros" #sudo apt-get -yq -o Acquire::Retries=3 --no-install-suggests --no-install-recommends install texlive dvipng
61 - name: Install and update Python dependencies
61 - name: Install and update Python dependencies
62 run: |
62 run: |
63 python -m pip install --upgrade pip setuptools wheel build
63 python -m pip install --upgrade pip setuptools wheel build
64 python -m pip install --upgrade -e .[${{ matrix.deps }}]
64 python -m pip install --upgrade -e .[${{ matrix.deps }}]
65 python -m pip install --upgrade check-manifest pytest-cov
65 python -m pip install --upgrade check-manifest pytest-cov
66 - name: Try building with Python build
66 - name: Try building with Python build
67 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
67 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
68 run: |
68 run: |
69 python -m build
69 python -m build
70 shasum -a 256 dist/*
70 shasum -a 256 dist/*
71 - name: Check manifest
71 - name: Check manifest
72 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
72 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
73 run: check-manifest
73 run: check-manifest
74 - name: pytest
74 - name: pytest
75 env:
75 env:
76 COLUMNS: 120
76 COLUMNS: 120
77 run: |
77 run: |
78 pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }}
78 pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }}
79 - name: Upload coverage to Codecov
79 - name: Upload coverage to Codecov
80 uses: codecov/codecov-action@v2
80 uses: codecov/codecov-action@v2
@@ -1,2858 +1,2862 b''
1 """Completion for IPython.
1 """Completion for IPython.
2
2
3 This module started as fork of the rlcompleter module in the Python standard
3 This module started as fork of the rlcompleter module in the Python standard
4 library. The original enhancements made to rlcompleter have been sent
4 library. The original enhancements made to rlcompleter have been sent
5 upstream and were accepted as of Python 2.3,
5 upstream and were accepted as of Python 2.3,
6
6
7 This module now support a wide variety of completion mechanism both available
7 This module now support a wide variety of completion mechanism both available
8 for normal classic Python code, as well as completer for IPython specific
8 for normal classic Python code, as well as completer for IPython specific
9 Syntax like magics.
9 Syntax like magics.
10
10
11 Latex and Unicode completion
11 Latex and Unicode completion
12 ============================
12 ============================
13
13
14 IPython and compatible frontends not only can complete your code, but can help
14 IPython and compatible frontends not only can complete your code, but can help
15 you to input a wide range of characters. In particular we allow you to insert
15 you to input a wide range of characters. In particular we allow you to insert
16 a unicode character using the tab completion mechanism.
16 a unicode character using the tab completion mechanism.
17
17
18 Forward latex/unicode completion
18 Forward latex/unicode completion
19 --------------------------------
19 --------------------------------
20
20
21 Forward completion allows you to easily type a unicode character using its latex
21 Forward completion allows you to easily type a unicode character using its latex
22 name, or unicode long description. To do so type a backslash follow by the
22 name, or unicode long description. To do so type a backslash follow by the
23 relevant name and press tab:
23 relevant name and press tab:
24
24
25
25
26 Using latex completion:
26 Using latex completion:
27
27
28 .. code::
28 .. code::
29
29
30 \\alpha<tab>
30 \\alpha<tab>
31 α
31 α
32
32
33 or using unicode completion:
33 or using unicode completion:
34
34
35
35
36 .. code::
36 .. code::
37
37
38 \\GREEK SMALL LETTER ALPHA<tab>
38 \\GREEK SMALL LETTER ALPHA<tab>
39 α
39 α
40
40
41
41
42 Only valid Python identifiers will complete. Combining characters (like arrow or
42 Only valid Python identifiers will complete. Combining characters (like arrow or
43 dots) are also available, unlike latex they need to be put after the their
43 dots) are also available, unlike latex they need to be put after the their
44 counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``.
44 counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``.
45
45
46 Some browsers are known to display combining characters incorrectly.
46 Some browsers are known to display combining characters incorrectly.
47
47
48 Backward latex completion
48 Backward latex completion
49 -------------------------
49 -------------------------
50
50
51 It is sometime challenging to know how to type a character, if you are using
51 It is sometime challenging to know how to type a character, if you are using
52 IPython, or any compatible frontend you can prepend backslash to the character
52 IPython, or any compatible frontend you can prepend backslash to the character
53 and press ``<tab>`` to expand it to its latex form.
53 and press ``<tab>`` to expand it to its latex form.
54
54
55 .. code::
55 .. code::
56
56
57 \\α<tab>
57 \\α<tab>
58 \\alpha
58 \\alpha
59
59
60
60
61 Both forward and backward completions can be deactivated by setting the
61 Both forward and backward completions can be deactivated by setting the
62 ``Completer.backslash_combining_completions`` option to ``False``.
62 ``Completer.backslash_combining_completions`` option to ``False``.
63
63
64
64
65 Experimental
65 Experimental
66 ============
66 ============
67
67
68 Starting with IPython 6.0, this module can make use of the Jedi library to
68 Starting with IPython 6.0, this module can make use of the Jedi library to
69 generate completions both using static analysis of the code, and dynamically
69 generate completions both using static analysis of the code, and dynamically
70 inspecting multiple namespaces. Jedi is an autocompletion and static analysis
70 inspecting multiple namespaces. Jedi is an autocompletion and static analysis
71 for Python. The APIs attached to this new mechanism is unstable and will
71 for Python. The APIs attached to this new mechanism is unstable and will
72 raise unless use in an :any:`provisionalcompleter` context manager.
72 raise unless use in an :any:`provisionalcompleter` context manager.
73
73
74 You will find that the following are experimental:
74 You will find that the following are experimental:
75
75
76 - :any:`provisionalcompleter`
76 - :any:`provisionalcompleter`
77 - :any:`IPCompleter.completions`
77 - :any:`IPCompleter.completions`
78 - :any:`Completion`
78 - :any:`Completion`
79 - :any:`rectify_completions`
79 - :any:`rectify_completions`
80
80
81 .. note::
81 .. note::
82
82
83 better name for :any:`rectify_completions` ?
83 better name for :any:`rectify_completions` ?
84
84
85 We welcome any feedback on these new API, and we also encourage you to try this
85 We welcome any feedback on these new API, and we also encourage you to try this
86 module in debug mode (start IPython with ``--Completer.debug=True``) in order
86 module in debug mode (start IPython with ``--Completer.debug=True``) in order
87 to have extra logging information if :any:`jedi` is crashing, or if current
87 to have extra logging information if :any:`jedi` is crashing, or if current
88 IPython completer pending deprecations are returning results not yet handled
88 IPython completer pending deprecations are returning results not yet handled
89 by :any:`jedi`
89 by :any:`jedi`
90
90
91 Using Jedi for tab completion allow snippets like the following to work without
91 Using Jedi for tab completion allow snippets like the following to work without
92 having to execute any code:
92 having to execute any code:
93
93
94 >>> myvar = ['hello', 42]
94 >>> myvar = ['hello', 42]
95 ... myvar[1].bi<tab>
95 ... myvar[1].bi<tab>
96
96
97 Tab completion will be able to infer that ``myvar[1]`` is a real number without
97 Tab completion will be able to infer that ``myvar[1]`` is a real number without
98 executing any code unlike the previously available ``IPCompleter.greedy``
98 executing any code unlike the previously available ``IPCompleter.greedy``
99 option.
99 option.
100
100
101 Be sure to update :any:`jedi` to the latest stable version or to try the
101 Be sure to update :any:`jedi` to the latest stable version or to try the
102 current development version to get better completions.
102 current development version to get better completions.
103
103
104 Matchers
104 Matchers
105 ========
105 ========
106
106
107 All completions routines are implemented using unified *Matchers* API.
107 All completions routines are implemented using unified *Matchers* API.
108 The matchers API is provisional and subject to change without notice.
108 The matchers API is provisional and subject to change without notice.
109
109
110 The built-in matchers include:
110 The built-in matchers include:
111
111
112 - ``IPCompleter.dict_key_matcher``: dictionary key completions,
112 - ``IPCompleter.dict_key_matcher``: dictionary key completions,
113 - ``IPCompleter.magic_matcher``: completions for magics,
113 - ``IPCompleter.magic_matcher``: completions for magics,
114 - ``IPCompleter.unicode_name_matcher``, ``IPCompleter.fwd_unicode_matcher`` and ``IPCompleter.latex_matcher``: see `Forward latex/unicode completion`_,
114 - ``IPCompleter.unicode_name_matcher``, ``IPCompleter.fwd_unicode_matcher`` and ``IPCompleter.latex_matcher``: see `Forward latex/unicode completion`_,
115 - ``back_unicode_name_matcher`` and ``back_latex_name_matcher``: see `Backward latex completion`_,
115 - ``back_unicode_name_matcher`` and ``back_latex_name_matcher``: see `Backward latex completion`_,
116 - ``IPCompleter.file_matcher``: paths to files and directories,
116 - ``IPCompleter.file_matcher``: paths to files and directories,
117 - ``IPCompleter.python_func_kw_matcher`` - function keywords,
117 - ``IPCompleter.python_func_kw_matcher`` - function keywords,
118 - ``IPCompleter.python_matches`` - globals and attributes (v1 API),
118 - ``IPCompleter.python_matches`` - globals and attributes (v1 API),
119 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
119 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
120 - ``IPCompleter.custom_completer_matcher`` - pluggable completer with a default implementation in any:`core.InteractiveShell`
120 - ``IPCompleter.custom_completer_matcher`` - pluggable completer with a default implementation in any:`core.InteractiveShell`
121 which uses uses IPython hooks system (`complete_command`) with string dispatch (including regular expressions).
121 which uses uses IPython hooks system (`complete_command`) with string dispatch (including regular expressions).
122 Differently to other matchers, ``custom_completer_matcher`` will not suppress Jedi results to match
122 Differently to other matchers, ``custom_completer_matcher`` will not suppress Jedi results to match
123 behaviour in earlier IPython versions.
123 behaviour in earlier IPython versions.
124
124
125 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
125 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
126
126
127 Suppression of competing matchers
127 Suppression of competing matchers
128 ---------------------------------
128 ---------------------------------
129
129
130 By default results from all matchers are combined, in the order determined by
130 By default results from all matchers are combined, in the order determined by
131 their priority. Matchers can request to suppress results from subsequent
131 their priority. Matchers can request to suppress results from subsequent
132 matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
132 matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
133
133
134 When multiple matchers simultaneously request surpression, the results from of
134 When multiple matchers simultaneously request surpression, the results from of
135 the matcher with higher priority will be returned.
135 the matcher with higher priority will be returned.
136
136
137 Sometimes it is desirable to suppress most but not all other matchers;
137 Sometimes it is desirable to suppress most but not all other matchers;
138 this can be achieved by adding a list of identifiers of matchers which
138 this can be achieved by adding a list of identifiers of matchers which
139 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
139 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
140 """
140 """
141
141
142
142
143 # Copyright (c) IPython Development Team.
143 # Copyright (c) IPython Development Team.
144 # Distributed under the terms of the Modified BSD License.
144 # Distributed under the terms of the Modified BSD License.
145 #
145 #
146 # Some of this code originated from rlcompleter in the Python standard library
146 # Some of this code originated from rlcompleter in the Python standard library
147 # Copyright (C) 2001 Python Software Foundation, www.python.org
147 # Copyright (C) 2001 Python Software Foundation, www.python.org
148
148
149
149
150 import builtins as builtin_mod
150 import builtins as builtin_mod
151 import glob
151 import glob
152 import inspect
152 import inspect
153 import itertools
153 import itertools
154 import keyword
154 import keyword
155 import os
155 import os
156 import re
156 import re
157 import string
157 import string
158 import sys
158 import sys
159 import time
159 import time
160 import unicodedata
160 import unicodedata
161 import uuid
161 import uuid
162 import warnings
162 import warnings
163 from contextlib import contextmanager
163 from contextlib import contextmanager
164 from functools import lru_cache, partial
164 from functools import lru_cache, partial
165 from importlib import import_module
165 from importlib import import_module
166 from types import SimpleNamespace
166 from types import SimpleNamespace
167 from typing import (
167 from typing import (
168 Iterable,
168 Iterable,
169 Iterator,
169 Iterator,
170 List,
170 List,
171 Tuple,
171 Tuple,
172 Union,
172 Union,
173 Any,
173 Any,
174 Sequence,
174 Sequence,
175 Dict,
175 Dict,
176 NamedTuple,
176 NamedTuple,
177 Pattern,
177 Pattern,
178 Optional,
178 Optional,
179 Callable,
179 Callable,
180 TYPE_CHECKING,
180 TYPE_CHECKING,
181 Set,
181 Set,
182 )
182 )
183
183
184 from IPython.core.error import TryNext
184 from IPython.core.error import TryNext
185 from IPython.core.inputtransformer2 import ESC_MAGIC
185 from IPython.core.inputtransformer2 import ESC_MAGIC
186 from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol
186 from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol
187 from IPython.core.oinspect import InspectColors
187 from IPython.core.oinspect import InspectColors
188 from IPython.testing.skipdoctest import skip_doctest
188 from IPython.testing.skipdoctest import skip_doctest
189 from IPython.utils import generics
189 from IPython.utils import generics
190 from IPython.utils.dir2 import dir2, get_real_method
190 from IPython.utils.dir2 import dir2, get_real_method
191 from IPython.utils.path import ensure_dir_exists
191 from IPython.utils.path import ensure_dir_exists
192 from IPython.utils.process import arg_split
192 from IPython.utils.process import arg_split
193 from traitlets import (
193 from traitlets import (
194 Bool,
194 Bool,
195 Enum,
195 Enum,
196 Int,
196 Int,
197 List as ListTrait,
197 List as ListTrait,
198 Unicode,
198 Unicode,
199 Dict as DictTrait,
199 Dict as DictTrait,
200 Union as UnionTrait,
200 Union as UnionTrait,
201 default,
201 default,
202 observe,
202 observe,
203 )
203 )
204 from traitlets.config.configurable import Configurable
204 from traitlets.config.configurable import Configurable
205
205
206 import __main__
206 import __main__
207
207
208 # skip module docstests
208 # skip module docstests
209 __skip_doctest__ = True
209 __skip_doctest__ = True
210
210
211
211
212 try:
212 try:
213 import jedi
213 import jedi
214 jedi.settings.case_insensitive_completion = False
214 jedi.settings.case_insensitive_completion = False
215 import jedi.api.helpers
215 import jedi.api.helpers
216 import jedi.api.classes
216 import jedi.api.classes
217 JEDI_INSTALLED = True
217 JEDI_INSTALLED = True
218 except ImportError:
218 except ImportError:
219 JEDI_INSTALLED = False
219 JEDI_INSTALLED = False
220
220
221 if TYPE_CHECKING:
221 if TYPE_CHECKING:
222 from typing import cast
222 from typing import cast
223 from typing_extensions import TypedDict, NotRequired
223 from typing_extensions import TypedDict, NotRequired
224 else:
224 else:
225
225
226 def cast(obj, _type):
226 def cast(obj, _type):
227 return obj
227 return obj
228
228
229 TypedDict = Dict
229 TypedDict = Dict
230 NotRequired = Tuple
230 NotRequired = Tuple
231
231
232 # -----------------------------------------------------------------------------
232 # -----------------------------------------------------------------------------
233 # Globals
233 # Globals
234 #-----------------------------------------------------------------------------
234 #-----------------------------------------------------------------------------
235
235
236 # ranges where we have most of the valid unicode names. We could be more finer
236 # ranges where we have most of the valid unicode names. We could be more finer
237 # grained but is it worth it for performance While unicode have character in the
237 # grained but is it worth it for performance While unicode have character in the
238 # range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I
238 # range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I
239 # write this). With below range we cover them all, with a density of ~67%
239 # write this). With below range we cover them all, with a density of ~67%
240 # biggest next gap we consider only adds up about 1% density and there are 600
240 # biggest next gap we consider only adds up about 1% density and there are 600
241 # gaps that would need hard coding.
241 # gaps that would need hard coding.
242 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
242 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
243
243
244 # Public API
244 # Public API
245 __all__ = ["Completer", "IPCompleter"]
245 __all__ = ["Completer", "IPCompleter"]
246
246
247 if sys.platform == 'win32':
247 if sys.platform == 'win32':
248 PROTECTABLES = ' '
248 PROTECTABLES = ' '
249 else:
249 else:
250 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
250 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
251
251
252 # Protect against returning an enormous number of completions which the frontend
252 # Protect against returning an enormous number of completions which the frontend
253 # may have trouble processing.
253 # may have trouble processing.
254 MATCHES_LIMIT = 500
254 MATCHES_LIMIT = 500
255
255
256 # Completion type reported when no type can be inferred.
256 # Completion type reported when no type can be inferred.
257 _UNKNOWN_TYPE = "<unknown>"
257 _UNKNOWN_TYPE = "<unknown>"
258
258
259 class ProvisionalCompleterWarning(FutureWarning):
259 class ProvisionalCompleterWarning(FutureWarning):
260 """
260 """
261 Exception raise by an experimental feature in this module.
261 Exception raise by an experimental feature in this module.
262
262
263 Wrap code in :any:`provisionalcompleter` context manager if you
263 Wrap code in :any:`provisionalcompleter` context manager if you
264 are certain you want to use an unstable feature.
264 are certain you want to use an unstable feature.
265 """
265 """
266 pass
266 pass
267
267
268 warnings.filterwarnings('error', category=ProvisionalCompleterWarning)
268 warnings.filterwarnings('error', category=ProvisionalCompleterWarning)
269
269
270
270
271 @skip_doctest
271 @skip_doctest
272 @contextmanager
272 @contextmanager
273 def provisionalcompleter(action='ignore'):
273 def provisionalcompleter(action='ignore'):
274 """
274 """
275 This context manager has to be used in any place where unstable completer
275 This context manager has to be used in any place where unstable completer
276 behavior and API may be called.
276 behavior and API may be called.
277
277
278 >>> with provisionalcompleter():
278 >>> with provisionalcompleter():
279 ... completer.do_experimental_things() # works
279 ... completer.do_experimental_things() # works
280
280
281 >>> completer.do_experimental_things() # raises.
281 >>> completer.do_experimental_things() # raises.
282
282
283 .. note::
283 .. note::
284
284
285 Unstable
285 Unstable
286
286
287 By using this context manager you agree that the API in use may change
287 By using this context manager you agree that the API in use may change
288 without warning, and that you won't complain if they do so.
288 without warning, and that you won't complain if they do so.
289
289
290 You also understand that, if the API is not to your liking, you should report
290 You also understand that, if the API is not to your liking, you should report
291 a bug to explain your use case upstream.
291 a bug to explain your use case upstream.
292
292
293 We'll be happy to get your feedback, feature requests, and improvements on
293 We'll be happy to get your feedback, feature requests, and improvements on
294 any of the unstable APIs!
294 any of the unstable APIs!
295 """
295 """
296 with warnings.catch_warnings():
296 with warnings.catch_warnings():
297 warnings.filterwarnings(action, category=ProvisionalCompleterWarning)
297 warnings.filterwarnings(action, category=ProvisionalCompleterWarning)
298 yield
298 yield
299
299
300
300
301 def has_open_quotes(s):
301 def has_open_quotes(s):
302 """Return whether a string has open quotes.
302 """Return whether a string has open quotes.
303
303
304 This simply counts whether the number of quote characters of either type in
304 This simply counts whether the number of quote characters of either type in
305 the string is odd.
305 the string is odd.
306
306
307 Returns
307 Returns
308 -------
308 -------
309 If there is an open quote, the quote character is returned. Else, return
309 If there is an open quote, the quote character is returned. Else, return
310 False.
310 False.
311 """
311 """
312 # We check " first, then ', so complex cases with nested quotes will get
312 # We check " first, then ', so complex cases with nested quotes will get
313 # the " to take precedence.
313 # the " to take precedence.
314 if s.count('"') % 2:
314 if s.count('"') % 2:
315 return '"'
315 return '"'
316 elif s.count("'") % 2:
316 elif s.count("'") % 2:
317 return "'"
317 return "'"
318 else:
318 else:
319 return False
319 return False
320
320
321
321
322 def protect_filename(s, protectables=PROTECTABLES):
322 def protect_filename(s, protectables=PROTECTABLES):
323 """Escape a string to protect certain characters."""
323 """Escape a string to protect certain characters."""
324 if set(s) & set(protectables):
324 if set(s) & set(protectables):
325 if sys.platform == "win32":
325 if sys.platform == "win32":
326 return '"' + s + '"'
326 return '"' + s + '"'
327 else:
327 else:
328 return "".join(("\\" + c if c in protectables else c) for c in s)
328 return "".join(("\\" + c if c in protectables else c) for c in s)
329 else:
329 else:
330 return s
330 return s
331
331
332
332
333 def expand_user(path:str) -> Tuple[str, bool, str]:
333 def expand_user(path:str) -> Tuple[str, bool, str]:
334 """Expand ``~``-style usernames in strings.
334 """Expand ``~``-style usernames in strings.
335
335
336 This is similar to :func:`os.path.expanduser`, but it computes and returns
336 This is similar to :func:`os.path.expanduser`, but it computes and returns
337 extra information that will be useful if the input was being used in
337 extra information that will be useful if the input was being used in
338 computing completions, and you wish to return the completions with the
338 computing completions, and you wish to return the completions with the
339 original '~' instead of its expanded value.
339 original '~' instead of its expanded value.
340
340
341 Parameters
341 Parameters
342 ----------
342 ----------
343 path : str
343 path : str
344 String to be expanded. If no ~ is present, the output is the same as the
344 String to be expanded. If no ~ is present, the output is the same as the
345 input.
345 input.
346
346
347 Returns
347 Returns
348 -------
348 -------
349 newpath : str
349 newpath : str
350 Result of ~ expansion in the input path.
350 Result of ~ expansion in the input path.
351 tilde_expand : bool
351 tilde_expand : bool
352 Whether any expansion was performed or not.
352 Whether any expansion was performed or not.
353 tilde_val : str
353 tilde_val : str
354 The value that ~ was replaced with.
354 The value that ~ was replaced with.
355 """
355 """
356 # Default values
356 # Default values
357 tilde_expand = False
357 tilde_expand = False
358 tilde_val = ''
358 tilde_val = ''
359 newpath = path
359 newpath = path
360
360
361 if path.startswith('~'):
361 if path.startswith('~'):
362 tilde_expand = True
362 tilde_expand = True
363 rest = len(path)-1
363 rest = len(path)-1
364 newpath = os.path.expanduser(path)
364 newpath = os.path.expanduser(path)
365 if rest:
365 if rest:
366 tilde_val = newpath[:-rest]
366 tilde_val = newpath[:-rest]
367 else:
367 else:
368 tilde_val = newpath
368 tilde_val = newpath
369
369
370 return newpath, tilde_expand, tilde_val
370 return newpath, tilde_expand, tilde_val
371
371
372
372
373 def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str:
373 def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str:
374 """Does the opposite of expand_user, with its outputs.
374 """Does the opposite of expand_user, with its outputs.
375 """
375 """
376 if tilde_expand:
376 if tilde_expand:
377 return path.replace(tilde_val, '~')
377 return path.replace(tilde_val, '~')
378 else:
378 else:
379 return path
379 return path
380
380
381
381
382 def completions_sorting_key(word):
382 def completions_sorting_key(word):
383 """key for sorting completions
383 """key for sorting completions
384
384
385 This does several things:
385 This does several things:
386
386
387 - Demote any completions starting with underscores to the end
387 - Demote any completions starting with underscores to the end
388 - Insert any %magic and %%cellmagic completions in the alphabetical order
388 - Insert any %magic and %%cellmagic completions in the alphabetical order
389 by their name
389 by their name
390 """
390 """
391 prio1, prio2 = 0, 0
391 prio1, prio2 = 0, 0
392
392
393 if word.startswith('__'):
393 if word.startswith('__'):
394 prio1 = 2
394 prio1 = 2
395 elif word.startswith('_'):
395 elif word.startswith('_'):
396 prio1 = 1
396 prio1 = 1
397
397
398 if word.endswith('='):
398 if word.endswith('='):
399 prio1 = -1
399 prio1 = -1
400
400
401 if word.startswith('%%'):
401 if word.startswith('%%'):
402 # If there's another % in there, this is something else, so leave it alone
402 # If there's another % in there, this is something else, so leave it alone
403 if not "%" in word[2:]:
403 if not "%" in word[2:]:
404 word = word[2:]
404 word = word[2:]
405 prio2 = 2
405 prio2 = 2
406 elif word.startswith('%'):
406 elif word.startswith('%'):
407 if not "%" in word[1:]:
407 if not "%" in word[1:]:
408 word = word[1:]
408 word = word[1:]
409 prio2 = 1
409 prio2 = 1
410
410
411 return prio1, word, prio2
411 return prio1, word, prio2
412
412
413
413
414 class _FakeJediCompletion:
414 class _FakeJediCompletion:
415 """
415 """
416 This is a workaround to communicate to the UI that Jedi has crashed and to
416 This is a workaround to communicate to the UI that Jedi has crashed and to
417 report a bug. Will be used only id :any:`IPCompleter.debug` is set to true.
417 report a bug. Will be used only id :any:`IPCompleter.debug` is set to true.
418
418
419 Added in IPython 6.0 so should likely be removed for 7.0
419 Added in IPython 6.0 so should likely be removed for 7.0
420
420
421 """
421 """
422
422
423 def __init__(self, name):
423 def __init__(self, name):
424
424
425 self.name = name
425 self.name = name
426 self.complete = name
426 self.complete = name
427 self.type = 'crashed'
427 self.type = 'crashed'
428 self.name_with_symbols = name
428 self.name_with_symbols = name
429 self.signature = ''
429 self.signature = ''
430 self._origin = 'fake'
430 self._origin = 'fake'
431
431
432 def __repr__(self):
432 def __repr__(self):
433 return '<Fake completion object jedi has crashed>'
433 return '<Fake completion object jedi has crashed>'
434
434
435
435
436 _JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion]
436 _JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion]
437
437
438
438
439 class Completion:
439 class Completion:
440 """
440 """
441 Completion object used and returned by IPython completers.
441 Completion object used and returned by IPython completers.
442
442
443 .. warning::
443 .. warning::
444
444
445 Unstable
445 Unstable
446
446
447 This function is unstable, API may change without warning.
447 This function is unstable, API may change without warning.
448 It will also raise unless use in proper context manager.
448 It will also raise unless use in proper context manager.
449
449
450 This act as a middle ground :any:`Completion` object between the
450 This act as a middle ground :any:`Completion` object between the
451 :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion
451 :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion
452 object. While Jedi need a lot of information about evaluator and how the
452 object. While Jedi need a lot of information about evaluator and how the
453 code should be ran/inspected, PromptToolkit (and other frontend) mostly
453 code should be ran/inspected, PromptToolkit (and other frontend) mostly
454 need user facing information.
454 need user facing information.
455
455
456 - Which range should be replaced replaced by what.
456 - Which range should be replaced replaced by what.
457 - Some metadata (like completion type), or meta information to displayed to
457 - Some metadata (like completion type), or meta information to displayed to
458 the use user.
458 the use user.
459
459
460 For debugging purpose we can also store the origin of the completion (``jedi``,
460 For debugging purpose we can also store the origin of the completion (``jedi``,
461 ``IPython.python_matches``, ``IPython.magics_matches``...).
461 ``IPython.python_matches``, ``IPython.magics_matches``...).
462 """
462 """
463
463
464 __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin']
464 __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin']
465
465
466 def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None:
466 def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None:
467 warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). "
467 warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). "
468 "It may change without warnings. "
468 "It may change without warnings. "
469 "Use in corresponding context manager.",
469 "Use in corresponding context manager.",
470 category=ProvisionalCompleterWarning, stacklevel=2)
470 category=ProvisionalCompleterWarning, stacklevel=2)
471
471
472 self.start = start
472 self.start = start
473 self.end = end
473 self.end = end
474 self.text = text
474 self.text = text
475 self.type = type
475 self.type = type
476 self.signature = signature
476 self.signature = signature
477 self._origin = _origin
477 self._origin = _origin
478
478
479 def __repr__(self):
479 def __repr__(self):
480 return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \
480 return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \
481 (self.start, self.end, self.text, self.type or '?', self.signature or '?')
481 (self.start, self.end, self.text, self.type or '?', self.signature or '?')
482
482
483 def __eq__(self, other)->Bool:
483 def __eq__(self, other)->Bool:
484 """
484 """
485 Equality and hash do not hash the type (as some completer may not be
485 Equality and hash do not hash the type (as some completer may not be
486 able to infer the type), but are use to (partially) de-duplicate
486 able to infer the type), but are use to (partially) de-duplicate
487 completion.
487 completion.
488
488
489 Completely de-duplicating completion is a bit tricker that just
489 Completely de-duplicating completion is a bit tricker that just
490 comparing as it depends on surrounding text, which Completions are not
490 comparing as it depends on surrounding text, which Completions are not
491 aware of.
491 aware of.
492 """
492 """
493 return self.start == other.start and \
493 return self.start == other.start and \
494 self.end == other.end and \
494 self.end == other.end and \
495 self.text == other.text
495 self.text == other.text
496
496
497 def __hash__(self):
497 def __hash__(self):
498 return hash((self.start, self.end, self.text))
498 return hash((self.start, self.end, self.text))
499
499
500
500
501 class SimpleCompletion:
501 class SimpleCompletion:
502 """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
502 """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
503
503
504 .. warning::
504 .. warning::
505
505
506 Provisional
506 Provisional
507
507
508 This class is used to describe the currently supported attributes of
508 This class is used to describe the currently supported attributes of
509 simple completion items, and any additional implementation details
509 simple completion items, and any additional implementation details
510 should not be relied on. Additional attributes may be included in
510 should not be relied on. Additional attributes may be included in
511 future versions, and meaning of text disambiguated from the current
511 future versions, and meaning of text disambiguated from the current
512 dual meaning of "text to insert" and "text to used as a label".
512 dual meaning of "text to insert" and "text to used as a label".
513 """
513 """
514
514
515 __slots__ = ["text", "type"]
515 __slots__ = ["text", "type"]
516
516
517 def __init__(self, text: str, *, type: str = None):
517 def __init__(self, text: str, *, type: str = None):
518 self.text = text
518 self.text = text
519 self.type = type
519 self.type = type
520
520
521 def __repr__(self):
521 def __repr__(self):
522 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
522 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
523
523
524
524
525 class MatcherResultBase(TypedDict):
525 class MatcherResultBase(TypedDict):
526 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
526 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
527
527
528 #: suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
528 #: suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
529 matched_fragment: NotRequired[str]
529 matched_fragment: NotRequired[str]
530
530
531 #: whether to suppress results from all other matchers (True), some
531 #: whether to suppress results from all other matchers (True), some
532 #: matchers (set of identifiers) or none (False); default is False.
532 #: matchers (set of identifiers) or none (False); default is False.
533 suppress: NotRequired[Union[bool, Set[str]]]
533 suppress: NotRequired[Union[bool, Set[str]]]
534
534
535 #: identifiers of matchers which should NOT be suppressed
535 #: identifiers of matchers which should NOT be suppressed
536 do_not_suppress: NotRequired[Set[str]]
536 do_not_suppress: NotRequired[Set[str]]
537
537
538 #: are completions already ordered and should be left as-is? default is False.
538 #: are completions already ordered and should be left as-is? default is False.
539 ordered: NotRequired[bool]
539 ordered: NotRequired[bool]
540
540
541
541
542 class SimpleMatcherResult(MatcherResultBase):
542 class SimpleMatcherResult(MatcherResultBase):
543 """Result of new-style completion matcher."""
543 """Result of new-style completion matcher."""
544
544
545 #: list of candidate completions
545 #: list of candidate completions
546 completions: Sequence[SimpleCompletion]
546 completions: Sequence[SimpleCompletion]
547
547
548
548
549 class _JediMatcherResult(MatcherResultBase):
549 class _JediMatcherResult(MatcherResultBase):
550 """Matching result returned by Jedi (will be processed differently)"""
550 """Matching result returned by Jedi (will be processed differently)"""
551
551
552 #: list of candidate completions
552 #: list of candidate completions
553 completions: Iterable[_JediCompletionLike]
553 completions: Iterable[_JediCompletionLike]
554
554
555
555
556 class CompletionContext(NamedTuple):
556 class CompletionContext(NamedTuple):
557 """Completion context provided as an argument to matchers in the Matcher API v2."""
557 """Completion context provided as an argument to matchers in the Matcher API v2."""
558
558
559 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
559 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
560 # which was not explicitly visible as an argument of the matcher, making any refactor
560 # which was not explicitly visible as an argument of the matcher, making any refactor
561 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
561 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
562 # from the completer, and make substituting them in sub-classes easier.
562 # from the completer, and make substituting them in sub-classes easier.
563
563
564 #: Relevant fragment of code directly preceding the cursor.
564 #: Relevant fragment of code directly preceding the cursor.
565 #: The extraction of token is implemented via splitter heuristic
565 #: The extraction of token is implemented via splitter heuristic
566 #: (following readline behaviour for legacy reasons), which is user configurable
566 #: (following readline behaviour for legacy reasons), which is user configurable
567 #: (by switching the greedy mode).
567 #: (by switching the greedy mode).
568 token: str
568 token: str
569
569
570 #: The full available content of the editor or buffer
570 #: The full available content of the editor or buffer
571 full_text: str
571 full_text: str
572
572
573 #: Cursor position in the line (the same for ``full_text`` and ``text``).
573 #: Cursor position in the line (the same for ``full_text`` and ``text``).
574 cursor_position: int
574 cursor_position: int
575
575
576 #: Cursor line in ``full_text``.
576 #: Cursor line in ``full_text``.
577 cursor_line: int
577 cursor_line: int
578
578
579 #: The maximum number of completions that will be used downstream.
579 #: The maximum number of completions that will be used downstream.
580 #: Matchers can use this information to abort early.
580 #: Matchers can use this information to abort early.
581 #: The built-in Jedi matcher is currently excepted from this limit.
581 #: The built-in Jedi matcher is currently excepted from this limit.
582 limit: int
582 limit: int
583
583
584 @property
584 @property
585 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
585 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
586 def text_until_cursor(self) -> str:
586 def text_until_cursor(self) -> str:
587 return self.line_with_cursor[: self.cursor_position]
587 return self.line_with_cursor[: self.cursor_position]
588
588
589 @property
589 @property
590 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
590 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
591 def line_with_cursor(self) -> str:
591 def line_with_cursor(self) -> str:
592 return self.full_text.split("\n")[self.cursor_line]
592 return self.full_text.split("\n")[self.cursor_line]
593
593
594
594
595 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
595 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
596
596
597 MatcherAPIv1 = Callable[[str], List[str]]
597 MatcherAPIv1 = Callable[[str], List[str]]
598 MatcherAPIv2 = Callable[[CompletionContext], MatcherResult]
598 MatcherAPIv2 = Callable[[CompletionContext], MatcherResult]
599 Matcher = Union[MatcherAPIv1, MatcherAPIv2]
599 Matcher = Union[MatcherAPIv1, MatcherAPIv2]
600
600
601
601
602 def completion_matcher(
602 def completion_matcher(
603 *, priority: float = None, identifier: str = None, api_version: int = 1
603 *, priority: float = None, identifier: str = None, api_version: int = 1
604 ):
604 ):
605 """Adds attributes describing the matcher.
605 """Adds attributes describing the matcher.
606
606
607 Parameters
607 Parameters
608 ----------
608 ----------
609 priority : Optional[float]
609 priority : Optional[float]
610 The priority of the matcher, determines the order of execution of matchers.
610 The priority of the matcher, determines the order of execution of matchers.
611 Higher priority means that the matcher will be executed first. Defaults to 0.
611 Higher priority means that the matcher will be executed first. Defaults to 0.
612 identifier : Optional[str]
612 identifier : Optional[str]
613 identifier of the matcher allowing users to modify the behaviour via traitlets,
613 identifier of the matcher allowing users to modify the behaviour via traitlets,
614 and also used to for debugging (will be passed as ``origin`` with the completions).
614 and also used to for debugging (will be passed as ``origin`` with the completions).
615 Defaults to matcher function ``__qualname__``.
615 Defaults to matcher function ``__qualname__``.
616 api_version: Optional[int]
616 api_version: Optional[int]
617 version of the Matcher API used by this matcher.
617 version of the Matcher API used by this matcher.
618 Currently supported values are 1 and 2.
618 Currently supported values are 1 and 2.
619 Defaults to 1.
619 Defaults to 1.
620 """
620 """
621
621
622 def wrapper(func: Matcher):
622 def wrapper(func: Matcher):
623 func.matcher_priority = priority or 0
623 func.matcher_priority = priority or 0
624 func.matcher_identifier = identifier or func.__qualname__
624 func.matcher_identifier = identifier or func.__qualname__
625 func.matcher_api_version = api_version
625 func.matcher_api_version = api_version
626 if TYPE_CHECKING:
626 if TYPE_CHECKING:
627 if api_version == 1:
627 if api_version == 1:
628 func = cast(func, MatcherAPIv1)
628 func = cast(func, MatcherAPIv1)
629 elif api_version == 2:
629 elif api_version == 2:
630 func = cast(func, MatcherAPIv2)
630 func = cast(func, MatcherAPIv2)
631 return func
631 return func
632
632
633 return wrapper
633 return wrapper
634
634
635
635
636 def _get_matcher_priority(matcher: Matcher):
636 def _get_matcher_priority(matcher: Matcher):
637 return getattr(matcher, "matcher_priority", 0)
637 return getattr(matcher, "matcher_priority", 0)
638
638
639
639
640 def _get_matcher_id(matcher: Matcher):
640 def _get_matcher_id(matcher: Matcher):
641 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
641 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
642
642
643
643
644 def _get_matcher_api_version(matcher):
644 def _get_matcher_api_version(matcher):
645 return getattr(matcher, "matcher_api_version", 1)
645 return getattr(matcher, "matcher_api_version", 1)
646
646
647
647
648 context_matcher = partial(completion_matcher, api_version=2)
648 context_matcher = partial(completion_matcher, api_version=2)
649
649
650
650
651 _IC = Iterable[Completion]
651 _IC = Iterable[Completion]
652
652
653
653
654 def _deduplicate_completions(text: str, completions: _IC)-> _IC:
654 def _deduplicate_completions(text: str, completions: _IC)-> _IC:
655 """
655 """
656 Deduplicate a set of completions.
656 Deduplicate a set of completions.
657
657
658 .. warning::
658 .. warning::
659
659
660 Unstable
660 Unstable
661
661
662 This function is unstable, API may change without warning.
662 This function is unstable, API may change without warning.
663
663
664 Parameters
664 Parameters
665 ----------
665 ----------
666 text : str
666 text : str
667 text that should be completed.
667 text that should be completed.
668 completions : Iterator[Completion]
668 completions : Iterator[Completion]
669 iterator over the completions to deduplicate
669 iterator over the completions to deduplicate
670
670
671 Yields
671 Yields
672 ------
672 ------
673 `Completions` objects
673 `Completions` objects
674 Completions coming from multiple sources, may be different but end up having
674 Completions coming from multiple sources, may be different but end up having
675 the same effect when applied to ``text``. If this is the case, this will
675 the same effect when applied to ``text``. If this is the case, this will
676 consider completions as equal and only emit the first encountered.
676 consider completions as equal and only emit the first encountered.
677 Not folded in `completions()` yet for debugging purpose, and to detect when
677 Not folded in `completions()` yet for debugging purpose, and to detect when
678 the IPython completer does return things that Jedi does not, but should be
678 the IPython completer does return things that Jedi does not, but should be
679 at some point.
679 at some point.
680 """
680 """
681 completions = list(completions)
681 completions = list(completions)
682 if not completions:
682 if not completions:
683 return
683 return
684
684
685 new_start = min(c.start for c in completions)
685 new_start = min(c.start for c in completions)
686 new_end = max(c.end for c in completions)
686 new_end = max(c.end for c in completions)
687
687
688 seen = set()
688 seen = set()
689 for c in completions:
689 for c in completions:
690 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
690 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
691 if new_text not in seen:
691 if new_text not in seen:
692 yield c
692 yield c
693 seen.add(new_text)
693 seen.add(new_text)
694
694
695
695
696 def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC:
696 def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC:
697 """
697 """
698 Rectify a set of completions to all have the same ``start`` and ``end``
698 Rectify a set of completions to all have the same ``start`` and ``end``
699
699
700 .. warning::
700 .. warning::
701
701
702 Unstable
702 Unstable
703
703
704 This function is unstable, API may change without warning.
704 This function is unstable, API may change without warning.
705 It will also raise unless use in proper context manager.
705 It will also raise unless use in proper context manager.
706
706
707 Parameters
707 Parameters
708 ----------
708 ----------
709 text : str
709 text : str
710 text that should be completed.
710 text that should be completed.
711 completions : Iterator[Completion]
711 completions : Iterator[Completion]
712 iterator over the completions to rectify
712 iterator over the completions to rectify
713 _debug : bool
713 _debug : bool
714 Log failed completion
714 Log failed completion
715
715
716 Notes
716 Notes
717 -----
717 -----
718 :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though
718 :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though
719 the Jupyter Protocol requires them to behave like so. This will readjust
719 the Jupyter Protocol requires them to behave like so. This will readjust
720 the completion to have the same ``start`` and ``end`` by padding both
720 the completion to have the same ``start`` and ``end`` by padding both
721 extremities with surrounding text.
721 extremities with surrounding text.
722
722
723 During stabilisation should support a ``_debug`` option to log which
723 During stabilisation should support a ``_debug`` option to log which
724 completion are return by the IPython completer and not found in Jedi in
724 completion are return by the IPython completer and not found in Jedi in
725 order to make upstream bug report.
725 order to make upstream bug report.
726 """
726 """
727 warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). "
727 warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). "
728 "It may change without warnings. "
728 "It may change without warnings. "
729 "Use in corresponding context manager.",
729 "Use in corresponding context manager.",
730 category=ProvisionalCompleterWarning, stacklevel=2)
730 category=ProvisionalCompleterWarning, stacklevel=2)
731
731
732 completions = list(completions)
732 completions = list(completions)
733 if not completions:
733 if not completions:
734 return
734 return
735 starts = (c.start for c in completions)
735 starts = (c.start for c in completions)
736 ends = (c.end for c in completions)
736 ends = (c.end for c in completions)
737
737
738 new_start = min(starts)
738 new_start = min(starts)
739 new_end = max(ends)
739 new_end = max(ends)
740
740
741 seen_jedi = set()
741 seen_jedi = set()
742 seen_python_matches = set()
742 seen_python_matches = set()
743 for c in completions:
743 for c in completions:
744 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
744 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
745 if c._origin == 'jedi':
745 if c._origin == 'jedi':
746 seen_jedi.add(new_text)
746 seen_jedi.add(new_text)
747 elif c._origin == 'IPCompleter.python_matches':
747 elif c._origin == 'IPCompleter.python_matches':
748 seen_python_matches.add(new_text)
748 seen_python_matches.add(new_text)
749 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
749 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
750 diff = seen_python_matches.difference(seen_jedi)
750 diff = seen_python_matches.difference(seen_jedi)
751 if diff and _debug:
751 if diff and _debug:
752 print('IPython.python matches have extras:', diff)
752 print('IPython.python matches have extras:', diff)
753
753
754
754
755 if sys.platform == 'win32':
755 if sys.platform == 'win32':
756 DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?'
756 DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?'
757 else:
757 else:
758 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
758 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
759
759
760 GREEDY_DELIMS = ' =\r\n'
760 GREEDY_DELIMS = ' =\r\n'
761
761
762
762
763 class CompletionSplitter(object):
763 class CompletionSplitter(object):
764 """An object to split an input line in a manner similar to readline.
764 """An object to split an input line in a manner similar to readline.
765
765
766 By having our own implementation, we can expose readline-like completion in
766 By having our own implementation, we can expose readline-like completion in
767 a uniform manner to all frontends. This object only needs to be given the
767 a uniform manner to all frontends. This object only needs to be given the
768 line of text to be split and the cursor position on said line, and it
768 line of text to be split and the cursor position on said line, and it
769 returns the 'word' to be completed on at the cursor after splitting the
769 returns the 'word' to be completed on at the cursor after splitting the
770 entire line.
770 entire line.
771
771
772 What characters are used as splitting delimiters can be controlled by
772 What characters are used as splitting delimiters can be controlled by
773 setting the ``delims`` attribute (this is a property that internally
773 setting the ``delims`` attribute (this is a property that internally
774 automatically builds the necessary regular expression)"""
774 automatically builds the necessary regular expression)"""
775
775
776 # Private interface
776 # Private interface
777
777
778 # A string of delimiter characters. The default value makes sense for
778 # A string of delimiter characters. The default value makes sense for
779 # IPython's most typical usage patterns.
779 # IPython's most typical usage patterns.
780 _delims = DELIMS
780 _delims = DELIMS
781
781
782 # The expression (a normal string) to be compiled into a regular expression
782 # The expression (a normal string) to be compiled into a regular expression
783 # for actual splitting. We store it as an attribute mostly for ease of
783 # for actual splitting. We store it as an attribute mostly for ease of
784 # debugging, since this type of code can be so tricky to debug.
784 # debugging, since this type of code can be so tricky to debug.
785 _delim_expr = None
785 _delim_expr = None
786
786
787 # The regular expression that does the actual splitting
787 # The regular expression that does the actual splitting
788 _delim_re = None
788 _delim_re = None
789
789
790 def __init__(self, delims=None):
790 def __init__(self, delims=None):
791 delims = CompletionSplitter._delims if delims is None else delims
791 delims = CompletionSplitter._delims if delims is None else delims
792 self.delims = delims
792 self.delims = delims
793
793
794 @property
794 @property
795 def delims(self):
795 def delims(self):
796 """Return the string of delimiter characters."""
796 """Return the string of delimiter characters."""
797 return self._delims
797 return self._delims
798
798
799 @delims.setter
799 @delims.setter
800 def delims(self, delims):
800 def delims(self, delims):
801 """Set the delimiters for line splitting."""
801 """Set the delimiters for line splitting."""
802 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
802 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
803 self._delim_re = re.compile(expr)
803 self._delim_re = re.compile(expr)
804 self._delims = delims
804 self._delims = delims
805 self._delim_expr = expr
805 self._delim_expr = expr
806
806
807 def split_line(self, line, cursor_pos=None):
807 def split_line(self, line, cursor_pos=None):
808 """Split a line of text with a cursor at the given position.
808 """Split a line of text with a cursor at the given position.
809 """
809 """
810 l = line if cursor_pos is None else line[:cursor_pos]
810 l = line if cursor_pos is None else line[:cursor_pos]
811 return self._delim_re.split(l)[-1]
811 return self._delim_re.split(l)[-1]
812
812
813
813
814
814
815 class Completer(Configurable):
815 class Completer(Configurable):
816
816
817 greedy = Bool(False,
817 greedy = Bool(False,
818 help="""Activate greedy completion
818 help="""Activate greedy completion
819 PENDING DEPRECATION. this is now mostly taken care of with Jedi.
819 PENDING DEPRECATION. this is now mostly taken care of with Jedi.
820
820
821 This will enable completion on elements of lists, results of function calls, etc.,
821 This will enable completion on elements of lists, results of function calls, etc.,
822 but can be unsafe because the code is actually evaluated on TAB.
822 but can be unsafe because the code is actually evaluated on TAB.
823 """,
823 """,
824 ).tag(config=True)
824 ).tag(config=True)
825
825
826 use_jedi = Bool(default_value=JEDI_INSTALLED,
826 use_jedi = Bool(default_value=JEDI_INSTALLED,
827 help="Experimental: Use Jedi to generate autocompletions. "
827 help="Experimental: Use Jedi to generate autocompletions. "
828 "Default to True if jedi is installed.").tag(config=True)
828 "Default to True if jedi is installed.").tag(config=True)
829
829
830 jedi_compute_type_timeout = Int(default_value=400,
830 jedi_compute_type_timeout = Int(default_value=400,
831 help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types.
831 help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types.
832 Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt
832 Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt
833 performance by preventing jedi to build its cache.
833 performance by preventing jedi to build its cache.
834 """).tag(config=True)
834 """).tag(config=True)
835
835
836 debug = Bool(default_value=False,
836 debug = Bool(default_value=False,
837 help='Enable debug for the Completer. Mostly print extra '
837 help='Enable debug for the Completer. Mostly print extra '
838 'information for experimental jedi integration.')\
838 'information for experimental jedi integration.')\
839 .tag(config=True)
839 .tag(config=True)
840
840
841 backslash_combining_completions = Bool(True,
841 backslash_combining_completions = Bool(True,
842 help="Enable unicode completions, e.g. \\alpha<tab> . "
842 help="Enable unicode completions, e.g. \\alpha<tab> . "
843 "Includes completion of latex commands, unicode names, and expanding "
843 "Includes completion of latex commands, unicode names, and expanding "
844 "unicode characters back to latex commands.").tag(config=True)
844 "unicode characters back to latex commands.").tag(config=True)
845
845
846 def __init__(self, namespace=None, global_namespace=None, **kwargs):
846 def __init__(self, namespace=None, global_namespace=None, **kwargs):
847 """Create a new completer for the command line.
847 """Create a new completer for the command line.
848
848
849 Completer(namespace=ns, global_namespace=ns2) -> completer instance.
849 Completer(namespace=ns, global_namespace=ns2) -> completer instance.
850
850
851 If unspecified, the default namespace where completions are performed
851 If unspecified, the default namespace where completions are performed
852 is __main__ (technically, __main__.__dict__). Namespaces should be
852 is __main__ (technically, __main__.__dict__). Namespaces should be
853 given as dictionaries.
853 given as dictionaries.
854
854
855 An optional second namespace can be given. This allows the completer
855 An optional second namespace can be given. This allows the completer
856 to handle cases where both the local and global scopes need to be
856 to handle cases where both the local and global scopes need to be
857 distinguished.
857 distinguished.
858 """
858 """
859
859
860 # Don't bind to namespace quite yet, but flag whether the user wants a
860 # Don't bind to namespace quite yet, but flag whether the user wants a
861 # specific namespace or to use __main__.__dict__. This will allow us
861 # specific namespace or to use __main__.__dict__. This will allow us
862 # to bind to __main__.__dict__ at completion time, not now.
862 # to bind to __main__.__dict__ at completion time, not now.
863 if namespace is None:
863 if namespace is None:
864 self.use_main_ns = True
864 self.use_main_ns = True
865 else:
865 else:
866 self.use_main_ns = False
866 self.use_main_ns = False
867 self.namespace = namespace
867 self.namespace = namespace
868
868
869 # The global namespace, if given, can be bound directly
869 # The global namespace, if given, can be bound directly
870 if global_namespace is None:
870 if global_namespace is None:
871 self.global_namespace = {}
871 self.global_namespace = {}
872 else:
872 else:
873 self.global_namespace = global_namespace
873 self.global_namespace = global_namespace
874
874
875 self.custom_matchers = []
875 self.custom_matchers = []
876
876
877 super(Completer, self).__init__(**kwargs)
877 super(Completer, self).__init__(**kwargs)
878
878
879 def complete(self, text, state):
879 def complete(self, text, state):
880 """Return the next possible completion for 'text'.
880 """Return the next possible completion for 'text'.
881
881
882 This is called successively with state == 0, 1, 2, ... until it
882 This is called successively with state == 0, 1, 2, ... until it
883 returns None. The completion should begin with 'text'.
883 returns None. The completion should begin with 'text'.
884
884
885 """
885 """
886 if self.use_main_ns:
886 if self.use_main_ns:
887 self.namespace = __main__.__dict__
887 self.namespace = __main__.__dict__
888
888
889 if state == 0:
889 if state == 0:
890 if "." in text:
890 if "." in text:
891 self.matches = self.attr_matches(text)
891 self.matches = self.attr_matches(text)
892 else:
892 else:
893 self.matches = self.global_matches(text)
893 self.matches = self.global_matches(text)
894 try:
894 try:
895 return self.matches[state]
895 return self.matches[state]
896 except IndexError:
896 except IndexError:
897 return None
897 return None
898
898
899 def global_matches(self, text):
899 def global_matches(self, text):
900 """Compute matches when text is a simple name.
900 """Compute matches when text is a simple name.
901
901
902 Return a list of all keywords, built-in functions and names currently
902 Return a list of all keywords, built-in functions and names currently
903 defined in self.namespace or self.global_namespace that match.
903 defined in self.namespace or self.global_namespace that match.
904
904
905 """
905 """
906 matches = []
906 matches = []
907 match_append = matches.append
907 match_append = matches.append
908 n = len(text)
908 n = len(text)
909 for lst in [keyword.kwlist,
909 for lst in [
910 builtin_mod.__dict__.keys(),
910 keyword.kwlist,
911 self.namespace.keys(),
911 builtin_mod.__dict__.keys(),
912 self.global_namespace.keys()]:
912 list(self.namespace.keys()),
913 list(self.global_namespace.keys()),
914 ]:
913 for word in lst:
915 for word in lst:
914 if word[:n] == text and word != "__builtins__":
916 if word[:n] == text and word != "__builtins__":
915 match_append(word)
917 match_append(word)
916
918
917 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
919 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
918 for lst in [self.namespace.keys(),
920 for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]:
919 self.global_namespace.keys()]:
921 shortened = {
920 shortened = {"_".join([sub[0] for sub in word.split('_')]) : word
922 "_".join([sub[0] for sub in word.split("_")]): word
921 for word in lst if snake_case_re.match(word)}
923 for word in lst
924 if snake_case_re.match(word)
925 }
922 for word in shortened.keys():
926 for word in shortened.keys():
923 if word[:n] == text and word != "__builtins__":
927 if word[:n] == text and word != "__builtins__":
924 match_append(shortened[word])
928 match_append(shortened[word])
925 return matches
929 return matches
926
930
927 def attr_matches(self, text):
931 def attr_matches(self, text):
928 """Compute matches when text contains a dot.
932 """Compute matches when text contains a dot.
929
933
930 Assuming the text is of the form NAME.NAME....[NAME], and is
934 Assuming the text is of the form NAME.NAME....[NAME], and is
931 evaluatable in self.namespace or self.global_namespace, it will be
935 evaluatable in self.namespace or self.global_namespace, it will be
932 evaluated and its attributes (as revealed by dir()) are used as
936 evaluated and its attributes (as revealed by dir()) are used as
933 possible completions. (For class instances, class members are
937 possible completions. (For class instances, class members are
934 also considered.)
938 also considered.)
935
939
936 WARNING: this can still invoke arbitrary C code, if an object
940 WARNING: this can still invoke arbitrary C code, if an object
937 with a __getattr__ hook is evaluated.
941 with a __getattr__ hook is evaluated.
938
942
939 """
943 """
940
944
941 # Another option, seems to work great. Catches things like ''.<tab>
945 # Another option, seems to work great. Catches things like ''.<tab>
942 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
946 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
943
947
944 if m:
948 if m:
945 expr, attr = m.group(1, 3)
949 expr, attr = m.group(1, 3)
946 elif self.greedy:
950 elif self.greedy:
947 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
951 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
948 if not m2:
952 if not m2:
949 return []
953 return []
950 expr, attr = m2.group(1,2)
954 expr, attr = m2.group(1,2)
951 else:
955 else:
952 return []
956 return []
953
957
954 try:
958 try:
955 obj = eval(expr, self.namespace)
959 obj = eval(expr, self.namespace)
956 except:
960 except:
957 try:
961 try:
958 obj = eval(expr, self.global_namespace)
962 obj = eval(expr, self.global_namespace)
959 except:
963 except:
960 return []
964 return []
961
965
962 if self.limit_to__all__ and hasattr(obj, '__all__'):
966 if self.limit_to__all__ and hasattr(obj, '__all__'):
963 words = get__all__entries(obj)
967 words = get__all__entries(obj)
964 else:
968 else:
965 words = dir2(obj)
969 words = dir2(obj)
966
970
967 try:
971 try:
968 words = generics.complete_object(obj, words)
972 words = generics.complete_object(obj, words)
969 except TryNext:
973 except TryNext:
970 pass
974 pass
971 except AssertionError:
975 except AssertionError:
972 raise
976 raise
973 except Exception:
977 except Exception:
974 # Silence errors from completion function
978 # Silence errors from completion function
975 #raise # dbg
979 #raise # dbg
976 pass
980 pass
977 # Build match list to return
981 # Build match list to return
978 n = len(attr)
982 n = len(attr)
979 return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ]
983 return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ]
980
984
981
985
982 def get__all__entries(obj):
986 def get__all__entries(obj):
983 """returns the strings in the __all__ attribute"""
987 """returns the strings in the __all__ attribute"""
984 try:
988 try:
985 words = getattr(obj, '__all__')
989 words = getattr(obj, '__all__')
986 except:
990 except:
987 return []
991 return []
988
992
989 return [w for w in words if isinstance(w, str)]
993 return [w for w in words if isinstance(w, str)]
990
994
991
995
992 def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str,
996 def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str,
993 extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]:
997 extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]:
994 """Used by dict_key_matches, matching the prefix to a list of keys
998 """Used by dict_key_matches, matching the prefix to a list of keys
995
999
996 Parameters
1000 Parameters
997 ----------
1001 ----------
998 keys
1002 keys
999 list of keys in dictionary currently being completed.
1003 list of keys in dictionary currently being completed.
1000 prefix
1004 prefix
1001 Part of the text already typed by the user. E.g. `mydict[b'fo`
1005 Part of the text already typed by the user. E.g. `mydict[b'fo`
1002 delims
1006 delims
1003 String of delimiters to consider when finding the current key.
1007 String of delimiters to consider when finding the current key.
1004 extra_prefix : optional
1008 extra_prefix : optional
1005 Part of the text already typed in multi-key index cases. E.g. for
1009 Part of the text already typed in multi-key index cases. E.g. for
1006 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
1010 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
1007
1011
1008 Returns
1012 Returns
1009 -------
1013 -------
1010 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
1014 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
1011 ``quote`` being the quote that need to be used to close current string.
1015 ``quote`` being the quote that need to be used to close current string.
1012 ``token_start`` the position where the replacement should start occurring,
1016 ``token_start`` the position where the replacement should start occurring,
1013 ``matches`` a list of replacement/completion
1017 ``matches`` a list of replacement/completion
1014
1018
1015 """
1019 """
1016 prefix_tuple = extra_prefix if extra_prefix else ()
1020 prefix_tuple = extra_prefix if extra_prefix else ()
1017 Nprefix = len(prefix_tuple)
1021 Nprefix = len(prefix_tuple)
1018 def filter_prefix_tuple(key):
1022 def filter_prefix_tuple(key):
1019 # Reject too short keys
1023 # Reject too short keys
1020 if len(key) <= Nprefix:
1024 if len(key) <= Nprefix:
1021 return False
1025 return False
1022 # Reject keys with non str/bytes in it
1026 # Reject keys with non str/bytes in it
1023 for k in key:
1027 for k in key:
1024 if not isinstance(k, (str, bytes)):
1028 if not isinstance(k, (str, bytes)):
1025 return False
1029 return False
1026 # Reject keys that do not match the prefix
1030 # Reject keys that do not match the prefix
1027 for k, pt in zip(key, prefix_tuple):
1031 for k, pt in zip(key, prefix_tuple):
1028 if k != pt:
1032 if k != pt:
1029 return False
1033 return False
1030 # All checks passed!
1034 # All checks passed!
1031 return True
1035 return True
1032
1036
1033 filtered_keys:List[Union[str,bytes]] = []
1037 filtered_keys:List[Union[str,bytes]] = []
1034 def _add_to_filtered_keys(key):
1038 def _add_to_filtered_keys(key):
1035 if isinstance(key, (str, bytes)):
1039 if isinstance(key, (str, bytes)):
1036 filtered_keys.append(key)
1040 filtered_keys.append(key)
1037
1041
1038 for k in keys:
1042 for k in keys:
1039 if isinstance(k, tuple):
1043 if isinstance(k, tuple):
1040 if filter_prefix_tuple(k):
1044 if filter_prefix_tuple(k):
1041 _add_to_filtered_keys(k[Nprefix])
1045 _add_to_filtered_keys(k[Nprefix])
1042 else:
1046 else:
1043 _add_to_filtered_keys(k)
1047 _add_to_filtered_keys(k)
1044
1048
1045 if not prefix:
1049 if not prefix:
1046 return '', 0, [repr(k) for k in filtered_keys]
1050 return '', 0, [repr(k) for k in filtered_keys]
1047 quote_match = re.search('["\']', prefix)
1051 quote_match = re.search('["\']', prefix)
1048 assert quote_match is not None # silence mypy
1052 assert quote_match is not None # silence mypy
1049 quote = quote_match.group()
1053 quote = quote_match.group()
1050 try:
1054 try:
1051 prefix_str = eval(prefix + quote, {})
1055 prefix_str = eval(prefix + quote, {})
1052 except Exception:
1056 except Exception:
1053 return '', 0, []
1057 return '', 0, []
1054
1058
1055 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1059 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1056 token_match = re.search(pattern, prefix, re.UNICODE)
1060 token_match = re.search(pattern, prefix, re.UNICODE)
1057 assert token_match is not None # silence mypy
1061 assert token_match is not None # silence mypy
1058 token_start = token_match.start()
1062 token_start = token_match.start()
1059 token_prefix = token_match.group()
1063 token_prefix = token_match.group()
1060
1064
1061 matched:List[str] = []
1065 matched:List[str] = []
1062 for key in filtered_keys:
1066 for key in filtered_keys:
1063 try:
1067 try:
1064 if not key.startswith(prefix_str):
1068 if not key.startswith(prefix_str):
1065 continue
1069 continue
1066 except (AttributeError, TypeError, UnicodeError):
1070 except (AttributeError, TypeError, UnicodeError):
1067 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1071 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1068 continue
1072 continue
1069
1073
1070 # reformat remainder of key to begin with prefix
1074 # reformat remainder of key to begin with prefix
1071 rem = key[len(prefix_str):]
1075 rem = key[len(prefix_str):]
1072 # force repr wrapped in '
1076 # force repr wrapped in '
1073 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
1077 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
1074 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
1078 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
1075 if quote == '"':
1079 if quote == '"':
1076 # The entered prefix is quoted with ",
1080 # The entered prefix is quoted with ",
1077 # but the match is quoted with '.
1081 # but the match is quoted with '.
1078 # A contained " hence needs escaping for comparison:
1082 # A contained " hence needs escaping for comparison:
1079 rem_repr = rem_repr.replace('"', '\\"')
1083 rem_repr = rem_repr.replace('"', '\\"')
1080
1084
1081 # then reinsert prefix from start of token
1085 # then reinsert prefix from start of token
1082 matched.append('%s%s' % (token_prefix, rem_repr))
1086 matched.append('%s%s' % (token_prefix, rem_repr))
1083 return quote, token_start, matched
1087 return quote, token_start, matched
1084
1088
1085
1089
1086 def cursor_to_position(text:str, line:int, column:int)->int:
1090 def cursor_to_position(text:str, line:int, column:int)->int:
1087 """
1091 """
1088 Convert the (line,column) position of the cursor in text to an offset in a
1092 Convert the (line,column) position of the cursor in text to an offset in a
1089 string.
1093 string.
1090
1094
1091 Parameters
1095 Parameters
1092 ----------
1096 ----------
1093 text : str
1097 text : str
1094 The text in which to calculate the cursor offset
1098 The text in which to calculate the cursor offset
1095 line : int
1099 line : int
1096 Line of the cursor; 0-indexed
1100 Line of the cursor; 0-indexed
1097 column : int
1101 column : int
1098 Column of the cursor 0-indexed
1102 Column of the cursor 0-indexed
1099
1103
1100 Returns
1104 Returns
1101 -------
1105 -------
1102 Position of the cursor in ``text``, 0-indexed.
1106 Position of the cursor in ``text``, 0-indexed.
1103
1107
1104 See Also
1108 See Also
1105 --------
1109 --------
1106 position_to_cursor : reciprocal of this function
1110 position_to_cursor : reciprocal of this function
1107
1111
1108 """
1112 """
1109 lines = text.split('\n')
1113 lines = text.split('\n')
1110 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
1114 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
1111
1115
1112 return sum(len(l) + 1 for l in lines[:line]) + column
1116 return sum(len(l) + 1 for l in lines[:line]) + column
1113
1117
1114 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
1118 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
1115 """
1119 """
1116 Convert the position of the cursor in text (0 indexed) to a line
1120 Convert the position of the cursor in text (0 indexed) to a line
1117 number(0-indexed) and a column number (0-indexed) pair
1121 number(0-indexed) and a column number (0-indexed) pair
1118
1122
1119 Position should be a valid position in ``text``.
1123 Position should be a valid position in ``text``.
1120
1124
1121 Parameters
1125 Parameters
1122 ----------
1126 ----------
1123 text : str
1127 text : str
1124 The text in which to calculate the cursor offset
1128 The text in which to calculate the cursor offset
1125 offset : int
1129 offset : int
1126 Position of the cursor in ``text``, 0-indexed.
1130 Position of the cursor in ``text``, 0-indexed.
1127
1131
1128 Returns
1132 Returns
1129 -------
1133 -------
1130 (line, column) : (int, int)
1134 (line, column) : (int, int)
1131 Line of the cursor; 0-indexed, column of the cursor 0-indexed
1135 Line of the cursor; 0-indexed, column of the cursor 0-indexed
1132
1136
1133 See Also
1137 See Also
1134 --------
1138 --------
1135 cursor_to_position : reciprocal of this function
1139 cursor_to_position : reciprocal of this function
1136
1140
1137 """
1141 """
1138
1142
1139 assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text))
1143 assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text))
1140
1144
1141 before = text[:offset]
1145 before = text[:offset]
1142 blines = before.split('\n') # ! splitnes trim trailing \n
1146 blines = before.split('\n') # ! splitnes trim trailing \n
1143 line = before.count('\n')
1147 line = before.count('\n')
1144 col = len(blines[-1])
1148 col = len(blines[-1])
1145 return line, col
1149 return line, col
1146
1150
1147
1151
1148 def _safe_isinstance(obj, module, class_name):
1152 def _safe_isinstance(obj, module, class_name):
1149 """Checks if obj is an instance of module.class_name if loaded
1153 """Checks if obj is an instance of module.class_name if loaded
1150 """
1154 """
1151 return (module in sys.modules and
1155 return (module in sys.modules and
1152 isinstance(obj, getattr(import_module(module), class_name)))
1156 isinstance(obj, getattr(import_module(module), class_name)))
1153
1157
1154
1158
1155 @context_matcher()
1159 @context_matcher()
1156 def back_unicode_name_matcher(context):
1160 def back_unicode_name_matcher(context):
1157 """Match Unicode characters back to Unicode name
1161 """Match Unicode characters back to Unicode name
1158
1162
1159 Same as ``back_unicode_name_matches``, but adopted to new Matcher API.
1163 Same as ``back_unicode_name_matches``, but adopted to new Matcher API.
1160 """
1164 """
1161 fragment, matches = back_unicode_name_matches(context.token)
1165 fragment, matches = back_unicode_name_matches(context.token)
1162 return _convert_matcher_v1_result_to_v2(
1166 return _convert_matcher_v1_result_to_v2(
1163 matches, type="unicode", fragment=fragment, suppress_if_matches=True
1167 matches, type="unicode", fragment=fragment, suppress_if_matches=True
1164 )
1168 )
1165
1169
1166
1170
1167 def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1171 def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1168 """Match Unicode characters back to Unicode name
1172 """Match Unicode characters back to Unicode name
1169
1173
1170 This does ``☃`` -> ``\\snowman``
1174 This does ``☃`` -> ``\\snowman``
1171
1175
1172 Note that snowman is not a valid python3 combining character but will be expanded.
1176 Note that snowman is not a valid python3 combining character but will be expanded.
1173 Though it will not recombine back to the snowman character by the completion machinery.
1177 Though it will not recombine back to the snowman character by the completion machinery.
1174
1178
1175 This will not either back-complete standard sequences like \\n, \\b ...
1179 This will not either back-complete standard sequences like \\n, \\b ...
1176
1180
1177 Returns
1181 Returns
1178 =======
1182 =======
1179
1183
1180 Return a tuple with two elements:
1184 Return a tuple with two elements:
1181
1185
1182 - The Unicode character that was matched (preceded with a backslash), or
1186 - The Unicode character that was matched (preceded with a backslash), or
1183 empty string,
1187 empty string,
1184 - a sequence (of 1), name for the match Unicode character, preceded by
1188 - a sequence (of 1), name for the match Unicode character, preceded by
1185 backslash, or empty if no match.
1189 backslash, or empty if no match.
1186
1190
1187 """
1191 """
1188 if len(text)<2:
1192 if len(text)<2:
1189 return '', ()
1193 return '', ()
1190 maybe_slash = text[-2]
1194 maybe_slash = text[-2]
1191 if maybe_slash != '\\':
1195 if maybe_slash != '\\':
1192 return '', ()
1196 return '', ()
1193
1197
1194 char = text[-1]
1198 char = text[-1]
1195 # no expand on quote for completion in strings.
1199 # no expand on quote for completion in strings.
1196 # nor backcomplete standard ascii keys
1200 # nor backcomplete standard ascii keys
1197 if char in string.ascii_letters or char in ('"',"'"):
1201 if char in string.ascii_letters or char in ('"',"'"):
1198 return '', ()
1202 return '', ()
1199 try :
1203 try :
1200 unic = unicodedata.name(char)
1204 unic = unicodedata.name(char)
1201 return '\\'+char,('\\'+unic,)
1205 return '\\'+char,('\\'+unic,)
1202 except KeyError:
1206 except KeyError:
1203 pass
1207 pass
1204 return '', ()
1208 return '', ()
1205
1209
1206
1210
1207 @context_matcher()
1211 @context_matcher()
1208 def back_latex_name_matcher(context):
1212 def back_latex_name_matcher(context):
1209 """Match latex characters back to unicode name
1213 """Match latex characters back to unicode name
1210
1214
1211 Same as ``back_latex_name_matches``, but adopted to new Matcher API.
1215 Same as ``back_latex_name_matches``, but adopted to new Matcher API.
1212 """
1216 """
1213 fragment, matches = back_latex_name_matches(context.token)
1217 fragment, matches = back_latex_name_matches(context.token)
1214 return _convert_matcher_v1_result_to_v2(
1218 return _convert_matcher_v1_result_to_v2(
1215 matches, type="latex", fragment=fragment, suppress_if_matches=True
1219 matches, type="latex", fragment=fragment, suppress_if_matches=True
1216 )
1220 )
1217
1221
1218
1222
1219 def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1223 def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1220 """Match latex characters back to unicode name
1224 """Match latex characters back to unicode name
1221
1225
1222 This does ``\\ℵ`` -> ``\\aleph``
1226 This does ``\\ℵ`` -> ``\\aleph``
1223
1227
1224 """
1228 """
1225 if len(text)<2:
1229 if len(text)<2:
1226 return '', ()
1230 return '', ()
1227 maybe_slash = text[-2]
1231 maybe_slash = text[-2]
1228 if maybe_slash != '\\':
1232 if maybe_slash != '\\':
1229 return '', ()
1233 return '', ()
1230
1234
1231
1235
1232 char = text[-1]
1236 char = text[-1]
1233 # no expand on quote for completion in strings.
1237 # no expand on quote for completion in strings.
1234 # nor backcomplete standard ascii keys
1238 # nor backcomplete standard ascii keys
1235 if char in string.ascii_letters or char in ('"',"'"):
1239 if char in string.ascii_letters or char in ('"',"'"):
1236 return '', ()
1240 return '', ()
1237 try :
1241 try :
1238 latex = reverse_latex_symbol[char]
1242 latex = reverse_latex_symbol[char]
1239 # '\\' replace the \ as well
1243 # '\\' replace the \ as well
1240 return '\\'+char,[latex]
1244 return '\\'+char,[latex]
1241 except KeyError:
1245 except KeyError:
1242 pass
1246 pass
1243 return '', ()
1247 return '', ()
1244
1248
1245
1249
1246 def _formatparamchildren(parameter) -> str:
1250 def _formatparamchildren(parameter) -> str:
1247 """
1251 """
1248 Get parameter name and value from Jedi Private API
1252 Get parameter name and value from Jedi Private API
1249
1253
1250 Jedi does not expose a simple way to get `param=value` from its API.
1254 Jedi does not expose a simple way to get `param=value` from its API.
1251
1255
1252 Parameters
1256 Parameters
1253 ----------
1257 ----------
1254 parameter
1258 parameter
1255 Jedi's function `Param`
1259 Jedi's function `Param`
1256
1260
1257 Returns
1261 Returns
1258 -------
1262 -------
1259 A string like 'a', 'b=1', '*args', '**kwargs'
1263 A string like 'a', 'b=1', '*args', '**kwargs'
1260
1264
1261 """
1265 """
1262 description = parameter.description
1266 description = parameter.description
1263 if not description.startswith('param '):
1267 if not description.startswith('param '):
1264 raise ValueError('Jedi function parameter description have change format.'
1268 raise ValueError('Jedi function parameter description have change format.'
1265 'Expected "param ...", found %r".' % description)
1269 'Expected "param ...", found %r".' % description)
1266 return description[6:]
1270 return description[6:]
1267
1271
1268 def _make_signature(completion)-> str:
1272 def _make_signature(completion)-> str:
1269 """
1273 """
1270 Make the signature from a jedi completion
1274 Make the signature from a jedi completion
1271
1275
1272 Parameters
1276 Parameters
1273 ----------
1277 ----------
1274 completion : jedi.Completion
1278 completion : jedi.Completion
1275 object does not complete a function type
1279 object does not complete a function type
1276
1280
1277 Returns
1281 Returns
1278 -------
1282 -------
1279 a string consisting of the function signature, with the parenthesis but
1283 a string consisting of the function signature, with the parenthesis but
1280 without the function name. example:
1284 without the function name. example:
1281 `(a, *args, b=1, **kwargs)`
1285 `(a, *args, b=1, **kwargs)`
1282
1286
1283 """
1287 """
1284
1288
1285 # it looks like this might work on jedi 0.17
1289 # it looks like this might work on jedi 0.17
1286 if hasattr(completion, 'get_signatures'):
1290 if hasattr(completion, 'get_signatures'):
1287 signatures = completion.get_signatures()
1291 signatures = completion.get_signatures()
1288 if not signatures:
1292 if not signatures:
1289 return '(?)'
1293 return '(?)'
1290
1294
1291 c0 = completion.get_signatures()[0]
1295 c0 = completion.get_signatures()[0]
1292 return '('+c0.to_string().split('(', maxsplit=1)[1]
1296 return '('+c0.to_string().split('(', maxsplit=1)[1]
1293
1297
1294 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1298 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1295 for p in signature.defined_names()) if f])
1299 for p in signature.defined_names()) if f])
1296
1300
1297
1301
1298 _CompleteResult = Dict[str, MatcherResult]
1302 _CompleteResult = Dict[str, MatcherResult]
1299
1303
1300
1304
1301 def _convert_matcher_v1_result_to_v2(
1305 def _convert_matcher_v1_result_to_v2(
1302 matches: Sequence[str],
1306 matches: Sequence[str],
1303 type: str,
1307 type: str,
1304 fragment: str = None,
1308 fragment: str = None,
1305 suppress_if_matches: bool = False,
1309 suppress_if_matches: bool = False,
1306 ) -> SimpleMatcherResult:
1310 ) -> SimpleMatcherResult:
1307 """Utility to help with transition"""
1311 """Utility to help with transition"""
1308 result = {
1312 result = {
1309 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1313 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1310 "suppress": (True if matches else False) if suppress_if_matches else False,
1314 "suppress": (True if matches else False) if suppress_if_matches else False,
1311 }
1315 }
1312 if fragment is not None:
1316 if fragment is not None:
1313 result["matched_fragment"] = fragment
1317 result["matched_fragment"] = fragment
1314 return result
1318 return result
1315
1319
1316
1320
1317 class IPCompleter(Completer):
1321 class IPCompleter(Completer):
1318 """Extension of the completer class with IPython-specific features"""
1322 """Extension of the completer class with IPython-specific features"""
1319
1323
1320 __dict_key_regexps: Optional[Dict[bool,Pattern]] = None
1324 __dict_key_regexps: Optional[Dict[bool,Pattern]] = None
1321
1325
1322 @observe('greedy')
1326 @observe('greedy')
1323 def _greedy_changed(self, change):
1327 def _greedy_changed(self, change):
1324 """update the splitter and readline delims when greedy is changed"""
1328 """update the splitter and readline delims when greedy is changed"""
1325 if change['new']:
1329 if change['new']:
1326 self.splitter.delims = GREEDY_DELIMS
1330 self.splitter.delims = GREEDY_DELIMS
1327 else:
1331 else:
1328 self.splitter.delims = DELIMS
1332 self.splitter.delims = DELIMS
1329
1333
1330 dict_keys_only = Bool(
1334 dict_keys_only = Bool(
1331 False,
1335 False,
1332 help="""
1336 help="""
1333 Whether to show dict key matches only.
1337 Whether to show dict key matches only.
1334
1338
1335 (disables all matchers except for `IPCompleter.dict_key_matcher`).
1339 (disables all matchers except for `IPCompleter.dict_key_matcher`).
1336 """,
1340 """,
1337 )
1341 )
1338
1342
1339 suppress_competing_matchers = UnionTrait(
1343 suppress_competing_matchers = UnionTrait(
1340 [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))],
1344 [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))],
1341 default_value=None,
1345 default_value=None,
1342 help="""
1346 help="""
1343 Whether to suppress completions from other *Matchers*.
1347 Whether to suppress completions from other *Matchers*.
1344
1348
1345 When set to ``None`` (default) the matchers will attempt to auto-detect
1349 When set to ``None`` (default) the matchers will attempt to auto-detect
1346 whether suppression of other matchers is desirable. For example, at
1350 whether suppression of other matchers is desirable. For example, at
1347 the beginning of a line followed by `%` we expect a magic completion
1351 the beginning of a line followed by `%` we expect a magic completion
1348 to be the only applicable option, and after ``my_dict['`` we usually
1352 to be the only applicable option, and after ``my_dict['`` we usually
1349 expect a completion with an existing dictionary key.
1353 expect a completion with an existing dictionary key.
1350
1354
1351 If you want to disable this heuristic and see completions from all matchers,
1355 If you want to disable this heuristic and see completions from all matchers,
1352 set ``IPCompleter.suppress_competing_matchers = False``.
1356 set ``IPCompleter.suppress_competing_matchers = False``.
1353 To disable the heuristic for specific matchers provide a dictionary mapping:
1357 To disable the heuristic for specific matchers provide a dictionary mapping:
1354 ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``.
1358 ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``.
1355
1359
1356 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1360 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1357 completions to the set of matchers with the highest priority;
1361 completions to the set of matchers with the highest priority;
1358 this is equivalent to ``IPCompleter.merge_completions`` and
1362 this is equivalent to ``IPCompleter.merge_completions`` and
1359 can be beneficial for performance, but will sometimes omit relevant
1363 can be beneficial for performance, but will sometimes omit relevant
1360 candidates from matchers further down the priority list.
1364 candidates from matchers further down the priority list.
1361 """,
1365 """,
1362 ).tag(config=True)
1366 ).tag(config=True)
1363
1367
1364 merge_completions = Bool(
1368 merge_completions = Bool(
1365 True,
1369 True,
1366 help="""Whether to merge completion results into a single list
1370 help="""Whether to merge completion results into a single list
1367
1371
1368 If False, only the completion results from the first non-empty
1372 If False, only the completion results from the first non-empty
1369 completer will be returned.
1373 completer will be returned.
1370
1374
1371 As of version 8.6.0, setting the value to ``False`` is an alias for:
1375 As of version 8.6.0, setting the value to ``False`` is an alias for:
1372 ``IPCompleter.suppress_competing_matchers = True.``.
1376 ``IPCompleter.suppress_competing_matchers = True.``.
1373 """,
1377 """,
1374 ).tag(config=True)
1378 ).tag(config=True)
1375
1379
1376 disable_matchers = ListTrait(
1380 disable_matchers = ListTrait(
1377 Unicode(), help="""List of matchers to disable."""
1381 Unicode(), help="""List of matchers to disable."""
1378 ).tag(config=True)
1382 ).tag(config=True)
1379
1383
1380 omit__names = Enum(
1384 omit__names = Enum(
1381 (0, 1, 2),
1385 (0, 1, 2),
1382 default_value=2,
1386 default_value=2,
1383 help="""Instruct the completer to omit private method names
1387 help="""Instruct the completer to omit private method names
1384
1388
1385 Specifically, when completing on ``object.<tab>``.
1389 Specifically, when completing on ``object.<tab>``.
1386
1390
1387 When 2 [default]: all names that start with '_' will be excluded.
1391 When 2 [default]: all names that start with '_' will be excluded.
1388
1392
1389 When 1: all 'magic' names (``__foo__``) will be excluded.
1393 When 1: all 'magic' names (``__foo__``) will be excluded.
1390
1394
1391 When 0: nothing will be excluded.
1395 When 0: nothing will be excluded.
1392 """
1396 """
1393 ).tag(config=True)
1397 ).tag(config=True)
1394 limit_to__all__ = Bool(False,
1398 limit_to__all__ = Bool(False,
1395 help="""
1399 help="""
1396 DEPRECATED as of version 5.0.
1400 DEPRECATED as of version 5.0.
1397
1401
1398 Instruct the completer to use __all__ for the completion
1402 Instruct the completer to use __all__ for the completion
1399
1403
1400 Specifically, when completing on ``object.<tab>``.
1404 Specifically, when completing on ``object.<tab>``.
1401
1405
1402 When True: only those names in obj.__all__ will be included.
1406 When True: only those names in obj.__all__ will be included.
1403
1407
1404 When False [default]: the __all__ attribute is ignored
1408 When False [default]: the __all__ attribute is ignored
1405 """,
1409 """,
1406 ).tag(config=True)
1410 ).tag(config=True)
1407
1411
1408 profile_completions = Bool(
1412 profile_completions = Bool(
1409 default_value=False,
1413 default_value=False,
1410 help="If True, emit profiling data for completion subsystem using cProfile."
1414 help="If True, emit profiling data for completion subsystem using cProfile."
1411 ).tag(config=True)
1415 ).tag(config=True)
1412
1416
1413 profiler_output_dir = Unicode(
1417 profiler_output_dir = Unicode(
1414 default_value=".completion_profiles",
1418 default_value=".completion_profiles",
1415 help="Template for path at which to output profile data for completions."
1419 help="Template for path at which to output profile data for completions."
1416 ).tag(config=True)
1420 ).tag(config=True)
1417
1421
1418 @observe('limit_to__all__')
1422 @observe('limit_to__all__')
1419 def _limit_to_all_changed(self, change):
1423 def _limit_to_all_changed(self, change):
1420 warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration '
1424 warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration '
1421 'value has been deprecated since IPython 5.0, will be made to have '
1425 'value has been deprecated since IPython 5.0, will be made to have '
1422 'no effects and then removed in future version of IPython.',
1426 'no effects and then removed in future version of IPython.',
1423 UserWarning)
1427 UserWarning)
1424
1428
1425 def __init__(
1429 def __init__(
1426 self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs
1430 self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs
1427 ):
1431 ):
1428 """IPCompleter() -> completer
1432 """IPCompleter() -> completer
1429
1433
1430 Return a completer object.
1434 Return a completer object.
1431
1435
1432 Parameters
1436 Parameters
1433 ----------
1437 ----------
1434 shell
1438 shell
1435 a pointer to the ipython shell itself. This is needed
1439 a pointer to the ipython shell itself. This is needed
1436 because this completer knows about magic functions, and those can
1440 because this completer knows about magic functions, and those can
1437 only be accessed via the ipython instance.
1441 only be accessed via the ipython instance.
1438 namespace : dict, optional
1442 namespace : dict, optional
1439 an optional dict where completions are performed.
1443 an optional dict where completions are performed.
1440 global_namespace : dict, optional
1444 global_namespace : dict, optional
1441 secondary optional dict for completions, to
1445 secondary optional dict for completions, to
1442 handle cases (such as IPython embedded inside functions) where
1446 handle cases (such as IPython embedded inside functions) where
1443 both Python scopes are visible.
1447 both Python scopes are visible.
1444 config : Config
1448 config : Config
1445 traitlet's config object
1449 traitlet's config object
1446 **kwargs
1450 **kwargs
1447 passed to super class unmodified.
1451 passed to super class unmodified.
1448 """
1452 """
1449
1453
1450 self.magic_escape = ESC_MAGIC
1454 self.magic_escape = ESC_MAGIC
1451 self.splitter = CompletionSplitter()
1455 self.splitter = CompletionSplitter()
1452
1456
1453 # _greedy_changed() depends on splitter and readline being defined:
1457 # _greedy_changed() depends on splitter and readline being defined:
1454 super().__init__(
1458 super().__init__(
1455 namespace=namespace,
1459 namespace=namespace,
1456 global_namespace=global_namespace,
1460 global_namespace=global_namespace,
1457 config=config,
1461 config=config,
1458 **kwargs,
1462 **kwargs,
1459 )
1463 )
1460
1464
1461 # List where completion matches will be stored
1465 # List where completion matches will be stored
1462 self.matches = []
1466 self.matches = []
1463 self.shell = shell
1467 self.shell = shell
1464 # Regexp to split filenames with spaces in them
1468 # Regexp to split filenames with spaces in them
1465 self.space_name_re = re.compile(r'([^\\] )')
1469 self.space_name_re = re.compile(r'([^\\] )')
1466 # Hold a local ref. to glob.glob for speed
1470 # Hold a local ref. to glob.glob for speed
1467 self.glob = glob.glob
1471 self.glob = glob.glob
1468
1472
1469 # Determine if we are running on 'dumb' terminals, like (X)Emacs
1473 # Determine if we are running on 'dumb' terminals, like (X)Emacs
1470 # buffers, to avoid completion problems.
1474 # buffers, to avoid completion problems.
1471 term = os.environ.get('TERM','xterm')
1475 term = os.environ.get('TERM','xterm')
1472 self.dumb_terminal = term in ['dumb','emacs']
1476 self.dumb_terminal = term in ['dumb','emacs']
1473
1477
1474 # Special handling of backslashes needed in win32 platforms
1478 # Special handling of backslashes needed in win32 platforms
1475 if sys.platform == "win32":
1479 if sys.platform == "win32":
1476 self.clean_glob = self._clean_glob_win32
1480 self.clean_glob = self._clean_glob_win32
1477 else:
1481 else:
1478 self.clean_glob = self._clean_glob
1482 self.clean_glob = self._clean_glob
1479
1483
1480 #regexp to parse docstring for function signature
1484 #regexp to parse docstring for function signature
1481 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1485 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1482 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1486 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1483 #use this if positional argument name is also needed
1487 #use this if positional argument name is also needed
1484 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1488 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1485
1489
1486 self.magic_arg_matchers = [
1490 self.magic_arg_matchers = [
1487 self.magic_config_matcher,
1491 self.magic_config_matcher,
1488 self.magic_color_matcher,
1492 self.magic_color_matcher,
1489 ]
1493 ]
1490
1494
1491 # This is set externally by InteractiveShell
1495 # This is set externally by InteractiveShell
1492 self.custom_completers = None
1496 self.custom_completers = None
1493
1497
1494 # This is a list of names of unicode characters that can be completed
1498 # This is a list of names of unicode characters that can be completed
1495 # into their corresponding unicode value. The list is large, so we
1499 # into their corresponding unicode value. The list is large, so we
1496 # lazily initialize it on first use. Consuming code should access this
1500 # lazily initialize it on first use. Consuming code should access this
1497 # attribute through the `@unicode_names` property.
1501 # attribute through the `@unicode_names` property.
1498 self._unicode_names = None
1502 self._unicode_names = None
1499
1503
1500 self._backslash_combining_matchers = [
1504 self._backslash_combining_matchers = [
1501 self.latex_name_matcher,
1505 self.latex_name_matcher,
1502 self.unicode_name_matcher,
1506 self.unicode_name_matcher,
1503 back_latex_name_matcher,
1507 back_latex_name_matcher,
1504 back_unicode_name_matcher,
1508 back_unicode_name_matcher,
1505 self.fwd_unicode_matcher,
1509 self.fwd_unicode_matcher,
1506 ]
1510 ]
1507
1511
1508 if not self.backslash_combining_completions:
1512 if not self.backslash_combining_completions:
1509 for matcher in self._backslash_combining_matchers:
1513 for matcher in self._backslash_combining_matchers:
1510 self.disable_matchers.append(matcher.matcher_identifier)
1514 self.disable_matchers.append(matcher.matcher_identifier)
1511
1515
1512 if not self.merge_completions:
1516 if not self.merge_completions:
1513 self.suppress_competing_matchers = True
1517 self.suppress_competing_matchers = True
1514
1518
1515 @property
1519 @property
1516 def matchers(self) -> List[Matcher]:
1520 def matchers(self) -> List[Matcher]:
1517 """All active matcher routines for completion"""
1521 """All active matcher routines for completion"""
1518 if self.dict_keys_only:
1522 if self.dict_keys_only:
1519 return [self.dict_key_matcher]
1523 return [self.dict_key_matcher]
1520
1524
1521 if self.use_jedi:
1525 if self.use_jedi:
1522 return [
1526 return [
1523 *self.custom_matchers,
1527 *self.custom_matchers,
1524 *self._backslash_combining_matchers,
1528 *self._backslash_combining_matchers,
1525 *self.magic_arg_matchers,
1529 *self.magic_arg_matchers,
1526 self.custom_completer_matcher,
1530 self.custom_completer_matcher,
1527 self.magic_matcher,
1531 self.magic_matcher,
1528 self._jedi_matcher,
1532 self._jedi_matcher,
1529 self.dict_key_matcher,
1533 self.dict_key_matcher,
1530 self.file_matcher,
1534 self.file_matcher,
1531 ]
1535 ]
1532 else:
1536 else:
1533 return [
1537 return [
1534 *self.custom_matchers,
1538 *self.custom_matchers,
1535 *self._backslash_combining_matchers,
1539 *self._backslash_combining_matchers,
1536 *self.magic_arg_matchers,
1540 *self.magic_arg_matchers,
1537 self.custom_completer_matcher,
1541 self.custom_completer_matcher,
1538 self.dict_key_matcher,
1542 self.dict_key_matcher,
1539 # TODO: convert python_matches to v2 API
1543 # TODO: convert python_matches to v2 API
1540 self.magic_matcher,
1544 self.magic_matcher,
1541 self.python_matches,
1545 self.python_matches,
1542 self.file_matcher,
1546 self.file_matcher,
1543 self.python_func_kw_matcher,
1547 self.python_func_kw_matcher,
1544 ]
1548 ]
1545
1549
1546 def all_completions(self, text:str) -> List[str]:
1550 def all_completions(self, text:str) -> List[str]:
1547 """
1551 """
1548 Wrapper around the completion methods for the benefit of emacs.
1552 Wrapper around the completion methods for the benefit of emacs.
1549 """
1553 """
1550 prefix = text.rpartition('.')[0]
1554 prefix = text.rpartition('.')[0]
1551 with provisionalcompleter():
1555 with provisionalcompleter():
1552 return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text
1556 return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text
1553 for c in self.completions(text, len(text))]
1557 for c in self.completions(text, len(text))]
1554
1558
1555 return self.complete(text)[1]
1559 return self.complete(text)[1]
1556
1560
1557 def _clean_glob(self, text:str):
1561 def _clean_glob(self, text:str):
1558 return self.glob("%s*" % text)
1562 return self.glob("%s*" % text)
1559
1563
1560 def _clean_glob_win32(self, text:str):
1564 def _clean_glob_win32(self, text:str):
1561 return [f.replace("\\","/")
1565 return [f.replace("\\","/")
1562 for f in self.glob("%s*" % text)]
1566 for f in self.glob("%s*" % text)]
1563
1567
1564 @context_matcher()
1568 @context_matcher()
1565 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1569 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1566 """Same as ``file_matches``, but adopted to new Matcher API."""
1570 """Same as ``file_matches``, but adopted to new Matcher API."""
1567 matches = self.file_matches(context.token)
1571 matches = self.file_matches(context.token)
1568 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1572 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1569 # starts with `/home/`, `C:\`, etc)
1573 # starts with `/home/`, `C:\`, etc)
1570 return _convert_matcher_v1_result_to_v2(matches, type="path")
1574 return _convert_matcher_v1_result_to_v2(matches, type="path")
1571
1575
1572 def file_matches(self, text: str) -> List[str]:
1576 def file_matches(self, text: str) -> List[str]:
1573 """Match filenames, expanding ~USER type strings.
1577 """Match filenames, expanding ~USER type strings.
1574
1578
1575 Most of the seemingly convoluted logic in this completer is an
1579 Most of the seemingly convoluted logic in this completer is an
1576 attempt to handle filenames with spaces in them. And yet it's not
1580 attempt to handle filenames with spaces in them. And yet it's not
1577 quite perfect, because Python's readline doesn't expose all of the
1581 quite perfect, because Python's readline doesn't expose all of the
1578 GNU readline details needed for this to be done correctly.
1582 GNU readline details needed for this to be done correctly.
1579
1583
1580 For a filename with a space in it, the printed completions will be
1584 For a filename with a space in it, the printed completions will be
1581 only the parts after what's already been typed (instead of the
1585 only the parts after what's already been typed (instead of the
1582 full completions, as is normally done). I don't think with the
1586 full completions, as is normally done). I don't think with the
1583 current (as of Python 2.3) Python readline it's possible to do
1587 current (as of Python 2.3) Python readline it's possible to do
1584 better.
1588 better.
1585
1589
1586 DEPRECATED: Deprecated since 8.6. Use ``file_matcher`` instead.
1590 DEPRECATED: Deprecated since 8.6. Use ``file_matcher`` instead.
1587 """
1591 """
1588
1592
1589 # chars that require escaping with backslash - i.e. chars
1593 # chars that require escaping with backslash - i.e. chars
1590 # that readline treats incorrectly as delimiters, but we
1594 # that readline treats incorrectly as delimiters, but we
1591 # don't want to treat as delimiters in filename matching
1595 # don't want to treat as delimiters in filename matching
1592 # when escaped with backslash
1596 # when escaped with backslash
1593 if text.startswith('!'):
1597 if text.startswith('!'):
1594 text = text[1:]
1598 text = text[1:]
1595 text_prefix = u'!'
1599 text_prefix = u'!'
1596 else:
1600 else:
1597 text_prefix = u''
1601 text_prefix = u''
1598
1602
1599 text_until_cursor = self.text_until_cursor
1603 text_until_cursor = self.text_until_cursor
1600 # track strings with open quotes
1604 # track strings with open quotes
1601 open_quotes = has_open_quotes(text_until_cursor)
1605 open_quotes = has_open_quotes(text_until_cursor)
1602
1606
1603 if '(' in text_until_cursor or '[' in text_until_cursor:
1607 if '(' in text_until_cursor or '[' in text_until_cursor:
1604 lsplit = text
1608 lsplit = text
1605 else:
1609 else:
1606 try:
1610 try:
1607 # arg_split ~ shlex.split, but with unicode bugs fixed by us
1611 # arg_split ~ shlex.split, but with unicode bugs fixed by us
1608 lsplit = arg_split(text_until_cursor)[-1]
1612 lsplit = arg_split(text_until_cursor)[-1]
1609 except ValueError:
1613 except ValueError:
1610 # typically an unmatched ", or backslash without escaped char.
1614 # typically an unmatched ", or backslash without escaped char.
1611 if open_quotes:
1615 if open_quotes:
1612 lsplit = text_until_cursor.split(open_quotes)[-1]
1616 lsplit = text_until_cursor.split(open_quotes)[-1]
1613 else:
1617 else:
1614 return []
1618 return []
1615 except IndexError:
1619 except IndexError:
1616 # tab pressed on empty line
1620 # tab pressed on empty line
1617 lsplit = ""
1621 lsplit = ""
1618
1622
1619 if not open_quotes and lsplit != protect_filename(lsplit):
1623 if not open_quotes and lsplit != protect_filename(lsplit):
1620 # if protectables are found, do matching on the whole escaped name
1624 # if protectables are found, do matching on the whole escaped name
1621 has_protectables = True
1625 has_protectables = True
1622 text0,text = text,lsplit
1626 text0,text = text,lsplit
1623 else:
1627 else:
1624 has_protectables = False
1628 has_protectables = False
1625 text = os.path.expanduser(text)
1629 text = os.path.expanduser(text)
1626
1630
1627 if text == "":
1631 if text == "":
1628 return [text_prefix + protect_filename(f) for f in self.glob("*")]
1632 return [text_prefix + protect_filename(f) for f in self.glob("*")]
1629
1633
1630 # Compute the matches from the filesystem
1634 # Compute the matches from the filesystem
1631 if sys.platform == 'win32':
1635 if sys.platform == 'win32':
1632 m0 = self.clean_glob(text)
1636 m0 = self.clean_glob(text)
1633 else:
1637 else:
1634 m0 = self.clean_glob(text.replace('\\', ''))
1638 m0 = self.clean_glob(text.replace('\\', ''))
1635
1639
1636 if has_protectables:
1640 if has_protectables:
1637 # If we had protectables, we need to revert our changes to the
1641 # If we had protectables, we need to revert our changes to the
1638 # beginning of filename so that we don't double-write the part
1642 # beginning of filename so that we don't double-write the part
1639 # of the filename we have so far
1643 # of the filename we have so far
1640 len_lsplit = len(lsplit)
1644 len_lsplit = len(lsplit)
1641 matches = [text_prefix + text0 +
1645 matches = [text_prefix + text0 +
1642 protect_filename(f[len_lsplit:]) for f in m0]
1646 protect_filename(f[len_lsplit:]) for f in m0]
1643 else:
1647 else:
1644 if open_quotes:
1648 if open_quotes:
1645 # if we have a string with an open quote, we don't need to
1649 # if we have a string with an open quote, we don't need to
1646 # protect the names beyond the quote (and we _shouldn't_, as
1650 # protect the names beyond the quote (and we _shouldn't_, as
1647 # it would cause bugs when the filesystem call is made).
1651 # it would cause bugs when the filesystem call is made).
1648 matches = m0 if sys.platform == "win32" else\
1652 matches = m0 if sys.platform == "win32" else\
1649 [protect_filename(f, open_quotes) for f in m0]
1653 [protect_filename(f, open_quotes) for f in m0]
1650 else:
1654 else:
1651 matches = [text_prefix +
1655 matches = [text_prefix +
1652 protect_filename(f) for f in m0]
1656 protect_filename(f) for f in m0]
1653
1657
1654 # Mark directories in input list by appending '/' to their names.
1658 # Mark directories in input list by appending '/' to their names.
1655 return [x+'/' if os.path.isdir(x) else x for x in matches]
1659 return [x+'/' if os.path.isdir(x) else x for x in matches]
1656
1660
1657 @context_matcher()
1661 @context_matcher()
1658 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1662 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1659 text = context.token
1663 text = context.token
1660 matches = self.magic_matches(text)
1664 matches = self.magic_matches(text)
1661 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
1665 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
1662 is_magic_prefix = len(text) > 0 and text[0] == "%"
1666 is_magic_prefix = len(text) > 0 and text[0] == "%"
1663 result["suppress"] = is_magic_prefix and bool(result["completions"])
1667 result["suppress"] = is_magic_prefix and bool(result["completions"])
1664 return result
1668 return result
1665
1669
1666 def magic_matches(self, text: str):
1670 def magic_matches(self, text: str):
1667 """Match magics.
1671 """Match magics.
1668
1672
1669 DEPRECATED: Deprecated since 8.6. Use ``magic_matcher`` instead.
1673 DEPRECATED: Deprecated since 8.6. Use ``magic_matcher`` instead.
1670 """
1674 """
1671 # Get all shell magics now rather than statically, so magics loaded at
1675 # Get all shell magics now rather than statically, so magics loaded at
1672 # runtime show up too.
1676 # runtime show up too.
1673 lsm = self.shell.magics_manager.lsmagic()
1677 lsm = self.shell.magics_manager.lsmagic()
1674 line_magics = lsm['line']
1678 line_magics = lsm['line']
1675 cell_magics = lsm['cell']
1679 cell_magics = lsm['cell']
1676 pre = self.magic_escape
1680 pre = self.magic_escape
1677 pre2 = pre+pre
1681 pre2 = pre+pre
1678
1682
1679 explicit_magic = text.startswith(pre)
1683 explicit_magic = text.startswith(pre)
1680
1684
1681 # Completion logic:
1685 # Completion logic:
1682 # - user gives %%: only do cell magics
1686 # - user gives %%: only do cell magics
1683 # - user gives %: do both line and cell magics
1687 # - user gives %: do both line and cell magics
1684 # - no prefix: do both
1688 # - no prefix: do both
1685 # In other words, line magics are skipped if the user gives %% explicitly
1689 # In other words, line magics are skipped if the user gives %% explicitly
1686 #
1690 #
1687 # We also exclude magics that match any currently visible names:
1691 # We also exclude magics that match any currently visible names:
1688 # https://github.com/ipython/ipython/issues/4877, unless the user has
1692 # https://github.com/ipython/ipython/issues/4877, unless the user has
1689 # typed a %:
1693 # typed a %:
1690 # https://github.com/ipython/ipython/issues/10754
1694 # https://github.com/ipython/ipython/issues/10754
1691 bare_text = text.lstrip(pre)
1695 bare_text = text.lstrip(pre)
1692 global_matches = self.global_matches(bare_text)
1696 global_matches = self.global_matches(bare_text)
1693 if not explicit_magic:
1697 if not explicit_magic:
1694 def matches(magic):
1698 def matches(magic):
1695 """
1699 """
1696 Filter magics, in particular remove magics that match
1700 Filter magics, in particular remove magics that match
1697 a name present in global namespace.
1701 a name present in global namespace.
1698 """
1702 """
1699 return ( magic.startswith(bare_text) and
1703 return ( magic.startswith(bare_text) and
1700 magic not in global_matches )
1704 magic not in global_matches )
1701 else:
1705 else:
1702 def matches(magic):
1706 def matches(magic):
1703 return magic.startswith(bare_text)
1707 return magic.startswith(bare_text)
1704
1708
1705 comp = [ pre2+m for m in cell_magics if matches(m)]
1709 comp = [ pre2+m for m in cell_magics if matches(m)]
1706 if not text.startswith(pre2):
1710 if not text.startswith(pre2):
1707 comp += [ pre+m for m in line_magics if matches(m)]
1711 comp += [ pre+m for m in line_magics if matches(m)]
1708
1712
1709 return comp
1713 return comp
1710
1714
1711 @context_matcher()
1715 @context_matcher()
1712 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1716 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1713 """Match class names and attributes for %config magic."""
1717 """Match class names and attributes for %config magic."""
1714 # NOTE: uses `line_buffer` equivalent for compatibility
1718 # NOTE: uses `line_buffer` equivalent for compatibility
1715 matches = self.magic_config_matches(context.line_with_cursor)
1719 matches = self.magic_config_matches(context.line_with_cursor)
1716 return _convert_matcher_v1_result_to_v2(matches, type="param")
1720 return _convert_matcher_v1_result_to_v2(matches, type="param")
1717
1721
1718 def magic_config_matches(self, text: str) -> List[str]:
1722 def magic_config_matches(self, text: str) -> List[str]:
1719 """Match class names and attributes for %config magic.
1723 """Match class names and attributes for %config magic.
1720
1724
1721 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1725 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1722 """
1726 """
1723 texts = text.strip().split()
1727 texts = text.strip().split()
1724
1728
1725 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
1729 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
1726 # get all configuration classes
1730 # get all configuration classes
1727 classes = sorted(set([ c for c in self.shell.configurables
1731 classes = sorted(set([ c for c in self.shell.configurables
1728 if c.__class__.class_traits(config=True)
1732 if c.__class__.class_traits(config=True)
1729 ]), key=lambda x: x.__class__.__name__)
1733 ]), key=lambda x: x.__class__.__name__)
1730 classnames = [ c.__class__.__name__ for c in classes ]
1734 classnames = [ c.__class__.__name__ for c in classes ]
1731
1735
1732 # return all classnames if config or %config is given
1736 # return all classnames if config or %config is given
1733 if len(texts) == 1:
1737 if len(texts) == 1:
1734 return classnames
1738 return classnames
1735
1739
1736 # match classname
1740 # match classname
1737 classname_texts = texts[1].split('.')
1741 classname_texts = texts[1].split('.')
1738 classname = classname_texts[0]
1742 classname = classname_texts[0]
1739 classname_matches = [ c for c in classnames
1743 classname_matches = [ c for c in classnames
1740 if c.startswith(classname) ]
1744 if c.startswith(classname) ]
1741
1745
1742 # return matched classes or the matched class with attributes
1746 # return matched classes or the matched class with attributes
1743 if texts[1].find('.') < 0:
1747 if texts[1].find('.') < 0:
1744 return classname_matches
1748 return classname_matches
1745 elif len(classname_matches) == 1 and \
1749 elif len(classname_matches) == 1 and \
1746 classname_matches[0] == classname:
1750 classname_matches[0] == classname:
1747 cls = classes[classnames.index(classname)].__class__
1751 cls = classes[classnames.index(classname)].__class__
1748 help = cls.class_get_help()
1752 help = cls.class_get_help()
1749 # strip leading '--' from cl-args:
1753 # strip leading '--' from cl-args:
1750 help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
1754 help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
1751 return [ attr.split('=')[0]
1755 return [ attr.split('=')[0]
1752 for attr in help.strip().splitlines()
1756 for attr in help.strip().splitlines()
1753 if attr.startswith(texts[1]) ]
1757 if attr.startswith(texts[1]) ]
1754 return []
1758 return []
1755
1759
1756 @context_matcher()
1760 @context_matcher()
1757 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1761 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1758 """Match color schemes for %colors magic."""
1762 """Match color schemes for %colors magic."""
1759 # NOTE: uses `line_buffer` equivalent for compatibility
1763 # NOTE: uses `line_buffer` equivalent for compatibility
1760 matches = self.magic_color_matches(context.line_with_cursor)
1764 matches = self.magic_color_matches(context.line_with_cursor)
1761 return _convert_matcher_v1_result_to_v2(matches, type="param")
1765 return _convert_matcher_v1_result_to_v2(matches, type="param")
1762
1766
1763 def magic_color_matches(self, text: str) -> List[str]:
1767 def magic_color_matches(self, text: str) -> List[str]:
1764 """Match color schemes for %colors magic.
1768 """Match color schemes for %colors magic.
1765
1769
1766 DEPRECATED: Deprecated since 8.6. Use ``magic_color_matcher`` instead.
1770 DEPRECATED: Deprecated since 8.6. Use ``magic_color_matcher`` instead.
1767 """
1771 """
1768 texts = text.split()
1772 texts = text.split()
1769 if text.endswith(' '):
1773 if text.endswith(' '):
1770 # .split() strips off the trailing whitespace. Add '' back
1774 # .split() strips off the trailing whitespace. Add '' back
1771 # so that: '%colors ' -> ['%colors', '']
1775 # so that: '%colors ' -> ['%colors', '']
1772 texts.append('')
1776 texts.append('')
1773
1777
1774 if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'):
1778 if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'):
1775 prefix = texts[1]
1779 prefix = texts[1]
1776 return [ color for color in InspectColors.keys()
1780 return [ color for color in InspectColors.keys()
1777 if color.startswith(prefix) ]
1781 if color.startswith(prefix) ]
1778 return []
1782 return []
1779
1783
1780 @context_matcher(identifier="IPCompleter.jedi_matcher")
1784 @context_matcher(identifier="IPCompleter.jedi_matcher")
1781 def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
1785 def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
1782 matches = self._jedi_matches(
1786 matches = self._jedi_matches(
1783 cursor_column=context.cursor_position,
1787 cursor_column=context.cursor_position,
1784 cursor_line=context.cursor_line,
1788 cursor_line=context.cursor_line,
1785 text=context.full_text,
1789 text=context.full_text,
1786 )
1790 )
1787 return {
1791 return {
1788 "completions": matches,
1792 "completions": matches,
1789 # static analysis should not suppress other matchers
1793 # static analysis should not suppress other matchers
1790 "suppress": False,
1794 "suppress": False,
1791 }
1795 }
1792
1796
1793 def _jedi_matches(
1797 def _jedi_matches(
1794 self, cursor_column: int, cursor_line: int, text: str
1798 self, cursor_column: int, cursor_line: int, text: str
1795 ) -> Iterable[_JediCompletionLike]:
1799 ) -> Iterable[_JediCompletionLike]:
1796 """
1800 """
1797 Return a list of :any:`jedi.api.Completion`s object from a ``text`` and
1801 Return a list of :any:`jedi.api.Completion`s object from a ``text`` and
1798 cursor position.
1802 cursor position.
1799
1803
1800 Parameters
1804 Parameters
1801 ----------
1805 ----------
1802 cursor_column : int
1806 cursor_column : int
1803 column position of the cursor in ``text``, 0-indexed.
1807 column position of the cursor in ``text``, 0-indexed.
1804 cursor_line : int
1808 cursor_line : int
1805 line position of the cursor in ``text``, 0-indexed
1809 line position of the cursor in ``text``, 0-indexed
1806 text : str
1810 text : str
1807 text to complete
1811 text to complete
1808
1812
1809 Notes
1813 Notes
1810 -----
1814 -----
1811 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1815 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1812 object containing a string with the Jedi debug information attached.
1816 object containing a string with the Jedi debug information attached.
1813
1817
1814 DEPRECATED: Deprecated since 8.6. Use ``_jedi_matcher`` instead.
1818 DEPRECATED: Deprecated since 8.6. Use ``_jedi_matcher`` instead.
1815 """
1819 """
1816 namespaces = [self.namespace]
1820 namespaces = [self.namespace]
1817 if self.global_namespace is not None:
1821 if self.global_namespace is not None:
1818 namespaces.append(self.global_namespace)
1822 namespaces.append(self.global_namespace)
1819
1823
1820 completion_filter = lambda x:x
1824 completion_filter = lambda x:x
1821 offset = cursor_to_position(text, cursor_line, cursor_column)
1825 offset = cursor_to_position(text, cursor_line, cursor_column)
1822 # filter output if we are completing for object members
1826 # filter output if we are completing for object members
1823 if offset:
1827 if offset:
1824 pre = text[offset-1]
1828 pre = text[offset-1]
1825 if pre == '.':
1829 if pre == '.':
1826 if self.omit__names == 2:
1830 if self.omit__names == 2:
1827 completion_filter = lambda c:not c.name.startswith('_')
1831 completion_filter = lambda c:not c.name.startswith('_')
1828 elif self.omit__names == 1:
1832 elif self.omit__names == 1:
1829 completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__'))
1833 completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__'))
1830 elif self.omit__names == 0:
1834 elif self.omit__names == 0:
1831 completion_filter = lambda x:x
1835 completion_filter = lambda x:x
1832 else:
1836 else:
1833 raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names))
1837 raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names))
1834
1838
1835 interpreter = jedi.Interpreter(text[:offset], namespaces)
1839 interpreter = jedi.Interpreter(text[:offset], namespaces)
1836 try_jedi = True
1840 try_jedi = True
1837
1841
1838 try:
1842 try:
1839 # find the first token in the current tree -- if it is a ' or " then we are in a string
1843 # find the first token in the current tree -- if it is a ' or " then we are in a string
1840 completing_string = False
1844 completing_string = False
1841 try:
1845 try:
1842 first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value'))
1846 first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value'))
1843 except StopIteration:
1847 except StopIteration:
1844 pass
1848 pass
1845 else:
1849 else:
1846 # note the value may be ', ", or it may also be ''' or """, or
1850 # note the value may be ', ", or it may also be ''' or """, or
1847 # in some cases, """what/you/typed..., but all of these are
1851 # in some cases, """what/you/typed..., but all of these are
1848 # strings.
1852 # strings.
1849 completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'}
1853 completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'}
1850
1854
1851 # if we are in a string jedi is likely not the right candidate for
1855 # if we are in a string jedi is likely not the right candidate for
1852 # now. Skip it.
1856 # now. Skip it.
1853 try_jedi = not completing_string
1857 try_jedi = not completing_string
1854 except Exception as e:
1858 except Exception as e:
1855 # many of things can go wrong, we are using private API just don't crash.
1859 # many of things can go wrong, we are using private API just don't crash.
1856 if self.debug:
1860 if self.debug:
1857 print("Error detecting if completing a non-finished string :", e, '|')
1861 print("Error detecting if completing a non-finished string :", e, '|')
1858
1862
1859 if not try_jedi:
1863 if not try_jedi:
1860 return []
1864 return []
1861 try:
1865 try:
1862 return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1))
1866 return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1))
1863 except Exception as e:
1867 except Exception as e:
1864 if self.debug:
1868 if self.debug:
1865 return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))]
1869 return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))]
1866 else:
1870 else:
1867 return []
1871 return []
1868
1872
1869 def python_matches(self, text:str)->List[str]:
1873 def python_matches(self, text:str)->List[str]:
1870 """Match attributes or global python names"""
1874 """Match attributes or global python names"""
1871 if "." in text:
1875 if "." in text:
1872 try:
1876 try:
1873 matches = self.attr_matches(text)
1877 matches = self.attr_matches(text)
1874 if text.endswith('.') and self.omit__names:
1878 if text.endswith('.') and self.omit__names:
1875 if self.omit__names == 1:
1879 if self.omit__names == 1:
1876 # true if txt is _not_ a __ name, false otherwise:
1880 # true if txt is _not_ a __ name, false otherwise:
1877 no__name = (lambda txt:
1881 no__name = (lambda txt:
1878 re.match(r'.*\.__.*?__',txt) is None)
1882 re.match(r'.*\.__.*?__',txt) is None)
1879 else:
1883 else:
1880 # true if txt is _not_ a _ name, false otherwise:
1884 # true if txt is _not_ a _ name, false otherwise:
1881 no__name = (lambda txt:
1885 no__name = (lambda txt:
1882 re.match(r'\._.*?',txt[txt.rindex('.'):]) is None)
1886 re.match(r'\._.*?',txt[txt.rindex('.'):]) is None)
1883 matches = filter(no__name, matches)
1887 matches = filter(no__name, matches)
1884 except NameError:
1888 except NameError:
1885 # catches <undefined attributes>.<tab>
1889 # catches <undefined attributes>.<tab>
1886 matches = []
1890 matches = []
1887 else:
1891 else:
1888 matches = self.global_matches(text)
1892 matches = self.global_matches(text)
1889 return matches
1893 return matches
1890
1894
1891 def _default_arguments_from_docstring(self, doc):
1895 def _default_arguments_from_docstring(self, doc):
1892 """Parse the first line of docstring for call signature.
1896 """Parse the first line of docstring for call signature.
1893
1897
1894 Docstring should be of the form 'min(iterable[, key=func])\n'.
1898 Docstring should be of the form 'min(iterable[, key=func])\n'.
1895 It can also parse cython docstring of the form
1899 It can also parse cython docstring of the form
1896 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
1900 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
1897 """
1901 """
1898 if doc is None:
1902 if doc is None:
1899 return []
1903 return []
1900
1904
1901 #care only the firstline
1905 #care only the firstline
1902 line = doc.lstrip().splitlines()[0]
1906 line = doc.lstrip().splitlines()[0]
1903
1907
1904 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1908 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1905 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
1909 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
1906 sig = self.docstring_sig_re.search(line)
1910 sig = self.docstring_sig_re.search(line)
1907 if sig is None:
1911 if sig is None:
1908 return []
1912 return []
1909 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
1913 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
1910 sig = sig.groups()[0].split(',')
1914 sig = sig.groups()[0].split(',')
1911 ret = []
1915 ret = []
1912 for s in sig:
1916 for s in sig:
1913 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1917 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1914 ret += self.docstring_kwd_re.findall(s)
1918 ret += self.docstring_kwd_re.findall(s)
1915 return ret
1919 return ret
1916
1920
1917 def _default_arguments(self, obj):
1921 def _default_arguments(self, obj):
1918 """Return the list of default arguments of obj if it is callable,
1922 """Return the list of default arguments of obj if it is callable,
1919 or empty list otherwise."""
1923 or empty list otherwise."""
1920 call_obj = obj
1924 call_obj = obj
1921 ret = []
1925 ret = []
1922 if inspect.isbuiltin(obj):
1926 if inspect.isbuiltin(obj):
1923 pass
1927 pass
1924 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
1928 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
1925 if inspect.isclass(obj):
1929 if inspect.isclass(obj):
1926 #for cython embedsignature=True the constructor docstring
1930 #for cython embedsignature=True the constructor docstring
1927 #belongs to the object itself not __init__
1931 #belongs to the object itself not __init__
1928 ret += self._default_arguments_from_docstring(
1932 ret += self._default_arguments_from_docstring(
1929 getattr(obj, '__doc__', ''))
1933 getattr(obj, '__doc__', ''))
1930 # for classes, check for __init__,__new__
1934 # for classes, check for __init__,__new__
1931 call_obj = (getattr(obj, '__init__', None) or
1935 call_obj = (getattr(obj, '__init__', None) or
1932 getattr(obj, '__new__', None))
1936 getattr(obj, '__new__', None))
1933 # for all others, check if they are __call__able
1937 # for all others, check if they are __call__able
1934 elif hasattr(obj, '__call__'):
1938 elif hasattr(obj, '__call__'):
1935 call_obj = obj.__call__
1939 call_obj = obj.__call__
1936 ret += self._default_arguments_from_docstring(
1940 ret += self._default_arguments_from_docstring(
1937 getattr(call_obj, '__doc__', ''))
1941 getattr(call_obj, '__doc__', ''))
1938
1942
1939 _keeps = (inspect.Parameter.KEYWORD_ONLY,
1943 _keeps = (inspect.Parameter.KEYWORD_ONLY,
1940 inspect.Parameter.POSITIONAL_OR_KEYWORD)
1944 inspect.Parameter.POSITIONAL_OR_KEYWORD)
1941
1945
1942 try:
1946 try:
1943 sig = inspect.signature(obj)
1947 sig = inspect.signature(obj)
1944 ret.extend(k for k, v in sig.parameters.items() if
1948 ret.extend(k for k, v in sig.parameters.items() if
1945 v.kind in _keeps)
1949 v.kind in _keeps)
1946 except ValueError:
1950 except ValueError:
1947 pass
1951 pass
1948
1952
1949 return list(set(ret))
1953 return list(set(ret))
1950
1954
1951 @context_matcher()
1955 @context_matcher()
1952 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1956 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1953 """Match named parameters (kwargs) of the last open function."""
1957 """Match named parameters (kwargs) of the last open function."""
1954 matches = self.python_func_kw_matches(context.token)
1958 matches = self.python_func_kw_matches(context.token)
1955 return _convert_matcher_v1_result_to_v2(matches, type="param")
1959 return _convert_matcher_v1_result_to_v2(matches, type="param")
1956
1960
1957 def python_func_kw_matches(self, text):
1961 def python_func_kw_matches(self, text):
1958 """Match named parameters (kwargs) of the last open function.
1962 """Match named parameters (kwargs) of the last open function.
1959
1963
1960 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1964 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1961 """
1965 """
1962
1966
1963 if "." in text: # a parameter cannot be dotted
1967 if "." in text: # a parameter cannot be dotted
1964 return []
1968 return []
1965 try: regexp = self.__funcParamsRegex
1969 try: regexp = self.__funcParamsRegex
1966 except AttributeError:
1970 except AttributeError:
1967 regexp = self.__funcParamsRegex = re.compile(r'''
1971 regexp = self.__funcParamsRegex = re.compile(r'''
1968 '.*?(?<!\\)' | # single quoted strings or
1972 '.*?(?<!\\)' | # single quoted strings or
1969 ".*?(?<!\\)" | # double quoted strings or
1973 ".*?(?<!\\)" | # double quoted strings or
1970 \w+ | # identifier
1974 \w+ | # identifier
1971 \S # other characters
1975 \S # other characters
1972 ''', re.VERBOSE | re.DOTALL)
1976 ''', re.VERBOSE | re.DOTALL)
1973 # 1. find the nearest identifier that comes before an unclosed
1977 # 1. find the nearest identifier that comes before an unclosed
1974 # parenthesis before the cursor
1978 # parenthesis before the cursor
1975 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
1979 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
1976 tokens = regexp.findall(self.text_until_cursor)
1980 tokens = regexp.findall(self.text_until_cursor)
1977 iterTokens = reversed(tokens); openPar = 0
1981 iterTokens = reversed(tokens); openPar = 0
1978
1982
1979 for token in iterTokens:
1983 for token in iterTokens:
1980 if token == ')':
1984 if token == ')':
1981 openPar -= 1
1985 openPar -= 1
1982 elif token == '(':
1986 elif token == '(':
1983 openPar += 1
1987 openPar += 1
1984 if openPar > 0:
1988 if openPar > 0:
1985 # found the last unclosed parenthesis
1989 # found the last unclosed parenthesis
1986 break
1990 break
1987 else:
1991 else:
1988 return []
1992 return []
1989 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
1993 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
1990 ids = []
1994 ids = []
1991 isId = re.compile(r'\w+$').match
1995 isId = re.compile(r'\w+$').match
1992
1996
1993 while True:
1997 while True:
1994 try:
1998 try:
1995 ids.append(next(iterTokens))
1999 ids.append(next(iterTokens))
1996 if not isId(ids[-1]):
2000 if not isId(ids[-1]):
1997 ids.pop(); break
2001 ids.pop(); break
1998 if not next(iterTokens) == '.':
2002 if not next(iterTokens) == '.':
1999 break
2003 break
2000 except StopIteration:
2004 except StopIteration:
2001 break
2005 break
2002
2006
2003 # Find all named arguments already assigned to, as to avoid suggesting
2007 # Find all named arguments already assigned to, as to avoid suggesting
2004 # them again
2008 # them again
2005 usedNamedArgs = set()
2009 usedNamedArgs = set()
2006 par_level = -1
2010 par_level = -1
2007 for token, next_token in zip(tokens, tokens[1:]):
2011 for token, next_token in zip(tokens, tokens[1:]):
2008 if token == '(':
2012 if token == '(':
2009 par_level += 1
2013 par_level += 1
2010 elif token == ')':
2014 elif token == ')':
2011 par_level -= 1
2015 par_level -= 1
2012
2016
2013 if par_level != 0:
2017 if par_level != 0:
2014 continue
2018 continue
2015
2019
2016 if next_token != '=':
2020 if next_token != '=':
2017 continue
2021 continue
2018
2022
2019 usedNamedArgs.add(token)
2023 usedNamedArgs.add(token)
2020
2024
2021 argMatches = []
2025 argMatches = []
2022 try:
2026 try:
2023 callableObj = '.'.join(ids[::-1])
2027 callableObj = '.'.join(ids[::-1])
2024 namedArgs = self._default_arguments(eval(callableObj,
2028 namedArgs = self._default_arguments(eval(callableObj,
2025 self.namespace))
2029 self.namespace))
2026
2030
2027 # Remove used named arguments from the list, no need to show twice
2031 # Remove used named arguments from the list, no need to show twice
2028 for namedArg in set(namedArgs) - usedNamedArgs:
2032 for namedArg in set(namedArgs) - usedNamedArgs:
2029 if namedArg.startswith(text):
2033 if namedArg.startswith(text):
2030 argMatches.append("%s=" %namedArg)
2034 argMatches.append("%s=" %namedArg)
2031 except:
2035 except:
2032 pass
2036 pass
2033
2037
2034 return argMatches
2038 return argMatches
2035
2039
2036 @staticmethod
2040 @staticmethod
2037 def _get_keys(obj: Any) -> List[Any]:
2041 def _get_keys(obj: Any) -> List[Any]:
2038 # Objects can define their own completions by defining an
2042 # Objects can define their own completions by defining an
2039 # _ipy_key_completions_() method.
2043 # _ipy_key_completions_() method.
2040 method = get_real_method(obj, '_ipython_key_completions_')
2044 method = get_real_method(obj, '_ipython_key_completions_')
2041 if method is not None:
2045 if method is not None:
2042 return method()
2046 return method()
2043
2047
2044 # Special case some common in-memory dict-like types
2048 # Special case some common in-memory dict-like types
2045 if isinstance(obj, dict) or\
2049 if isinstance(obj, dict) or\
2046 _safe_isinstance(obj, 'pandas', 'DataFrame'):
2050 _safe_isinstance(obj, 'pandas', 'DataFrame'):
2047 try:
2051 try:
2048 return list(obj.keys())
2052 return list(obj.keys())
2049 except Exception:
2053 except Exception:
2050 return []
2054 return []
2051 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
2055 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
2052 _safe_isinstance(obj, 'numpy', 'void'):
2056 _safe_isinstance(obj, 'numpy', 'void'):
2053 return obj.dtype.names or []
2057 return obj.dtype.names or []
2054 return []
2058 return []
2055
2059
2056 @context_matcher()
2060 @context_matcher()
2057 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2061 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2058 """Match string keys in a dictionary, after e.g. ``foo[``."""
2062 """Match string keys in a dictionary, after e.g. ``foo[``."""
2059 matches = self.dict_key_matches(context.token)
2063 matches = self.dict_key_matches(context.token)
2060 return _convert_matcher_v1_result_to_v2(
2064 return _convert_matcher_v1_result_to_v2(
2061 matches, type="dict key", suppress_if_matches=True
2065 matches, type="dict key", suppress_if_matches=True
2062 )
2066 )
2063
2067
2064 def dict_key_matches(self, text: str) -> List[str]:
2068 def dict_key_matches(self, text: str) -> List[str]:
2065 """Match string keys in a dictionary, after e.g. ``foo[``.
2069 """Match string keys in a dictionary, after e.g. ``foo[``.
2066
2070
2067 DEPRECATED: Deprecated since 8.6. Use `dict_key_matcher` instead.
2071 DEPRECATED: Deprecated since 8.6. Use `dict_key_matcher` instead.
2068 """
2072 """
2069
2073
2070 if self.__dict_key_regexps is not None:
2074 if self.__dict_key_regexps is not None:
2071 regexps = self.__dict_key_regexps
2075 regexps = self.__dict_key_regexps
2072 else:
2076 else:
2073 dict_key_re_fmt = r'''(?x)
2077 dict_key_re_fmt = r'''(?x)
2074 ( # match dict-referring expression wrt greedy setting
2078 ( # match dict-referring expression wrt greedy setting
2075 %s
2079 %s
2076 )
2080 )
2077 \[ # open bracket
2081 \[ # open bracket
2078 \s* # and optional whitespace
2082 \s* # and optional whitespace
2079 # Capture any number of str-like objects (e.g. "a", "b", 'c')
2083 # Capture any number of str-like objects (e.g. "a", "b", 'c')
2080 ((?:[uUbB]? # string prefix (r not handled)
2084 ((?:[uUbB]? # string prefix (r not handled)
2081 (?:
2085 (?:
2082 '(?:[^']|(?<!\\)\\')*'
2086 '(?:[^']|(?<!\\)\\')*'
2083 |
2087 |
2084 "(?:[^"]|(?<!\\)\\")*"
2088 "(?:[^"]|(?<!\\)\\")*"
2085 )
2089 )
2086 \s*,\s*
2090 \s*,\s*
2087 )*)
2091 )*)
2088 ([uUbB]? # string prefix (r not handled)
2092 ([uUbB]? # string prefix (r not handled)
2089 (?: # unclosed string
2093 (?: # unclosed string
2090 '(?:[^']|(?<!\\)\\')*
2094 '(?:[^']|(?<!\\)\\')*
2091 |
2095 |
2092 "(?:[^"]|(?<!\\)\\")*
2096 "(?:[^"]|(?<!\\)\\")*
2093 )
2097 )
2094 )?
2098 )?
2095 $
2099 $
2096 '''
2100 '''
2097 regexps = self.__dict_key_regexps = {
2101 regexps = self.__dict_key_regexps = {
2098 False: re.compile(dict_key_re_fmt % r'''
2102 False: re.compile(dict_key_re_fmt % r'''
2099 # identifiers separated by .
2103 # identifiers separated by .
2100 (?!\d)\w+
2104 (?!\d)\w+
2101 (?:\.(?!\d)\w+)*
2105 (?:\.(?!\d)\w+)*
2102 '''),
2106 '''),
2103 True: re.compile(dict_key_re_fmt % '''
2107 True: re.compile(dict_key_re_fmt % '''
2104 .+
2108 .+
2105 ''')
2109 ''')
2106 }
2110 }
2107
2111
2108 match = regexps[self.greedy].search(self.text_until_cursor)
2112 match = regexps[self.greedy].search(self.text_until_cursor)
2109
2113
2110 if match is None:
2114 if match is None:
2111 return []
2115 return []
2112
2116
2113 expr, prefix0, prefix = match.groups()
2117 expr, prefix0, prefix = match.groups()
2114 try:
2118 try:
2115 obj = eval(expr, self.namespace)
2119 obj = eval(expr, self.namespace)
2116 except Exception:
2120 except Exception:
2117 try:
2121 try:
2118 obj = eval(expr, self.global_namespace)
2122 obj = eval(expr, self.global_namespace)
2119 except Exception:
2123 except Exception:
2120 return []
2124 return []
2121
2125
2122 keys = self._get_keys(obj)
2126 keys = self._get_keys(obj)
2123 if not keys:
2127 if not keys:
2124 return keys
2128 return keys
2125
2129
2126 extra_prefix = eval(prefix0) if prefix0 != '' else None
2130 extra_prefix = eval(prefix0) if prefix0 != '' else None
2127
2131
2128 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix)
2132 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix)
2129 if not matches:
2133 if not matches:
2130 return matches
2134 return matches
2131
2135
2132 # get the cursor position of
2136 # get the cursor position of
2133 # - the text being completed
2137 # - the text being completed
2134 # - the start of the key text
2138 # - the start of the key text
2135 # - the start of the completion
2139 # - the start of the completion
2136 text_start = len(self.text_until_cursor) - len(text)
2140 text_start = len(self.text_until_cursor) - len(text)
2137 if prefix:
2141 if prefix:
2138 key_start = match.start(3)
2142 key_start = match.start(3)
2139 completion_start = key_start + token_offset
2143 completion_start = key_start + token_offset
2140 else:
2144 else:
2141 key_start = completion_start = match.end()
2145 key_start = completion_start = match.end()
2142
2146
2143 # grab the leading prefix, to make sure all completions start with `text`
2147 # grab the leading prefix, to make sure all completions start with `text`
2144 if text_start > key_start:
2148 if text_start > key_start:
2145 leading = ''
2149 leading = ''
2146 else:
2150 else:
2147 leading = text[text_start:completion_start]
2151 leading = text[text_start:completion_start]
2148
2152
2149 # the index of the `[` character
2153 # the index of the `[` character
2150 bracket_idx = match.end(1)
2154 bracket_idx = match.end(1)
2151
2155
2152 # append closing quote and bracket as appropriate
2156 # append closing quote and bracket as appropriate
2153 # this is *not* appropriate if the opening quote or bracket is outside
2157 # this is *not* appropriate if the opening quote or bracket is outside
2154 # the text given to this method
2158 # the text given to this method
2155 suf = ''
2159 suf = ''
2156 continuation = self.line_buffer[len(self.text_until_cursor):]
2160 continuation = self.line_buffer[len(self.text_until_cursor):]
2157 if key_start > text_start and closing_quote:
2161 if key_start > text_start and closing_quote:
2158 # quotes were opened inside text, maybe close them
2162 # quotes were opened inside text, maybe close them
2159 if continuation.startswith(closing_quote):
2163 if continuation.startswith(closing_quote):
2160 continuation = continuation[len(closing_quote):]
2164 continuation = continuation[len(closing_quote):]
2161 else:
2165 else:
2162 suf += closing_quote
2166 suf += closing_quote
2163 if bracket_idx > text_start:
2167 if bracket_idx > text_start:
2164 # brackets were opened inside text, maybe close them
2168 # brackets were opened inside text, maybe close them
2165 if not continuation.startswith(']'):
2169 if not continuation.startswith(']'):
2166 suf += ']'
2170 suf += ']'
2167
2171
2168 return [leading + k + suf for k in matches]
2172 return [leading + k + suf for k in matches]
2169
2173
2170 @context_matcher()
2174 @context_matcher()
2171 def unicode_name_matcher(self, context):
2175 def unicode_name_matcher(self, context):
2172 fragment, matches = self.unicode_name_matches(context.token)
2176 fragment, matches = self.unicode_name_matches(context.token)
2173 return _convert_matcher_v1_result_to_v2(
2177 return _convert_matcher_v1_result_to_v2(
2174 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2178 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2175 )
2179 )
2176
2180
2177 @staticmethod
2181 @staticmethod
2178 def unicode_name_matches(text: str) -> Tuple[str, List[str]]:
2182 def unicode_name_matches(text: str) -> Tuple[str, List[str]]:
2179 """Match Latex-like syntax for unicode characters base
2183 """Match Latex-like syntax for unicode characters base
2180 on the name of the character.
2184 on the name of the character.
2181
2185
2182 This does ``\\GREEK SMALL LETTER ETA`` -> ``η``
2186 This does ``\\GREEK SMALL LETTER ETA`` -> ``η``
2183
2187
2184 Works only on valid python 3 identifier, or on combining characters that
2188 Works only on valid python 3 identifier, or on combining characters that
2185 will combine to form a valid identifier.
2189 will combine to form a valid identifier.
2186 """
2190 """
2187 slashpos = text.rfind('\\')
2191 slashpos = text.rfind('\\')
2188 if slashpos > -1:
2192 if slashpos > -1:
2189 s = text[slashpos+1:]
2193 s = text[slashpos+1:]
2190 try :
2194 try :
2191 unic = unicodedata.lookup(s)
2195 unic = unicodedata.lookup(s)
2192 # allow combining chars
2196 # allow combining chars
2193 if ('a'+unic).isidentifier():
2197 if ('a'+unic).isidentifier():
2194 return '\\'+s,[unic]
2198 return '\\'+s,[unic]
2195 except KeyError:
2199 except KeyError:
2196 pass
2200 pass
2197 return '', []
2201 return '', []
2198
2202
2199 @context_matcher()
2203 @context_matcher()
2200 def latex_name_matcher(self, context):
2204 def latex_name_matcher(self, context):
2201 """Match Latex syntax for unicode characters.
2205 """Match Latex syntax for unicode characters.
2202
2206
2203 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2207 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2204 """
2208 """
2205 fragment, matches = self.latex_matches(context.token)
2209 fragment, matches = self.latex_matches(context.token)
2206 return _convert_matcher_v1_result_to_v2(
2210 return _convert_matcher_v1_result_to_v2(
2207 matches, type="latex", fragment=fragment, suppress_if_matches=True
2211 matches, type="latex", fragment=fragment, suppress_if_matches=True
2208 )
2212 )
2209
2213
2210 def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]:
2214 def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]:
2211 """Match Latex syntax for unicode characters.
2215 """Match Latex syntax for unicode characters.
2212
2216
2213 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2217 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2214
2218
2215 DEPRECATED: Deprecated since 8.6. Use `latex_matcher` instead.
2219 DEPRECATED: Deprecated since 8.6. Use `latex_matcher` instead.
2216 """
2220 """
2217 slashpos = text.rfind('\\')
2221 slashpos = text.rfind('\\')
2218 if slashpos > -1:
2222 if slashpos > -1:
2219 s = text[slashpos:]
2223 s = text[slashpos:]
2220 if s in latex_symbols:
2224 if s in latex_symbols:
2221 # Try to complete a full latex symbol to unicode
2225 # Try to complete a full latex symbol to unicode
2222 # \\alpha -> α
2226 # \\alpha -> α
2223 return s, [latex_symbols[s]]
2227 return s, [latex_symbols[s]]
2224 else:
2228 else:
2225 # If a user has partially typed a latex symbol, give them
2229 # If a user has partially typed a latex symbol, give them
2226 # a full list of options \al -> [\aleph, \alpha]
2230 # a full list of options \al -> [\aleph, \alpha]
2227 matches = [k for k in latex_symbols if k.startswith(s)]
2231 matches = [k for k in latex_symbols if k.startswith(s)]
2228 if matches:
2232 if matches:
2229 return s, matches
2233 return s, matches
2230 return '', ()
2234 return '', ()
2231
2235
2232 @context_matcher()
2236 @context_matcher()
2233 def custom_completer_matcher(self, context):
2237 def custom_completer_matcher(self, context):
2234 matches = self.dispatch_custom_completer(context.token) or []
2238 matches = self.dispatch_custom_completer(context.token) or []
2235 result = _convert_matcher_v1_result_to_v2(
2239 result = _convert_matcher_v1_result_to_v2(
2236 matches, type="<unknown>", suppress_if_matches=True
2240 matches, type="<unknown>", suppress_if_matches=True
2237 )
2241 )
2238 result["ordered"] = True
2242 result["ordered"] = True
2239 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2243 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2240 return result
2244 return result
2241
2245
2242 def dispatch_custom_completer(self, text):
2246 def dispatch_custom_completer(self, text):
2243 """
2247 """
2244 DEPRECATED: Deprecated since 8.6. Use `custom_completer_matcher` instead.
2248 DEPRECATED: Deprecated since 8.6. Use `custom_completer_matcher` instead.
2245 """
2249 """
2246 if not self.custom_completers:
2250 if not self.custom_completers:
2247 return
2251 return
2248
2252
2249 line = self.line_buffer
2253 line = self.line_buffer
2250 if not line.strip():
2254 if not line.strip():
2251 return None
2255 return None
2252
2256
2253 # Create a little structure to pass all the relevant information about
2257 # Create a little structure to pass all the relevant information about
2254 # the current completion to any custom completer.
2258 # the current completion to any custom completer.
2255 event = SimpleNamespace()
2259 event = SimpleNamespace()
2256 event.line = line
2260 event.line = line
2257 event.symbol = text
2261 event.symbol = text
2258 cmd = line.split(None,1)[0]
2262 cmd = line.split(None,1)[0]
2259 event.command = cmd
2263 event.command = cmd
2260 event.text_until_cursor = self.text_until_cursor
2264 event.text_until_cursor = self.text_until_cursor
2261
2265
2262 # for foo etc, try also to find completer for %foo
2266 # for foo etc, try also to find completer for %foo
2263 if not cmd.startswith(self.magic_escape):
2267 if not cmd.startswith(self.magic_escape):
2264 try_magic = self.custom_completers.s_matches(
2268 try_magic = self.custom_completers.s_matches(
2265 self.magic_escape + cmd)
2269 self.magic_escape + cmd)
2266 else:
2270 else:
2267 try_magic = []
2271 try_magic = []
2268
2272
2269 for c in itertools.chain(self.custom_completers.s_matches(cmd),
2273 for c in itertools.chain(self.custom_completers.s_matches(cmd),
2270 try_magic,
2274 try_magic,
2271 self.custom_completers.flat_matches(self.text_until_cursor)):
2275 self.custom_completers.flat_matches(self.text_until_cursor)):
2272 try:
2276 try:
2273 res = c(event)
2277 res = c(event)
2274 if res:
2278 if res:
2275 # first, try case sensitive match
2279 # first, try case sensitive match
2276 withcase = [r for r in res if r.startswith(text)]
2280 withcase = [r for r in res if r.startswith(text)]
2277 if withcase:
2281 if withcase:
2278 return withcase
2282 return withcase
2279 # if none, then case insensitive ones are ok too
2283 # if none, then case insensitive ones are ok too
2280 text_low = text.lower()
2284 text_low = text.lower()
2281 return [r for r in res if r.lower().startswith(text_low)]
2285 return [r for r in res if r.lower().startswith(text_low)]
2282 except TryNext:
2286 except TryNext:
2283 pass
2287 pass
2284 except KeyboardInterrupt:
2288 except KeyboardInterrupt:
2285 """
2289 """
2286 If custom completer take too long,
2290 If custom completer take too long,
2287 let keyboard interrupt abort and return nothing.
2291 let keyboard interrupt abort and return nothing.
2288 """
2292 """
2289 break
2293 break
2290
2294
2291 return None
2295 return None
2292
2296
2293 def completions(self, text: str, offset: int)->Iterator[Completion]:
2297 def completions(self, text: str, offset: int)->Iterator[Completion]:
2294 """
2298 """
2295 Returns an iterator over the possible completions
2299 Returns an iterator over the possible completions
2296
2300
2297 .. warning::
2301 .. warning::
2298
2302
2299 Unstable
2303 Unstable
2300
2304
2301 This function is unstable, API may change without warning.
2305 This function is unstable, API may change without warning.
2302 It will also raise unless use in proper context manager.
2306 It will also raise unless use in proper context manager.
2303
2307
2304 Parameters
2308 Parameters
2305 ----------
2309 ----------
2306 text : str
2310 text : str
2307 Full text of the current input, multi line string.
2311 Full text of the current input, multi line string.
2308 offset : int
2312 offset : int
2309 Integer representing the position of the cursor in ``text``. Offset
2313 Integer representing the position of the cursor in ``text``. Offset
2310 is 0-based indexed.
2314 is 0-based indexed.
2311
2315
2312 Yields
2316 Yields
2313 ------
2317 ------
2314 Completion
2318 Completion
2315
2319
2316 Notes
2320 Notes
2317 -----
2321 -----
2318 The cursor on a text can either be seen as being "in between"
2322 The cursor on a text can either be seen as being "in between"
2319 characters or "On" a character depending on the interface visible to
2323 characters or "On" a character depending on the interface visible to
2320 the user. For consistency the cursor being on "in between" characters X
2324 the user. For consistency the cursor being on "in between" characters X
2321 and Y is equivalent to the cursor being "on" character Y, that is to say
2325 and Y is equivalent to the cursor being "on" character Y, that is to say
2322 the character the cursor is on is considered as being after the cursor.
2326 the character the cursor is on is considered as being after the cursor.
2323
2327
2324 Combining characters may span more that one position in the
2328 Combining characters may span more that one position in the
2325 text.
2329 text.
2326
2330
2327 .. note::
2331 .. note::
2328
2332
2329 If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--``
2333 If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--``
2330 fake Completion token to distinguish completion returned by Jedi
2334 fake Completion token to distinguish completion returned by Jedi
2331 and usual IPython completion.
2335 and usual IPython completion.
2332
2336
2333 .. note::
2337 .. note::
2334
2338
2335 Completions are not completely deduplicated yet. If identical
2339 Completions are not completely deduplicated yet. If identical
2336 completions are coming from different sources this function does not
2340 completions are coming from different sources this function does not
2337 ensure that each completion object will only be present once.
2341 ensure that each completion object will only be present once.
2338 """
2342 """
2339 warnings.warn("_complete is a provisional API (as of IPython 6.0). "
2343 warnings.warn("_complete is a provisional API (as of IPython 6.0). "
2340 "It may change without warnings. "
2344 "It may change without warnings. "
2341 "Use in corresponding context manager.",
2345 "Use in corresponding context manager.",
2342 category=ProvisionalCompleterWarning, stacklevel=2)
2346 category=ProvisionalCompleterWarning, stacklevel=2)
2343
2347
2344 seen = set()
2348 seen = set()
2345 profiler:Optional[cProfile.Profile]
2349 profiler:Optional[cProfile.Profile]
2346 try:
2350 try:
2347 if self.profile_completions:
2351 if self.profile_completions:
2348 import cProfile
2352 import cProfile
2349 profiler = cProfile.Profile()
2353 profiler = cProfile.Profile()
2350 profiler.enable()
2354 profiler.enable()
2351 else:
2355 else:
2352 profiler = None
2356 profiler = None
2353
2357
2354 for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
2358 for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
2355 if c and (c in seen):
2359 if c and (c in seen):
2356 continue
2360 continue
2357 yield c
2361 yield c
2358 seen.add(c)
2362 seen.add(c)
2359 except KeyboardInterrupt:
2363 except KeyboardInterrupt:
2360 """if completions take too long and users send keyboard interrupt,
2364 """if completions take too long and users send keyboard interrupt,
2361 do not crash and return ASAP. """
2365 do not crash and return ASAP. """
2362 pass
2366 pass
2363 finally:
2367 finally:
2364 if profiler is not None:
2368 if profiler is not None:
2365 profiler.disable()
2369 profiler.disable()
2366 ensure_dir_exists(self.profiler_output_dir)
2370 ensure_dir_exists(self.profiler_output_dir)
2367 output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4()))
2371 output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4()))
2368 print("Writing profiler output to", output_path)
2372 print("Writing profiler output to", output_path)
2369 profiler.dump_stats(output_path)
2373 profiler.dump_stats(output_path)
2370
2374
2371 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
2375 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
2372 """
2376 """
2373 Core completion module.Same signature as :any:`completions`, with the
2377 Core completion module.Same signature as :any:`completions`, with the
2374 extra `timeout` parameter (in seconds).
2378 extra `timeout` parameter (in seconds).
2375
2379
2376 Computing jedi's completion ``.type`` can be quite expensive (it is a
2380 Computing jedi's completion ``.type`` can be quite expensive (it is a
2377 lazy property) and can require some warm-up, more warm up than just
2381 lazy property) and can require some warm-up, more warm up than just
2378 computing the ``name`` of a completion. The warm-up can be :
2382 computing the ``name`` of a completion. The warm-up can be :
2379
2383
2380 - Long warm-up the first time a module is encountered after
2384 - Long warm-up the first time a module is encountered after
2381 install/update: actually build parse/inference tree.
2385 install/update: actually build parse/inference tree.
2382
2386
2383 - first time the module is encountered in a session: load tree from
2387 - first time the module is encountered in a session: load tree from
2384 disk.
2388 disk.
2385
2389
2386 We don't want to block completions for tens of seconds so we give the
2390 We don't want to block completions for tens of seconds so we give the
2387 completer a "budget" of ``_timeout`` seconds per invocation to compute
2391 completer a "budget" of ``_timeout`` seconds per invocation to compute
2388 completions types, the completions that have not yet been computed will
2392 completions types, the completions that have not yet been computed will
2389 be marked as "unknown" an will have a chance to be computed next round
2393 be marked as "unknown" an will have a chance to be computed next round
2390 are things get cached.
2394 are things get cached.
2391
2395
2392 Keep in mind that Jedi is not the only thing treating the completion so
2396 Keep in mind that Jedi is not the only thing treating the completion so
2393 keep the timeout short-ish as if we take more than 0.3 second we still
2397 keep the timeout short-ish as if we take more than 0.3 second we still
2394 have lots of processing to do.
2398 have lots of processing to do.
2395
2399
2396 """
2400 """
2397 deadline = time.monotonic() + _timeout
2401 deadline = time.monotonic() + _timeout
2398
2402
2399 before = full_text[:offset]
2403 before = full_text[:offset]
2400 cursor_line, cursor_column = position_to_cursor(full_text, offset)
2404 cursor_line, cursor_column = position_to_cursor(full_text, offset)
2401
2405
2402 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2406 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2403
2407
2404 results = self._complete(
2408 results = self._complete(
2405 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column
2409 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column
2406 )
2410 )
2407 non_jedi_results: Dict[str, SimpleMatcherResult] = {
2411 non_jedi_results: Dict[str, SimpleMatcherResult] = {
2408 identifier: result
2412 identifier: result
2409 for identifier, result in results.items()
2413 for identifier, result in results.items()
2410 if identifier != jedi_matcher_id
2414 if identifier != jedi_matcher_id
2411 }
2415 }
2412
2416
2413 jedi_matches = (
2417 jedi_matches = (
2414 cast(results[jedi_matcher_id], _JediMatcherResult)["completions"]
2418 cast(results[jedi_matcher_id], _JediMatcherResult)["completions"]
2415 if jedi_matcher_id in results
2419 if jedi_matcher_id in results
2416 else ()
2420 else ()
2417 )
2421 )
2418
2422
2419 iter_jm = iter(jedi_matches)
2423 iter_jm = iter(jedi_matches)
2420 if _timeout:
2424 if _timeout:
2421 for jm in iter_jm:
2425 for jm in iter_jm:
2422 try:
2426 try:
2423 type_ = jm.type
2427 type_ = jm.type
2424 except Exception:
2428 except Exception:
2425 if self.debug:
2429 if self.debug:
2426 print("Error in Jedi getting type of ", jm)
2430 print("Error in Jedi getting type of ", jm)
2427 type_ = None
2431 type_ = None
2428 delta = len(jm.name_with_symbols) - len(jm.complete)
2432 delta = len(jm.name_with_symbols) - len(jm.complete)
2429 if type_ == 'function':
2433 if type_ == 'function':
2430 signature = _make_signature(jm)
2434 signature = _make_signature(jm)
2431 else:
2435 else:
2432 signature = ''
2436 signature = ''
2433 yield Completion(start=offset - delta,
2437 yield Completion(start=offset - delta,
2434 end=offset,
2438 end=offset,
2435 text=jm.name_with_symbols,
2439 text=jm.name_with_symbols,
2436 type=type_,
2440 type=type_,
2437 signature=signature,
2441 signature=signature,
2438 _origin='jedi')
2442 _origin='jedi')
2439
2443
2440 if time.monotonic() > deadline:
2444 if time.monotonic() > deadline:
2441 break
2445 break
2442
2446
2443 for jm in iter_jm:
2447 for jm in iter_jm:
2444 delta = len(jm.name_with_symbols) - len(jm.complete)
2448 delta = len(jm.name_with_symbols) - len(jm.complete)
2445 yield Completion(
2449 yield Completion(
2446 start=offset - delta,
2450 start=offset - delta,
2447 end=offset,
2451 end=offset,
2448 text=jm.name_with_symbols,
2452 text=jm.name_with_symbols,
2449 type=_UNKNOWN_TYPE, # don't compute type for speed
2453 type=_UNKNOWN_TYPE, # don't compute type for speed
2450 _origin="jedi",
2454 _origin="jedi",
2451 signature="",
2455 signature="",
2452 )
2456 )
2453
2457
2454 # TODO:
2458 # TODO:
2455 # Suppress this, right now just for debug.
2459 # Suppress this, right now just for debug.
2456 if jedi_matches and non_jedi_results and self.debug:
2460 if jedi_matches and non_jedi_results and self.debug:
2457 some_start_offset = before.rfind(
2461 some_start_offset = before.rfind(
2458 next(iter(non_jedi_results.values()))["matched_fragment"]
2462 next(iter(non_jedi_results.values()))["matched_fragment"]
2459 )
2463 )
2460 yield Completion(
2464 yield Completion(
2461 start=some_start_offset,
2465 start=some_start_offset,
2462 end=offset,
2466 end=offset,
2463 text="--jedi/ipython--",
2467 text="--jedi/ipython--",
2464 _origin="debug",
2468 _origin="debug",
2465 type="none",
2469 type="none",
2466 signature="",
2470 signature="",
2467 )
2471 )
2468
2472
2469 ordered = []
2473 ordered = []
2470 sortable = []
2474 sortable = []
2471
2475
2472 for origin, result in non_jedi_results.items():
2476 for origin, result in non_jedi_results.items():
2473 matched_text = result["matched_fragment"]
2477 matched_text = result["matched_fragment"]
2474 start_offset = before.rfind(matched_text)
2478 start_offset = before.rfind(matched_text)
2475 is_ordered = result.get("ordered", False)
2479 is_ordered = result.get("ordered", False)
2476 container = ordered if is_ordered else sortable
2480 container = ordered if is_ordered else sortable
2477
2481
2478 # I'm unsure if this is always true, so let's assert and see if it
2482 # I'm unsure if this is always true, so let's assert and see if it
2479 # crash
2483 # crash
2480 assert before.endswith(matched_text)
2484 assert before.endswith(matched_text)
2481
2485
2482 for simple_completion in result["completions"]:
2486 for simple_completion in result["completions"]:
2483 completion = Completion(
2487 completion = Completion(
2484 start=start_offset,
2488 start=start_offset,
2485 end=offset,
2489 end=offset,
2486 text=simple_completion.text,
2490 text=simple_completion.text,
2487 _origin=origin,
2491 _origin=origin,
2488 signature="",
2492 signature="",
2489 type=simple_completion.type or _UNKNOWN_TYPE,
2493 type=simple_completion.type or _UNKNOWN_TYPE,
2490 )
2494 )
2491 container.append(completion)
2495 container.append(completion)
2492
2496
2493 yield from list(self._deduplicate(ordered + self._sort(sortable)))[
2497 yield from list(self._deduplicate(ordered + self._sort(sortable)))[
2494 :MATCHES_LIMIT
2498 :MATCHES_LIMIT
2495 ]
2499 ]
2496
2500
2497 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2501 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2498 """Find completions for the given text and line context.
2502 """Find completions for the given text and line context.
2499
2503
2500 Note that both the text and the line_buffer are optional, but at least
2504 Note that both the text and the line_buffer are optional, but at least
2501 one of them must be given.
2505 one of them must be given.
2502
2506
2503 Parameters
2507 Parameters
2504 ----------
2508 ----------
2505 text : string, optional
2509 text : string, optional
2506 Text to perform the completion on. If not given, the line buffer
2510 Text to perform the completion on. If not given, the line buffer
2507 is split using the instance's CompletionSplitter object.
2511 is split using the instance's CompletionSplitter object.
2508 line_buffer : string, optional
2512 line_buffer : string, optional
2509 If not given, the completer attempts to obtain the current line
2513 If not given, the completer attempts to obtain the current line
2510 buffer via readline. This keyword allows clients which are
2514 buffer via readline. This keyword allows clients which are
2511 requesting for text completions in non-readline contexts to inform
2515 requesting for text completions in non-readline contexts to inform
2512 the completer of the entire text.
2516 the completer of the entire text.
2513 cursor_pos : int, optional
2517 cursor_pos : int, optional
2514 Index of the cursor in the full line buffer. Should be provided by
2518 Index of the cursor in the full line buffer. Should be provided by
2515 remote frontends where kernel has no access to frontend state.
2519 remote frontends where kernel has no access to frontend state.
2516
2520
2517 Returns
2521 Returns
2518 -------
2522 -------
2519 Tuple of two items:
2523 Tuple of two items:
2520 text : str
2524 text : str
2521 Text that was actually used in the completion.
2525 Text that was actually used in the completion.
2522 matches : list
2526 matches : list
2523 A list of completion matches.
2527 A list of completion matches.
2524
2528
2525 Notes
2529 Notes
2526 -----
2530 -----
2527 This API is likely to be deprecated and replaced by
2531 This API is likely to be deprecated and replaced by
2528 :any:`IPCompleter.completions` in the future.
2532 :any:`IPCompleter.completions` in the future.
2529
2533
2530 """
2534 """
2531 warnings.warn('`Completer.complete` is pending deprecation since '
2535 warnings.warn('`Completer.complete` is pending deprecation since '
2532 'IPython 6.0 and will be replaced by `Completer.completions`.',
2536 'IPython 6.0 and will be replaced by `Completer.completions`.',
2533 PendingDeprecationWarning)
2537 PendingDeprecationWarning)
2534 # potential todo, FOLD the 3rd throw away argument of _complete
2538 # potential todo, FOLD the 3rd throw away argument of _complete
2535 # into the first 2 one.
2539 # into the first 2 one.
2536 # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?)
2540 # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?)
2537 # TODO: should we deprecate now, or does it stay?
2541 # TODO: should we deprecate now, or does it stay?
2538
2542
2539 results = self._complete(
2543 results = self._complete(
2540 line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0
2544 line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0
2541 )
2545 )
2542
2546
2543 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2547 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2544
2548
2545 return self._arrange_and_extract(
2549 return self._arrange_and_extract(
2546 results,
2550 results,
2547 # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version?
2551 # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version?
2548 skip_matchers={jedi_matcher_id},
2552 skip_matchers={jedi_matcher_id},
2549 # this API does not support different start/end positions (fragments of token).
2553 # this API does not support different start/end positions (fragments of token).
2550 abort_if_offset_changes=True,
2554 abort_if_offset_changes=True,
2551 )
2555 )
2552
2556
2553 def _arrange_and_extract(
2557 def _arrange_and_extract(
2554 self,
2558 self,
2555 results: Dict[str, MatcherResult],
2559 results: Dict[str, MatcherResult],
2556 skip_matchers: Set[str],
2560 skip_matchers: Set[str],
2557 abort_if_offset_changes: bool,
2561 abort_if_offset_changes: bool,
2558 ):
2562 ):
2559
2563
2560 sortable = []
2564 sortable = []
2561 ordered = []
2565 ordered = []
2562 most_recent_fragment = None
2566 most_recent_fragment = None
2563 for identifier, result in results.items():
2567 for identifier, result in results.items():
2564 if identifier in skip_matchers:
2568 if identifier in skip_matchers:
2565 continue
2569 continue
2566 if not result["completions"]:
2570 if not result["completions"]:
2567 continue
2571 continue
2568 if not most_recent_fragment:
2572 if not most_recent_fragment:
2569 most_recent_fragment = result["matched_fragment"]
2573 most_recent_fragment = result["matched_fragment"]
2570 if (
2574 if (
2571 abort_if_offset_changes
2575 abort_if_offset_changes
2572 and result["matched_fragment"] != most_recent_fragment
2576 and result["matched_fragment"] != most_recent_fragment
2573 ):
2577 ):
2574 break
2578 break
2575 if result.get("ordered", False):
2579 if result.get("ordered", False):
2576 ordered.extend(result["completions"])
2580 ordered.extend(result["completions"])
2577 else:
2581 else:
2578 sortable.extend(result["completions"])
2582 sortable.extend(result["completions"])
2579
2583
2580 if not most_recent_fragment:
2584 if not most_recent_fragment:
2581 most_recent_fragment = "" # to satisfy typechecker (and just in case)
2585 most_recent_fragment = "" # to satisfy typechecker (and just in case)
2582
2586
2583 return most_recent_fragment, [
2587 return most_recent_fragment, [
2584 m.text for m in self._deduplicate(ordered + self._sort(sortable))
2588 m.text for m in self._deduplicate(ordered + self._sort(sortable))
2585 ]
2589 ]
2586
2590
2587 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
2591 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
2588 full_text=None) -> _CompleteResult:
2592 full_text=None) -> _CompleteResult:
2589 """
2593 """
2590 Like complete but can also returns raw jedi completions as well as the
2594 Like complete but can also returns raw jedi completions as well as the
2591 origin of the completion text. This could (and should) be made much
2595 origin of the completion text. This could (and should) be made much
2592 cleaner but that will be simpler once we drop the old (and stateful)
2596 cleaner but that will be simpler once we drop the old (and stateful)
2593 :any:`complete` API.
2597 :any:`complete` API.
2594
2598
2595 With current provisional API, cursor_pos act both (depending on the
2599 With current provisional API, cursor_pos act both (depending on the
2596 caller) as the offset in the ``text`` or ``line_buffer``, or as the
2600 caller) as the offset in the ``text`` or ``line_buffer``, or as the
2597 ``column`` when passing multiline strings this could/should be renamed
2601 ``column`` when passing multiline strings this could/should be renamed
2598 but would add extra noise.
2602 but would add extra noise.
2599
2603
2600 Parameters
2604 Parameters
2601 ----------
2605 ----------
2602 cursor_line
2606 cursor_line
2603 Index of the line the cursor is on. 0 indexed.
2607 Index of the line the cursor is on. 0 indexed.
2604 cursor_pos
2608 cursor_pos
2605 Position of the cursor in the current line/line_buffer/text. 0
2609 Position of the cursor in the current line/line_buffer/text. 0
2606 indexed.
2610 indexed.
2607 line_buffer : optional, str
2611 line_buffer : optional, str
2608 The current line the cursor is in, this is mostly due to legacy
2612 The current line the cursor is in, this is mostly due to legacy
2609 reason that readline could only give a us the single current line.
2613 reason that readline could only give a us the single current line.
2610 Prefer `full_text`.
2614 Prefer `full_text`.
2611 text : str
2615 text : str
2612 The current "token" the cursor is in, mostly also for historical
2616 The current "token" the cursor is in, mostly also for historical
2613 reasons. as the completer would trigger only after the current line
2617 reasons. as the completer would trigger only after the current line
2614 was parsed.
2618 was parsed.
2615 full_text : str
2619 full_text : str
2616 Full text of the current cell.
2620 Full text of the current cell.
2617
2621
2618 Returns
2622 Returns
2619 -------
2623 -------
2620 An ordered dictionary where keys are identifiers of completion
2624 An ordered dictionary where keys are identifiers of completion
2621 matchers and values are ``MatcherResult``s.
2625 matchers and values are ``MatcherResult``s.
2622 """
2626 """
2623
2627
2624 # if the cursor position isn't given, the only sane assumption we can
2628 # if the cursor position isn't given, the only sane assumption we can
2625 # make is that it's at the end of the line (the common case)
2629 # make is that it's at the end of the line (the common case)
2626 if cursor_pos is None:
2630 if cursor_pos is None:
2627 cursor_pos = len(line_buffer) if text is None else len(text)
2631 cursor_pos = len(line_buffer) if text is None else len(text)
2628
2632
2629 if self.use_main_ns:
2633 if self.use_main_ns:
2630 self.namespace = __main__.__dict__
2634 self.namespace = __main__.__dict__
2631
2635
2632 # if text is either None or an empty string, rely on the line buffer
2636 # if text is either None or an empty string, rely on the line buffer
2633 if (not line_buffer) and full_text:
2637 if (not line_buffer) and full_text:
2634 line_buffer = full_text.split('\n')[cursor_line]
2638 line_buffer = full_text.split('\n')[cursor_line]
2635 if not text: # issue #11508: check line_buffer before calling split_line
2639 if not text: # issue #11508: check line_buffer before calling split_line
2636 text = (
2640 text = (
2637 self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ""
2641 self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ""
2638 )
2642 )
2639
2643
2640 # If no line buffer is given, assume the input text is all there was
2644 # If no line buffer is given, assume the input text is all there was
2641 if line_buffer is None:
2645 if line_buffer is None:
2642 line_buffer = text
2646 line_buffer = text
2643
2647
2644 # deprecated - do not use `line_buffer` in new code.
2648 # deprecated - do not use `line_buffer` in new code.
2645 self.line_buffer = line_buffer
2649 self.line_buffer = line_buffer
2646 self.text_until_cursor = self.line_buffer[:cursor_pos]
2650 self.text_until_cursor = self.line_buffer[:cursor_pos]
2647
2651
2648 if not full_text:
2652 if not full_text:
2649 full_text = line_buffer
2653 full_text = line_buffer
2650
2654
2651 context = CompletionContext(
2655 context = CompletionContext(
2652 full_text=full_text,
2656 full_text=full_text,
2653 cursor_position=cursor_pos,
2657 cursor_position=cursor_pos,
2654 cursor_line=cursor_line,
2658 cursor_line=cursor_line,
2655 token=text,
2659 token=text,
2656 limit=MATCHES_LIMIT,
2660 limit=MATCHES_LIMIT,
2657 )
2661 )
2658
2662
2659 # Start with a clean slate of completions
2663 # Start with a clean slate of completions
2660 results = {}
2664 results = {}
2661
2665
2662 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2666 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2663
2667
2664 suppressed_matchers = set()
2668 suppressed_matchers = set()
2665
2669
2666 matchers = {
2670 matchers = {
2667 _get_matcher_id(matcher): matcher
2671 _get_matcher_id(matcher): matcher
2668 for matcher in sorted(
2672 for matcher in sorted(
2669 self.matchers, key=_get_matcher_priority, reverse=True
2673 self.matchers, key=_get_matcher_priority, reverse=True
2670 )
2674 )
2671 }
2675 }
2672
2676
2673 for matcher_id, matcher in matchers.items():
2677 for matcher_id, matcher in matchers.items():
2674 api_version = _get_matcher_api_version(matcher)
2678 api_version = _get_matcher_api_version(matcher)
2675 matcher_id = _get_matcher_id(matcher)
2679 matcher_id = _get_matcher_id(matcher)
2676
2680
2677 if matcher_id in self.disable_matchers:
2681 if matcher_id in self.disable_matchers:
2678 continue
2682 continue
2679
2683
2680 if matcher_id in results:
2684 if matcher_id in results:
2681 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
2685 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
2682
2686
2683 if matcher_id in suppressed_matchers:
2687 if matcher_id in suppressed_matchers:
2684 continue
2688 continue
2685
2689
2686 try:
2690 try:
2687 if api_version == 1:
2691 if api_version == 1:
2688 result = _convert_matcher_v1_result_to_v2(
2692 result = _convert_matcher_v1_result_to_v2(
2689 matcher(text), type=_UNKNOWN_TYPE
2693 matcher(text), type=_UNKNOWN_TYPE
2690 )
2694 )
2691 elif api_version == 2:
2695 elif api_version == 2:
2692 result = cast(matcher, MatcherAPIv2)(context)
2696 result = cast(matcher, MatcherAPIv2)(context)
2693 else:
2697 else:
2694 raise ValueError(f"Unsupported API version {api_version}")
2698 raise ValueError(f"Unsupported API version {api_version}")
2695 except:
2699 except:
2696 # Show the ugly traceback if the matcher causes an
2700 # Show the ugly traceback if the matcher causes an
2697 # exception, but do NOT crash the kernel!
2701 # exception, but do NOT crash the kernel!
2698 sys.excepthook(*sys.exc_info())
2702 sys.excepthook(*sys.exc_info())
2699 continue
2703 continue
2700
2704
2701 # set default value for matched fragment if suffix was not selected.
2705 # set default value for matched fragment if suffix was not selected.
2702 result["matched_fragment"] = result.get("matched_fragment", context.token)
2706 result["matched_fragment"] = result.get("matched_fragment", context.token)
2703
2707
2704 if not suppressed_matchers:
2708 if not suppressed_matchers:
2705 suppression_recommended = result.get("suppress", False)
2709 suppression_recommended = result.get("suppress", False)
2706
2710
2707 suppression_config = (
2711 suppression_config = (
2708 self.suppress_competing_matchers.get(matcher_id, None)
2712 self.suppress_competing_matchers.get(matcher_id, None)
2709 if isinstance(self.suppress_competing_matchers, dict)
2713 if isinstance(self.suppress_competing_matchers, dict)
2710 else self.suppress_competing_matchers
2714 else self.suppress_competing_matchers
2711 )
2715 )
2712 should_suppress = (
2716 should_suppress = (
2713 (suppression_config is True)
2717 (suppression_config is True)
2714 or (suppression_recommended and (suppression_config is not False))
2718 or (suppression_recommended and (suppression_config is not False))
2715 ) and len(result["completions"])
2719 ) and len(result["completions"])
2716
2720
2717 if should_suppress:
2721 if should_suppress:
2718 suppression_exceptions = result.get("do_not_suppress", set())
2722 suppression_exceptions = result.get("do_not_suppress", set())
2719 try:
2723 try:
2720 to_suppress = set(suppression_recommended)
2724 to_suppress = set(suppression_recommended)
2721 except TypeError:
2725 except TypeError:
2722 to_suppress = set(matchers)
2726 to_suppress = set(matchers)
2723 suppressed_matchers = to_suppress - suppression_exceptions
2727 suppressed_matchers = to_suppress - suppression_exceptions
2724
2728
2725 new_results = {}
2729 new_results = {}
2726 for previous_matcher_id, previous_result in results.items():
2730 for previous_matcher_id, previous_result in results.items():
2727 if previous_matcher_id not in suppressed_matchers:
2731 if previous_matcher_id not in suppressed_matchers:
2728 new_results[previous_matcher_id] = previous_result
2732 new_results[previous_matcher_id] = previous_result
2729 results = new_results
2733 results = new_results
2730
2734
2731 results[matcher_id] = result
2735 results[matcher_id] = result
2732
2736
2733 _, matches = self._arrange_and_extract(
2737 _, matches = self._arrange_and_extract(
2734 results,
2738 results,
2735 # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission?
2739 # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission?
2736 # if it was omission, we can remove the filtering step, otherwise remove this comment.
2740 # if it was omission, we can remove the filtering step, otherwise remove this comment.
2737 skip_matchers={jedi_matcher_id},
2741 skip_matchers={jedi_matcher_id},
2738 abort_if_offset_changes=False,
2742 abort_if_offset_changes=False,
2739 )
2743 )
2740
2744
2741 # populate legacy stateful API
2745 # populate legacy stateful API
2742 self.matches = matches
2746 self.matches = matches
2743
2747
2744 return results
2748 return results
2745
2749
2746 @staticmethod
2750 @staticmethod
2747 def _deduplicate(
2751 def _deduplicate(
2748 matches: Sequence[SimpleCompletion],
2752 matches: Sequence[SimpleCompletion],
2749 ) -> Iterable[SimpleCompletion]:
2753 ) -> Iterable[SimpleCompletion]:
2750 filtered_matches = {}
2754 filtered_matches = {}
2751 for match in matches:
2755 for match in matches:
2752 text = match.text
2756 text = match.text
2753 if (
2757 if (
2754 text not in filtered_matches
2758 text not in filtered_matches
2755 or filtered_matches[text].type == _UNKNOWN_TYPE
2759 or filtered_matches[text].type == _UNKNOWN_TYPE
2756 ):
2760 ):
2757 filtered_matches[text] = match
2761 filtered_matches[text] = match
2758
2762
2759 return filtered_matches.values()
2763 return filtered_matches.values()
2760
2764
2761 @staticmethod
2765 @staticmethod
2762 def _sort(matches: Sequence[SimpleCompletion]):
2766 def _sort(matches: Sequence[SimpleCompletion]):
2763 return sorted(matches, key=lambda x: completions_sorting_key(x.text))
2767 return sorted(matches, key=lambda x: completions_sorting_key(x.text))
2764
2768
2765 @context_matcher()
2769 @context_matcher()
2766 def fwd_unicode_matcher(self, context):
2770 def fwd_unicode_matcher(self, context):
2767 """Same as ``fwd_unicode_match``, but adopted to new Matcher API."""
2771 """Same as ``fwd_unicode_match``, but adopted to new Matcher API."""
2768 fragment, matches = self.latex_matches(context.token)
2772 fragment, matches = self.latex_matches(context.token)
2769 return _convert_matcher_v1_result_to_v2(
2773 return _convert_matcher_v1_result_to_v2(
2770 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2774 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2771 )
2775 )
2772
2776
2773 def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]:
2777 def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]:
2774 """
2778 """
2775 Forward match a string starting with a backslash with a list of
2779 Forward match a string starting with a backslash with a list of
2776 potential Unicode completions.
2780 potential Unicode completions.
2777
2781
2778 Will compute list list of Unicode character names on first call and cache it.
2782 Will compute list list of Unicode character names on first call and cache it.
2779
2783
2780 Returns
2784 Returns
2781 -------
2785 -------
2782 At tuple with:
2786 At tuple with:
2783 - matched text (empty if no matches)
2787 - matched text (empty if no matches)
2784 - list of potential completions, empty tuple otherwise)
2788 - list of potential completions, empty tuple otherwise)
2785
2789
2786 DEPRECATED: Deprecated since 8.6. Use `fwd_unicode_matcher` instead.
2790 DEPRECATED: Deprecated since 8.6. Use `fwd_unicode_matcher` instead.
2787 """
2791 """
2788 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2792 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2789 # We could do a faster match using a Trie.
2793 # We could do a faster match using a Trie.
2790
2794
2791 # Using pygtrie the following seem to work:
2795 # Using pygtrie the following seem to work:
2792
2796
2793 # s = PrefixSet()
2797 # s = PrefixSet()
2794
2798
2795 # for c in range(0,0x10FFFF + 1):
2799 # for c in range(0,0x10FFFF + 1):
2796 # try:
2800 # try:
2797 # s.add(unicodedata.name(chr(c)))
2801 # s.add(unicodedata.name(chr(c)))
2798 # except ValueError:
2802 # except ValueError:
2799 # pass
2803 # pass
2800 # [''.join(k) for k in s.iter(prefix)]
2804 # [''.join(k) for k in s.iter(prefix)]
2801
2805
2802 # But need to be timed and adds an extra dependency.
2806 # But need to be timed and adds an extra dependency.
2803
2807
2804 slashpos = text.rfind('\\')
2808 slashpos = text.rfind('\\')
2805 # if text starts with slash
2809 # if text starts with slash
2806 if slashpos > -1:
2810 if slashpos > -1:
2807 # PERF: It's important that we don't access self._unicode_names
2811 # PERF: It's important that we don't access self._unicode_names
2808 # until we're inside this if-block. _unicode_names is lazily
2812 # until we're inside this if-block. _unicode_names is lazily
2809 # initialized, and it takes a user-noticeable amount of time to
2813 # initialized, and it takes a user-noticeable amount of time to
2810 # initialize it, so we don't want to initialize it unless we're
2814 # initialize it, so we don't want to initialize it unless we're
2811 # actually going to use it.
2815 # actually going to use it.
2812 s = text[slashpos + 1 :]
2816 s = text[slashpos + 1 :]
2813 sup = s.upper()
2817 sup = s.upper()
2814 candidates = [x for x in self.unicode_names if x.startswith(sup)]
2818 candidates = [x for x in self.unicode_names if x.startswith(sup)]
2815 if candidates:
2819 if candidates:
2816 return s, candidates
2820 return s, candidates
2817 candidates = [x for x in self.unicode_names if sup in x]
2821 candidates = [x for x in self.unicode_names if sup in x]
2818 if candidates:
2822 if candidates:
2819 return s, candidates
2823 return s, candidates
2820 splitsup = sup.split(" ")
2824 splitsup = sup.split(" ")
2821 candidates = [
2825 candidates = [
2822 x for x in self.unicode_names if all(u in x for u in splitsup)
2826 x for x in self.unicode_names if all(u in x for u in splitsup)
2823 ]
2827 ]
2824 if candidates:
2828 if candidates:
2825 return s, candidates
2829 return s, candidates
2826
2830
2827 return "", ()
2831 return "", ()
2828
2832
2829 # if text does not start with slash
2833 # if text does not start with slash
2830 else:
2834 else:
2831 return '', ()
2835 return '', ()
2832
2836
2833 @property
2837 @property
2834 def unicode_names(self) -> List[str]:
2838 def unicode_names(self) -> List[str]:
2835 """List of names of unicode code points that can be completed.
2839 """List of names of unicode code points that can be completed.
2836
2840
2837 The list is lazily initialized on first access.
2841 The list is lazily initialized on first access.
2838 """
2842 """
2839 if self._unicode_names is None:
2843 if self._unicode_names is None:
2840 names = []
2844 names = []
2841 for c in range(0,0x10FFFF + 1):
2845 for c in range(0,0x10FFFF + 1):
2842 try:
2846 try:
2843 names.append(unicodedata.name(chr(c)))
2847 names.append(unicodedata.name(chr(c)))
2844 except ValueError:
2848 except ValueError:
2845 pass
2849 pass
2846 self._unicode_names = _unicode_name_compute(_UNICODE_RANGES)
2850 self._unicode_names = _unicode_name_compute(_UNICODE_RANGES)
2847
2851
2848 return self._unicode_names
2852 return self._unicode_names
2849
2853
2850 def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]:
2854 def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]:
2851 names = []
2855 names = []
2852 for start,stop in ranges:
2856 for start,stop in ranges:
2853 for c in range(start, stop) :
2857 for c in range(start, stop) :
2854 try:
2858 try:
2855 names.append(unicodedata.name(chr(c)))
2859 names.append(unicodedata.name(chr(c)))
2856 except ValueError:
2860 except ValueError:
2857 pass
2861 pass
2858 return names
2862 return names
@@ -1,938 +1,965 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6
6
7 import atexit
7 import atexit
8 import datetime
8 import datetime
9 from pathlib import Path
9 from pathlib import Path
10 import re
10 import re
11 import sqlite3
11 import sqlite3
12 import threading
12 import threading
13
13
14 from traitlets.config.configurable import LoggingConfigurable
14 from traitlets.config.configurable import LoggingConfigurable
15 from decorator import decorator
15 from decorator import decorator
16 from IPython.utils.decorators import undoc
16 from IPython.utils.decorators import undoc
17 from IPython.paths import locate_profile
17 from IPython.paths import locate_profile
18 from traitlets import (
18 from traitlets import (
19 Any,
19 Any,
20 Bool,
20 Bool,
21 Dict,
21 Dict,
22 Instance,
22 Instance,
23 Integer,
23 Integer,
24 List,
24 List,
25 Unicode,
25 Unicode,
26 Union,
26 Union,
27 TraitError,
27 TraitError,
28 default,
28 default,
29 observe,
29 observe,
30 )
30 )
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Classes and functions
33 # Classes and functions
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 @undoc
36 @undoc
37 class DummyDB(object):
37 class DummyDB(object):
38 """Dummy DB that will act as a black hole for history.
38 """Dummy DB that will act as a black hole for history.
39
39
40 Only used in the absence of sqlite"""
40 Only used in the absence of sqlite"""
41 def execute(*args, **kwargs):
41 def execute(*args, **kwargs):
42 return []
42 return []
43
43
44 def commit(self, *args, **kwargs):
44 def commit(self, *args, **kwargs):
45 pass
45 pass
46
46
47 def __enter__(self, *args, **kwargs):
47 def __enter__(self, *args, **kwargs):
48 pass
48 pass
49
49
50 def __exit__(self, *args, **kwargs):
50 def __exit__(self, *args, **kwargs):
51 pass
51 pass
52
52
53
53
54 @decorator
54 @decorator
55 def only_when_enabled(f, self, *a, **kw):
55 def only_when_enabled(f, self, *a, **kw):
56 """Decorator: return an empty list in the absence of sqlite."""
56 """Decorator: return an empty list in the absence of sqlite."""
57 if not self.enabled:
57 if not self.enabled:
58 return []
58 return []
59 else:
59 else:
60 return f(self, *a, **kw)
60 return f(self, *a, **kw)
61
61
62
62
63 # use 16kB as threshold for whether a corrupt history db should be saved
63 # use 16kB as threshold for whether a corrupt history db should be saved
64 # that should be at least 100 entries or so
64 # that should be at least 100 entries or so
65 _SAVE_DB_SIZE = 16384
65 _SAVE_DB_SIZE = 16384
66
66
67 @decorator
67 @decorator
68 def catch_corrupt_db(f, self, *a, **kw):
68 def catch_corrupt_db(f, self, *a, **kw):
69 """A decorator which wraps HistoryAccessor method calls to catch errors from
69 """A decorator which wraps HistoryAccessor method calls to catch errors from
70 a corrupt SQLite database, move the old database out of the way, and create
70 a corrupt SQLite database, move the old database out of the way, and create
71 a new one.
71 a new one.
72
72
73 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
73 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
74 not just a corrupt file.
74 not just a corrupt file.
75 """
75 """
76 try:
76 try:
77 return f(self, *a, **kw)
77 return f(self, *a, **kw)
78 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
78 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
79 self._corrupt_db_counter += 1
79 self._corrupt_db_counter += 1
80 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
80 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
81 if self.hist_file != ':memory:':
81 if self.hist_file != ':memory:':
82 if self._corrupt_db_counter > self._corrupt_db_limit:
82 if self._corrupt_db_counter > self._corrupt_db_limit:
83 self.hist_file = ':memory:'
83 self.hist_file = ':memory:'
84 self.log.error("Failed to load history too many times, history will not be saved.")
84 self.log.error("Failed to load history too many times, history will not be saved.")
85 elif self.hist_file.is_file():
85 elif self.hist_file.is_file():
86 # move the file out of the way
86 # move the file out of the way
87 base = str(self.hist_file.parent / self.hist_file.stem)
87 base = str(self.hist_file.parent / self.hist_file.stem)
88 ext = self.hist_file.suffix
88 ext = self.hist_file.suffix
89 size = self.hist_file.stat().st_size
89 size = self.hist_file.stat().st_size
90 if size >= _SAVE_DB_SIZE:
90 if size >= _SAVE_DB_SIZE:
91 # if there's significant content, avoid clobbering
91 # if there's significant content, avoid clobbering
92 now = datetime.datetime.now().isoformat().replace(':', '.')
92 now = datetime.datetime.now().isoformat().replace(':', '.')
93 newpath = base + '-corrupt-' + now + ext
93 newpath = base + '-corrupt-' + now + ext
94 # don't clobber previous corrupt backups
94 # don't clobber previous corrupt backups
95 for i in range(100):
95 for i in range(100):
96 if not Path(newpath).exists():
96 if not Path(newpath).exists():
97 break
97 break
98 else:
98 else:
99 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
99 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
100 else:
100 else:
101 # not much content, possibly empty; don't worry about clobbering
101 # not much content, possibly empty; don't worry about clobbering
102 # maybe we should just delete it?
102 # maybe we should just delete it?
103 newpath = base + '-corrupt' + ext
103 newpath = base + '-corrupt' + ext
104 self.hist_file.rename(newpath)
104 self.hist_file.rename(newpath)
105 self.log.error("History file was moved to %s and a new file created.", newpath)
105 self.log.error("History file was moved to %s and a new file created.", newpath)
106 self.init_db()
106 self.init_db()
107 return []
107 return []
108 else:
108 else:
109 # Failed with :memory:, something serious is wrong
109 # Failed with :memory:, something serious is wrong
110 raise
110 raise
111
111
112
112
113 class HistoryAccessorBase(LoggingConfigurable):
113 class HistoryAccessorBase(LoggingConfigurable):
114 """An abstract class for History Accessors """
114 """An abstract class for History Accessors """
115
115
116 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
116 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
117 raise NotImplementedError
117 raise NotImplementedError
118
118
119 def search(self, pattern="*", raw=True, search_raw=True,
119 def search(self, pattern="*", raw=True, search_raw=True,
120 output=False, n=None, unique=False):
120 output=False, n=None, unique=False):
121 raise NotImplementedError
121 raise NotImplementedError
122
122
123 def get_range(self, session, start=1, stop=None, raw=True,output=False):
123 def get_range(self, session, start=1, stop=None, raw=True,output=False):
124 raise NotImplementedError
124 raise NotImplementedError
125
125
126 def get_range_by_str(self, rangestr, raw=True, output=False):
126 def get_range_by_str(self, rangestr, raw=True, output=False):
127 raise NotImplementedError
127 raise NotImplementedError
128
128
129
129
130 class HistoryAccessor(HistoryAccessorBase):
130 class HistoryAccessor(HistoryAccessorBase):
131 """Access the history database without adding to it.
131 """Access the history database without adding to it.
132
132
133 This is intended for use by standalone history tools. IPython shells use
133 This is intended for use by standalone history tools. IPython shells use
134 HistoryManager, below, which is a subclass of this."""
134 HistoryManager, below, which is a subclass of this."""
135
135
136 # counter for init_db retries, so we don't keep trying over and over
136 # counter for init_db retries, so we don't keep trying over and over
137 _corrupt_db_counter = 0
137 _corrupt_db_counter = 0
138 # after two failures, fallback on :memory:
138 # after two failures, fallback on :memory:
139 _corrupt_db_limit = 2
139 _corrupt_db_limit = 2
140
140
141 # String holding the path to the history file
141 # String holding the path to the history file
142 hist_file = Union(
142 hist_file = Union(
143 [Instance(Path), Unicode()],
143 [Instance(Path), Unicode()],
144 help="""Path to file to use for SQLite history database.
144 help="""Path to file to use for SQLite history database.
145
145
146 By default, IPython will put the history database in the IPython
146 By default, IPython will put the history database in the IPython
147 profile directory. If you would rather share one history among
147 profile directory. If you would rather share one history among
148 profiles, you can set this value in each, so that they are consistent.
148 profiles, you can set this value in each, so that they are consistent.
149
149
150 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
150 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
151 mounts. If you see IPython hanging, try setting this to something on a
151 mounts. If you see IPython hanging, try setting this to something on a
152 local disk, e.g::
152 local disk, e.g::
153
153
154 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
154 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
155
155
156 you can also use the specific value `:memory:` (including the colon
156 you can also use the specific value `:memory:` (including the colon
157 at both end but not the back ticks), to avoid creating an history file.
157 at both end but not the back ticks), to avoid creating an history file.
158
158
159 """,
159 """,
160 ).tag(config=True)
160 ).tag(config=True)
161
161
162 enabled = Bool(True,
162 enabled = Bool(True,
163 help="""enable the SQLite history
163 help="""enable the SQLite history
164
164
165 set enabled=False to disable the SQLite history,
165 set enabled=False to disable the SQLite history,
166 in which case there will be no stored history, no SQLite connection,
166 in which case there will be no stored history, no SQLite connection,
167 and no background saving thread. This may be necessary in some
167 and no background saving thread. This may be necessary in some
168 threaded environments where IPython is embedded.
168 threaded environments where IPython is embedded.
169 """,
169 """,
170 ).tag(config=True)
170 ).tag(config=True)
171
171
172 connection_options = Dict(
172 connection_options = Dict(
173 help="""Options for configuring the SQLite connection
173 help="""Options for configuring the SQLite connection
174
174
175 These options are passed as keyword args to sqlite3.connect
175 These options are passed as keyword args to sqlite3.connect
176 when establishing database connections.
176 when establishing database connections.
177 """
177 """
178 ).tag(config=True)
178 ).tag(config=True)
179
179
180 # The SQLite database
180 # The SQLite database
181 db = Any()
181 db = Any()
182 @observe('db')
182 @observe('db')
183 def _db_changed(self, change):
183 def _db_changed(self, change):
184 """validate the db, since it can be an Instance of two different types"""
184 """validate the db, since it can be an Instance of two different types"""
185 new = change['new']
185 new = change['new']
186 connection_types = (DummyDB, sqlite3.Connection)
186 connection_types = (DummyDB, sqlite3.Connection)
187 if not isinstance(new, connection_types):
187 if not isinstance(new, connection_types):
188 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
188 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
189 (self.__class__.__name__, new)
189 (self.__class__.__name__, new)
190 raise TraitError(msg)
190 raise TraitError(msg)
191
191
192 def __init__(self, profile="default", hist_file="", **traits):
192 def __init__(self, profile="default", hist_file="", **traits):
193 """Create a new history accessor.
193 """Create a new history accessor.
194
194
195 Parameters
195 Parameters
196 ----------
196 ----------
197 profile : str
197 profile : str
198 The name of the profile from which to open history.
198 The name of the profile from which to open history.
199 hist_file : str
199 hist_file : str
200 Path to an SQLite history database stored by IPython. If specified,
200 Path to an SQLite history database stored by IPython. If specified,
201 hist_file overrides profile.
201 hist_file overrides profile.
202 config : :class:`~traitlets.config.loader.Config`
202 config : :class:`~traitlets.config.loader.Config`
203 Config object. hist_file can also be set through this.
203 Config object. hist_file can also be set through this.
204 """
204 """
205 # We need a pointer back to the shell for various tasks.
206 super(HistoryAccessor, self).__init__(**traits)
205 super(HistoryAccessor, self).__init__(**traits)
207 # defer setting hist_file from kwarg until after init,
206 # defer setting hist_file from kwarg until after init,
208 # otherwise the default kwarg value would clobber any value
207 # otherwise the default kwarg value would clobber any value
209 # set by config
208 # set by config
210 if hist_file:
209 if hist_file:
211 self.hist_file = hist_file
210 self.hist_file = hist_file
212
211
213 try:
212 try:
214 self.hist_file
213 self.hist_file
215 except TraitError:
214 except TraitError:
216 # No one has set the hist_file, yet.
215 # No one has set the hist_file, yet.
217 self.hist_file = self._get_hist_file_name(profile)
216 self.hist_file = self._get_hist_file_name(profile)
218
217
219 self.init_db()
218 self.init_db()
220
219
221 def _get_hist_file_name(self, profile='default'):
220 def _get_hist_file_name(self, profile='default'):
222 """Find the history file for the given profile name.
221 """Find the history file for the given profile name.
223
222
224 This is overridden by the HistoryManager subclass, to use the shell's
223 This is overridden by the HistoryManager subclass, to use the shell's
225 active profile.
224 active profile.
226
225
227 Parameters
226 Parameters
228 ----------
227 ----------
229 profile : str
228 profile : str
230 The name of a profile which has a history file.
229 The name of a profile which has a history file.
231 """
230 """
232 return Path(locate_profile(profile)) / "history.sqlite"
231 return Path(locate_profile(profile)) / "history.sqlite"
233
232
234 @catch_corrupt_db
233 @catch_corrupt_db
235 def init_db(self):
234 def init_db(self):
236 """Connect to the database, and create tables if necessary."""
235 """Connect to the database, and create tables if necessary."""
237 if not self.enabled:
236 if not self.enabled:
238 self.db = DummyDB()
237 self.db = DummyDB()
239 return
238 return
240
239
241 # use detect_types so that timestamps return datetime objects
240 # use detect_types so that timestamps return datetime objects
242 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
241 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
243 kwargs.update(self.connection_options)
242 kwargs.update(self.connection_options)
244 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
243 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
245 with self.db:
244 with self.db:
246 self.db.execute(
245 self.db.execute(
247 """CREATE TABLE IF NOT EXISTS sessions (session integer
246 """CREATE TABLE IF NOT EXISTS sessions (session integer
248 primary key autoincrement, start timestamp,
247 primary key autoincrement, start timestamp,
249 end timestamp, num_cmds integer, remark text)"""
248 end timestamp, num_cmds integer, remark text)"""
250 )
249 )
251 self.db.execute(
250 self.db.execute(
252 """CREATE TABLE IF NOT EXISTS history
251 """CREATE TABLE IF NOT EXISTS history
253 (session integer, line integer, source text, source_raw text,
252 (session integer, line integer, source text, source_raw text,
254 PRIMARY KEY (session, line))"""
253 PRIMARY KEY (session, line))"""
255 )
254 )
256 # Output history is optional, but ensure the table's there so it can be
255 # Output history is optional, but ensure the table's there so it can be
257 # enabled later.
256 # enabled later.
258 self.db.execute(
257 self.db.execute(
259 """CREATE TABLE IF NOT EXISTS output_history
258 """CREATE TABLE IF NOT EXISTS output_history
260 (session integer, line integer, output text,
259 (session integer, line integer, output text,
261 PRIMARY KEY (session, line))"""
260 PRIMARY KEY (session, line))"""
262 )
261 )
263 # success! reset corrupt db count
262 # success! reset corrupt db count
264 self._corrupt_db_counter = 0
263 self._corrupt_db_counter = 0
265
264
266 def writeout_cache(self):
265 def writeout_cache(self):
267 """Overridden by HistoryManager to dump the cache before certain
266 """Overridden by HistoryManager to dump the cache before certain
268 database lookups."""
267 database lookups."""
269 pass
268 pass
270
269
271 ## -------------------------------
270 ## -------------------------------
272 ## Methods for retrieving history:
271 ## Methods for retrieving history:
273 ## -------------------------------
272 ## -------------------------------
274 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
273 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
275 """Prepares and runs an SQL query for the history database.
274 """Prepares and runs an SQL query for the history database.
276
275
277 Parameters
276 Parameters
278 ----------
277 ----------
279 sql : str
278 sql : str
280 Any filtering expressions to go after SELECT ... FROM ...
279 Any filtering expressions to go after SELECT ... FROM ...
281 params : tuple
280 params : tuple
282 Parameters passed to the SQL query (to replace "?")
281 Parameters passed to the SQL query (to replace "?")
283 raw, output : bool
282 raw, output : bool
284 See :meth:`get_range`
283 See :meth:`get_range`
285 latest : bool
284 latest : bool
286 Select rows with max (session, line)
285 Select rows with max (session, line)
287
286
288 Returns
287 Returns
289 -------
288 -------
290 Tuples as :meth:`get_range`
289 Tuples as :meth:`get_range`
291 """
290 """
292 toget = 'source_raw' if raw else 'source'
291 toget = 'source_raw' if raw else 'source'
293 sqlfrom = "history"
292 sqlfrom = "history"
294 if output:
293 if output:
295 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
294 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
296 toget = "history.%s, output_history.output" % toget
295 toget = "history.%s, output_history.output" % toget
297 if latest:
296 if latest:
298 toget += ", MAX(session * 128 * 1024 + line)"
297 toget += ", MAX(session * 128 * 1024 + line)"
299 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
298 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
300 cur = self.db.execute(this_querry, params)
299 cur = self.db.execute(this_querry, params)
301 if latest:
300 if latest:
302 cur = (row[:-1] for row in cur)
301 cur = (row[:-1] for row in cur)
303 if output: # Regroup into 3-tuples, and parse JSON
302 if output: # Regroup into 3-tuples, and parse JSON
304 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
303 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
305 return cur
304 return cur
306
305
307 @only_when_enabled
306 @only_when_enabled
308 @catch_corrupt_db
307 @catch_corrupt_db
309 def get_session_info(self, session):
308 def get_session_info(self, session):
310 """Get info about a session.
309 """Get info about a session.
311
310
312 Parameters
311 Parameters
313 ----------
312 ----------
314 session : int
313 session : int
315 Session number to retrieve.
314 Session number to retrieve.
316
315
317 Returns
316 Returns
318 -------
317 -------
319 session_id : int
318 session_id : int
320 Session ID number
319 Session ID number
321 start : datetime
320 start : datetime
322 Timestamp for the start of the session.
321 Timestamp for the start of the session.
323 end : datetime
322 end : datetime
324 Timestamp for the end of the session, or None if IPython crashed.
323 Timestamp for the end of the session, or None if IPython crashed.
325 num_cmds : int
324 num_cmds : int
326 Number of commands run, or None if IPython crashed.
325 Number of commands run, or None if IPython crashed.
327 remark : unicode
326 remark : unicode
328 A manually set description.
327 A manually set description.
329 """
328 """
330 query = "SELECT * from sessions where session == ?"
329 query = "SELECT * from sessions where session == ?"
331 return self.db.execute(query, (session,)).fetchone()
330 return self.db.execute(query, (session,)).fetchone()
332
331
333 @catch_corrupt_db
332 @catch_corrupt_db
334 def get_last_session_id(self):
333 def get_last_session_id(self):
335 """Get the last session ID currently in the database.
334 """Get the last session ID currently in the database.
336
335
337 Within IPython, this should be the same as the value stored in
336 Within IPython, this should be the same as the value stored in
338 :attr:`HistoryManager.session_number`.
337 :attr:`HistoryManager.session_number`.
339 """
338 """
340 for record in self.get_tail(n=1, include_latest=True):
339 for record in self.get_tail(n=1, include_latest=True):
341 return record[0]
340 return record[0]
342
341
343 @catch_corrupt_db
342 @catch_corrupt_db
344 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
343 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
345 """Get the last n lines from the history database.
344 """Get the last n lines from the history database.
346
345
347 Most recent entry last.
348
349 Completion will be reordered so that that the last ones are when
350 possible from current session.
351
352 Parameters
346 Parameters
353 ----------
347 ----------
354 n : int
348 n : int
355 The number of lines to get
349 The number of lines to get
356 raw, output : bool
350 raw, output : bool
357 See :meth:`get_range`
351 See :meth:`get_range`
358 include_latest : bool
352 include_latest : bool
359 If False (default), n+1 lines are fetched, and the latest one
353 If False (default), n+1 lines are fetched, and the latest one
360 is discarded. This is intended to be used where the function
354 is discarded. This is intended to be used where the function
361 is called by a user command, which it should not return.
355 is called by a user command, which it should not return.
362
356
363 Returns
357 Returns
364 -------
358 -------
365 Tuples as :meth:`get_range`
359 Tuples as :meth:`get_range`
366 """
360 """
367 self.writeout_cache()
361 self.writeout_cache()
368 if not include_latest:
362 if not include_latest:
369 n += 1
363 n += 1
370 # cursor/line/entry
364 cur = self._run_sql(
371 this_cur = list(
365 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
372 self._run_sql(
373 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
374 (self.session_number, n),
375 raw=raw,
376 output=output,
377 )
378 )
379 other_cur = list(
380 self._run_sql(
381 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
382 (self.session_number, n),
383 raw=raw,
384 output=output,
385 )
386 )
366 )
387
388 everything = this_cur + other_cur
389
390 everything = everything[:n]
391
392 if not include_latest:
367 if not include_latest:
393 return list(everything)[:0:-1]
368 return reversed(list(cur)[1:])
394 return list(everything)[::-1]
369 return reversed(list(cur))
395
370
396 @catch_corrupt_db
371 @catch_corrupt_db
397 def search(self, pattern="*", raw=True, search_raw=True,
372 def search(self, pattern="*", raw=True, search_raw=True,
398 output=False, n=None, unique=False):
373 output=False, n=None, unique=False):
399 """Search the database using unix glob-style matching (wildcards
374 """Search the database using unix glob-style matching (wildcards
400 * and ?).
375 * and ?).
401
376
402 Parameters
377 Parameters
403 ----------
378 ----------
404 pattern : str
379 pattern : str
405 The wildcarded pattern to match when searching
380 The wildcarded pattern to match when searching
406 search_raw : bool
381 search_raw : bool
407 If True, search the raw input, otherwise, the parsed input
382 If True, search the raw input, otherwise, the parsed input
408 raw, output : bool
383 raw, output : bool
409 See :meth:`get_range`
384 See :meth:`get_range`
410 n : None or int
385 n : None or int
411 If an integer is given, it defines the limit of
386 If an integer is given, it defines the limit of
412 returned entries.
387 returned entries.
413 unique : bool
388 unique : bool
414 When it is true, return only unique entries.
389 When it is true, return only unique entries.
415
390
416 Returns
391 Returns
417 -------
392 -------
418 Tuples as :meth:`get_range`
393 Tuples as :meth:`get_range`
419 """
394 """
420 tosearch = "source_raw" if search_raw else "source"
395 tosearch = "source_raw" if search_raw else "source"
421 if output:
396 if output:
422 tosearch = "history." + tosearch
397 tosearch = "history." + tosearch
423 self.writeout_cache()
398 self.writeout_cache()
424 sqlform = "WHERE %s GLOB ?" % tosearch
399 sqlform = "WHERE %s GLOB ?" % tosearch
425 params = (pattern,)
400 params = (pattern,)
426 if unique:
401 if unique:
427 sqlform += ' GROUP BY {0}'.format(tosearch)
402 sqlform += ' GROUP BY {0}'.format(tosearch)
428 if n is not None:
403 if n is not None:
429 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
404 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
430 params += (n,)
405 params += (n,)
431 elif unique:
406 elif unique:
432 sqlform += " ORDER BY session, line"
407 sqlform += " ORDER BY session, line"
433 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
408 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
434 if n is not None:
409 if n is not None:
435 return reversed(list(cur))
410 return reversed(list(cur))
436 return cur
411 return cur
437
412
438 @catch_corrupt_db
413 @catch_corrupt_db
439 def get_range(self, session, start=1, stop=None, raw=True,output=False):
414 def get_range(self, session, start=1, stop=None, raw=True,output=False):
440 """Retrieve input by session.
415 """Retrieve input by session.
441
416
442 Parameters
417 Parameters
443 ----------
418 ----------
444 session : int
419 session : int
445 Session number to retrieve.
420 Session number to retrieve.
446 start : int
421 start : int
447 First line to retrieve.
422 First line to retrieve.
448 stop : int
423 stop : int
449 End of line range (excluded from output itself). If None, retrieve
424 End of line range (excluded from output itself). If None, retrieve
450 to the end of the session.
425 to the end of the session.
451 raw : bool
426 raw : bool
452 If True, return untranslated input
427 If True, return untranslated input
453 output : bool
428 output : bool
454 If True, attempt to include output. This will be 'real' Python
429 If True, attempt to include output. This will be 'real' Python
455 objects for the current session, or text reprs from previous
430 objects for the current session, or text reprs from previous
456 sessions if db_log_output was enabled at the time. Where no output
431 sessions if db_log_output was enabled at the time. Where no output
457 is found, None is used.
432 is found, None is used.
458
433
459 Returns
434 Returns
460 -------
435 -------
461 entries
436 entries
462 An iterator over the desired lines. Each line is a 3-tuple, either
437 An iterator over the desired lines. Each line is a 3-tuple, either
463 (session, line, input) if output is False, or
438 (session, line, input) if output is False, or
464 (session, line, (input, output)) if output is True.
439 (session, line, (input, output)) if output is True.
465 """
440 """
466 if stop:
441 if stop:
467 lineclause = "line >= ? AND line < ?"
442 lineclause = "line >= ? AND line < ?"
468 params = (session, start, stop)
443 params = (session, start, stop)
469 else:
444 else:
470 lineclause = "line>=?"
445 lineclause = "line>=?"
471 params = (session, start)
446 params = (session, start)
472
447
473 return self._run_sql("WHERE session==? AND %s" % lineclause,
448 return self._run_sql("WHERE session==? AND %s" % lineclause,
474 params, raw=raw, output=output)
449 params, raw=raw, output=output)
475
450
476 def get_range_by_str(self, rangestr, raw=True, output=False):
451 def get_range_by_str(self, rangestr, raw=True, output=False):
477 """Get lines of history from a string of ranges, as used by magic
452 """Get lines of history from a string of ranges, as used by magic
478 commands %hist, %save, %macro, etc.
453 commands %hist, %save, %macro, etc.
479
454
480 Parameters
455 Parameters
481 ----------
456 ----------
482 rangestr : str
457 rangestr : str
483 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
458 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
484 this will return everything from current session's history.
459 this will return everything from current session's history.
485
460
486 See the documentation of :func:`%history` for the full details.
461 See the documentation of :func:`%history` for the full details.
487
462
488 raw, output : bool
463 raw, output : bool
489 As :meth:`get_range`
464 As :meth:`get_range`
490
465
491 Returns
466 Returns
492 -------
467 -------
493 Tuples as :meth:`get_range`
468 Tuples as :meth:`get_range`
494 """
469 """
495 for sess, s, e in extract_hist_ranges(rangestr):
470 for sess, s, e in extract_hist_ranges(rangestr):
496 for line in self.get_range(sess, s, e, raw=raw, output=output):
471 for line in self.get_range(sess, s, e, raw=raw, output=output):
497 yield line
472 yield line
498
473
499
474
500 class HistoryManager(HistoryAccessor):
475 class HistoryManager(HistoryAccessor):
501 """A class to organize all history-related functionality in one place.
476 """A class to organize all history-related functionality in one place.
502 """
477 """
503 # Public interface
478 # Public interface
504
479
505 # An instance of the IPython shell we are attached to
480 # An instance of the IPython shell we are attached to
506 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
481 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
507 allow_none=True)
482 allow_none=True)
508 # Lists to hold processed and raw history. These start with a blank entry
483 # Lists to hold processed and raw history. These start with a blank entry
509 # so that we can index them starting from 1
484 # so that we can index them starting from 1
510 input_hist_parsed = List([""])
485 input_hist_parsed = List([""])
511 input_hist_raw = List([""])
486 input_hist_raw = List([""])
512 # A list of directories visited during session
487 # A list of directories visited during session
513 dir_hist = List()
488 dir_hist = List()
514 @default('dir_hist')
489 @default('dir_hist')
515 def _dir_hist_default(self):
490 def _dir_hist_default(self):
516 try:
491 try:
517 return [Path.cwd()]
492 return [Path.cwd()]
518 except OSError:
493 except OSError:
519 return []
494 return []
520
495
521 # A dict of output history, keyed with ints from the shell's
496 # A dict of output history, keyed with ints from the shell's
522 # execution count.
497 # execution count.
523 output_hist = Dict()
498 output_hist = Dict()
524 # The text/plain repr of outputs.
499 # The text/plain repr of outputs.
525 output_hist_reprs = Dict()
500 output_hist_reprs = Dict()
526
501
527 # The number of the current session in the history database
502 # The number of the current session in the history database
528 session_number = Integer()
503 session_number = Integer()
529
504
530 db_log_output = Bool(False,
505 db_log_output = Bool(False,
531 help="Should the history database include output? (default: no)"
506 help="Should the history database include output? (default: no)"
532 ).tag(config=True)
507 ).tag(config=True)
533 db_cache_size = Integer(0,
508 db_cache_size = Integer(0,
534 help="Write to database every x commands (higher values save disk access & power).\n"
509 help="Write to database every x commands (higher values save disk access & power).\n"
535 "Values of 1 or less effectively disable caching."
510 "Values of 1 or less effectively disable caching."
536 ).tag(config=True)
511 ).tag(config=True)
537 # The input and output caches
512 # The input and output caches
538 db_input_cache = List()
513 db_input_cache = List()
539 db_output_cache = List()
514 db_output_cache = List()
540
515
541 # History saving in separate thread
516 # History saving in separate thread
542 save_thread = Instance('IPython.core.history.HistorySavingThread',
517 save_thread = Instance('IPython.core.history.HistorySavingThread',
543 allow_none=True)
518 allow_none=True)
544 save_flag = Instance(threading.Event, allow_none=True)
519 save_flag = Instance(threading.Event, allow_none=True)
545
520
546 # Private interface
521 # Private interface
547 # Variables used to store the three last inputs from the user. On each new
522 # Variables used to store the three last inputs from the user. On each new
548 # history update, we populate the user's namespace with these, shifted as
523 # history update, we populate the user's namespace with these, shifted as
549 # necessary.
524 # necessary.
550 _i00 = Unicode(u'')
525 _i00 = Unicode(u'')
551 _i = Unicode(u'')
526 _i = Unicode(u'')
552 _ii = Unicode(u'')
527 _ii = Unicode(u'')
553 _iii = Unicode(u'')
528 _iii = Unicode(u'')
554
529
555 # A regex matching all forms of the exit command, so that we don't store
530 # A regex matching all forms of the exit command, so that we don't store
556 # them in the history (it's annoying to rewind the first entry and land on
531 # them in the history (it's annoying to rewind the first entry and land on
557 # an exit call).
532 # an exit call).
558 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
533 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
559
534
560 def __init__(self, shell=None, config=None, **traits):
535 def __init__(self, shell=None, config=None, **traits):
561 """Create a new history manager associated with a shell instance.
536 """Create a new history manager associated with a shell instance.
562 """
537 """
563 # We need a pointer back to the shell for various tasks.
564 super(HistoryManager, self).__init__(shell=shell, config=config,
538 super(HistoryManager, self).__init__(shell=shell, config=config,
565 **traits)
539 **traits)
566 self.save_flag = threading.Event()
540 self.save_flag = threading.Event()
567 self.db_input_cache_lock = threading.Lock()
541 self.db_input_cache_lock = threading.Lock()
568 self.db_output_cache_lock = threading.Lock()
542 self.db_output_cache_lock = threading.Lock()
569
543
570 try:
544 try:
571 self.new_session()
545 self.new_session()
572 except sqlite3.OperationalError:
546 except sqlite3.OperationalError:
573 self.log.error("Failed to create history session in %s. History will not be saved.",
547 self.log.error("Failed to create history session in %s. History will not be saved.",
574 self.hist_file, exc_info=True)
548 self.hist_file, exc_info=True)
575 self.hist_file = ':memory:'
549 self.hist_file = ':memory:'
576
550
577 if self.enabled and self.hist_file != ':memory:':
551 if self.enabled and self.hist_file != ':memory:':
578 self.save_thread = HistorySavingThread(self)
552 self.save_thread = HistorySavingThread(self)
579 self.save_thread.start()
553 self.save_thread.start()
580
554
581 def _get_hist_file_name(self, profile=None):
555 def _get_hist_file_name(self, profile=None):
582 """Get default history file name based on the Shell's profile.
556 """Get default history file name based on the Shell's profile.
583
557
584 The profile parameter is ignored, but must exist for compatibility with
558 The profile parameter is ignored, but must exist for compatibility with
585 the parent class."""
559 the parent class."""
586 profile_dir = self.shell.profile_dir.location
560 profile_dir = self.shell.profile_dir.location
587 return Path(profile_dir) / "history.sqlite"
561 return Path(profile_dir) / "history.sqlite"
588
562
589 @only_when_enabled
563 @only_when_enabled
590 def new_session(self, conn=None):
564 def new_session(self, conn=None):
591 """Get a new session number."""
565 """Get a new session number."""
592 if conn is None:
566 if conn is None:
593 conn = self.db
567 conn = self.db
594
568
595 with conn:
569 with conn:
596 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
570 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
597 NULL, "") """, (datetime.datetime.now(),))
571 NULL, "") """, (datetime.datetime.now(),))
598 self.session_number = cur.lastrowid
572 self.session_number = cur.lastrowid
599
573
600 def end_session(self):
574 def end_session(self):
601 """Close the database session, filling in the end time and line count."""
575 """Close the database session, filling in the end time and line count."""
602 self.writeout_cache()
576 self.writeout_cache()
603 with self.db:
577 with self.db:
604 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
578 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
605 session==?""", (datetime.datetime.now(),
579 session==?""", (datetime.datetime.now(),
606 len(self.input_hist_parsed)-1, self.session_number))
580 len(self.input_hist_parsed)-1, self.session_number))
607 self.session_number = 0
581 self.session_number = 0
608
582
609 def name_session(self, name):
583 def name_session(self, name):
610 """Give the current session a name in the history database."""
584 """Give the current session a name in the history database."""
611 with self.db:
585 with self.db:
612 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
586 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
613 (name, self.session_number))
587 (name, self.session_number))
614
588
615 def reset(self, new_session=True):
589 def reset(self, new_session=True):
616 """Clear the session history, releasing all object references, and
590 """Clear the session history, releasing all object references, and
617 optionally open a new session."""
591 optionally open a new session."""
618 self.output_hist.clear()
592 self.output_hist.clear()
619 # The directory history can't be completely empty
593 # The directory history can't be completely empty
620 self.dir_hist[:] = [Path.cwd()]
594 self.dir_hist[:] = [Path.cwd()]
621
595
622 if new_session:
596 if new_session:
623 if self.session_number:
597 if self.session_number:
624 self.end_session()
598 self.end_session()
625 self.input_hist_parsed[:] = [""]
599 self.input_hist_parsed[:] = [""]
626 self.input_hist_raw[:] = [""]
600 self.input_hist_raw[:] = [""]
627 self.new_session()
601 self.new_session()
628
602
629 # ------------------------------
603 # ------------------------------
630 # Methods for retrieving history
604 # Methods for retrieving history
631 # ------------------------------
605 # ------------------------------
632 def get_session_info(self, session=0):
606 def get_session_info(self, session=0):
633 """Get info about a session.
607 """Get info about a session.
634
608
635 Parameters
609 Parameters
636 ----------
610 ----------
637 session : int
611 session : int
638 Session number to retrieve. The current session is 0, and negative
612 Session number to retrieve. The current session is 0, and negative
639 numbers count back from current session, so -1 is the previous session.
613 numbers count back from current session, so -1 is the previous session.
640
614
641 Returns
615 Returns
642 -------
616 -------
643 session_id : int
617 session_id : int
644 Session ID number
618 Session ID number
645 start : datetime
619 start : datetime
646 Timestamp for the start of the session.
620 Timestamp for the start of the session.
647 end : datetime
621 end : datetime
648 Timestamp for the end of the session, or None if IPython crashed.
622 Timestamp for the end of the session, or None if IPython crashed.
649 num_cmds : int
623 num_cmds : int
650 Number of commands run, or None if IPython crashed.
624 Number of commands run, or None if IPython crashed.
651 remark : unicode
625 remark : unicode
652 A manually set description.
626 A manually set description.
653 """
627 """
654 if session <= 0:
628 if session <= 0:
655 session += self.session_number
629 session += self.session_number
656
630
657 return super(HistoryManager, self).get_session_info(session=session)
631 return super(HistoryManager, self).get_session_info(session=session)
658
632
633 @catch_corrupt_db
634 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
635 """Get the last n lines from the history database.
636
637 Most recent entry last.
638
639 Completion will be reordered so that that the last ones are when
640 possible from current session.
641
642 Parameters
643 ----------
644 n : int
645 The number of lines to get
646 raw, output : bool
647 See :meth:`get_range`
648 include_latest : bool
649 If False (default), n+1 lines are fetched, and the latest one
650 is discarded. This is intended to be used where the function
651 is called by a user command, which it should not return.
652
653 Returns
654 -------
655 Tuples as :meth:`get_range`
656 """
657 self.writeout_cache()
658 if not include_latest:
659 n += 1
660 # cursor/line/entry
661 this_cur = list(
662 self._run_sql(
663 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
664 (self.session_number, n),
665 raw=raw,
666 output=output,
667 )
668 )
669 other_cur = list(
670 self._run_sql(
671 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
672 (self.session_number, n),
673 raw=raw,
674 output=output,
675 )
676 )
677
678 everything = this_cur + other_cur
679
680 everything = everything[:n]
681
682 if not include_latest:
683 return list(everything)[:0:-1]
684 return list(everything)[::-1]
685
659 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
686 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
660 """Get input and output history from the current session. Called by
687 """Get input and output history from the current session. Called by
661 get_range, and takes similar parameters."""
688 get_range, and takes similar parameters."""
662 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
689 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
663
690
664 n = len(input_hist)
691 n = len(input_hist)
665 if start < 0:
692 if start < 0:
666 start += n
693 start += n
667 if not stop or (stop > n):
694 if not stop or (stop > n):
668 stop = n
695 stop = n
669 elif stop < 0:
696 elif stop < 0:
670 stop += n
697 stop += n
671
698
672 for i in range(start, stop):
699 for i in range(start, stop):
673 if output:
700 if output:
674 line = (input_hist[i], self.output_hist_reprs.get(i))
701 line = (input_hist[i], self.output_hist_reprs.get(i))
675 else:
702 else:
676 line = input_hist[i]
703 line = input_hist[i]
677 yield (0, i, line)
704 yield (0, i, line)
678
705
679 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
706 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
680 """Retrieve input by session.
707 """Retrieve input by session.
681
708
682 Parameters
709 Parameters
683 ----------
710 ----------
684 session : int
711 session : int
685 Session number to retrieve. The current session is 0, and negative
712 Session number to retrieve. The current session is 0, and negative
686 numbers count back from current session, so -1 is previous session.
713 numbers count back from current session, so -1 is previous session.
687 start : int
714 start : int
688 First line to retrieve.
715 First line to retrieve.
689 stop : int
716 stop : int
690 End of line range (excluded from output itself). If None, retrieve
717 End of line range (excluded from output itself). If None, retrieve
691 to the end of the session.
718 to the end of the session.
692 raw : bool
719 raw : bool
693 If True, return untranslated input
720 If True, return untranslated input
694 output : bool
721 output : bool
695 If True, attempt to include output. This will be 'real' Python
722 If True, attempt to include output. This will be 'real' Python
696 objects for the current session, or text reprs from previous
723 objects for the current session, or text reprs from previous
697 sessions if db_log_output was enabled at the time. Where no output
724 sessions if db_log_output was enabled at the time. Where no output
698 is found, None is used.
725 is found, None is used.
699
726
700 Returns
727 Returns
701 -------
728 -------
702 entries
729 entries
703 An iterator over the desired lines. Each line is a 3-tuple, either
730 An iterator over the desired lines. Each line is a 3-tuple, either
704 (session, line, input) if output is False, or
731 (session, line, input) if output is False, or
705 (session, line, (input, output)) if output is True.
732 (session, line, (input, output)) if output is True.
706 """
733 """
707 if session <= 0:
734 if session <= 0:
708 session += self.session_number
735 session += self.session_number
709 if session==self.session_number: # Current session
736 if session==self.session_number: # Current session
710 return self._get_range_session(start, stop, raw, output)
737 return self._get_range_session(start, stop, raw, output)
711 return super(HistoryManager, self).get_range(session, start, stop, raw,
738 return super(HistoryManager, self).get_range(session, start, stop, raw,
712 output)
739 output)
713
740
714 ## ----------------------------
741 ## ----------------------------
715 ## Methods for storing history:
742 ## Methods for storing history:
716 ## ----------------------------
743 ## ----------------------------
717 def store_inputs(self, line_num, source, source_raw=None):
744 def store_inputs(self, line_num, source, source_raw=None):
718 """Store source and raw input in history and create input cache
745 """Store source and raw input in history and create input cache
719 variables ``_i*``.
746 variables ``_i*``.
720
747
721 Parameters
748 Parameters
722 ----------
749 ----------
723 line_num : int
750 line_num : int
724 The prompt number of this input.
751 The prompt number of this input.
725 source : str
752 source : str
726 Python input.
753 Python input.
727 source_raw : str, optional
754 source_raw : str, optional
728 If given, this is the raw input without any IPython transformations
755 If given, this is the raw input without any IPython transformations
729 applied to it. If not given, ``source`` is used.
756 applied to it. If not given, ``source`` is used.
730 """
757 """
731 if source_raw is None:
758 if source_raw is None:
732 source_raw = source
759 source_raw = source
733 source = source.rstrip('\n')
760 source = source.rstrip('\n')
734 source_raw = source_raw.rstrip('\n')
761 source_raw = source_raw.rstrip('\n')
735
762
736 # do not store exit/quit commands
763 # do not store exit/quit commands
737 if self._exit_re.match(source_raw.strip()):
764 if self._exit_re.match(source_raw.strip()):
738 return
765 return
739
766
740 self.input_hist_parsed.append(source)
767 self.input_hist_parsed.append(source)
741 self.input_hist_raw.append(source_raw)
768 self.input_hist_raw.append(source_raw)
742
769
743 with self.db_input_cache_lock:
770 with self.db_input_cache_lock:
744 self.db_input_cache.append((line_num, source, source_raw))
771 self.db_input_cache.append((line_num, source, source_raw))
745 # Trigger to flush cache and write to DB.
772 # Trigger to flush cache and write to DB.
746 if len(self.db_input_cache) >= self.db_cache_size:
773 if len(self.db_input_cache) >= self.db_cache_size:
747 self.save_flag.set()
774 self.save_flag.set()
748
775
749 # update the auto _i variables
776 # update the auto _i variables
750 self._iii = self._ii
777 self._iii = self._ii
751 self._ii = self._i
778 self._ii = self._i
752 self._i = self._i00
779 self._i = self._i00
753 self._i00 = source_raw
780 self._i00 = source_raw
754
781
755 # hackish access to user namespace to create _i1,_i2... dynamically
782 # hackish access to user namespace to create _i1,_i2... dynamically
756 new_i = '_i%s' % line_num
783 new_i = '_i%s' % line_num
757 to_main = {'_i': self._i,
784 to_main = {'_i': self._i,
758 '_ii': self._ii,
785 '_ii': self._ii,
759 '_iii': self._iii,
786 '_iii': self._iii,
760 new_i : self._i00 }
787 new_i : self._i00 }
761
788
762 if self.shell is not None:
789 if self.shell is not None:
763 self.shell.push(to_main, interactive=False)
790 self.shell.push(to_main, interactive=False)
764
791
765 def store_output(self, line_num):
792 def store_output(self, line_num):
766 """If database output logging is enabled, this saves all the
793 """If database output logging is enabled, this saves all the
767 outputs from the indicated prompt number to the database. It's
794 outputs from the indicated prompt number to the database. It's
768 called by run_cell after code has been executed.
795 called by run_cell after code has been executed.
769
796
770 Parameters
797 Parameters
771 ----------
798 ----------
772 line_num : int
799 line_num : int
773 The line number from which to save outputs
800 The line number from which to save outputs
774 """
801 """
775 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
802 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
776 return
803 return
777 output = self.output_hist_reprs[line_num]
804 output = self.output_hist_reprs[line_num]
778
805
779 with self.db_output_cache_lock:
806 with self.db_output_cache_lock:
780 self.db_output_cache.append((line_num, output))
807 self.db_output_cache.append((line_num, output))
781 if self.db_cache_size <= 1:
808 if self.db_cache_size <= 1:
782 self.save_flag.set()
809 self.save_flag.set()
783
810
784 def _writeout_input_cache(self, conn):
811 def _writeout_input_cache(self, conn):
785 with conn:
812 with conn:
786 for line in self.db_input_cache:
813 for line in self.db_input_cache:
787 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
814 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
788 (self.session_number,)+line)
815 (self.session_number,)+line)
789
816
790 def _writeout_output_cache(self, conn):
817 def _writeout_output_cache(self, conn):
791 with conn:
818 with conn:
792 for line in self.db_output_cache:
819 for line in self.db_output_cache:
793 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
820 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
794 (self.session_number,)+line)
821 (self.session_number,)+line)
795
822
796 @only_when_enabled
823 @only_when_enabled
797 def writeout_cache(self, conn=None):
824 def writeout_cache(self, conn=None):
798 """Write any entries in the cache to the database."""
825 """Write any entries in the cache to the database."""
799 if conn is None:
826 if conn is None:
800 conn = self.db
827 conn = self.db
801
828
802 with self.db_input_cache_lock:
829 with self.db_input_cache_lock:
803 try:
830 try:
804 self._writeout_input_cache(conn)
831 self._writeout_input_cache(conn)
805 except sqlite3.IntegrityError:
832 except sqlite3.IntegrityError:
806 self.new_session(conn)
833 self.new_session(conn)
807 print("ERROR! Session/line number was not unique in",
834 print("ERROR! Session/line number was not unique in",
808 "database. History logging moved to new session",
835 "database. History logging moved to new session",
809 self.session_number)
836 self.session_number)
810 try:
837 try:
811 # Try writing to the new session. If this fails, don't
838 # Try writing to the new session. If this fails, don't
812 # recurse
839 # recurse
813 self._writeout_input_cache(conn)
840 self._writeout_input_cache(conn)
814 except sqlite3.IntegrityError:
841 except sqlite3.IntegrityError:
815 pass
842 pass
816 finally:
843 finally:
817 self.db_input_cache = []
844 self.db_input_cache = []
818
845
819 with self.db_output_cache_lock:
846 with self.db_output_cache_lock:
820 try:
847 try:
821 self._writeout_output_cache(conn)
848 self._writeout_output_cache(conn)
822 except sqlite3.IntegrityError:
849 except sqlite3.IntegrityError:
823 print("!! Session/line number for output was not unique",
850 print("!! Session/line number for output was not unique",
824 "in database. Output will not be stored.")
851 "in database. Output will not be stored.")
825 finally:
852 finally:
826 self.db_output_cache = []
853 self.db_output_cache = []
827
854
828
855
829 class HistorySavingThread(threading.Thread):
856 class HistorySavingThread(threading.Thread):
830 """This thread takes care of writing history to the database, so that
857 """This thread takes care of writing history to the database, so that
831 the UI isn't held up while that happens.
858 the UI isn't held up while that happens.
832
859
833 It waits for the HistoryManager's save_flag to be set, then writes out
860 It waits for the HistoryManager's save_flag to be set, then writes out
834 the history cache. The main thread is responsible for setting the flag when
861 the history cache. The main thread is responsible for setting the flag when
835 the cache size reaches a defined threshold."""
862 the cache size reaches a defined threshold."""
836 daemon = True
863 daemon = True
837 stop_now = False
864 stop_now = False
838 enabled = True
865 enabled = True
839 def __init__(self, history_manager):
866 def __init__(self, history_manager):
840 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
867 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
841 self.history_manager = history_manager
868 self.history_manager = history_manager
842 self.enabled = history_manager.enabled
869 self.enabled = history_manager.enabled
843 atexit.register(self.stop)
870 atexit.register(self.stop)
844
871
845 @only_when_enabled
872 @only_when_enabled
846 def run(self):
873 def run(self):
847 # We need a separate db connection per thread:
874 # We need a separate db connection per thread:
848 try:
875 try:
849 self.db = sqlite3.connect(
876 self.db = sqlite3.connect(
850 str(self.history_manager.hist_file),
877 str(self.history_manager.hist_file),
851 **self.history_manager.connection_options,
878 **self.history_manager.connection_options,
852 )
879 )
853 while True:
880 while True:
854 self.history_manager.save_flag.wait()
881 self.history_manager.save_flag.wait()
855 if self.stop_now:
882 if self.stop_now:
856 self.db.close()
883 self.db.close()
857 return
884 return
858 self.history_manager.save_flag.clear()
885 self.history_manager.save_flag.clear()
859 self.history_manager.writeout_cache(self.db)
886 self.history_manager.writeout_cache(self.db)
860 except Exception as e:
887 except Exception as e:
861 print(("The history saving thread hit an unexpected error (%s)."
888 print(("The history saving thread hit an unexpected error (%s)."
862 "History will not be written to the database.") % repr(e))
889 "History will not be written to the database.") % repr(e))
863
890
864 def stop(self):
891 def stop(self):
865 """This can be called from the main thread to safely stop this thread.
892 """This can be called from the main thread to safely stop this thread.
866
893
867 Note that it does not attempt to write out remaining history before
894 Note that it does not attempt to write out remaining history before
868 exiting. That should be done by calling the HistoryManager's
895 exiting. That should be done by calling the HistoryManager's
869 end_session method."""
896 end_session method."""
870 self.stop_now = True
897 self.stop_now = True
871 self.history_manager.save_flag.set()
898 self.history_manager.save_flag.set()
872 self.join()
899 self.join()
873
900
874
901
875 # To match, e.g. ~5/8-~2/3
902 # To match, e.g. ~5/8-~2/3
876 range_re = re.compile(r"""
903 range_re = re.compile(r"""
877 ((?P<startsess>~?\d+)/)?
904 ((?P<startsess>~?\d+)/)?
878 (?P<start>\d+)?
905 (?P<start>\d+)?
879 ((?P<sep>[\-:])
906 ((?P<sep>[\-:])
880 ((?P<endsess>~?\d+)/)?
907 ((?P<endsess>~?\d+)/)?
881 (?P<end>\d+))?
908 (?P<end>\d+))?
882 $""", re.VERBOSE)
909 $""", re.VERBOSE)
883
910
884
911
885 def extract_hist_ranges(ranges_str):
912 def extract_hist_ranges(ranges_str):
886 """Turn a string of history ranges into 3-tuples of (session, start, stop).
913 """Turn a string of history ranges into 3-tuples of (session, start, stop).
887
914
888 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
915 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
889 session".
916 session".
890
917
891 Examples
918 Examples
892 --------
919 --------
893 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
920 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
894 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
921 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
895 """
922 """
896 if ranges_str == "":
923 if ranges_str == "":
897 yield (0, 1, None) # Everything from current session
924 yield (0, 1, None) # Everything from current session
898 return
925 return
899
926
900 for range_str in ranges_str.split():
927 for range_str in ranges_str.split():
901 rmatch = range_re.match(range_str)
928 rmatch = range_re.match(range_str)
902 if not rmatch:
929 if not rmatch:
903 continue
930 continue
904 start = rmatch.group("start")
931 start = rmatch.group("start")
905 if start:
932 if start:
906 start = int(start)
933 start = int(start)
907 end = rmatch.group("end")
934 end = rmatch.group("end")
908 # If no end specified, get (a, a + 1)
935 # If no end specified, get (a, a + 1)
909 end = int(end) if end else start + 1
936 end = int(end) if end else start + 1
910 else: # start not specified
937 else: # start not specified
911 if not rmatch.group('startsess'): # no startsess
938 if not rmatch.group('startsess'): # no startsess
912 continue
939 continue
913 start = 1
940 start = 1
914 end = None # provide the entire session hist
941 end = None # provide the entire session hist
915
942
916 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
943 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
917 end += 1
944 end += 1
918 startsess = rmatch.group("startsess") or "0"
945 startsess = rmatch.group("startsess") or "0"
919 endsess = rmatch.group("endsess") or startsess
946 endsess = rmatch.group("endsess") or startsess
920 startsess = int(startsess.replace("~","-"))
947 startsess = int(startsess.replace("~","-"))
921 endsess = int(endsess.replace("~","-"))
948 endsess = int(endsess.replace("~","-"))
922 assert endsess >= startsess, "start session must be earlier than end session"
949 assert endsess >= startsess, "start session must be earlier than end session"
923
950
924 if endsess == startsess:
951 if endsess == startsess:
925 yield (startsess, start, end)
952 yield (startsess, start, end)
926 continue
953 continue
927 # Multiple sessions in one range:
954 # Multiple sessions in one range:
928 yield (startsess, start, None)
955 yield (startsess, start, None)
929 for sess in range(startsess+1, endsess):
956 for sess in range(startsess+1, endsess):
930 yield (sess, 1, None)
957 yield (sess, 1, None)
931 yield (endsess, 1, end)
958 yield (endsess, 1, end)
932
959
933
960
934 def _format_lineno(session, line):
961 def _format_lineno(session, line):
935 """Helper function to format line numbers properly."""
962 """Helper function to format line numbers properly."""
936 if session == 0:
963 if session == 0:
937 return str(line)
964 return str(line)
938 return "%s#%s" % (session, line)
965 return "%s#%s" % (session, line)
@@ -1,171 +1,173 b''
1 """Hooks for IPython.
1 """Hooks for IPython.
2
2
3 In Python, it is possible to overwrite any method of any object if you really
3 In Python, it is possible to overwrite any method of any object if you really
4 want to. But IPython exposes a few 'hooks', methods which are *designed* to
4 want to. But IPython exposes a few 'hooks', methods which are *designed* to
5 be overwritten by users for customization purposes. This module defines the
5 be overwritten by users for customization purposes. This module defines the
6 default versions of all such hooks, which get used by IPython if not
6 default versions of all such hooks, which get used by IPython if not
7 overridden by the user.
7 overridden by the user.
8
8
9 Hooks are simple functions, but they should be declared with ``self`` as their
9 Hooks are simple functions, but they should be declared with ``self`` as their
10 first argument, because when activated they are registered into IPython as
10 first argument, because when activated they are registered into IPython as
11 instance methods. The self argument will be the IPython running instance
11 instance methods. The self argument will be the IPython running instance
12 itself, so hooks have full access to the entire IPython object.
12 itself, so hooks have full access to the entire IPython object.
13
13
14 If you wish to define a new hook and activate it, you can make an :doc:`extension
14 If you wish to define a new hook and activate it, you can make an :doc:`extension
15 </config/extensions/index>` or a :ref:`startup script <startup_files>`. For
15 </config/extensions/index>` or a :ref:`startup script <startup_files>`. For
16 example, you could use a startup file like this::
16 example, you could use a startup file like this::
17
17
18 import os
18 import os
19
19
20 def calljed(self,filename, linenum):
20 def calljed(self,filename, linenum):
21 "My editor hook calls the jed editor directly."
21 "My editor hook calls the jed editor directly."
22 print "Calling my own editor, jed ..."
22 print "Calling my own editor, jed ..."
23 if os.system('jed +%d %s' % (linenum,filename)) != 0:
23 if os.system('jed +%d %s' % (linenum,filename)) != 0:
24 raise TryNext()
24 raise TryNext()
25
25
26 def load_ipython_extension(ip):
26 def load_ipython_extension(ip):
27 ip.set_hook('editor', calljed)
27 ip.set_hook('editor', calljed)
28
28
29 """
29 """
30
30
31 #*****************************************************************************
31 #*****************************************************************************
32 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
32 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
33 #
33 #
34 # Distributed under the terms of the BSD License. The full license is in
34 # Distributed under the terms of the BSD License. The full license is in
35 # the file COPYING, distributed as part of this software.
35 # the file COPYING, distributed as part of this software.
36 #*****************************************************************************
36 #*****************************************************************************
37
37
38 import os
38 import os
39 import subprocess
39 import subprocess
40 import sys
40 import sys
41
41
42 from .error import TryNext
42 from .error import TryNext
43
43
44 # List here all the default hooks. For now it's just the editor functions
44 # List here all the default hooks. For now it's just the editor functions
45 # but over time we'll move here all the public API for user-accessible things.
45 # but over time we'll move here all the public API for user-accessible things.
46
46
47 __all__ = [
47 __all__ = [
48 "editor",
48 "editor",
49 "synchronize_with_editor",
49 "synchronize_with_editor",
50 "show_in_pager",
50 "show_in_pager",
51 "pre_prompt_hook",
51 "pre_prompt_hook",
52 "clipboard_get",
52 "clipboard_get",
53 ]
53 ]
54
54
55 deprecated = {'pre_run_code_hook': "a callback for the 'pre_execute' or 'pre_run_cell' event",
55 deprecated = {'pre_run_code_hook': "a callback for the 'pre_execute' or 'pre_run_cell' event",
56 'late_startup_hook': "a callback for the 'shell_initialized' event",
56 'late_startup_hook': "a callback for the 'shell_initialized' event",
57 'shutdown_hook': "the atexit module",
57 'shutdown_hook': "the atexit module",
58 }
58 }
59
59
60 def editor(self, filename, linenum=None, wait=True):
60 def editor(self, filename, linenum=None, wait=True):
61 """Open the default editor at the given filename and linenumber.
61 """Open the default editor at the given filename and linenumber.
62
62
63 This is IPython's default editor hook, you can use it as an example to
63 This is IPython's default editor hook, you can use it as an example to
64 write your own modified one. To set your own editor function as the
64 write your own modified one. To set your own editor function as the
65 new editor hook, call ip.set_hook('editor',yourfunc)."""
65 new editor hook, call ip.set_hook('editor',yourfunc)."""
66
66
67 # IPython configures a default editor at startup by reading $EDITOR from
67 # IPython configures a default editor at startup by reading $EDITOR from
68 # the environment, and falling back on vi (unix) or notepad (win32).
68 # the environment, and falling back on vi (unix) or notepad (win32).
69 editor = self.editor
69 editor = self.editor
70
70
71 # marker for at which line to open the file (for existing objects)
71 # marker for at which line to open the file (for existing objects)
72 if linenum is None or editor=='notepad':
72 if linenum is None or editor=='notepad':
73 linemark = ''
73 linemark = ''
74 else:
74 else:
75 linemark = '+%d' % int(linenum)
75 linemark = '+%d' % int(linenum)
76
76
77 # Enclose in quotes if necessary and legal
77 # Enclose in quotes if necessary and legal
78 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
78 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
79 editor = '"%s"' % editor
79 editor = '"%s"' % editor
80
80
81 # Call the actual editor
81 # Call the actual editor
82 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
82 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
83 shell=True)
83 shell=True)
84 if wait and proc.wait() != 0:
84 if wait and proc.wait() != 0:
85 raise TryNext()
85 raise TryNext()
86
86
87
87
88 def synchronize_with_editor(self, filename, linenum, column):
88 def synchronize_with_editor(self, filename, linenum, column):
89 pass
89 pass
90
90
91
91
92 class CommandChainDispatcher:
92 class CommandChainDispatcher:
93 """ Dispatch calls to a chain of commands until some func can handle it
93 """ Dispatch calls to a chain of commands until some func can handle it
94
94
95 Usage: instantiate, execute "add" to add commands (with optional
95 Usage: instantiate, execute "add" to add commands (with optional
96 priority), execute normally via f() calling mechanism.
96 priority), execute normally via f() calling mechanism.
97
97
98 """
98 """
99 def __init__(self,commands=None):
99 def __init__(self,commands=None):
100 if commands is None:
100 if commands is None:
101 self.chain = []
101 self.chain = []
102 else:
102 else:
103 self.chain = commands
103 self.chain = commands
104
104
105
105
106 def __call__(self,*args, **kw):
106 def __call__(self,*args, **kw):
107 """ Command chain is called just like normal func.
107 """ Command chain is called just like normal func.
108
108
109 This will call all funcs in chain with the same args as were given to
109 This will call all funcs in chain with the same args as were given to
110 this function, and return the result of first func that didn't raise
110 this function, and return the result of first func that didn't raise
111 TryNext"""
111 TryNext"""
112 last_exc = TryNext()
112 last_exc = TryNext()
113 for prio,cmd in self.chain:
113 for prio,cmd in self.chain:
114 #print "prio",prio,"cmd",cmd #dbg
114 #print "prio",prio,"cmd",cmd #dbg
115 try:
115 try:
116 return cmd(*args, **kw)
116 return cmd(*args, **kw)
117 except TryNext as exc:
117 except TryNext as exc:
118 last_exc = exc
118 last_exc = exc
119 # if no function will accept it, raise TryNext up to the caller
119 # if no function will accept it, raise TryNext up to the caller
120 raise last_exc
120 raise last_exc
121
121
122 def __str__(self):
122 def __str__(self):
123 return str(self.chain)
123 return str(self.chain)
124
124
125 def add(self, func, priority=0):
125 def add(self, func, priority=0):
126 """ Add a func to the cmd chain with given priority """
126 """ Add a func to the cmd chain with given priority """
127 self.chain.append((priority, func))
127 self.chain.append((priority, func))
128 self.chain.sort(key=lambda x: x[0])
128 self.chain.sort(key=lambda x: x[0])
129
129
130 def __iter__(self):
130 def __iter__(self):
131 """ Return all objects in chain.
131 """ Return all objects in chain.
132
132
133 Handy if the objects are not callable.
133 Handy if the objects are not callable.
134 """
134 """
135 return iter(self.chain)
135 return iter(self.chain)
136
136
137
137
138 def show_in_pager(self, data, start, screen_lines):
138 def show_in_pager(self, data, start, screen_lines):
139 """ Run a string through pager """
139 """ Run a string through pager """
140 # raising TryNext here will use the default paging functionality
140 # raising TryNext here will use the default paging functionality
141 raise TryNext
141 raise TryNext
142
142
143
143
144 def pre_prompt_hook(self):
144 def pre_prompt_hook(self):
145 """ Run before displaying the next prompt
145 """ Run before displaying the next prompt
146
146
147 Use this e.g. to display output from asynchronous operations (in order
147 Use this e.g. to display output from asynchronous operations (in order
148 to not mess up text entry)
148 to not mess up text entry)
149 """
149 """
150
150
151 return None
151 return None
152
152
153
153
154 def clipboard_get(self):
154 def clipboard_get(self):
155 """ Get text from the clipboard.
155 """ Get text from the clipboard.
156 """
156 """
157 from ..lib.clipboard import (
157 from ..lib.clipboard import (
158 osx_clipboard_get, tkinter_clipboard_get,
158 osx_clipboard_get,
159 win32_clipboard_get
159 tkinter_clipboard_get,
160 win32_clipboard_get,
161 wayland_clipboard_get,
160 )
162 )
161 if sys.platform == 'win32':
163 if sys.platform == 'win32':
162 chain = [win32_clipboard_get, tkinter_clipboard_get]
164 chain = [win32_clipboard_get, tkinter_clipboard_get]
163 elif sys.platform == 'darwin':
165 elif sys.platform == 'darwin':
164 chain = [osx_clipboard_get, tkinter_clipboard_get]
166 chain = [osx_clipboard_get, tkinter_clipboard_get]
165 else:
167 else:
166 chain = [tkinter_clipboard_get]
168 chain = [wayland_clipboard_get, tkinter_clipboard_get]
167 dispatcher = CommandChainDispatcher()
169 dispatcher = CommandChainDispatcher()
168 for func in chain:
170 for func in chain:
169 dispatcher.add(func)
171 dispatcher.add(func)
170 text = dispatcher()
172 text = dispatcher()
171 return text
173 return text
@@ -1,854 +1,855 b''
1 """Implementation of magic functions for interaction with the OS.
1 """Implementation of magic functions for interaction with the OS.
2
2
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
4 builtin.
4 builtin.
5 """
5 """
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import io
9 import io
10 import os
10 import os
11 import pathlib
11 import re
12 import re
12 import sys
13 import sys
13 from pprint import pformat
14 from pprint import pformat
14
15
15 from IPython.core import magic_arguments
16 from IPython.core import magic_arguments
16 from IPython.core import oinspect
17 from IPython.core import oinspect
17 from IPython.core import page
18 from IPython.core import page
18 from IPython.core.alias import AliasError, Alias
19 from IPython.core.alias import AliasError, Alias
19 from IPython.core.error import UsageError
20 from IPython.core.error import UsageError
20 from IPython.core.magic import (
21 from IPython.core.magic import (
21 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
22 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
22 )
23 )
23 from IPython.testing.skipdoctest import skip_doctest
24 from IPython.testing.skipdoctest import skip_doctest
24 from IPython.utils.openpy import source_to_unicode
25 from IPython.utils.openpy import source_to_unicode
25 from IPython.utils.process import abbrev_cwd
26 from IPython.utils.process import abbrev_cwd
26 from IPython.utils.terminal import set_term_title
27 from IPython.utils.terminal import set_term_title
27 from traitlets import Bool
28 from traitlets import Bool
28 from warnings import warn
29 from warnings import warn
29
30
30
31
31 @magics_class
32 @magics_class
32 class OSMagics(Magics):
33 class OSMagics(Magics):
33 """Magics to interact with the underlying OS (shell-type functionality).
34 """Magics to interact with the underlying OS (shell-type functionality).
34 """
35 """
35
36
36 cd_force_quiet = Bool(False,
37 cd_force_quiet = Bool(False,
37 help="Force %cd magic to be quiet even if -q is not passed."
38 help="Force %cd magic to be quiet even if -q is not passed."
38 ).tag(config=True)
39 ).tag(config=True)
39
40
40 def __init__(self, shell=None, **kwargs):
41 def __init__(self, shell=None, **kwargs):
41
42
42 # Now define isexec in a cross platform manner.
43 # Now define isexec in a cross platform manner.
43 self.is_posix = False
44 self.is_posix = False
44 self.execre = None
45 self.execre = None
45 if os.name == 'posix':
46 if os.name == 'posix':
46 self.is_posix = True
47 self.is_posix = True
47 else:
48 else:
48 try:
49 try:
49 winext = os.environ['pathext'].replace(';','|').replace('.','')
50 winext = os.environ['pathext'].replace(';','|').replace('.','')
50 except KeyError:
51 except KeyError:
51 winext = 'exe|com|bat|py'
52 winext = 'exe|com|bat|py'
52 try:
53 try:
53 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
54 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
54 except re.error:
55 except re.error:
55 warn("Seems like your pathext environmental "
56 warn("Seems like your pathext environmental "
56 "variable is malformed. Please check it to "
57 "variable is malformed. Please check it to "
57 "enable a proper handle of file extensions "
58 "enable a proper handle of file extensions "
58 "managed for your system")
59 "managed for your system")
59 winext = 'exe|com|bat|py'
60 winext = 'exe|com|bat|py'
60 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
61 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
61
62
62 # call up the chain
63 # call up the chain
63 super().__init__(shell=shell, **kwargs)
64 super().__init__(shell=shell, **kwargs)
64
65
65
66
66 def _isexec_POSIX(self, file):
67 def _isexec_POSIX(self, file):
67 """
68 """
68 Test for executable on a POSIX system
69 Test for executable on a POSIX system
69 """
70 """
70 if os.access(file.path, os.X_OK):
71 if os.access(file.path, os.X_OK):
71 # will fail on maxOS if access is not X_OK
72 # will fail on maxOS if access is not X_OK
72 return file.is_file()
73 return file.is_file()
73 return False
74 return False
74
75
75
76
76
77
77 def _isexec_WIN(self, file):
78 def _isexec_WIN(self, file):
78 """
79 """
79 Test for executable file on non POSIX system
80 Test for executable file on non POSIX system
80 """
81 """
81 return file.is_file() and self.execre.match(file.name) is not None
82 return file.is_file() and self.execre.match(file.name) is not None
82
83
83 def isexec(self, file):
84 def isexec(self, file):
84 """
85 """
85 Test for executable file on non POSIX system
86 Test for executable file on non POSIX system
86 """
87 """
87 if self.is_posix:
88 if self.is_posix:
88 return self._isexec_POSIX(file)
89 return self._isexec_POSIX(file)
89 else:
90 else:
90 return self._isexec_WIN(file)
91 return self._isexec_WIN(file)
91
92
92
93
93 @skip_doctest
94 @skip_doctest
94 @line_magic
95 @line_magic
95 def alias(self, parameter_s=''):
96 def alias(self, parameter_s=''):
96 """Define an alias for a system command.
97 """Define an alias for a system command.
97
98
98 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
99 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
99
100
100 Then, typing 'alias_name params' will execute the system command 'cmd
101 Then, typing 'alias_name params' will execute the system command 'cmd
101 params' (from your underlying operating system).
102 params' (from your underlying operating system).
102
103
103 Aliases have lower precedence than magic functions and Python normal
104 Aliases have lower precedence than magic functions and Python normal
104 variables, so if 'foo' is both a Python variable and an alias, the
105 variables, so if 'foo' is both a Python variable and an alias, the
105 alias can not be executed until 'del foo' removes the Python variable.
106 alias can not be executed until 'del foo' removes the Python variable.
106
107
107 You can use the %l specifier in an alias definition to represent the
108 You can use the %l specifier in an alias definition to represent the
108 whole line when the alias is called. For example::
109 whole line when the alias is called. For example::
109
110
110 In [2]: alias bracket echo "Input in brackets: <%l>"
111 In [2]: alias bracket echo "Input in brackets: <%l>"
111 In [3]: bracket hello world
112 In [3]: bracket hello world
112 Input in brackets: <hello world>
113 Input in brackets: <hello world>
113
114
114 You can also define aliases with parameters using %s specifiers (one
115 You can also define aliases with parameters using %s specifiers (one
115 per parameter)::
116 per parameter)::
116
117
117 In [1]: alias parts echo first %s second %s
118 In [1]: alias parts echo first %s second %s
118 In [2]: %parts A B
119 In [2]: %parts A B
119 first A second B
120 first A second B
120 In [3]: %parts A
121 In [3]: %parts A
121 Incorrect number of arguments: 2 expected.
122 Incorrect number of arguments: 2 expected.
122 parts is an alias to: 'echo first %s second %s'
123 parts is an alias to: 'echo first %s second %s'
123
124
124 Note that %l and %s are mutually exclusive. You can only use one or
125 Note that %l and %s are mutually exclusive. You can only use one or
125 the other in your aliases.
126 the other in your aliases.
126
127
127 Aliases expand Python variables just like system calls using ! or !!
128 Aliases expand Python variables just like system calls using ! or !!
128 do: all expressions prefixed with '$' get expanded. For details of
129 do: all expressions prefixed with '$' get expanded. For details of
129 the semantic rules, see PEP-215:
130 the semantic rules, see PEP-215:
130 https://peps.python.org/pep-0215/. This is the library used by
131 https://peps.python.org/pep-0215/. This is the library used by
131 IPython for variable expansion. If you want to access a true shell
132 IPython for variable expansion. If you want to access a true shell
132 variable, an extra $ is necessary to prevent its expansion by
133 variable, an extra $ is necessary to prevent its expansion by
133 IPython::
134 IPython::
134
135
135 In [6]: alias show echo
136 In [6]: alias show echo
136 In [7]: PATH='A Python string'
137 In [7]: PATH='A Python string'
137 In [8]: show $PATH
138 In [8]: show $PATH
138 A Python string
139 A Python string
139 In [9]: show $$PATH
140 In [9]: show $$PATH
140 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
141 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
141
142
142 You can use the alias facility to access all of $PATH. See the %rehashx
143 You can use the alias facility to access all of $PATH. See the %rehashx
143 function, which automatically creates aliases for the contents of your
144 function, which automatically creates aliases for the contents of your
144 $PATH.
145 $PATH.
145
146
146 If called with no parameters, %alias prints the current alias table
147 If called with no parameters, %alias prints the current alias table
147 for your system. For posix systems, the default aliases are 'cat',
148 for your system. For posix systems, the default aliases are 'cat',
148 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
149 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
149 aliases are added. For windows-based systems, the default aliases are
150 aliases are added. For windows-based systems, the default aliases are
150 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
151 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
151
152
152 You can see the definition of alias by adding a question mark in the
153 You can see the definition of alias by adding a question mark in the
153 end::
154 end::
154
155
155 In [1]: cat?
156 In [1]: cat?
156 Repr: <alias cat for 'cat'>"""
157 Repr: <alias cat for 'cat'>"""
157
158
158 par = parameter_s.strip()
159 par = parameter_s.strip()
159 if not par:
160 if not par:
160 aliases = sorted(self.shell.alias_manager.aliases)
161 aliases = sorted(self.shell.alias_manager.aliases)
161 # stored = self.shell.db.get('stored_aliases', {} )
162 # stored = self.shell.db.get('stored_aliases', {} )
162 # for k, v in stored:
163 # for k, v in stored:
163 # atab.append(k, v[0])
164 # atab.append(k, v[0])
164
165
165 print("Total number of aliases:", len(aliases))
166 print("Total number of aliases:", len(aliases))
166 sys.stdout.flush()
167 sys.stdout.flush()
167 return aliases
168 return aliases
168
169
169 # Now try to define a new one
170 # Now try to define a new one
170 try:
171 try:
171 alias,cmd = par.split(None, 1)
172 alias,cmd = par.split(None, 1)
172 except TypeError:
173 except TypeError:
173 print(oinspect.getdoc(self.alias))
174 print(oinspect.getdoc(self.alias))
174 return
175 return
175
176
176 try:
177 try:
177 self.shell.alias_manager.define_alias(alias, cmd)
178 self.shell.alias_manager.define_alias(alias, cmd)
178 except AliasError as e:
179 except AliasError as e:
179 print(e)
180 print(e)
180 # end magic_alias
181 # end magic_alias
181
182
182 @line_magic
183 @line_magic
183 def unalias(self, parameter_s=''):
184 def unalias(self, parameter_s=''):
184 """Remove an alias"""
185 """Remove an alias"""
185
186
186 aname = parameter_s.strip()
187 aname = parameter_s.strip()
187 try:
188 try:
188 self.shell.alias_manager.undefine_alias(aname)
189 self.shell.alias_manager.undefine_alias(aname)
189 except ValueError as e:
190 except ValueError as e:
190 print(e)
191 print(e)
191 return
192 return
192
193
193 stored = self.shell.db.get('stored_aliases', {} )
194 stored = self.shell.db.get('stored_aliases', {} )
194 if aname in stored:
195 if aname in stored:
195 print("Removing %stored alias",aname)
196 print("Removing %stored alias",aname)
196 del stored[aname]
197 del stored[aname]
197 self.shell.db['stored_aliases'] = stored
198 self.shell.db['stored_aliases'] = stored
198
199
199 @line_magic
200 @line_magic
200 def rehashx(self, parameter_s=''):
201 def rehashx(self, parameter_s=''):
201 """Update the alias table with all executable files in $PATH.
202 """Update the alias table with all executable files in $PATH.
202
203
203 rehashx explicitly checks that every entry in $PATH is a file
204 rehashx explicitly checks that every entry in $PATH is a file
204 with execute access (os.X_OK).
205 with execute access (os.X_OK).
205
206
206 Under Windows, it checks executability as a match against a
207 Under Windows, it checks executability as a match against a
207 '|'-separated string of extensions, stored in the IPython config
208 '|'-separated string of extensions, stored in the IPython config
208 variable win_exec_ext. This defaults to 'exe|com|bat'.
209 variable win_exec_ext. This defaults to 'exe|com|bat'.
209
210
210 This function also resets the root module cache of module completer,
211 This function also resets the root module cache of module completer,
211 used on slow filesystems.
212 used on slow filesystems.
212 """
213 """
213 from IPython.core.alias import InvalidAliasError
214 from IPython.core.alias import InvalidAliasError
214
215
215 # for the benefit of module completer in ipy_completers.py
216 # for the benefit of module completer in ipy_completers.py
216 del self.shell.db['rootmodules_cache']
217 del self.shell.db['rootmodules_cache']
217
218
218 path = [os.path.abspath(os.path.expanduser(p)) for p in
219 path = [os.path.abspath(os.path.expanduser(p)) for p in
219 os.environ.get('PATH','').split(os.pathsep)]
220 os.environ.get('PATH','').split(os.pathsep)]
220
221
221 syscmdlist = []
222 syscmdlist = []
222 savedir = os.getcwd()
223 savedir = os.getcwd()
223
224
224 # Now walk the paths looking for executables to alias.
225 # Now walk the paths looking for executables to alias.
225 try:
226 try:
226 # write the whole loop for posix/Windows so we don't have an if in
227 # write the whole loop for posix/Windows so we don't have an if in
227 # the innermost part
228 # the innermost part
228 if self.is_posix:
229 if self.is_posix:
229 for pdir in path:
230 for pdir in path:
230 try:
231 try:
231 os.chdir(pdir)
232 os.chdir(pdir)
232 except OSError:
233 except OSError:
233 continue
234 continue
234
235
235 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
236 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
236 dirlist = os.scandir(path=pdir)
237 dirlist = os.scandir(path=pdir)
237 for ff in dirlist:
238 for ff in dirlist:
238 if self.isexec(ff):
239 if self.isexec(ff):
239 fname = ff.name
240 fname = ff.name
240 try:
241 try:
241 # Removes dots from the name since ipython
242 # Removes dots from the name since ipython
242 # will assume names with dots to be python.
243 # will assume names with dots to be python.
243 if not self.shell.alias_manager.is_alias(fname):
244 if not self.shell.alias_manager.is_alias(fname):
244 self.shell.alias_manager.define_alias(
245 self.shell.alias_manager.define_alias(
245 fname.replace('.',''), fname)
246 fname.replace('.',''), fname)
246 except InvalidAliasError:
247 except InvalidAliasError:
247 pass
248 pass
248 else:
249 else:
249 syscmdlist.append(fname)
250 syscmdlist.append(fname)
250 else:
251 else:
251 no_alias = Alias.blacklist
252 no_alias = Alias.blacklist
252 for pdir in path:
253 for pdir in path:
253 try:
254 try:
254 os.chdir(pdir)
255 os.chdir(pdir)
255 except OSError:
256 except OSError:
256 continue
257 continue
257
258
258 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
259 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
259 dirlist = os.scandir(pdir)
260 dirlist = os.scandir(pdir)
260 for ff in dirlist:
261 for ff in dirlist:
261 fname = ff.name
262 fname = ff.name
262 base, ext = os.path.splitext(fname)
263 base, ext = os.path.splitext(fname)
263 if self.isexec(ff) and base.lower() not in no_alias:
264 if self.isexec(ff) and base.lower() not in no_alias:
264 if ext.lower() == '.exe':
265 if ext.lower() == '.exe':
265 fname = base
266 fname = base
266 try:
267 try:
267 # Removes dots from the name since ipython
268 # Removes dots from the name since ipython
268 # will assume names with dots to be python.
269 # will assume names with dots to be python.
269 self.shell.alias_manager.define_alias(
270 self.shell.alias_manager.define_alias(
270 base.lower().replace('.',''), fname)
271 base.lower().replace('.',''), fname)
271 except InvalidAliasError:
272 except InvalidAliasError:
272 pass
273 pass
273 syscmdlist.append(fname)
274 syscmdlist.append(fname)
274
275
275 self.shell.db['syscmdlist'] = syscmdlist
276 self.shell.db['syscmdlist'] = syscmdlist
276 finally:
277 finally:
277 os.chdir(savedir)
278 os.chdir(savedir)
278
279
279 @skip_doctest
280 @skip_doctest
280 @line_magic
281 @line_magic
281 def pwd(self, parameter_s=''):
282 def pwd(self, parameter_s=''):
282 """Return the current working directory path.
283 """Return the current working directory path.
283
284
284 Examples
285 Examples
285 --------
286 --------
286 ::
287 ::
287
288
288 In [9]: pwd
289 In [9]: pwd
289 Out[9]: '/home/tsuser/sprint/ipython'
290 Out[9]: '/home/tsuser/sprint/ipython'
290 """
291 """
291 try:
292 try:
292 return os.getcwd()
293 return os.getcwd()
293 except FileNotFoundError as e:
294 except FileNotFoundError as e:
294 raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
295 raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
295
296
296 @skip_doctest
297 @skip_doctest
297 @line_magic
298 @line_magic
298 def cd(self, parameter_s=''):
299 def cd(self, parameter_s=''):
299 """Change the current working directory.
300 """Change the current working directory.
300
301
301 This command automatically maintains an internal list of directories
302 This command automatically maintains an internal list of directories
302 you visit during your IPython session, in the variable ``_dh``. The
303 you visit during your IPython session, in the variable ``_dh``. The
303 command :magic:`%dhist` shows this history nicely formatted. You can
304 command :magic:`%dhist` shows this history nicely formatted. You can
304 also do ``cd -<tab>`` to see directory history conveniently.
305 also do ``cd -<tab>`` to see directory history conveniently.
305 Usage:
306 Usage:
306
307
307 - ``cd 'dir'``: changes to directory 'dir'.
308 - ``cd 'dir'``: changes to directory 'dir'.
308 - ``cd -``: changes to the last visited directory.
309 - ``cd -``: changes to the last visited directory.
309 - ``cd -<n>``: changes to the n-th directory in the directory history.
310 - ``cd -<n>``: changes to the n-th directory in the directory history.
310 - ``cd --foo``: change to directory that matches 'foo' in history
311 - ``cd --foo``: change to directory that matches 'foo' in history
311 - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
312 - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
312 - Hitting a tab key after ``cd -b`` allows you to tab-complete
313 - Hitting a tab key after ``cd -b`` allows you to tab-complete
313 bookmark names.
314 bookmark names.
314
315
315 .. note::
316 .. note::
316 ``cd <bookmark_name>`` is enough if there is no directory
317 ``cd <bookmark_name>`` is enough if there is no directory
317 ``<bookmark_name>``, but a bookmark with the name exists.
318 ``<bookmark_name>``, but a bookmark with the name exists.
318
319
319 Options:
320 Options:
320
321
321 -q Be quiet. Do not print the working directory after the
322 -q Be quiet. Do not print the working directory after the
322 cd command is executed. By default IPython's cd
323 cd command is executed. By default IPython's cd
323 command does print this directory, since the default
324 command does print this directory, since the default
324 prompts do not display path information.
325 prompts do not display path information.
325
326
326 .. note::
327 .. note::
327 Note that ``!cd`` doesn't work for this purpose because the shell
328 Note that ``!cd`` doesn't work for this purpose because the shell
328 where ``!command`` runs is immediately discarded after executing
329 where ``!command`` runs is immediately discarded after executing
329 'command'.
330 'command'.
330
331
331 Examples
332 Examples
332 --------
333 --------
333 ::
334 ::
334
335
335 In [10]: cd parent/child
336 In [10]: cd parent/child
336 /home/tsuser/parent/child
337 /home/tsuser/parent/child
337 """
338 """
338
339
339 try:
340 try:
340 oldcwd = os.getcwd()
341 oldcwd = os.getcwd()
341 except FileNotFoundError:
342 except FileNotFoundError:
342 # Happens if the CWD has been deleted.
343 # Happens if the CWD has been deleted.
343 oldcwd = None
344 oldcwd = None
344
345
345 numcd = re.match(r'(-)(\d+)$',parameter_s)
346 numcd = re.match(r'(-)(\d+)$',parameter_s)
346 # jump in directory history by number
347 # jump in directory history by number
347 if numcd:
348 if numcd:
348 nn = int(numcd.group(2))
349 nn = int(numcd.group(2))
349 try:
350 try:
350 ps = self.shell.user_ns['_dh'][nn]
351 ps = self.shell.user_ns['_dh'][nn]
351 except IndexError:
352 except IndexError:
352 print('The requested directory does not exist in history.')
353 print('The requested directory does not exist in history.')
353 return
354 return
354 else:
355 else:
355 opts = {}
356 opts = {}
356 elif parameter_s.startswith('--'):
357 elif parameter_s.startswith('--'):
357 ps = None
358 ps = None
358 fallback = None
359 fallback = None
359 pat = parameter_s[2:]
360 pat = parameter_s[2:]
360 dh = self.shell.user_ns['_dh']
361 dh = self.shell.user_ns['_dh']
361 # first search only by basename (last component)
362 # first search only by basename (last component)
362 for ent in reversed(dh):
363 for ent in reversed(dh):
363 if pat in os.path.basename(ent) and os.path.isdir(ent):
364 if pat in os.path.basename(ent) and os.path.isdir(ent):
364 ps = ent
365 ps = ent
365 break
366 break
366
367
367 if fallback is None and pat in ent and os.path.isdir(ent):
368 if fallback is None and pat in ent and os.path.isdir(ent):
368 fallback = ent
369 fallback = ent
369
370
370 # if we have no last part match, pick the first full path match
371 # if we have no last part match, pick the first full path match
371 if ps is None:
372 if ps is None:
372 ps = fallback
373 ps = fallback
373
374
374 if ps is None:
375 if ps is None:
375 print("No matching entry in directory history")
376 print("No matching entry in directory history")
376 return
377 return
377 else:
378 else:
378 opts = {}
379 opts = {}
379
380
380
381
381 else:
382 else:
382 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
383 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
383 # jump to previous
384 # jump to previous
384 if ps == '-':
385 if ps == '-':
385 try:
386 try:
386 ps = self.shell.user_ns['_dh'][-2]
387 ps = self.shell.user_ns['_dh'][-2]
387 except IndexError as e:
388 except IndexError as e:
388 raise UsageError('%cd -: No previous directory to change to.') from e
389 raise UsageError('%cd -: No previous directory to change to.') from e
389 # jump to bookmark if needed
390 # jump to bookmark if needed
390 else:
391 else:
391 if not os.path.isdir(ps) or 'b' in opts:
392 if not os.path.isdir(ps) or 'b' in opts:
392 bkms = self.shell.db.get('bookmarks', {})
393 bkms = self.shell.db.get('bookmarks', {})
393
394
394 if ps in bkms:
395 if ps in bkms:
395 target = bkms[ps]
396 target = bkms[ps]
396 print('(bookmark:%s) -> %s' % (ps, target))
397 print('(bookmark:%s) -> %s' % (ps, target))
397 ps = target
398 ps = target
398 else:
399 else:
399 if 'b' in opts:
400 if 'b' in opts:
400 raise UsageError("Bookmark '%s' not found. "
401 raise UsageError("Bookmark '%s' not found. "
401 "Use '%%bookmark -l' to see your bookmarks." % ps)
402 "Use '%%bookmark -l' to see your bookmarks." % ps)
402
403
403 # at this point ps should point to the target dir
404 # at this point ps should point to the target dir
404 if ps:
405 if ps:
405 try:
406 try:
406 os.chdir(os.path.expanduser(ps))
407 os.chdir(os.path.expanduser(ps))
407 if hasattr(self.shell, 'term_title') and self.shell.term_title:
408 if hasattr(self.shell, 'term_title') and self.shell.term_title:
408 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
409 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
409 except OSError:
410 except OSError:
410 print(sys.exc_info()[1])
411 print(sys.exc_info()[1])
411 else:
412 else:
412 cwd = os.getcwd()
413 cwd = pathlib.Path.cwd()
413 dhist = self.shell.user_ns['_dh']
414 dhist = self.shell.user_ns['_dh']
414 if oldcwd != cwd:
415 if oldcwd != cwd:
415 dhist.append(cwd)
416 dhist.append(cwd)
416 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
417 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
417
418
418 else:
419 else:
419 os.chdir(self.shell.home_dir)
420 os.chdir(self.shell.home_dir)
420 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 cwd = os.getcwd()
423 cwd = pathlib.Path.cwd()
423 dhist = self.shell.user_ns['_dh']
424 dhist = self.shell.user_ns['_dh']
424
425
425 if oldcwd != cwd:
426 if oldcwd != cwd:
426 dhist.append(cwd)
427 dhist.append(cwd)
427 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
428 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
428 if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']:
429 if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']:
429 print(self.shell.user_ns['_dh'][-1])
430 print(self.shell.user_ns['_dh'][-1])
430
431
431 @line_magic
432 @line_magic
432 def env(self, parameter_s=''):
433 def env(self, parameter_s=''):
433 """Get, set, or list environment variables.
434 """Get, set, or list environment variables.
434
435
435 Usage:\\
436 Usage:\\
436
437
437 :``%env``: lists all environment variables/values
438 :``%env``: lists all environment variables/values
438 :``%env var``: get value for var
439 :``%env var``: get value for var
439 :``%env var val``: set value for var
440 :``%env var val``: set value for var
440 :``%env var=val``: set value for var
441 :``%env var=val``: set value for var
441 :``%env var=$val``: set value for var, using python expansion if possible
442 :``%env var=$val``: set value for var, using python expansion if possible
442 """
443 """
443 if parameter_s.strip():
444 if parameter_s.strip():
444 split = '=' if '=' in parameter_s else ' '
445 split = '=' if '=' in parameter_s else ' '
445 bits = parameter_s.split(split)
446 bits = parameter_s.split(split)
446 if len(bits) == 1:
447 if len(bits) == 1:
447 key = parameter_s.strip()
448 key = parameter_s.strip()
448 if key in os.environ:
449 if key in os.environ:
449 return os.environ[key]
450 return os.environ[key]
450 else:
451 else:
451 err = "Environment does not have key: {0}".format(key)
452 err = "Environment does not have key: {0}".format(key)
452 raise UsageError(err)
453 raise UsageError(err)
453 if len(bits) > 1:
454 if len(bits) > 1:
454 return self.set_env(parameter_s)
455 return self.set_env(parameter_s)
455 env = dict(os.environ)
456 env = dict(os.environ)
456 # hide likely secrets when printing the whole environment
457 # hide likely secrets when printing the whole environment
457 for key in list(env):
458 for key in list(env):
458 if any(s in key.lower() for s in ('key', 'token', 'secret')):
459 if any(s in key.lower() for s in ('key', 'token', 'secret')):
459 env[key] = '<hidden>'
460 env[key] = '<hidden>'
460
461
461 return env
462 return env
462
463
463 @line_magic
464 @line_magic
464 def set_env(self, parameter_s):
465 def set_env(self, parameter_s):
465 """Set environment variables. Assumptions are that either "val" is a
466 """Set environment variables. Assumptions are that either "val" is a
466 name in the user namespace, or val is something that evaluates to a
467 name in the user namespace, or val is something that evaluates to a
467 string.
468 string.
468
469
469 Usage:\\
470 Usage:\\
470 %set_env var val: set value for var
471 %set_env var val: set value for var
471 %set_env var=val: set value for var
472 %set_env var=val: set value for var
472 %set_env var=$val: set value for var, using python expansion if possible
473 %set_env var=$val: set value for var, using python expansion if possible
473 """
474 """
474 split = '=' if '=' in parameter_s else ' '
475 split = '=' if '=' in parameter_s else ' '
475 bits = parameter_s.split(split, 1)
476 bits = parameter_s.split(split, 1)
476 if not parameter_s.strip() or len(bits)<2:
477 if not parameter_s.strip() or len(bits)<2:
477 raise UsageError("usage is 'set_env var=val'")
478 raise UsageError("usage is 'set_env var=val'")
478 var = bits[0].strip()
479 var = bits[0].strip()
479 val = bits[1].strip()
480 val = bits[1].strip()
480 if re.match(r'.*\s.*', var):
481 if re.match(r'.*\s.*', var):
481 # an environment variable with whitespace is almost certainly
482 # an environment variable with whitespace is almost certainly
482 # not what the user intended. what's more likely is the wrong
483 # not what the user intended. what's more likely is the wrong
483 # split was chosen, ie for "set_env cmd_args A=B", we chose
484 # split was chosen, ie for "set_env cmd_args A=B", we chose
484 # '=' for the split and should have chosen ' '. to get around
485 # '=' for the split and should have chosen ' '. to get around
485 # this, users should just assign directly to os.environ or use
486 # this, users should just assign directly to os.environ or use
486 # standard magic {var} expansion.
487 # standard magic {var} expansion.
487 err = "refusing to set env var with whitespace: '{0}'"
488 err = "refusing to set env var with whitespace: '{0}'"
488 err = err.format(val)
489 err = err.format(val)
489 raise UsageError(err)
490 raise UsageError(err)
490 os.environ[var] = val
491 os.environ[var] = val
491 print('env: {0}={1}'.format(var,val))
492 print('env: {0}={1}'.format(var,val))
492
493
493 @line_magic
494 @line_magic
494 def pushd(self, parameter_s=''):
495 def pushd(self, parameter_s=''):
495 """Place the current dir on stack and change directory.
496 """Place the current dir on stack and change directory.
496
497
497 Usage:\\
498 Usage:\\
498 %pushd ['dirname']
499 %pushd ['dirname']
499 """
500 """
500
501
501 dir_s = self.shell.dir_stack
502 dir_s = self.shell.dir_stack
502 tgt = os.path.expanduser(parameter_s)
503 tgt = os.path.expanduser(parameter_s)
503 cwd = os.getcwd().replace(self.shell.home_dir,'~')
504 cwd = os.getcwd().replace(self.shell.home_dir,'~')
504 if tgt:
505 if tgt:
505 self.cd(parameter_s)
506 self.cd(parameter_s)
506 dir_s.insert(0,cwd)
507 dir_s.insert(0,cwd)
507 return self.shell.run_line_magic('dirs', '')
508 return self.shell.run_line_magic('dirs', '')
508
509
509 @line_magic
510 @line_magic
510 def popd(self, parameter_s=''):
511 def popd(self, parameter_s=''):
511 """Change to directory popped off the top of the stack.
512 """Change to directory popped off the top of the stack.
512 """
513 """
513 if not self.shell.dir_stack:
514 if not self.shell.dir_stack:
514 raise UsageError("%popd on empty stack")
515 raise UsageError("%popd on empty stack")
515 top = self.shell.dir_stack.pop(0)
516 top = self.shell.dir_stack.pop(0)
516 self.cd(top)
517 self.cd(top)
517 print("popd ->",top)
518 print("popd ->",top)
518
519
519 @line_magic
520 @line_magic
520 def dirs(self, parameter_s=''):
521 def dirs(self, parameter_s=''):
521 """Return the current directory stack."""
522 """Return the current directory stack."""
522
523
523 return self.shell.dir_stack
524 return self.shell.dir_stack
524
525
525 @line_magic
526 @line_magic
526 def dhist(self, parameter_s=''):
527 def dhist(self, parameter_s=''):
527 """Print your history of visited directories.
528 """Print your history of visited directories.
528
529
529 %dhist -> print full history\\
530 %dhist -> print full history\\
530 %dhist n -> print last n entries only\\
531 %dhist n -> print last n entries only\\
531 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
532 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
532
533
533 This history is automatically maintained by the %cd command, and
534 This history is automatically maintained by the %cd command, and
534 always available as the global list variable _dh. You can use %cd -<n>
535 always available as the global list variable _dh. You can use %cd -<n>
535 to go to directory number <n>.
536 to go to directory number <n>.
536
537
537 Note that most of time, you should view directory history by entering
538 Note that most of time, you should view directory history by entering
538 cd -<TAB>.
539 cd -<TAB>.
539
540
540 """
541 """
541
542
542 dh = self.shell.user_ns['_dh']
543 dh = self.shell.user_ns['_dh']
543 if parameter_s:
544 if parameter_s:
544 try:
545 try:
545 args = map(int,parameter_s.split())
546 args = map(int,parameter_s.split())
546 except:
547 except:
547 self.arg_err(self.dhist)
548 self.arg_err(self.dhist)
548 return
549 return
549 if len(args) == 1:
550 if len(args) == 1:
550 ini,fin = max(len(dh)-(args[0]),0),len(dh)
551 ini,fin = max(len(dh)-(args[0]),0),len(dh)
551 elif len(args) == 2:
552 elif len(args) == 2:
552 ini,fin = args
553 ini,fin = args
553 fin = min(fin, len(dh))
554 fin = min(fin, len(dh))
554 else:
555 else:
555 self.arg_err(self.dhist)
556 self.arg_err(self.dhist)
556 return
557 return
557 else:
558 else:
558 ini,fin = 0,len(dh)
559 ini,fin = 0,len(dh)
559 print('Directory history (kept in _dh)')
560 print('Directory history (kept in _dh)')
560 for i in range(ini, fin):
561 for i in range(ini, fin):
561 print("%d: %s" % (i, dh[i]))
562 print("%d: %s" % (i, dh[i]))
562
563
563 @skip_doctest
564 @skip_doctest
564 @line_magic
565 @line_magic
565 def sc(self, parameter_s=''):
566 def sc(self, parameter_s=''):
566 """Shell capture - run shell command and capture output (DEPRECATED use !).
567 """Shell capture - run shell command and capture output (DEPRECATED use !).
567
568
568 DEPRECATED. Suboptimal, retained for backwards compatibility.
569 DEPRECATED. Suboptimal, retained for backwards compatibility.
569
570
570 You should use the form 'var = !command' instead. Example:
571 You should use the form 'var = !command' instead. Example:
571
572
572 "%sc -l myfiles = ls ~" should now be written as
573 "%sc -l myfiles = ls ~" should now be written as
573
574
574 "myfiles = !ls ~"
575 "myfiles = !ls ~"
575
576
576 myfiles.s, myfiles.l and myfiles.n still apply as documented
577 myfiles.s, myfiles.l and myfiles.n still apply as documented
577 below.
578 below.
578
579
579 --
580 --
580 %sc [options] varname=command
581 %sc [options] varname=command
581
582
582 IPython will run the given command using commands.getoutput(), and
583 IPython will run the given command using commands.getoutput(), and
583 will then update the user's interactive namespace with a variable
584 will then update the user's interactive namespace with a variable
584 called varname, containing the value of the call. Your command can
585 called varname, containing the value of the call. Your command can
585 contain shell wildcards, pipes, etc.
586 contain shell wildcards, pipes, etc.
586
587
587 The '=' sign in the syntax is mandatory, and the variable name you
588 The '=' sign in the syntax is mandatory, and the variable name you
588 supply must follow Python's standard conventions for valid names.
589 supply must follow Python's standard conventions for valid names.
589
590
590 (A special format without variable name exists for internal use)
591 (A special format without variable name exists for internal use)
591
592
592 Options:
593 Options:
593
594
594 -l: list output. Split the output on newlines into a list before
595 -l: list output. Split the output on newlines into a list before
595 assigning it to the given variable. By default the output is stored
596 assigning it to the given variable. By default the output is stored
596 as a single string.
597 as a single string.
597
598
598 -v: verbose. Print the contents of the variable.
599 -v: verbose. Print the contents of the variable.
599
600
600 In most cases you should not need to split as a list, because the
601 In most cases you should not need to split as a list, because the
601 returned value is a special type of string which can automatically
602 returned value is a special type of string which can automatically
602 provide its contents either as a list (split on newlines) or as a
603 provide its contents either as a list (split on newlines) or as a
603 space-separated string. These are convenient, respectively, either
604 space-separated string. These are convenient, respectively, either
604 for sequential processing or to be passed to a shell command.
605 for sequential processing or to be passed to a shell command.
605
606
606 For example::
607 For example::
607
608
608 # Capture into variable a
609 # Capture into variable a
609 In [1]: sc a=ls *py
610 In [1]: sc a=ls *py
610
611
611 # a is a string with embedded newlines
612 # a is a string with embedded newlines
612 In [2]: a
613 In [2]: a
613 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
614 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
614
615
615 # which can be seen as a list:
616 # which can be seen as a list:
616 In [3]: a.l
617 In [3]: a.l
617 Out[3]: ['setup.py', 'win32_manual_post_install.py']
618 Out[3]: ['setup.py', 'win32_manual_post_install.py']
618
619
619 # or as a whitespace-separated string:
620 # or as a whitespace-separated string:
620 In [4]: a.s
621 In [4]: a.s
621 Out[4]: 'setup.py win32_manual_post_install.py'
622 Out[4]: 'setup.py win32_manual_post_install.py'
622
623
623 # a.s is useful to pass as a single command line:
624 # a.s is useful to pass as a single command line:
624 In [5]: !wc -l $a.s
625 In [5]: !wc -l $a.s
625 146 setup.py
626 146 setup.py
626 130 win32_manual_post_install.py
627 130 win32_manual_post_install.py
627 276 total
628 276 total
628
629
629 # while the list form is useful to loop over:
630 # while the list form is useful to loop over:
630 In [6]: for f in a.l:
631 In [6]: for f in a.l:
631 ...: !wc -l $f
632 ...: !wc -l $f
632 ...:
633 ...:
633 146 setup.py
634 146 setup.py
634 130 win32_manual_post_install.py
635 130 win32_manual_post_install.py
635
636
636 Similarly, the lists returned by the -l option are also special, in
637 Similarly, the lists returned by the -l option are also special, in
637 the sense that you can equally invoke the .s attribute on them to
638 the sense that you can equally invoke the .s attribute on them to
638 automatically get a whitespace-separated string from their contents::
639 automatically get a whitespace-separated string from their contents::
639
640
640 In [7]: sc -l b=ls *py
641 In [7]: sc -l b=ls *py
641
642
642 In [8]: b
643 In [8]: b
643 Out[8]: ['setup.py', 'win32_manual_post_install.py']
644 Out[8]: ['setup.py', 'win32_manual_post_install.py']
644
645
645 In [9]: b.s
646 In [9]: b.s
646 Out[9]: 'setup.py win32_manual_post_install.py'
647 Out[9]: 'setup.py win32_manual_post_install.py'
647
648
648 In summary, both the lists and strings used for output capture have
649 In summary, both the lists and strings used for output capture have
649 the following special attributes::
650 the following special attributes::
650
651
651 .l (or .list) : value as list.
652 .l (or .list) : value as list.
652 .n (or .nlstr): value as newline-separated string.
653 .n (or .nlstr): value as newline-separated string.
653 .s (or .spstr): value as space-separated string.
654 .s (or .spstr): value as space-separated string.
654 """
655 """
655
656
656 opts,args = self.parse_options(parameter_s, 'lv')
657 opts,args = self.parse_options(parameter_s, 'lv')
657 # Try to get a variable name and command to run
658 # Try to get a variable name and command to run
658 try:
659 try:
659 # the variable name must be obtained from the parse_options
660 # the variable name must be obtained from the parse_options
660 # output, which uses shlex.split to strip options out.
661 # output, which uses shlex.split to strip options out.
661 var,_ = args.split('=', 1)
662 var,_ = args.split('=', 1)
662 var = var.strip()
663 var = var.strip()
663 # But the command has to be extracted from the original input
664 # But the command has to be extracted from the original input
664 # parameter_s, not on what parse_options returns, to avoid the
665 # parameter_s, not on what parse_options returns, to avoid the
665 # quote stripping which shlex.split performs on it.
666 # quote stripping which shlex.split performs on it.
666 _,cmd = parameter_s.split('=', 1)
667 _,cmd = parameter_s.split('=', 1)
667 except ValueError:
668 except ValueError:
668 var,cmd = '',''
669 var,cmd = '',''
669 # If all looks ok, proceed
670 # If all looks ok, proceed
670 split = 'l' in opts
671 split = 'l' in opts
671 out = self.shell.getoutput(cmd, split=split)
672 out = self.shell.getoutput(cmd, split=split)
672 if 'v' in opts:
673 if 'v' in opts:
673 print('%s ==\n%s' % (var, pformat(out)))
674 print('%s ==\n%s' % (var, pformat(out)))
674 if var:
675 if var:
675 self.shell.user_ns.update({var:out})
676 self.shell.user_ns.update({var:out})
676 else:
677 else:
677 return out
678 return out
678
679
679 @line_cell_magic
680 @line_cell_magic
680 def sx(self, line='', cell=None):
681 def sx(self, line='', cell=None):
681 """Shell execute - run shell command and capture output (!! is short-hand).
682 """Shell execute - run shell command and capture output (!! is short-hand).
682
683
683 %sx command
684 %sx command
684
685
685 IPython will run the given command using commands.getoutput(), and
686 IPython will run the given command using commands.getoutput(), and
686 return the result formatted as a list (split on '\\n'). Since the
687 return the result formatted as a list (split on '\\n'). Since the
687 output is _returned_, it will be stored in ipython's regular output
688 output is _returned_, it will be stored in ipython's regular output
688 cache Out[N] and in the '_N' automatic variables.
689 cache Out[N] and in the '_N' automatic variables.
689
690
690 Notes:
691 Notes:
691
692
692 1) If an input line begins with '!!', then %sx is automatically
693 1) If an input line begins with '!!', then %sx is automatically
693 invoked. That is, while::
694 invoked. That is, while::
694
695
695 !ls
696 !ls
696
697
697 causes ipython to simply issue system('ls'), typing::
698 causes ipython to simply issue system('ls'), typing::
698
699
699 !!ls
700 !!ls
700
701
701 is a shorthand equivalent to::
702 is a shorthand equivalent to::
702
703
703 %sx ls
704 %sx ls
704
705
705 2) %sx differs from %sc in that %sx automatically splits into a list,
706 2) %sx differs from %sc in that %sx automatically splits into a list,
706 like '%sc -l'. The reason for this is to make it as easy as possible
707 like '%sc -l'. The reason for this is to make it as easy as possible
707 to process line-oriented shell output via further python commands.
708 to process line-oriented shell output via further python commands.
708 %sc is meant to provide much finer control, but requires more
709 %sc is meant to provide much finer control, but requires more
709 typing.
710 typing.
710
711
711 3) Just like %sc -l, this is a list with special attributes:
712 3) Just like %sc -l, this is a list with special attributes:
712 ::
713 ::
713
714
714 .l (or .list) : value as list.
715 .l (or .list) : value as list.
715 .n (or .nlstr): value as newline-separated string.
716 .n (or .nlstr): value as newline-separated string.
716 .s (or .spstr): value as whitespace-separated string.
717 .s (or .spstr): value as whitespace-separated string.
717
718
718 This is very useful when trying to use such lists as arguments to
719 This is very useful when trying to use such lists as arguments to
719 system commands."""
720 system commands."""
720
721
721 if cell is None:
722 if cell is None:
722 # line magic
723 # line magic
723 return self.shell.getoutput(line)
724 return self.shell.getoutput(line)
724 else:
725 else:
725 opts,args = self.parse_options(line, '', 'out=')
726 opts,args = self.parse_options(line, '', 'out=')
726 output = self.shell.getoutput(cell)
727 output = self.shell.getoutput(cell)
727 out_name = opts.get('out', opts.get('o'))
728 out_name = opts.get('out', opts.get('o'))
728 if out_name:
729 if out_name:
729 self.shell.user_ns[out_name] = output
730 self.shell.user_ns[out_name] = output
730 else:
731 else:
731 return output
732 return output
732
733
733 system = line_cell_magic('system')(sx)
734 system = line_cell_magic('system')(sx)
734 bang = cell_magic('!')(sx)
735 bang = cell_magic('!')(sx)
735
736
736 @line_magic
737 @line_magic
737 def bookmark(self, parameter_s=''):
738 def bookmark(self, parameter_s=''):
738 """Manage IPython's bookmark system.
739 """Manage IPython's bookmark system.
739
740
740 %bookmark <name> - set bookmark to current dir
741 %bookmark <name> - set bookmark to current dir
741 %bookmark <name> <dir> - set bookmark to <dir>
742 %bookmark <name> <dir> - set bookmark to <dir>
742 %bookmark -l - list all bookmarks
743 %bookmark -l - list all bookmarks
743 %bookmark -d <name> - remove bookmark
744 %bookmark -d <name> - remove bookmark
744 %bookmark -r - remove all bookmarks
745 %bookmark -r - remove all bookmarks
745
746
746 You can later on access a bookmarked folder with::
747 You can later on access a bookmarked folder with::
747
748
748 %cd -b <name>
749 %cd -b <name>
749
750
750 or simply '%cd <name>' if there is no directory called <name> AND
751 or simply '%cd <name>' if there is no directory called <name> AND
751 there is such a bookmark defined.
752 there is such a bookmark defined.
752
753
753 Your bookmarks persist through IPython sessions, but they are
754 Your bookmarks persist through IPython sessions, but they are
754 associated with each profile."""
755 associated with each profile."""
755
756
756 opts,args = self.parse_options(parameter_s,'drl',mode='list')
757 opts,args = self.parse_options(parameter_s,'drl',mode='list')
757 if len(args) > 2:
758 if len(args) > 2:
758 raise UsageError("%bookmark: too many arguments")
759 raise UsageError("%bookmark: too many arguments")
759
760
760 bkms = self.shell.db.get('bookmarks',{})
761 bkms = self.shell.db.get('bookmarks',{})
761
762
762 if 'd' in opts:
763 if 'd' in opts:
763 try:
764 try:
764 todel = args[0]
765 todel = args[0]
765 except IndexError as e:
766 except IndexError as e:
766 raise UsageError(
767 raise UsageError(
767 "%bookmark -d: must provide a bookmark to delete") from e
768 "%bookmark -d: must provide a bookmark to delete") from e
768 else:
769 else:
769 try:
770 try:
770 del bkms[todel]
771 del bkms[todel]
771 except KeyError as e:
772 except KeyError as e:
772 raise UsageError(
773 raise UsageError(
773 "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
774 "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
774
775
775 elif 'r' in opts:
776 elif 'r' in opts:
776 bkms = {}
777 bkms = {}
777 elif 'l' in opts:
778 elif 'l' in opts:
778 bks = sorted(bkms)
779 bks = sorted(bkms)
779 if bks:
780 if bks:
780 size = max(map(len, bks))
781 size = max(map(len, bks))
781 else:
782 else:
782 size = 0
783 size = 0
783 fmt = '%-'+str(size)+'s -> %s'
784 fmt = '%-'+str(size)+'s -> %s'
784 print('Current bookmarks:')
785 print('Current bookmarks:')
785 for bk in bks:
786 for bk in bks:
786 print(fmt % (bk, bkms[bk]))
787 print(fmt % (bk, bkms[bk]))
787 else:
788 else:
788 if not args:
789 if not args:
789 raise UsageError("%bookmark: You must specify the bookmark name")
790 raise UsageError("%bookmark: You must specify the bookmark name")
790 elif len(args)==1:
791 elif len(args)==1:
791 bkms[args[0]] = os.getcwd()
792 bkms[args[0]] = os.getcwd()
792 elif len(args)==2:
793 elif len(args)==2:
793 bkms[args[0]] = args[1]
794 bkms[args[0]] = args[1]
794 self.shell.db['bookmarks'] = bkms
795 self.shell.db['bookmarks'] = bkms
795
796
796 @line_magic
797 @line_magic
797 def pycat(self, parameter_s=''):
798 def pycat(self, parameter_s=''):
798 """Show a syntax-highlighted file through a pager.
799 """Show a syntax-highlighted file through a pager.
799
800
800 This magic is similar to the cat utility, but it will assume the file
801 This magic is similar to the cat utility, but it will assume the file
801 to be Python source and will show it with syntax highlighting.
802 to be Python source and will show it with syntax highlighting.
802
803
803 This magic command can either take a local filename, an url,
804 This magic command can either take a local filename, an url,
804 an history range (see %history) or a macro as argument.
805 an history range (see %history) or a macro as argument.
805
806
806 If no parameter is given, prints out history of current session up to
807 If no parameter is given, prints out history of current session up to
807 this point. ::
808 this point. ::
808
809
809 %pycat myscript.py
810 %pycat myscript.py
810 %pycat 7-27
811 %pycat 7-27
811 %pycat myMacro
812 %pycat myMacro
812 %pycat http://www.example.com/myscript.py
813 %pycat http://www.example.com/myscript.py
813 """
814 """
814 try:
815 try:
815 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
816 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
816 except (ValueError, IOError):
817 except (ValueError, IOError):
817 print("Error: no such file, variable, URL, history range or macro")
818 print("Error: no such file, variable, URL, history range or macro")
818 return
819 return
819
820
820 page.page(self.shell.pycolorize(source_to_unicode(cont)))
821 page.page(self.shell.pycolorize(source_to_unicode(cont)))
821
822
822 @magic_arguments.magic_arguments()
823 @magic_arguments.magic_arguments()
823 @magic_arguments.argument(
824 @magic_arguments.argument(
824 '-a', '--append', action='store_true', default=False,
825 '-a', '--append', action='store_true', default=False,
825 help='Append contents of the cell to an existing file. '
826 help='Append contents of the cell to an existing file. '
826 'The file will be created if it does not exist.'
827 'The file will be created if it does not exist.'
827 )
828 )
828 @magic_arguments.argument(
829 @magic_arguments.argument(
829 'filename', type=str,
830 'filename', type=str,
830 help='file to write'
831 help='file to write'
831 )
832 )
832 @cell_magic
833 @cell_magic
833 def writefile(self, line, cell):
834 def writefile(self, line, cell):
834 """Write the contents of the cell to a file.
835 """Write the contents of the cell to a file.
835
836
836 The file will be overwritten unless the -a (--append) flag is specified.
837 The file will be overwritten unless the -a (--append) flag is specified.
837 """
838 """
838 args = magic_arguments.parse_argstring(self.writefile, line)
839 args = magic_arguments.parse_argstring(self.writefile, line)
839 if re.match(r'^(\'.*\')|(".*")$', args.filename):
840 if re.match(r'^(\'.*\')|(".*")$', args.filename):
840 filename = os.path.expanduser(args.filename[1:-1])
841 filename = os.path.expanduser(args.filename[1:-1])
841 else:
842 else:
842 filename = os.path.expanduser(args.filename)
843 filename = os.path.expanduser(args.filename)
843
844
844 if os.path.exists(filename):
845 if os.path.exists(filename):
845 if args.append:
846 if args.append:
846 print("Appending to %s" % filename)
847 print("Appending to %s" % filename)
847 else:
848 else:
848 print("Overwriting %s" % filename)
849 print("Overwriting %s" % filename)
849 else:
850 else:
850 print("Writing %s" % filename)
851 print("Writing %s" % filename)
851
852
852 mode = 'a' if args.append else 'w'
853 mode = 'a' if args.append else 'w'
853 with io.open(filename, mode, encoding='utf-8') as f:
854 with io.open(filename, mode, encoding='utf-8') as f:
854 f.write(cell)
855 f.write(cell)
@@ -1,54 +1,54 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Release data for the IPython project."""
2 """Release data for the IPython project."""
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2008, IPython Development Team.
5 # Copyright (c) 2008, IPython Development Team.
6 # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
6 # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
7 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
7 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
8 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
8 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
9 #
9 #
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11 #
11 #
12 # The full license is in the file COPYING.txt, distributed with this software.
12 # The full license is in the file COPYING.txt, distributed with this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # IPython version information. An empty _version_extra corresponds to a full
15 # IPython version information. An empty _version_extra corresponds to a full
16 # release. 'dev' as a _version_extra string means this is a development
16 # release. 'dev' as a _version_extra string means this is a development
17 # version
17 # version
18 _version_major = 8
18 _version_major = 8
19 _version_minor = 5
19 _version_minor = 6
20 _version_patch = 0
20 _version_patch = 0
21 _version_extra = ".dev"
21 _version_extra = ".dev"
22 # _version_extra = "rc1"
22 # _version_extra = "rc1"
23 # _version_extra = "" # Uncomment this for full releases
23 # _version_extra = "" # Uncomment this for full releases
24
24
25 # Construct full version string from these.
25 # Construct full version string from these.
26 _ver = [_version_major, _version_minor, _version_patch]
26 _ver = [_version_major, _version_minor, _version_patch]
27
27
28 __version__ = '.'.join(map(str, _ver))
28 __version__ = '.'.join(map(str, _ver))
29 if _version_extra:
29 if _version_extra:
30 __version__ = __version__ + _version_extra
30 __version__ = __version__ + _version_extra
31
31
32 version = __version__ # backwards compatibility name
32 version = __version__ # backwards compatibility name
33 version_info = (_version_major, _version_minor, _version_patch, _version_extra)
33 version_info = (_version_major, _version_minor, _version_patch, _version_extra)
34
34
35 # Change this when incrementing the kernel protocol version
35 # Change this when incrementing the kernel protocol version
36 kernel_protocol_version_info = (5, 0)
36 kernel_protocol_version_info = (5, 0)
37 kernel_protocol_version = "%i.%i" % kernel_protocol_version_info
37 kernel_protocol_version = "%i.%i" % kernel_protocol_version_info
38
38
39 license = 'BSD'
39 license = 'BSD'
40
40
41 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
41 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
42 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
42 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
43 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
43 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
44 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
44 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
45 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
45 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
46 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'),
46 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'),
47 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'),
47 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'),
48 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'),
48 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'),
49 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'),
49 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'),
50 }
50 }
51
51
52 author = 'The IPython Development Team'
52 author = 'The IPython Development Team'
53
53
54 author_email = 'ipython-dev@python.org'
54 author_email = 'ipython-dev@python.org'
@@ -1,2 +1,3 b''
1 import sys
1 import sys
2
2 print(sys.argv[1:])
3 print(sys.argv[1:])
@@ -1,66 +1,67 b''
1 """These kinds of tests are less than ideal, but at least they run.
1 """These kinds of tests are less than ideal, but at least they run.
2
2
3 This was an old test that was being run interactively in the top-level tests/
3 This was an old test that was being run interactively in the top-level tests/
4 directory, which we are removing. For now putting this here ensures at least
4 directory, which we are removing. For now putting this here ensures at least
5 we do run the test, though ultimately this functionality should all be tested
5 we do run the test, though ultimately this functionality should all be tested
6 with better-isolated tests that don't rely on the global instance in iptest.
6 with better-isolated tests that don't rely on the global instance in iptest.
7 """
7 """
8 from IPython.core.splitinput import LineInfo
8 from IPython.core.splitinput import LineInfo
9 from IPython.core.prefilter import AutocallChecker
9 from IPython.core.prefilter import AutocallChecker
10
10
11
11 def doctest_autocall():
12 def doctest_autocall():
12 """
13 """
13 In [1]: def f1(a,b,c):
14 In [1]: def f1(a,b,c):
14 ...: return a+b+c
15 ...: return a+b+c
15 ...:
16 ...:
16
17
17 In [2]: def f2(a):
18 In [2]: def f2(a):
18 ...: return a + a
19 ...: return a + a
19 ...:
20 ...:
20
21
21 In [3]: def r(x):
22 In [3]: def r(x):
22 ...: return True
23 ...: return True
23 ...:
24 ...:
24
25
25 In [4]: ;f2 a b c
26 In [4]: ;f2 a b c
26 Out[4]: 'a b ca b c'
27 Out[4]: 'a b ca b c'
27
28
28 In [5]: assert _ == "a b ca b c"
29 In [5]: assert _ == "a b ca b c"
29
30
30 In [6]: ,f1 a b c
31 In [6]: ,f1 a b c
31 Out[6]: 'abc'
32 Out[6]: 'abc'
32
33
33 In [7]: assert _ == 'abc'
34 In [7]: assert _ == 'abc'
34
35
35 In [8]: print(_)
36 In [8]: print(_)
36 abc
37 abc
37
38
38 In [9]: /f1 1,2,3
39 In [9]: /f1 1,2,3
39 Out[9]: 6
40 Out[9]: 6
40
41
41 In [10]: assert _ == 6
42 In [10]: assert _ == 6
42
43
43 In [11]: /f2 4
44 In [11]: /f2 4
44 Out[11]: 8
45 Out[11]: 8
45
46
46 In [12]: assert _ == 8
47 In [12]: assert _ == 8
47
48
48 In [12]: del f1, f2
49 In [12]: del f1, f2
49
50
50 In [13]: ,r a
51 In [13]: ,r a
51 Out[13]: True
52 Out[13]: True
52
53
53 In [14]: assert _ == True
54 In [14]: assert _ == True
54
55
55 In [15]: r'a'
56 In [15]: r'a'
56 Out[15]: 'a'
57 Out[15]: 'a'
57
58
58 In [16]: assert _ == 'a'
59 In [16]: assert _ == 'a'
59 """
60 """
60
61
61
62
62 def test_autocall_should_ignore_raw_strings():
63 def test_autocall_should_ignore_raw_strings():
63 line_info = LineInfo("r'a'")
64 line_info = LineInfo("r'a'")
64 pm = ip.prefilter_manager
65 pm = ip.prefilter_manager
65 ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, config=pm.config)
66 ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, config=pm.config)
66 assert ac.check(line_info) is None
67 assert ac.check(line_info) is None
@@ -1,112 +1,112 b''
1 import sys
1 import sys
2 from IPython.testing.tools import AssertPrints, AssertNotPrints
2 from IPython.testing.tools import AssertPrints, AssertNotPrints
3 from IPython.core.displayhook import CapturingDisplayHook
3 from IPython.core.displayhook import CapturingDisplayHook
4 from IPython.utils.capture import CapturedIO
4 from IPython.utils.capture import CapturedIO
5
5
6 def test_output_displayed():
6 def test_output_displayed():
7 """Checking to make sure that output is displayed"""
7 """Checking to make sure that output is displayed"""
8
8
9 with AssertPrints('2'):
9 with AssertPrints('2'):
10 ip.run_cell('1+1', store_history=True)
10 ip.run_cell('1+1', store_history=True)
11
11
12 with AssertPrints('2'):
12 with AssertPrints('2'):
13 ip.run_cell('1+1 # comment with a semicolon;', store_history=True)
13 ip.run_cell('1+1 # comment with a semicolon;', store_history=True)
14
14
15 with AssertPrints('2'):
15 with AssertPrints('2'):
16 ip.run_cell('1+1\n#commented_out_function();', store_history=True)
16 ip.run_cell('1+1\n#commented_out_function();', store_history=True)
17
17
18
18
19 def test_output_quiet():
19 def test_output_quiet():
20 """Checking to make sure that output is quiet"""
20 """Checking to make sure that output is quiet"""
21
21
22 with AssertNotPrints('2'):
22 with AssertNotPrints('2'):
23 ip.run_cell('1+1;', store_history=True)
23 ip.run_cell('1+1;', store_history=True)
24
24
25 with AssertNotPrints('2'):
25 with AssertNotPrints('2'):
26 ip.run_cell('1+1; # comment with a semicolon', store_history=True)
26 ip.run_cell('1+1; # comment with a semicolon', store_history=True)
27
27
28 with AssertNotPrints('2'):
28 with AssertNotPrints('2'):
29 ip.run_cell('1+1;\n#commented_out_function()', store_history=True)
29 ip.run_cell('1+1;\n#commented_out_function()', store_history=True)
30
30
31 def test_underscore_no_overrite_user():
31 def test_underscore_no_overwrite_user():
32 ip.run_cell('_ = 42', store_history=True)
32 ip.run_cell('_ = 42', store_history=True)
33 ip.run_cell('1+1', store_history=True)
33 ip.run_cell('1+1', store_history=True)
34
34
35 with AssertPrints('42'):
35 with AssertPrints('42'):
36 ip.run_cell('print(_)', store_history=True)
36 ip.run_cell('print(_)', store_history=True)
37
37
38 ip.run_cell('del _', store_history=True)
38 ip.run_cell('del _', store_history=True)
39 ip.run_cell('6+6', store_history=True)
39 ip.run_cell('6+6', store_history=True)
40 with AssertPrints('12'):
40 with AssertPrints('12'):
41 ip.run_cell('_', store_history=True)
41 ip.run_cell('_', store_history=True)
42
42
43
43
44 def test_underscore_no_overrite_builtins():
44 def test_underscore_no_overwrite_builtins():
45 ip.run_cell("import gettext ; gettext.install('foo')", store_history=True)
45 ip.run_cell("import gettext ; gettext.install('foo')", store_history=True)
46 ip.run_cell('3+3', store_history=True)
46 ip.run_cell('3+3', store_history=True)
47
47
48 with AssertPrints('gettext'):
48 with AssertPrints('gettext'):
49 ip.run_cell('print(_)', store_history=True)
49 ip.run_cell('print(_)', store_history=True)
50
50
51 ip.run_cell('_ = "userset"', store_history=True)
51 ip.run_cell('_ = "userset"', store_history=True)
52
52
53 with AssertPrints('userset'):
53 with AssertPrints('userset'):
54 ip.run_cell('print(_)', store_history=True)
54 ip.run_cell('print(_)', store_history=True)
55 ip.run_cell('import builtins; del builtins._')
55 ip.run_cell('import builtins; del builtins._')
56
56
57
57
58 def test_interactivehooks_ast_modes():
58 def test_interactivehooks_ast_modes():
59 """
59 """
60 Test that ast nodes can be triggered with different modes
60 Test that ast nodes can be triggered with different modes
61 """
61 """
62 saved_mode = ip.ast_node_interactivity
62 saved_mode = ip.ast_node_interactivity
63 ip.ast_node_interactivity = 'last_expr_or_assign'
63 ip.ast_node_interactivity = 'last_expr_or_assign'
64
64
65 try:
65 try:
66 with AssertPrints('2'):
66 with AssertPrints('2'):
67 ip.run_cell('a = 1+1', store_history=True)
67 ip.run_cell('a = 1+1', store_history=True)
68
68
69 with AssertPrints('9'):
69 with AssertPrints('9'):
70 ip.run_cell('b = 1+8 # comment with a semicolon;', store_history=False)
70 ip.run_cell('b = 1+8 # comment with a semicolon;', store_history=False)
71
71
72 with AssertPrints('7'):
72 with AssertPrints('7'):
73 ip.run_cell('c = 1+6\n#commented_out_function();', store_history=True)
73 ip.run_cell('c = 1+6\n#commented_out_function();', store_history=True)
74
74
75 ip.run_cell('d = 11', store_history=True)
75 ip.run_cell('d = 11', store_history=True)
76 with AssertPrints('12'):
76 with AssertPrints('12'):
77 ip.run_cell('d += 1', store_history=True)
77 ip.run_cell('d += 1', store_history=True)
78
78
79 with AssertNotPrints('42'):
79 with AssertNotPrints('42'):
80 ip.run_cell('(u,v) = (41+1, 43-1)')
80 ip.run_cell('(u,v) = (41+1, 43-1)')
81
81
82 finally:
82 finally:
83 ip.ast_node_interactivity = saved_mode
83 ip.ast_node_interactivity = saved_mode
84
84
85 def test_interactivehooks_ast_modes_semi_suppress():
85 def test_interactivehooks_ast_modes_semi_suppress():
86 """
86 """
87 Test that ast nodes can be triggered with different modes and suppressed
87 Test that ast nodes can be triggered with different modes and suppressed
88 by semicolon
88 by semicolon
89 """
89 """
90 saved_mode = ip.ast_node_interactivity
90 saved_mode = ip.ast_node_interactivity
91 ip.ast_node_interactivity = 'last_expr_or_assign'
91 ip.ast_node_interactivity = 'last_expr_or_assign'
92
92
93 try:
93 try:
94 with AssertNotPrints('2'):
94 with AssertNotPrints('2'):
95 ip.run_cell('x = 1+1;', store_history=True)
95 ip.run_cell('x = 1+1;', store_history=True)
96
96
97 with AssertNotPrints('7'):
97 with AssertNotPrints('7'):
98 ip.run_cell('y = 1+6; # comment with a semicolon', store_history=True)
98 ip.run_cell('y = 1+6; # comment with a semicolon', store_history=True)
99
99
100 with AssertNotPrints('9'):
100 with AssertNotPrints('9'):
101 ip.run_cell('z = 1+8;\n#commented_out_function()', store_history=True)
101 ip.run_cell('z = 1+8;\n#commented_out_function()', store_history=True)
102
102
103 finally:
103 finally:
104 ip.ast_node_interactivity = saved_mode
104 ip.ast_node_interactivity = saved_mode
105
105
106 def test_capture_display_hook_format():
106 def test_capture_display_hook_format():
107 """Tests that the capture display hook conforms to the CapturedIO output format"""
107 """Tests that the capture display hook conforms to the CapturedIO output format"""
108 hook = CapturingDisplayHook(ip)
108 hook = CapturingDisplayHook(ip)
109 hook({"foo": "bar"})
109 hook({"foo": "bar"})
110 captured = CapturedIO(sys.stdout, sys.stderr, hook.outputs)
110 captured = CapturedIO(sys.stdout, sys.stderr, hook.outputs)
111 # Should not raise with RichOutput transformation error
111 # Should not raise with RichOutput transformation error
112 captured.outputs
112 captured.outputs
@@ -1,229 +1,307 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Tests for the IPython tab-completion machinery.
2 """Tests for the IPython tab-completion machinery.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Module imports
5 # Module imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib
8 # stdlib
9 import io
9 import io
10 import sqlite3
10 import sqlite3
11 import sys
11 import sys
12 import tempfile
12 import tempfile
13 from datetime import datetime
13 from datetime import datetime
14 from pathlib import Path
14 from pathlib import Path
15
15
16 from tempfile import TemporaryDirectory
16 from tempfile import TemporaryDirectory
17 # our own packages
17 # our own packages
18 from traitlets.config.loader import Config
18 from traitlets.config.loader import Config
19
19
20 from IPython.core.history import HistoryManager, extract_hist_ranges
20 from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges
21
21
22
22
23 def test_proper_default_encoding():
23 def test_proper_default_encoding():
24 assert sys.getdefaultencoding() == "utf-8"
24 assert sys.getdefaultencoding() == "utf-8"
25
25
26 def test_history():
26 def test_history():
27 ip = get_ipython()
27 ip = get_ipython()
28 with TemporaryDirectory() as tmpdir:
28 with TemporaryDirectory() as tmpdir:
29 tmp_path = Path(tmpdir)
29 tmp_path = Path(tmpdir)
30 hist_manager_ori = ip.history_manager
30 hist_manager_ori = ip.history_manager
31 hist_file = tmp_path / "history.sqlite"
31 hist_file = tmp_path / "history.sqlite"
32 try:
32 try:
33 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
33 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
34 hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"]
34 hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"]
35 for i, h in enumerate(hist, start=1):
35 for i, h in enumerate(hist, start=1):
36 ip.history_manager.store_inputs(i, h)
36 ip.history_manager.store_inputs(i, h)
37
37
38 ip.history_manager.db_log_output = True
38 ip.history_manager.db_log_output = True
39 # Doesn't match the input, but we'll just check it's stored.
39 # Doesn't match the input, but we'll just check it's stored.
40 ip.history_manager.output_hist_reprs[3] = "spam"
40 ip.history_manager.output_hist_reprs[3] = "spam"
41 ip.history_manager.store_output(3)
41 ip.history_manager.store_output(3)
42
42
43 assert ip.history_manager.input_hist_raw == [""] + hist
43 assert ip.history_manager.input_hist_raw == [""] + hist
44
44
45 # Detailed tests for _get_range_session
45 # Detailed tests for _get_range_session
46 grs = ip.history_manager._get_range_session
46 grs = ip.history_manager._get_range_session
47 assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1]))
47 assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1]))
48 assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:]))
48 assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:]))
49 assert list(grs(output=True)) == list(
49 assert list(grs(output=True)) == list(
50 zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"]))
50 zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"]))
51 )
51 )
52
52
53 # Check whether specifying a range beyond the end of the current
53 # Check whether specifying a range beyond the end of the current
54 # session results in an error (gh-804)
54 # session results in an error (gh-804)
55 ip.run_line_magic("hist", "2-500")
55 ip.run_line_magic("hist", "2-500")
56
56
57 # Check that we can write non-ascii characters to a file
57 # Check that we can write non-ascii characters to a file
58 ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1"))
58 ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1"))
59 ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2"))
59 ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2"))
60 ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3"))
60 ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3"))
61 ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4"))
61 ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4"))
62
62
63 # New session
63 # New session
64 ip.history_manager.reset()
64 ip.history_manager.reset()
65 newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"]
65 newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"]
66 for i, cmd in enumerate(newcmds, start=1):
66 for i, cmd in enumerate(newcmds, start=1):
67 ip.history_manager.store_inputs(i, cmd)
67 ip.history_manager.store_inputs(i, cmd)
68 gothist = ip.history_manager.get_range(start=1, stop=4)
68 gothist = ip.history_manager.get_range(start=1, stop=4)
69 assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds))
69 assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds))
70 # Previous session:
70 # Previous session:
71 gothist = ip.history_manager.get_range(-1, 1, 4)
71 gothist = ip.history_manager.get_range(-1, 1, 4)
72 assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist))
72 assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist))
73
73
74 newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
74 newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
75
75
76 # Check get_hist_tail
76 # Check get_hist_tail
77 gothist = ip.history_manager.get_tail(5, output=True,
77 gothist = ip.history_manager.get_tail(5, output=True,
78 include_latest=True)
78 include_latest=True)
79 expected = [(1, 3, (hist[-1], "spam"))] \
79 expected = [(1, 3, (hist[-1], "spam"))] \
80 + [(s, n, (c, None)) for (s, n, c) in newhist]
80 + [(s, n, (c, None)) for (s, n, c) in newhist]
81 assert list(gothist) == expected
81 assert list(gothist) == expected
82
82
83 gothist = ip.history_manager.get_tail(2)
83 gothist = ip.history_manager.get_tail(2)
84 expected = newhist[-3:-1]
84 expected = newhist[-3:-1]
85 assert list(gothist) == expected
85 assert list(gothist) == expected
86
86
87 # Check get_hist_search
87 # Check get_hist_search
88
88
89 gothist = ip.history_manager.search("*test*")
89 gothist = ip.history_manager.search("*test*")
90 assert list(gothist) == [(1, 2, hist[1])]
90 assert list(gothist) == [(1, 2, hist[1])]
91
91
92 gothist = ip.history_manager.search("*=*")
92 gothist = ip.history_manager.search("*=*")
93 assert list(gothist) == [
93 assert list(gothist) == [
94 (1, 1, hist[0]),
94 (1, 1, hist[0]),
95 (1, 2, hist[1]),
95 (1, 2, hist[1]),
96 (1, 3, hist[2]),
96 (1, 3, hist[2]),
97 newhist[0],
97 newhist[0],
98 newhist[2],
98 newhist[2],
99 newhist[3],
99 newhist[3],
100 ]
100 ]
101
101
102 gothist = ip.history_manager.search("*=*", n=4)
102 gothist = ip.history_manager.search("*=*", n=4)
103 assert list(gothist) == [
103 assert list(gothist) == [
104 (1, 3, hist[2]),
104 (1, 3, hist[2]),
105 newhist[0],
105 newhist[0],
106 newhist[2],
106 newhist[2],
107 newhist[3],
107 newhist[3],
108 ]
108 ]
109
109
110 gothist = ip.history_manager.search("*=*", unique=True)
110 gothist = ip.history_manager.search("*=*", unique=True)
111 assert list(gothist) == [
111 assert list(gothist) == [
112 (1, 1, hist[0]),
112 (1, 1, hist[0]),
113 (1, 2, hist[1]),
113 (1, 2, hist[1]),
114 (1, 3, hist[2]),
114 (1, 3, hist[2]),
115 newhist[2],
115 newhist[2],
116 newhist[3],
116 newhist[3],
117 ]
117 ]
118
118
119 gothist = ip.history_manager.search("*=*", unique=True, n=3)
119 gothist = ip.history_manager.search("*=*", unique=True, n=3)
120 assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]]
120 assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]]
121
121
122 gothist = ip.history_manager.search("b*", output=True)
122 gothist = ip.history_manager.search("b*", output=True)
123 assert list(gothist) == [(1, 3, (hist[2], "spam"))]
123 assert list(gothist) == [(1, 3, (hist[2], "spam"))]
124
124
125 # Cross testing: check that magic %save can get previous session.
125 # Cross testing: check that magic %save can get previous session.
126 testfilename = (tmp_path / "test.py").resolve()
126 testfilename = (tmp_path / "test.py").resolve()
127 ip.run_line_magic("save", str(testfilename) + " ~1/1-3")
127 ip.run_line_magic("save", str(testfilename) + " ~1/1-3")
128 with io.open(testfilename, encoding="utf-8") as testfile:
128 with io.open(testfilename, encoding="utf-8") as testfile:
129 assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n"
129 assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n"
130
130
131 # Duplicate line numbers - check that it doesn't crash, and
131 # Duplicate line numbers - check that it doesn't crash, and
132 # gets a new session
132 # gets a new session
133 ip.history_manager.store_inputs(1, "rogue")
133 ip.history_manager.store_inputs(1, "rogue")
134 ip.history_manager.writeout_cache()
134 ip.history_manager.writeout_cache()
135 assert ip.history_manager.session_number == 3
135 assert ip.history_manager.session_number == 3
136
136
137 # Check that session and line values are not just max values
137 # Check that session and line values are not just max values
138 sessid, lineno, entry = newhist[-1]
138 sessid, lineno, entry = newhist[-1]
139 assert lineno > 1
139 assert lineno > 1
140 ip.history_manager.reset()
140 ip.history_manager.reset()
141 lineno = 1
141 lineno = 1
142 ip.history_manager.store_inputs(lineno, entry)
142 ip.history_manager.store_inputs(lineno, entry)
143 gothist = ip.history_manager.search("*=*", unique=True)
143 gothist = ip.history_manager.search("*=*", unique=True)
144 hist = list(gothist)[-1]
144 hist = list(gothist)[-1]
145 assert sessid < hist[0]
145 assert sessid < hist[0]
146 assert hist[1:] == (lineno, entry)
146 assert hist[1:] == (lineno, entry)
147 finally:
147 finally:
148 # Ensure saving thread is shut down before we try to clean up the files
148 # Ensure saving thread is shut down before we try to clean up the files
149 ip.history_manager.save_thread.stop()
149 ip.history_manager.save_thread.stop()
150 # Forcibly close database rather than relying on garbage collection
150 # Forcibly close database rather than relying on garbage collection
151 ip.history_manager.db.close()
151 ip.history_manager.db.close()
152 # Restore history manager
152 # Restore history manager
153 ip.history_manager = hist_manager_ori
153 ip.history_manager = hist_manager_ori
154
154
155
155
156 def test_extract_hist_ranges():
156 def test_extract_hist_ranges():
157 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/"
157 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/"
158 expected = [(0, 1, 2), # 0 == current session
158 expected = [(0, 1, 2), # 0 == current session
159 (2, 3, 4),
159 (2, 3, 4),
160 (-4, 5, 7),
160 (-4, 5, 7),
161 (-4, 7, 10),
161 (-4, 7, 10),
162 (-9, 2, None), # None == to end
162 (-9, 2, None), # None == to end
163 (-8, 1, None),
163 (-8, 1, None),
164 (-7, 1, 6),
164 (-7, 1, 6),
165 (-10, 1, None)]
165 (-10, 1, None)]
166 actual = list(extract_hist_ranges(instr))
166 actual = list(extract_hist_ranges(instr))
167 assert actual == expected
167 assert actual == expected
168
168
169
169
170 def test_extract_hist_ranges_empty_str():
170 def test_extract_hist_ranges_empty_str():
171 instr = ""
171 instr = ""
172 expected = [(0, 1, None)] # 0 == current session, None == to end
172 expected = [(0, 1, None)] # 0 == current session, None == to end
173 actual = list(extract_hist_ranges(instr))
173 actual = list(extract_hist_ranges(instr))
174 assert actual == expected
174 assert actual == expected
175
175
176
176
177 def test_magic_rerun():
177 def test_magic_rerun():
178 """Simple test for %rerun (no args -> rerun last line)"""
178 """Simple test for %rerun (no args -> rerun last line)"""
179 ip = get_ipython()
179 ip = get_ipython()
180 ip.run_cell("a = 10", store_history=True)
180 ip.run_cell("a = 10", store_history=True)
181 ip.run_cell("a += 1", store_history=True)
181 ip.run_cell("a += 1", store_history=True)
182 assert ip.user_ns["a"] == 11
182 assert ip.user_ns["a"] == 11
183 ip.run_cell("%rerun", store_history=True)
183 ip.run_cell("%rerun", store_history=True)
184 assert ip.user_ns["a"] == 12
184 assert ip.user_ns["a"] == 12
185
185
186 def test_timestamp_type():
186 def test_timestamp_type():
187 ip = get_ipython()
187 ip = get_ipython()
188 info = ip.history_manager.get_session_info()
188 info = ip.history_manager.get_session_info()
189 assert isinstance(info[1], datetime)
189 assert isinstance(info[1], datetime)
190
190
191 def test_hist_file_config():
191 def test_hist_file_config():
192 cfg = Config()
192 cfg = Config()
193 tfile = tempfile.NamedTemporaryFile(delete=False)
193 tfile = tempfile.NamedTemporaryFile(delete=False)
194 cfg.HistoryManager.hist_file = Path(tfile.name)
194 cfg.HistoryManager.hist_file = Path(tfile.name)
195 try:
195 try:
196 hm = HistoryManager(shell=get_ipython(), config=cfg)
196 hm = HistoryManager(shell=get_ipython(), config=cfg)
197 assert hm.hist_file == cfg.HistoryManager.hist_file
197 assert hm.hist_file == cfg.HistoryManager.hist_file
198 finally:
198 finally:
199 try:
199 try:
200 Path(tfile.name).unlink()
200 Path(tfile.name).unlink()
201 except OSError:
201 except OSError:
202 # same catch as in testing.tools.TempFileMixin
202 # same catch as in testing.tools.TempFileMixin
203 # On Windows, even though we close the file, we still can't
203 # On Windows, even though we close the file, we still can't
204 # delete it. I have no clue why
204 # delete it. I have no clue why
205 pass
205 pass
206
206
207 def test_histmanager_disabled():
207 def test_histmanager_disabled():
208 """Ensure that disabling the history manager doesn't create a database."""
208 """Ensure that disabling the history manager doesn't create a database."""
209 cfg = Config()
209 cfg = Config()
210 cfg.HistoryAccessor.enabled = False
210 cfg.HistoryAccessor.enabled = False
211
211
212 ip = get_ipython()
212 ip = get_ipython()
213 with TemporaryDirectory() as tmpdir:
213 with TemporaryDirectory() as tmpdir:
214 hist_manager_ori = ip.history_manager
214 hist_manager_ori = ip.history_manager
215 hist_file = Path(tmpdir) / "history.sqlite"
215 hist_file = Path(tmpdir) / "history.sqlite"
216 cfg.HistoryManager.hist_file = hist_file
216 cfg.HistoryManager.hist_file = hist_file
217 try:
217 try:
218 ip.history_manager = HistoryManager(shell=ip, config=cfg)
218 ip.history_manager = HistoryManager(shell=ip, config=cfg)
219 hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"]
219 hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"]
220 for i, h in enumerate(hist, start=1):
220 for i, h in enumerate(hist, start=1):
221 ip.history_manager.store_inputs(i, h)
221 ip.history_manager.store_inputs(i, h)
222 assert ip.history_manager.input_hist_raw == [""] + hist
222 assert ip.history_manager.input_hist_raw == [""] + hist
223 ip.history_manager.reset()
223 ip.history_manager.reset()
224 ip.history_manager.end_session()
224 ip.history_manager.end_session()
225 finally:
225 finally:
226 ip.history_manager = hist_manager_ori
226 ip.history_manager = hist_manager_ori
227
227
228 # hist_file should not be created
228 # hist_file should not be created
229 assert hist_file.exists() is False
229 assert hist_file.exists() is False
230
231
232 def test_get_tail_session_awareness():
233 """Test .get_tail() is:
234 - session specific in HistoryManager
235 - session agnostic in HistoryAccessor
236 same for .get_last_session_id()
237 """
238 ip = get_ipython()
239 with TemporaryDirectory() as tmpdir:
240 tmp_path = Path(tmpdir)
241 hist_file = tmp_path / "history.sqlite"
242 get_source = lambda x: x[2]
243 hm1 = None
244 hm2 = None
245 ha = None
246 try:
247 # hm1 creates a new session and adds history entries,
248 # ha catches up
249 hm1 = HistoryManager(shell=ip, hist_file=hist_file)
250 hm1_last_sid = hm1.get_last_session_id
251 ha = HistoryAccessor(hist_file=hist_file)
252 ha_last_sid = ha.get_last_session_id
253
254 hist1 = ["a=1", "b=1", "c=1"]
255 for i, h in enumerate(hist1 + [""], start=1):
256 hm1.store_inputs(i, h)
257 assert list(map(get_source, hm1.get_tail())) == hist1
258 assert list(map(get_source, ha.get_tail())) == hist1
259 sid1 = hm1_last_sid()
260 assert sid1 is not None
261 assert ha_last_sid() == sid1
262
263 # hm2 creates a new session and adds entries,
264 # ha catches up
265 hm2 = HistoryManager(shell=ip, hist_file=hist_file)
266 hm2_last_sid = hm2.get_last_session_id
267
268 hist2 = ["a=2", "b=2", "c=2"]
269 for i, h in enumerate(hist2 + [""], start=1):
270 hm2.store_inputs(i, h)
271 tail = hm2.get_tail(n=3)
272 assert list(map(get_source, tail)) == hist2
273 tail = ha.get_tail(n=3)
274 assert list(map(get_source, tail)) == hist2
275 sid2 = hm2_last_sid()
276 assert sid2 is not None
277 assert ha_last_sid() == sid2
278 assert sid2 != sid1
279
280 # but hm1 still maintains its point of reference
281 # and adding more entries to it doesn't change others
282 # immediate perspective
283 assert hm1_last_sid() == sid1
284 tail = hm1.get_tail(n=3)
285 assert list(map(get_source, tail)) == hist1
286
287 hist3 = ["a=3", "b=3", "c=3"]
288 for i, h in enumerate(hist3 + [""], start=5):
289 hm1.store_inputs(i, h)
290 tail = hm1.get_tail(n=7)
291 assert list(map(get_source, tail)) == hist1 + [""] + hist3
292 tail = hm2.get_tail(n=3)
293 assert list(map(get_source, tail)) == hist2
294 tail = ha.get_tail(n=3)
295 assert list(map(get_source, tail)) == hist2
296 assert hm1_last_sid() == sid1
297 assert hm2_last_sid() == sid2
298 assert ha_last_sid() == sid2
299 finally:
300 if hm1:
301 hm1.save_thread.stop()
302 hm1.db.close()
303 if hm2:
304 hm2.save_thread.stop()
305 hm2.db.close()
306 if ha:
307 ha.db.close()
@@ -1,38 +1,39 b''
1 # coding: utf-8
1 # coding: utf-8
2
2
3 from IPython.core.splitinput import split_user_input, LineInfo
3 from IPython.core.splitinput import split_user_input, LineInfo
4 from IPython.testing import tools as tt
4 from IPython.testing import tools as tt
5
5
6 tests = [
6 tests = [
7 ("x=1", ("", "", "x", "=1")),
7 ("x=1", ("", "", "x", "=1")),
8 ("?", ("", "?", "", "")),
8 ("?", ("", "?", "", "")),
9 ("??", ("", "??", "", "")),
9 ("??", ("", "??", "", "")),
10 (" ?", (" ", "?", "", "")),
10 (" ?", (" ", "?", "", "")),
11 (" ??", (" ", "??", "", "")),
11 (" ??", (" ", "??", "", "")),
12 ("??x", ("", "??", "x", "")),
12 ("??x", ("", "??", "x", "")),
13 ("?x=1", ("", "?", "x", "=1")),
13 ("?x=1", ("", "?", "x", "=1")),
14 ("!ls", ("", "!", "ls", "")),
14 ("!ls", ("", "!", "ls", "")),
15 (" !ls", (" ", "!", "ls", "")),
15 (" !ls", (" ", "!", "ls", "")),
16 ("!!ls", ("", "!!", "ls", "")),
16 ("!!ls", ("", "!!", "ls", "")),
17 (" !!ls", (" ", "!!", "ls", "")),
17 (" !!ls", (" ", "!!", "ls", "")),
18 (",ls", ("", ",", "ls", "")),
18 (",ls", ("", ",", "ls", "")),
19 (";ls", ("", ";", "ls", "")),
19 (";ls", ("", ";", "ls", "")),
20 (" ;ls", (" ", ";", "ls", "")),
20 (" ;ls", (" ", ";", "ls", "")),
21 ("f.g(x)", ("", "", "f.g", "(x)")),
21 ("f.g(x)", ("", "", "f.g", "(x)")),
22 ("f.g (x)", ("", "", "f.g", "(x)")),
22 ("f.g (x)", ("", "", "f.g", "(x)")),
23 ("?%hist1", ("", "?", "%hist1", "")),
23 ("?%hist1", ("", "?", "%hist1", "")),
24 ("?%%hist2", ("", "?", "%%hist2", "")),
24 ("?%%hist2", ("", "?", "%%hist2", "")),
25 ("??%hist3", ("", "??", "%hist3", "")),
25 ("??%hist3", ("", "??", "%hist3", "")),
26 ("??%%hist4", ("", "??", "%%hist4", "")),
26 ("??%%hist4", ("", "??", "%%hist4", "")),
27 ("?x*", ("", "?", "x*", "")),
27 ("?x*", ("", "?", "x*", "")),
28 ]
28 ]
29 tests.append(("Pérez Fernando", ("", "", "Pérez", "Fernando")))
29 tests.append(("Pérez Fernando", ("", "", "Pérez", "Fernando")))
30
30
31
31
32 def test_split_user_input():
32 def test_split_user_input():
33 return tt.check_pairs(split_user_input, tests)
33 return tt.check_pairs(split_user_input, tests)
34
34
35
35 def test_LineInfo():
36 def test_LineInfo():
36 """Simple test for LineInfo construction and str()"""
37 """Simple test for LineInfo construction and str()"""
37 linfo = LineInfo(" %cd /home")
38 linfo = LineInfo(" %cd /home")
38 assert str(linfo) == "LineInfo [ |%|cd|/home]"
39 assert str(linfo) == "LineInfo [ |%|cd|/home]"
@@ -1,627 +1,627 b''
1 """IPython extension to reload modules before executing user code.
1 """IPython extension to reload modules before executing user code.
2
2
3 ``autoreload`` reloads modules automatically before entering the execution of
3 ``autoreload`` reloads modules automatically before entering the execution of
4 code typed at the IPython prompt.
4 code typed at the IPython prompt.
5
5
6 This makes for example the following workflow possible:
6 This makes for example the following workflow possible:
7
7
8 .. sourcecode:: ipython
8 .. sourcecode:: ipython
9
9
10 In [1]: %load_ext autoreload
10 In [1]: %load_ext autoreload
11
11
12 In [2]: %autoreload 2
12 In [2]: %autoreload 2
13
13
14 In [3]: from foo import some_function
14 In [3]: from foo import some_function
15
15
16 In [4]: some_function()
16 In [4]: some_function()
17 Out[4]: 42
17 Out[4]: 42
18
18
19 In [5]: # open foo.py in an editor and change some_function to return 43
19 In [5]: # open foo.py in an editor and change some_function to return 43
20
20
21 In [6]: some_function()
21 In [6]: some_function()
22 Out[6]: 43
22 Out[6]: 43
23
23
24 The module was reloaded without reloading it explicitly, and the object
24 The module was reloaded without reloading it explicitly, and the object
25 imported with ``from foo import ...`` was also updated.
25 imported with ``from foo import ...`` was also updated.
26
26
27 Usage
27 Usage
28 =====
28 =====
29
29
30 The following magic commands are provided:
30 The following magic commands are provided:
31
31
32 ``%autoreload``
32 ``%autoreload``
33
33
34 Reload all modules (except those excluded by ``%aimport``)
34 Reload all modules (except those excluded by ``%aimport``)
35 automatically now.
35 automatically now.
36
36
37 ``%autoreload 0``
37 ``%autoreload 0``
38
38
39 Disable automatic reloading.
39 Disable automatic reloading.
40
40
41 ``%autoreload 1``
41 ``%autoreload 1``
42
42
43 Reload all modules imported with ``%aimport`` every time before
43 Reload all modules imported with ``%aimport`` every time before
44 executing the Python code typed.
44 executing the Python code typed.
45
45
46 ``%autoreload 2``
46 ``%autoreload 2``
47
47
48 Reload all modules (except those excluded by ``%aimport``) every
48 Reload all modules (except those excluded by ``%aimport``) every
49 time before executing the Python code typed.
49 time before executing the Python code typed.
50
50
51 ``%autoreload 3``
51 ``%autoreload 3``
52
52
53 Reload all modules AND autoload newly added objects
53 Reload all modules AND autoload newly added objects
54 every time before executing the Python code typed.
54 every time before executing the Python code typed.
55
55
56 ``%aimport``
56 ``%aimport``
57
57
58 List modules which are to be automatically imported or not to be imported.
58 List modules which are to be automatically imported or not to be imported.
59
59
60 ``%aimport foo``
60 ``%aimport foo``
61
61
62 Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
62 Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
63
63
64 ``%aimport foo, bar``
64 ``%aimport foo, bar``
65
65
66 Import modules 'foo', 'bar' and mark them to be autoreloaded for ``%autoreload 1``
66 Import modules 'foo', 'bar' and mark them to be autoreloaded for ``%autoreload 1``
67
67
68 ``%aimport -foo``
68 ``%aimport -foo``
69
69
70 Mark module 'foo' to not be autoreloaded.
70 Mark module 'foo' to not be autoreloaded.
71
71
72 Caveats
72 Caveats
73 =======
73 =======
74
74
75 Reloading Python modules in a reliable way is in general difficult,
75 Reloading Python modules in a reliable way is in general difficult,
76 and unexpected things may occur. ``%autoreload`` tries to work around
76 and unexpected things may occur. ``%autoreload`` tries to work around
77 common pitfalls by replacing function code objects and parts of
77 common pitfalls by replacing function code objects and parts of
78 classes previously in the module with new versions. This makes the
78 classes previously in the module with new versions. This makes the
79 following things to work:
79 following things to work:
80
80
81 - Functions and classes imported via 'from xxx import foo' are upgraded
81 - Functions and classes imported via 'from xxx import foo' are upgraded
82 to new versions when 'xxx' is reloaded.
82 to new versions when 'xxx' is reloaded.
83
83
84 - Methods and properties of classes are upgraded on reload, so that
84 - Methods and properties of classes are upgraded on reload, so that
85 calling 'c.foo()' on an object 'c' created before the reload causes
85 calling 'c.foo()' on an object 'c' created before the reload causes
86 the new code for 'foo' to be executed.
86 the new code for 'foo' to be executed.
87
87
88 Some of the known remaining caveats are:
88 Some of the known remaining caveats are:
89
89
90 - Replacing code objects does not always succeed: changing a @property
90 - Replacing code objects does not always succeed: changing a @property
91 in a class to an ordinary method or a method to a member variable
91 in a class to an ordinary method or a method to a member variable
92 can cause problems (but in old objects only).
92 can cause problems (but in old objects only).
93
93
94 - Functions that are removed (eg. via monkey-patching) from a module
94 - Functions that are removed (eg. via monkey-patching) from a module
95 before it is reloaded are not upgraded.
95 before it is reloaded are not upgraded.
96
96
97 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
97 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
98 """
98 """
99
99
100 __skip_doctest__ = True
100 __skip_doctest__ = True
101
101
102 # -----------------------------------------------------------------------------
102 # -----------------------------------------------------------------------------
103 # Copyright (C) 2000 Thomas Heller
103 # Copyright (C) 2000 Thomas Heller
104 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
104 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
105 # Copyright (C) 2012 The IPython Development Team
105 # Copyright (C) 2012 The IPython Development Team
106 #
106 #
107 # Distributed under the terms of the BSD License. The full license is in
107 # Distributed under the terms of the BSD License. The full license is in
108 # the file COPYING, distributed as part of this software.
108 # the file COPYING, distributed as part of this software.
109 # -----------------------------------------------------------------------------
109 # -----------------------------------------------------------------------------
110 #
110 #
111 # This IPython module is written by Pauli Virtanen, based on the autoreload
111 # This IPython module is written by Pauli Virtanen, based on the autoreload
112 # code by Thomas Heller.
112 # code by Thomas Heller.
113
113
114 # -----------------------------------------------------------------------------
114 # -----------------------------------------------------------------------------
115 # Imports
115 # Imports
116 # -----------------------------------------------------------------------------
116 # -----------------------------------------------------------------------------
117
117
118 import os
118 import os
119 import sys
119 import sys
120 import traceback
120 import traceback
121 import types
121 import types
122 import weakref
122 import weakref
123 import gc
123 import gc
124 from importlib import import_module, reload
124 from importlib import import_module, reload
125 from importlib.util import source_from_cache
125 from importlib.util import source_from_cache
126
126
127 # ------------------------------------------------------------------------------
127 # ------------------------------------------------------------------------------
128 # Autoreload functionality
128 # Autoreload functionality
129 # ------------------------------------------------------------------------------
129 # ------------------------------------------------------------------------------
130
130
131
131
132 class ModuleReloader:
132 class ModuleReloader:
133 enabled = False
133 enabled = False
134 """Whether this reloader is enabled"""
134 """Whether this reloader is enabled"""
135
135
136 check_all = True
136 check_all = True
137 """Autoreload all modules, not just those listed in 'modules'"""
137 """Autoreload all modules, not just those listed in 'modules'"""
138
138
139 autoload_obj = False
139 autoload_obj = False
140 """Autoreload all modules AND autoload all new objects"""
140 """Autoreload all modules AND autoload all new objects"""
141
141
142 def __init__(self, shell=None):
142 def __init__(self, shell=None):
143 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
143 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
144 self.failed = {}
144 self.failed = {}
145 # Modules specially marked as autoreloadable.
145 # Modules specially marked as autoreloadable.
146 self.modules = {}
146 self.modules = {}
147 # Modules specially marked as not autoreloadable.
147 # Modules specially marked as not autoreloadable.
148 self.skip_modules = {}
148 self.skip_modules = {}
149 # (module-name, name) -> weakref, for replacing old code objects
149 # (module-name, name) -> weakref, for replacing old code objects
150 self.old_objects = {}
150 self.old_objects = {}
151 # Module modification timestamps
151 # Module modification timestamps
152 self.modules_mtimes = {}
152 self.modules_mtimes = {}
153 self.shell = shell
153 self.shell = shell
154
154
155 # Cache module modification times
155 # Cache module modification times
156 self.check(check_all=True, do_reload=False)
156 self.check(check_all=True, do_reload=False)
157
157
158 def mark_module_skipped(self, module_name):
158 def mark_module_skipped(self, module_name):
159 """Skip reloading the named module in the future"""
159 """Skip reloading the named module in the future"""
160 try:
160 try:
161 del self.modules[module_name]
161 del self.modules[module_name]
162 except KeyError:
162 except KeyError:
163 pass
163 pass
164 self.skip_modules[module_name] = True
164 self.skip_modules[module_name] = True
165
165
166 def mark_module_reloadable(self, module_name):
166 def mark_module_reloadable(self, module_name):
167 """Reload the named module in the future (if it is imported)"""
167 """Reload the named module in the future (if it is imported)"""
168 try:
168 try:
169 del self.skip_modules[module_name]
169 del self.skip_modules[module_name]
170 except KeyError:
170 except KeyError:
171 pass
171 pass
172 self.modules[module_name] = True
172 self.modules[module_name] = True
173
173
174 def aimport_module(self, module_name):
174 def aimport_module(self, module_name):
175 """Import a module, and mark it reloadable
175 """Import a module, and mark it reloadable
176
176
177 Returns
177 Returns
178 -------
178 -------
179 top_module : module
179 top_module : module
180 The imported module if it is top-level, or the top-level
180 The imported module if it is top-level, or the top-level
181 top_name : module
181 top_name : module
182 Name of top_module
182 Name of top_module
183
183
184 """
184 """
185 self.mark_module_reloadable(module_name)
185 self.mark_module_reloadable(module_name)
186
186
187 import_module(module_name)
187 import_module(module_name)
188 top_name = module_name.split(".")[0]
188 top_name = module_name.split(".")[0]
189 top_module = sys.modules[top_name]
189 top_module = sys.modules[top_name]
190 return top_module, top_name
190 return top_module, top_name
191
191
192 def filename_and_mtime(self, module):
192 def filename_and_mtime(self, module):
193 if not hasattr(module, "__file__") or module.__file__ is None:
193 if not hasattr(module, "__file__") or module.__file__ is None:
194 return None, None
194 return None, None
195
195
196 if getattr(module, "__name__", None) in [None, "__mp_main__", "__main__"]:
196 if getattr(module, "__name__", None) in [None, "__mp_main__", "__main__"]:
197 # we cannot reload(__main__) or reload(__mp_main__)
197 # we cannot reload(__main__) or reload(__mp_main__)
198 return None, None
198 return None, None
199
199
200 filename = module.__file__
200 filename = module.__file__
201 path, ext = os.path.splitext(filename)
201 path, ext = os.path.splitext(filename)
202
202
203 if ext.lower() == ".py":
203 if ext.lower() == ".py":
204 py_filename = filename
204 py_filename = filename
205 else:
205 else:
206 try:
206 try:
207 py_filename = source_from_cache(filename)
207 py_filename = source_from_cache(filename)
208 except ValueError:
208 except ValueError:
209 return None, None
209 return None, None
210
210
211 try:
211 try:
212 pymtime = os.stat(py_filename).st_mtime
212 pymtime = os.stat(py_filename).st_mtime
213 except OSError:
213 except OSError:
214 return None, None
214 return None, None
215
215
216 return py_filename, pymtime
216 return py_filename, pymtime
217
217
218 def check(self, check_all=False, do_reload=True):
218 def check(self, check_all=False, do_reload=True):
219 """Check whether some modules need to be reloaded."""
219 """Check whether some modules need to be reloaded."""
220
220
221 if not self.enabled and not check_all:
221 if not self.enabled and not check_all:
222 return
222 return
223
223
224 if check_all or self.check_all:
224 if check_all or self.check_all:
225 modules = list(sys.modules.keys())
225 modules = list(sys.modules.keys())
226 else:
226 else:
227 modules = list(self.modules.keys())
227 modules = list(self.modules.keys())
228
228
229 for modname in modules:
229 for modname in modules:
230 m = sys.modules.get(modname, None)
230 m = sys.modules.get(modname, None)
231
231
232 if modname in self.skip_modules:
232 if modname in self.skip_modules:
233 continue
233 continue
234
234
235 py_filename, pymtime = self.filename_and_mtime(m)
235 py_filename, pymtime = self.filename_and_mtime(m)
236 if py_filename is None:
236 if py_filename is None:
237 continue
237 continue
238
238
239 try:
239 try:
240 if pymtime <= self.modules_mtimes[modname]:
240 if pymtime <= self.modules_mtimes[modname]:
241 continue
241 continue
242 except KeyError:
242 except KeyError:
243 self.modules_mtimes[modname] = pymtime
243 self.modules_mtimes[modname] = pymtime
244 continue
244 continue
245 else:
245 else:
246 if self.failed.get(py_filename, None) == pymtime:
246 if self.failed.get(py_filename, None) == pymtime:
247 continue
247 continue
248
248
249 self.modules_mtimes[modname] = pymtime
249 self.modules_mtimes[modname] = pymtime
250
250
251 # If we've reached this point, we should try to reload the module
251 # If we've reached this point, we should try to reload the module
252 if do_reload:
252 if do_reload:
253 try:
253 try:
254 if self.autoload_obj:
254 if self.autoload_obj:
255 superreload(m, reload, self.old_objects, self.shell)
255 superreload(m, reload, self.old_objects, self.shell)
256 else:
256 else:
257 superreload(m, reload, self.old_objects)
257 superreload(m, reload, self.old_objects)
258 if py_filename in self.failed:
258 if py_filename in self.failed:
259 del self.failed[py_filename]
259 del self.failed[py_filename]
260 except:
260 except:
261 print(
261 print(
262 "[autoreload of {} failed: {}]".format(
262 "[autoreload of {} failed: {}]".format(
263 modname, traceback.format_exc(10)
263 modname, traceback.format_exc(10)
264 ),
264 ),
265 file=sys.stderr,
265 file=sys.stderr,
266 )
266 )
267 self.failed[py_filename] = pymtime
267 self.failed[py_filename] = pymtime
268
268
269
269
270 # ------------------------------------------------------------------------------
270 # ------------------------------------------------------------------------------
271 # superreload
271 # superreload
272 # ------------------------------------------------------------------------------
272 # ------------------------------------------------------------------------------
273
273
274
274
275 func_attrs = [
275 func_attrs = [
276 "__code__",
276 "__code__",
277 "__defaults__",
277 "__defaults__",
278 "__doc__",
278 "__doc__",
279 "__closure__",
279 "__closure__",
280 "__globals__",
280 "__globals__",
281 "__dict__",
281 "__dict__",
282 ]
282 ]
283
283
284
284
285 def update_function(old, new):
285 def update_function(old, new):
286 """Upgrade the code object of a function"""
286 """Upgrade the code object of a function"""
287 for name in func_attrs:
287 for name in func_attrs:
288 try:
288 try:
289 setattr(old, name, getattr(new, name))
289 setattr(old, name, getattr(new, name))
290 except (AttributeError, TypeError):
290 except (AttributeError, TypeError):
291 pass
291 pass
292
292
293
293
294 def update_instances(old, new):
294 def update_instances(old, new):
295 """Use garbage collector to find all instances that refer to the old
295 """Use garbage collector to find all instances that refer to the old
296 class definition and update their __class__ to point to the new class
296 class definition and update their __class__ to point to the new class
297 definition"""
297 definition"""
298
298
299 refs = gc.get_referrers(old)
299 refs = gc.get_referrers(old)
300
300
301 for ref in refs:
301 for ref in refs:
302 if type(ref) is old:
302 if type(ref) is old:
303 ref.__class__ = new
303 object.__setattr__(ref, "__class__", new)
304
304
305
305
306 def update_class(old, new):
306 def update_class(old, new):
307 """Replace stuff in the __dict__ of a class, and upgrade
307 """Replace stuff in the __dict__ of a class, and upgrade
308 method code objects, and add new methods, if any"""
308 method code objects, and add new methods, if any"""
309 for key in list(old.__dict__.keys()):
309 for key in list(old.__dict__.keys()):
310 old_obj = getattr(old, key)
310 old_obj = getattr(old, key)
311 try:
311 try:
312 new_obj = getattr(new, key)
312 new_obj = getattr(new, key)
313 # explicitly checking that comparison returns True to handle
313 # explicitly checking that comparison returns True to handle
314 # cases where `==` doesn't return a boolean.
314 # cases where `==` doesn't return a boolean.
315 if (old_obj == new_obj) is True:
315 if (old_obj == new_obj) is True:
316 continue
316 continue
317 except AttributeError:
317 except AttributeError:
318 # obsolete attribute: remove it
318 # obsolete attribute: remove it
319 try:
319 try:
320 delattr(old, key)
320 delattr(old, key)
321 except (AttributeError, TypeError):
321 except (AttributeError, TypeError):
322 pass
322 pass
323 continue
323 continue
324 except ValueError:
324 except ValueError:
325 # can't compare nested structures containing
325 # can't compare nested structures containing
326 # numpy arrays using `==`
326 # numpy arrays using `==`
327 pass
327 pass
328
328
329 if update_generic(old_obj, new_obj):
329 if update_generic(old_obj, new_obj):
330 continue
330 continue
331
331
332 try:
332 try:
333 setattr(old, key, getattr(new, key))
333 setattr(old, key, getattr(new, key))
334 except (AttributeError, TypeError):
334 except (AttributeError, TypeError):
335 pass # skip non-writable attributes
335 pass # skip non-writable attributes
336
336
337 for key in list(new.__dict__.keys()):
337 for key in list(new.__dict__.keys()):
338 if key not in list(old.__dict__.keys()):
338 if key not in list(old.__dict__.keys()):
339 try:
339 try:
340 setattr(old, key, getattr(new, key))
340 setattr(old, key, getattr(new, key))
341 except (AttributeError, TypeError):
341 except (AttributeError, TypeError):
342 pass # skip non-writable attributes
342 pass # skip non-writable attributes
343
343
344 # update all instances of class
344 # update all instances of class
345 update_instances(old, new)
345 update_instances(old, new)
346
346
347
347
348 def update_property(old, new):
348 def update_property(old, new):
349 """Replace get/set/del functions of a property"""
349 """Replace get/set/del functions of a property"""
350 update_generic(old.fdel, new.fdel)
350 update_generic(old.fdel, new.fdel)
351 update_generic(old.fget, new.fget)
351 update_generic(old.fget, new.fget)
352 update_generic(old.fset, new.fset)
352 update_generic(old.fset, new.fset)
353
353
354
354
355 def isinstance2(a, b, typ):
355 def isinstance2(a, b, typ):
356 return isinstance(a, typ) and isinstance(b, typ)
356 return isinstance(a, typ) and isinstance(b, typ)
357
357
358
358
359 UPDATE_RULES = [
359 UPDATE_RULES = [
360 (lambda a, b: isinstance2(a, b, type), update_class),
360 (lambda a, b: isinstance2(a, b, type), update_class),
361 (lambda a, b: isinstance2(a, b, types.FunctionType), update_function),
361 (lambda a, b: isinstance2(a, b, types.FunctionType), update_function),
362 (lambda a, b: isinstance2(a, b, property), update_property),
362 (lambda a, b: isinstance2(a, b, property), update_property),
363 ]
363 ]
364 UPDATE_RULES.extend(
364 UPDATE_RULES.extend(
365 [
365 [
366 (
366 (
367 lambda a, b: isinstance2(a, b, types.MethodType),
367 lambda a, b: isinstance2(a, b, types.MethodType),
368 lambda a, b: update_function(a.__func__, b.__func__),
368 lambda a, b: update_function(a.__func__, b.__func__),
369 ),
369 ),
370 ]
370 ]
371 )
371 )
372
372
373
373
374 def update_generic(a, b):
374 def update_generic(a, b):
375 for type_check, update in UPDATE_RULES:
375 for type_check, update in UPDATE_RULES:
376 if type_check(a, b):
376 if type_check(a, b):
377 update(a, b)
377 update(a, b)
378 return True
378 return True
379 return False
379 return False
380
380
381
381
382 class StrongRef:
382 class StrongRef:
383 def __init__(self, obj):
383 def __init__(self, obj):
384 self.obj = obj
384 self.obj = obj
385
385
386 def __call__(self):
386 def __call__(self):
387 return self.obj
387 return self.obj
388
388
389
389
390 mod_attrs = [
390 mod_attrs = [
391 "__name__",
391 "__name__",
392 "__doc__",
392 "__doc__",
393 "__package__",
393 "__package__",
394 "__loader__",
394 "__loader__",
395 "__spec__",
395 "__spec__",
396 "__file__",
396 "__file__",
397 "__cached__",
397 "__cached__",
398 "__builtins__",
398 "__builtins__",
399 ]
399 ]
400
400
401
401
402 def append_obj(module, d, name, obj, autoload=False):
402 def append_obj(module, d, name, obj, autoload=False):
403 in_module = hasattr(obj, "__module__") and obj.__module__ == module.__name__
403 in_module = hasattr(obj, "__module__") and obj.__module__ == module.__name__
404 if autoload:
404 if autoload:
405 # check needed for module global built-ins
405 # check needed for module global built-ins
406 if not in_module and name in mod_attrs:
406 if not in_module and name in mod_attrs:
407 return False
407 return False
408 else:
408 else:
409 if not in_module:
409 if not in_module:
410 return False
410 return False
411
411
412 key = (module.__name__, name)
412 key = (module.__name__, name)
413 try:
413 try:
414 d.setdefault(key, []).append(weakref.ref(obj))
414 d.setdefault(key, []).append(weakref.ref(obj))
415 except TypeError:
415 except TypeError:
416 pass
416 pass
417 return True
417 return True
418
418
419
419
420 def superreload(module, reload=reload, old_objects=None, shell=None):
420 def superreload(module, reload=reload, old_objects=None, shell=None):
421 """Enhanced version of the builtin reload function.
421 """Enhanced version of the builtin reload function.
422
422
423 superreload remembers objects previously in the module, and
423 superreload remembers objects previously in the module, and
424
424
425 - upgrades the class dictionary of every old class in the module
425 - upgrades the class dictionary of every old class in the module
426 - upgrades the code object of every old function and method
426 - upgrades the code object of every old function and method
427 - clears the module's namespace before reloading
427 - clears the module's namespace before reloading
428
428
429 """
429 """
430 if old_objects is None:
430 if old_objects is None:
431 old_objects = {}
431 old_objects = {}
432
432
433 # collect old objects in the module
433 # collect old objects in the module
434 for name, obj in list(module.__dict__.items()):
434 for name, obj in list(module.__dict__.items()):
435 if not append_obj(module, old_objects, name, obj):
435 if not append_obj(module, old_objects, name, obj):
436 continue
436 continue
437 key = (module.__name__, name)
437 key = (module.__name__, name)
438 try:
438 try:
439 old_objects.setdefault(key, []).append(weakref.ref(obj))
439 old_objects.setdefault(key, []).append(weakref.ref(obj))
440 except TypeError:
440 except TypeError:
441 pass
441 pass
442
442
443 # reload module
443 # reload module
444 try:
444 try:
445 # clear namespace first from old cruft
445 # clear namespace first from old cruft
446 old_dict = module.__dict__.copy()
446 old_dict = module.__dict__.copy()
447 old_name = module.__name__
447 old_name = module.__name__
448 module.__dict__.clear()
448 module.__dict__.clear()
449 module.__dict__["__name__"] = old_name
449 module.__dict__["__name__"] = old_name
450 module.__dict__["__loader__"] = old_dict["__loader__"]
450 module.__dict__["__loader__"] = old_dict["__loader__"]
451 except (TypeError, AttributeError, KeyError):
451 except (TypeError, AttributeError, KeyError):
452 pass
452 pass
453
453
454 try:
454 try:
455 module = reload(module)
455 module = reload(module)
456 except:
456 except:
457 # restore module dictionary on failed reload
457 # restore module dictionary on failed reload
458 module.__dict__.update(old_dict)
458 module.__dict__.update(old_dict)
459 raise
459 raise
460
460
461 # iterate over all objects and update functions & classes
461 # iterate over all objects and update functions & classes
462 for name, new_obj in list(module.__dict__.items()):
462 for name, new_obj in list(module.__dict__.items()):
463 key = (module.__name__, name)
463 key = (module.__name__, name)
464 if key not in old_objects:
464 if key not in old_objects:
465 # here 'shell' acts both as a flag and as an output var
465 # here 'shell' acts both as a flag and as an output var
466 if (
466 if (
467 shell is None
467 shell is None
468 or name == "Enum"
468 or name == "Enum"
469 or not append_obj(module, old_objects, name, new_obj, True)
469 or not append_obj(module, old_objects, name, new_obj, True)
470 ):
470 ):
471 continue
471 continue
472 shell.user_ns[name] = new_obj
472 shell.user_ns[name] = new_obj
473
473
474 new_refs = []
474 new_refs = []
475 for old_ref in old_objects[key]:
475 for old_ref in old_objects[key]:
476 old_obj = old_ref()
476 old_obj = old_ref()
477 if old_obj is None:
477 if old_obj is None:
478 continue
478 continue
479 new_refs.append(old_ref)
479 new_refs.append(old_ref)
480 update_generic(old_obj, new_obj)
480 update_generic(old_obj, new_obj)
481
481
482 if new_refs:
482 if new_refs:
483 old_objects[key] = new_refs
483 old_objects[key] = new_refs
484 else:
484 else:
485 del old_objects[key]
485 del old_objects[key]
486
486
487 return module
487 return module
488
488
489
489
490 # ------------------------------------------------------------------------------
490 # ------------------------------------------------------------------------------
491 # IPython connectivity
491 # IPython connectivity
492 # ------------------------------------------------------------------------------
492 # ------------------------------------------------------------------------------
493
493
494 from IPython.core.magic import Magics, magics_class, line_magic
494 from IPython.core.magic import Magics, magics_class, line_magic
495
495
496
496
497 @magics_class
497 @magics_class
498 class AutoreloadMagics(Magics):
498 class AutoreloadMagics(Magics):
499 def __init__(self, *a, **kw):
499 def __init__(self, *a, **kw):
500 super().__init__(*a, **kw)
500 super().__init__(*a, **kw)
501 self._reloader = ModuleReloader(self.shell)
501 self._reloader = ModuleReloader(self.shell)
502 self._reloader.check_all = False
502 self._reloader.check_all = False
503 self._reloader.autoload_obj = False
503 self._reloader.autoload_obj = False
504 self.loaded_modules = set(sys.modules)
504 self.loaded_modules = set(sys.modules)
505
505
506 @line_magic
506 @line_magic
507 def autoreload(self, parameter_s=""):
507 def autoreload(self, parameter_s=""):
508 r"""%autoreload => Reload modules automatically
508 r"""%autoreload => Reload modules automatically
509
509
510 %autoreload
510 %autoreload
511 Reload all modules (except those excluded by %aimport) automatically
511 Reload all modules (except those excluded by %aimport) automatically
512 now.
512 now.
513
513
514 %autoreload 0
514 %autoreload 0
515 Disable automatic reloading.
515 Disable automatic reloading.
516
516
517 %autoreload 1
517 %autoreload 1
518 Reload all modules imported with %aimport every time before executing
518 Reload all modules imported with %aimport every time before executing
519 the Python code typed.
519 the Python code typed.
520
520
521 %autoreload 2
521 %autoreload 2
522 Reload all modules (except those excluded by %aimport) every time
522 Reload all modules (except those excluded by %aimport) every time
523 before executing the Python code typed.
523 before executing the Python code typed.
524
524
525 Reloading Python modules in a reliable way is in general
525 Reloading Python modules in a reliable way is in general
526 difficult, and unexpected things may occur. %autoreload tries to
526 difficult, and unexpected things may occur. %autoreload tries to
527 work around common pitfalls by replacing function code objects and
527 work around common pitfalls by replacing function code objects and
528 parts of classes previously in the module with new versions. This
528 parts of classes previously in the module with new versions. This
529 makes the following things to work:
529 makes the following things to work:
530
530
531 - Functions and classes imported via 'from xxx import foo' are upgraded
531 - Functions and classes imported via 'from xxx import foo' are upgraded
532 to new versions when 'xxx' is reloaded.
532 to new versions when 'xxx' is reloaded.
533
533
534 - Methods and properties of classes are upgraded on reload, so that
534 - Methods and properties of classes are upgraded on reload, so that
535 calling 'c.foo()' on an object 'c' created before the reload causes
535 calling 'c.foo()' on an object 'c' created before the reload causes
536 the new code for 'foo' to be executed.
536 the new code for 'foo' to be executed.
537
537
538 Some of the known remaining caveats are:
538 Some of the known remaining caveats are:
539
539
540 - Replacing code objects does not always succeed: changing a @property
540 - Replacing code objects does not always succeed: changing a @property
541 in a class to an ordinary method or a method to a member variable
541 in a class to an ordinary method or a method to a member variable
542 can cause problems (but in old objects only).
542 can cause problems (but in old objects only).
543
543
544 - Functions that are removed (eg. via monkey-patching) from a module
544 - Functions that are removed (eg. via monkey-patching) from a module
545 before it is reloaded are not upgraded.
545 before it is reloaded are not upgraded.
546
546
547 - C extension modules cannot be reloaded, and so cannot be
547 - C extension modules cannot be reloaded, and so cannot be
548 autoreloaded.
548 autoreloaded.
549
549
550 """
550 """
551 if parameter_s == "":
551 if parameter_s == "":
552 self._reloader.check(True)
552 self._reloader.check(True)
553 elif parameter_s == "0":
553 elif parameter_s == "0":
554 self._reloader.enabled = False
554 self._reloader.enabled = False
555 elif parameter_s == "1":
555 elif parameter_s == "1":
556 self._reloader.check_all = False
556 self._reloader.check_all = False
557 self._reloader.enabled = True
557 self._reloader.enabled = True
558 elif parameter_s == "2":
558 elif parameter_s == "2":
559 self._reloader.check_all = True
559 self._reloader.check_all = True
560 self._reloader.enabled = True
560 self._reloader.enabled = True
561 self._reloader.enabled = True
561 self._reloader.enabled = True
562 elif parameter_s == "3":
562 elif parameter_s == "3":
563 self._reloader.check_all = True
563 self._reloader.check_all = True
564 self._reloader.enabled = True
564 self._reloader.enabled = True
565 self._reloader.autoload_obj = True
565 self._reloader.autoload_obj = True
566
566
567 @line_magic
567 @line_magic
568 def aimport(self, parameter_s="", stream=None):
568 def aimport(self, parameter_s="", stream=None):
569 """%aimport => Import modules for automatic reloading.
569 """%aimport => Import modules for automatic reloading.
570
570
571 %aimport
571 %aimport
572 List modules to automatically import and not to import.
572 List modules to automatically import and not to import.
573
573
574 %aimport foo
574 %aimport foo
575 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
575 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
576
576
577 %aimport foo, bar
577 %aimport foo, bar
578 Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload 1
578 Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload 1
579
579
580 %aimport -foo
580 %aimport -foo
581 Mark module 'foo' to not be autoreloaded for %autoreload 1
581 Mark module 'foo' to not be autoreloaded for %autoreload 1
582 """
582 """
583 modname = parameter_s
583 modname = parameter_s
584 if not modname:
584 if not modname:
585 to_reload = sorted(self._reloader.modules.keys())
585 to_reload = sorted(self._reloader.modules.keys())
586 to_skip = sorted(self._reloader.skip_modules.keys())
586 to_skip = sorted(self._reloader.skip_modules.keys())
587 if stream is None:
587 if stream is None:
588 stream = sys.stdout
588 stream = sys.stdout
589 if self._reloader.check_all:
589 if self._reloader.check_all:
590 stream.write("Modules to reload:\nall-except-skipped\n")
590 stream.write("Modules to reload:\nall-except-skipped\n")
591 else:
591 else:
592 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
592 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
593 stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip))
593 stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip))
594 elif modname.startswith("-"):
594 elif modname.startswith("-"):
595 modname = modname[1:]
595 modname = modname[1:]
596 self._reloader.mark_module_skipped(modname)
596 self._reloader.mark_module_skipped(modname)
597 else:
597 else:
598 for _module in [_.strip() for _ in modname.split(",")]:
598 for _module in [_.strip() for _ in modname.split(",")]:
599 top_module, top_name = self._reloader.aimport_module(_module)
599 top_module, top_name = self._reloader.aimport_module(_module)
600
600
601 # Inject module to user namespace
601 # Inject module to user namespace
602 self.shell.push({top_name: top_module})
602 self.shell.push({top_name: top_module})
603
603
604 def pre_run_cell(self):
604 def pre_run_cell(self):
605 if self._reloader.enabled:
605 if self._reloader.enabled:
606 try:
606 try:
607 self._reloader.check()
607 self._reloader.check()
608 except:
608 except:
609 pass
609 pass
610
610
611 def post_execute_hook(self):
611 def post_execute_hook(self):
612 """Cache the modification times of any modules imported in this execution"""
612 """Cache the modification times of any modules imported in this execution"""
613 newly_loaded_modules = set(sys.modules) - self.loaded_modules
613 newly_loaded_modules = set(sys.modules) - self.loaded_modules
614 for modname in newly_loaded_modules:
614 for modname in newly_loaded_modules:
615 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
615 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
616 if pymtime is not None:
616 if pymtime is not None:
617 self._reloader.modules_mtimes[modname] = pymtime
617 self._reloader.modules_mtimes[modname] = pymtime
618
618
619 self.loaded_modules.update(newly_loaded_modules)
619 self.loaded_modules.update(newly_loaded_modules)
620
620
621
621
622 def load_ipython_extension(ip):
622 def load_ipython_extension(ip):
623 """Load the extension in IPython."""
623 """Load the extension in IPython."""
624 auto_reload = AutoreloadMagics(ip)
624 auto_reload = AutoreloadMagics(ip)
625 ip.register_magics(auto_reload)
625 ip.register_magics(auto_reload)
626 ip.events.register("pre_run_cell", auto_reload.pre_run_cell)
626 ip.events.register("pre_run_cell", auto_reload.pre_run_cell)
627 ip.events.register("post_execute", auto_reload.post_execute_hook)
627 ip.events.register("post_execute", auto_reload.post_execute_hook)
@@ -1,69 +1,101 b''
1 """ Utilities for accessing the platform's clipboard.
1 """ Utilities for accessing the platform's clipboard.
2 """
2 """
3
3 import os
4 import subprocess
4 import subprocess
5
5
6 from IPython.core.error import TryNext
6 from IPython.core.error import TryNext
7 import IPython.utils.py3compat as py3compat
7 import IPython.utils.py3compat as py3compat
8
8
9
9 class ClipboardEmpty(ValueError):
10 class ClipboardEmpty(ValueError):
10 pass
11 pass
11
12
13
12 def win32_clipboard_get():
14 def win32_clipboard_get():
13 """ Get the current clipboard's text on Windows.
15 """ Get the current clipboard's text on Windows.
14
16
15 Requires Mark Hammond's pywin32 extensions.
17 Requires Mark Hammond's pywin32 extensions.
16 """
18 """
17 try:
19 try:
18 import win32clipboard
20 import win32clipboard
19 except ImportError as e:
21 except ImportError as e:
20 raise TryNext("Getting text from the clipboard requires the pywin32 "
22 raise TryNext("Getting text from the clipboard requires the pywin32 "
21 "extensions: http://sourceforge.net/projects/pywin32/") from e
23 "extensions: http://sourceforge.net/projects/pywin32/") from e
22 win32clipboard.OpenClipboard()
24 win32clipboard.OpenClipboard()
23 try:
25 try:
24 text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
26 text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
25 except (TypeError, win32clipboard.error):
27 except (TypeError, win32clipboard.error):
26 try:
28 try:
27 text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)
29 text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)
28 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
30 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
29 except (TypeError, win32clipboard.error) as e:
31 except (TypeError, win32clipboard.error) as e:
30 raise ClipboardEmpty from e
32 raise ClipboardEmpty from e
31 finally:
33 finally:
32 win32clipboard.CloseClipboard()
34 win32clipboard.CloseClipboard()
33 return text
35 return text
34
36
37
35 def osx_clipboard_get() -> str:
38 def osx_clipboard_get() -> str:
36 """ Get the clipboard's text on OS X.
39 """ Get the clipboard's text on OS X.
37 """
40 """
38 p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'],
41 p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'],
39 stdout=subprocess.PIPE)
42 stdout=subprocess.PIPE)
40 bytes_, stderr = p.communicate()
43 bytes_, stderr = p.communicate()
41 # Text comes in with old Mac \r line endings. Change them to \n.
44 # Text comes in with old Mac \r line endings. Change them to \n.
42 bytes_ = bytes_.replace(b'\r', b'\n')
45 bytes_ = bytes_.replace(b'\r', b'\n')
43 text = py3compat.decode(bytes_)
46 text = py3compat.decode(bytes_)
44 return text
47 return text
45
48
49
46 def tkinter_clipboard_get():
50 def tkinter_clipboard_get():
47 """ Get the clipboard's text using Tkinter.
51 """ Get the clipboard's text using Tkinter.
48
52
49 This is the default on systems that are not Windows or OS X. It may
53 This is the default on systems that are not Windows or OS X. It may
50 interfere with other UI toolkits and should be replaced with an
54 interfere with other UI toolkits and should be replaced with an
51 implementation that uses that toolkit.
55 implementation that uses that toolkit.
52 """
56 """
53 try:
57 try:
54 from tkinter import Tk, TclError
58 from tkinter import Tk, TclError
55 except ImportError as e:
59 except ImportError as e:
56 raise TryNext("Getting text from the clipboard on this platform requires tkinter.") from e
60 raise TryNext("Getting text from the clipboard on this platform requires tkinter.") from e
57
61
58 root = Tk()
62 root = Tk()
59 root.withdraw()
63 root.withdraw()
60 try:
64 try:
61 text = root.clipboard_get()
65 text = root.clipboard_get()
62 except TclError as e:
66 except TclError as e:
63 raise ClipboardEmpty from e
67 raise ClipboardEmpty from e
64 finally:
68 finally:
65 root.destroy()
69 root.destroy()
66 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
70 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
67 return text
71 return text
68
72
69
73
74 def wayland_clipboard_get():
75 """Get the clipboard's text under Wayland using wl-paste command.
76
77 This requires Wayland and wl-clipboard installed and running.
78 """
79 if os.environ.get("XDG_SESSION_TYPE") != "wayland":
80 raise TryNext("wayland is not detected")
81
82 try:
83 with subprocess.Popen(["wl-paste"], stdout=subprocess.PIPE) as p:
84 raw, err = p.communicate()
85 if p.wait():
86 raise TryNext(err)
87 except FileNotFoundError as e:
88 raise TryNext(
89 "Getting text from the clipboard under Wayland requires the wl-clipboard "
90 "extension: https://github.com/bugaevc/wl-clipboard"
91 ) from e
92
93 if not raw:
94 raise ClipboardEmpty
95
96 try:
97 text = py3compat.decode(raw)
98 except UnicodeDecodeError as e:
99 raise ClipboardEmpty from e
100
101 return text
@@ -1,246 +1,258 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX."""
2 """Tools for handling LaTeX."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from io import BytesIO, open
7 from io import BytesIO, open
8 import os
8 import os
9 import tempfile
9 import tempfile
10 import shutil
10 import shutil
11 import subprocess
11 import subprocess
12 from base64 import encodebytes
12 from base64 import encodebytes
13 import textwrap
13 import textwrap
14
14
15 from pathlib import Path, PurePath
15 from pathlib import Path, PurePath
16
16
17 from IPython.utils.process import find_cmd, FindCmdError
17 from IPython.utils.process import find_cmd, FindCmdError
18 from traitlets.config import get_config
18 from traitlets.config import get_config
19 from traitlets.config.configurable import SingletonConfigurable
19 from traitlets.config.configurable import SingletonConfigurable
20 from traitlets import List, Bool, Unicode
20 from traitlets import List, Bool, Unicode
21 from IPython.utils.py3compat import cast_unicode
21 from IPython.utils.py3compat import cast_unicode
22
22
23
23
24 class LaTeXTool(SingletonConfigurable):
24 class LaTeXTool(SingletonConfigurable):
25 """An object to store configuration of the LaTeX tool."""
25 """An object to store configuration of the LaTeX tool."""
26 def _config_default(self):
26 def _config_default(self):
27 return get_config()
27 return get_config()
28
28
29 backends = List(
29 backends = List(
30 Unicode(), ["matplotlib", "dvipng"],
30 Unicode(), ["matplotlib", "dvipng"],
31 help="Preferred backend to draw LaTeX math equations. "
31 help="Preferred backend to draw LaTeX math equations. "
32 "Backends in the list are checked one by one and the first "
32 "Backends in the list are checked one by one and the first "
33 "usable one is used. Note that `matplotlib` backend "
33 "usable one is used. Note that `matplotlib` backend "
34 "is usable only for inline style equations. To draw "
34 "is usable only for inline style equations. To draw "
35 "display style equations, `dvipng` backend must be specified. ",
35 "display style equations, `dvipng` backend must be specified. ",
36 # It is a List instead of Enum, to make configuration more
36 # It is a List instead of Enum, to make configuration more
37 # flexible. For example, to use matplotlib mainly but dvipng
37 # flexible. For example, to use matplotlib mainly but dvipng
38 # for display style, the default ["matplotlib", "dvipng"] can
38 # for display style, the default ["matplotlib", "dvipng"] can
39 # be used. To NOT use dvipng so that other repr such as
39 # be used. To NOT use dvipng so that other repr such as
40 # unicode pretty printing is used, you can use ["matplotlib"].
40 # unicode pretty printing is used, you can use ["matplotlib"].
41 ).tag(config=True)
41 ).tag(config=True)
42
42
43 use_breqn = Bool(
43 use_breqn = Bool(
44 True,
44 True,
45 help="Use breqn.sty to automatically break long equations. "
45 help="Use breqn.sty to automatically break long equations. "
46 "This configuration takes effect only for dvipng backend.",
46 "This configuration takes effect only for dvipng backend.",
47 ).tag(config=True)
47 ).tag(config=True)
48
48
49 packages = List(
49 packages = List(
50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
51 help="A list of packages to use for dvipng backend. "
51 help="A list of packages to use for dvipng backend. "
52 "'breqn' will be automatically appended when use_breqn=True.",
52 "'breqn' will be automatically appended when use_breqn=True.",
53 ).tag(config=True)
53 ).tag(config=True)
54
54
55 preamble = Unicode(
55 preamble = Unicode(
56 help="Additional preamble to use when generating LaTeX source "
56 help="Additional preamble to use when generating LaTeX source "
57 "for dvipng backend.",
57 "for dvipng backend.",
58 ).tag(config=True)
58 ).tag(config=True)
59
59
60
60
61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
62 scale=1.0):
62 scale=1.0):
63 """Render a LaTeX string to PNG.
63 """Render a LaTeX string to PNG.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 s : str
67 s : str
68 The raw string containing valid inline LaTeX.
68 The raw string containing valid inline LaTeX.
69 encode : bool, optional
69 encode : bool, optional
70 Should the PNG data base64 encoded to make it JSON'able.
70 Should the PNG data base64 encoded to make it JSON'able.
71 backend : {matplotlib, dvipng}
71 backend : {matplotlib, dvipng}
72 Backend for producing PNG data.
72 Backend for producing PNG data.
73 wrap : bool
73 wrap : bool
74 If true, Automatically wrap `s` as a LaTeX equation.
74 If true, Automatically wrap `s` as a LaTeX equation.
75 color : string
75 color : string
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
77 format, e.g. '#AA20FA'.
77 format, e.g. '#AA20FA'.
78 scale : float
78 scale : float
79 Scale factor for the resulting PNG.
79 Scale factor for the resulting PNG.
80 None is returned when the backend cannot be used.
80 None is returned when the backend cannot be used.
81
81
82 """
82 """
83 s = cast_unicode(s)
83 s = cast_unicode(s)
84 allowed_backends = LaTeXTool.instance().backends
84 allowed_backends = LaTeXTool.instance().backends
85 if backend is None:
85 if backend is None:
86 backend = allowed_backends[0]
86 backend = allowed_backends[0]
87 if backend not in allowed_backends:
87 if backend not in allowed_backends:
88 return None
88 return None
89 if backend == 'matplotlib':
89 if backend == 'matplotlib':
90 f = latex_to_png_mpl
90 f = latex_to_png_mpl
91 elif backend == 'dvipng':
91 elif backend == 'dvipng':
92 f = latex_to_png_dvipng
92 f = latex_to_png_dvipng
93 if color.startswith('#'):
93 if color.startswith('#'):
94 # Convert hex RGB color to LaTeX RGB color.
94 # Convert hex RGB color to LaTeX RGB color.
95 if len(color) == 7:
95 if len(color) == 7:
96 try:
96 try:
97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
98 textwrap.wrap(color[1:], 2)]))
98 textwrap.wrap(color[1:], 2)]))
99 except ValueError as e:
99 except ValueError as e:
100 raise ValueError('Invalid color specification {}.'.format(color)) from e
100 raise ValueError('Invalid color specification {}.'.format(color)) from e
101 else:
101 else:
102 raise ValueError('Invalid color specification {}.'.format(color))
102 raise ValueError('Invalid color specification {}.'.format(color))
103 else:
103 else:
104 raise ValueError('No such backend {0}'.format(backend))
104 raise ValueError('No such backend {0}'.format(backend))
105 bin_data = f(s, wrap, color, scale)
105 bin_data = f(s, wrap, color, scale)
106 if encode and bin_data:
106 if encode and bin_data:
107 bin_data = encodebytes(bin_data)
107 bin_data = encodebytes(bin_data)
108 return bin_data
108 return bin_data
109
109
110
110
111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
112 try:
112 try:
113 from matplotlib import figure, font_manager, mathtext
113 from matplotlib import figure, font_manager, mathtext
114 from matplotlib.backends import backend_agg
114 from matplotlib.backends import backend_agg
115 from pyparsing import ParseFatalException
115 from pyparsing import ParseFatalException
116 except ImportError:
116 except ImportError:
117 return None
117 return None
118
118
119 # mpl mathtext doesn't support display math, force inline
119 # mpl mathtext doesn't support display math, force inline
120 s = s.replace('$$', '$')
120 s = s.replace('$$', '$')
121 if wrap:
121 if wrap:
122 s = u'${0}$'.format(s)
122 s = u'${0}$'.format(s)
123
123
124 try:
124 try:
125 prop = font_manager.FontProperties(size=12)
125 prop = font_manager.FontProperties(size=12)
126 dpi = 120 * scale
126 dpi = 120 * scale
127 buffer = BytesIO()
127 buffer = BytesIO()
128
128
129 # Adapted from mathtext.math_to_image
129 # Adapted from mathtext.math_to_image
130 parser = mathtext.MathTextParser("path")
130 parser = mathtext.MathTextParser("path")
131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
132 fig = figure.Figure(figsize=(width / 72, height / 72))
132 fig = figure.Figure(figsize=(width / 72, height / 72))
133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
134 backend_agg.FigureCanvasAgg(fig)
134 backend_agg.FigureCanvasAgg(fig)
135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
136 return buffer.getvalue()
136 return buffer.getvalue()
137 except (ValueError, RuntimeError, ParseFatalException):
137 except (ValueError, RuntimeError, ParseFatalException):
138 return None
138 return None
139
139
140
140
141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
142 try:
142 try:
143 find_cmd('latex')
143 find_cmd('latex')
144 find_cmd('dvipng')
144 find_cmd('dvipng')
145 except FindCmdError:
145 except FindCmdError:
146 return None
146 return None
147
148 startupinfo = None
149 if os.name == "nt":
150 # prevent popup-windows
151 startupinfo = subprocess.STARTUPINFO()
152 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
153
147 try:
154 try:
148 workdir = Path(tempfile.mkdtemp())
155 workdir = Path(tempfile.mkdtemp())
149 tmpfile = workdir.joinpath("tmp.tex")
156 tmpfile = "tmp.tex"
150 dvifile = workdir.joinpath("tmp.dvi")
157 dvifile = "tmp.dvi"
151 outfile = workdir.joinpath("tmp.png")
158 outfile = "tmp.png"
152
159
153 with tmpfile.open("w", encoding="utf8") as f:
160 with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
154 f.writelines(genelatex(s, wrap))
161 f.writelines(genelatex(s, wrap))
155
162
156 with open(os.devnull, 'wb') as devnull:
163 with open(os.devnull, 'wb') as devnull:
157 subprocess.check_call(
164 subprocess.check_call(
158 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
165 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
159 cwd=workdir, stdout=devnull, stderr=devnull)
166 cwd=workdir,
167 stdout=devnull,
168 stderr=devnull,
169 startupinfo=startupinfo,
170 )
160
171
161 resolution = round(150*scale)
172 resolution = round(150*scale)
162 subprocess.check_call(
173 subprocess.check_call(
163 [
174 [
164 "dvipng",
175 "dvipng",
165 "-T",
176 "-T",
166 "tight",
177 "tight",
167 "-D",
178 "-D",
168 str(resolution),
179 str(resolution),
169 "-z",
180 "-z",
170 "9",
181 "9",
171 "-bg",
182 "-bg",
172 "Transparent",
183 "Transparent",
173 "-o",
184 "-o",
174 outfile,
185 outfile,
175 dvifile,
186 dvifile,
176 "-fg",
187 "-fg",
177 color,
188 color,
178 ],
189 ],
179 cwd=workdir,
190 cwd=workdir,
180 stdout=devnull,
191 stdout=devnull,
181 stderr=devnull,
192 stderr=devnull,
193 startupinfo=startupinfo,
182 )
194 )
183
195
184 with outfile.open("rb") as f:
196 with workdir.joinpath(outfile).open("rb") as f:
185 return f.read()
197 return f.read()
186 except subprocess.CalledProcessError:
198 except subprocess.CalledProcessError:
187 return None
199 return None
188 finally:
200 finally:
189 shutil.rmtree(workdir)
201 shutil.rmtree(workdir)
190
202
191
203
192 def kpsewhich(filename):
204 def kpsewhich(filename):
193 """Invoke kpsewhich command with an argument `filename`."""
205 """Invoke kpsewhich command with an argument `filename`."""
194 try:
206 try:
195 find_cmd("kpsewhich")
207 find_cmd("kpsewhich")
196 proc = subprocess.Popen(
208 proc = subprocess.Popen(
197 ["kpsewhich", filename],
209 ["kpsewhich", filename],
198 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
210 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
199 (stdout, stderr) = proc.communicate()
211 (stdout, stderr) = proc.communicate()
200 return stdout.strip().decode('utf8', 'replace')
212 return stdout.strip().decode('utf8', 'replace')
201 except FindCmdError:
213 except FindCmdError:
202 pass
214 pass
203
215
204
216
205 def genelatex(body, wrap):
217 def genelatex(body, wrap):
206 """Generate LaTeX document for dvipng backend."""
218 """Generate LaTeX document for dvipng backend."""
207 lt = LaTeXTool.instance()
219 lt = LaTeXTool.instance()
208 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
220 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
209 yield r'\documentclass{article}'
221 yield r'\documentclass{article}'
210 packages = lt.packages
222 packages = lt.packages
211 if breqn:
223 if breqn:
212 packages = packages + ['breqn']
224 packages = packages + ['breqn']
213 for pack in packages:
225 for pack in packages:
214 yield r'\usepackage{{{0}}}'.format(pack)
226 yield r'\usepackage{{{0}}}'.format(pack)
215 yield r'\pagestyle{empty}'
227 yield r'\pagestyle{empty}'
216 if lt.preamble:
228 if lt.preamble:
217 yield lt.preamble
229 yield lt.preamble
218 yield r'\begin{document}'
230 yield r'\begin{document}'
219 if breqn:
231 if breqn:
220 yield r'\begin{dmath*}'
232 yield r'\begin{dmath*}'
221 yield body
233 yield body
222 yield r'\end{dmath*}'
234 yield r'\end{dmath*}'
223 elif wrap:
235 elif wrap:
224 yield u'$${0}$$'.format(body)
236 yield u'$${0}$$'.format(body)
225 else:
237 else:
226 yield body
238 yield body
227 yield u'\\end{document}'
239 yield u'\\end{document}'
228
240
229
241
230 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
242 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
231
243
232 def latex_to_html(s, alt='image'):
244 def latex_to_html(s, alt='image'):
233 """Render LaTeX to HTML with embedded PNG data using data URIs.
245 """Render LaTeX to HTML with embedded PNG data using data URIs.
234
246
235 Parameters
247 Parameters
236 ----------
248 ----------
237 s : str
249 s : str
238 The raw string containing valid inline LateX.
250 The raw string containing valid inline LateX.
239 alt : str
251 alt : str
240 The alt text to use for the HTML.
252 The alt text to use for the HTML.
241 """
253 """
242 base64_data = latex_to_png(s, encode=True).decode('ascii')
254 base64_data = latex_to_png(s, encode=True).decode('ascii')
243 if base64_data:
255 if base64_data:
244 return _data_uri_template_png % (base64_data, alt)
256 return _data_uri_template_png % (base64_data, alt)
245
257
246
258
@@ -1,951 +1,953 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Python advanced pretty printer. This pretty printer is intended to
3 Python advanced pretty printer. This pretty printer is intended to
4 replace the old `pprint` python module which does not allow developers
4 replace the old `pprint` python module which does not allow developers
5 to provide their own pretty print callbacks.
5 to provide their own pretty print callbacks.
6
6
7 This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`.
7 This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`.
8
8
9
9
10 Example Usage
10 Example Usage
11 -------------
11 -------------
12
12
13 To directly print the representation of an object use `pprint`::
13 To directly print the representation of an object use `pprint`::
14
14
15 from pretty import pprint
15 from pretty import pprint
16 pprint(complex_object)
16 pprint(complex_object)
17
17
18 To get a string of the output use `pretty`::
18 To get a string of the output use `pretty`::
19
19
20 from pretty import pretty
20 from pretty import pretty
21 string = pretty(complex_object)
21 string = pretty(complex_object)
22
22
23
23
24 Extending
24 Extending
25 ---------
25 ---------
26
26
27 The pretty library allows developers to add pretty printing rules for their
27 The pretty library allows developers to add pretty printing rules for their
28 own objects. This process is straightforward. All you have to do is to
28 own objects. This process is straightforward. All you have to do is to
29 add a `_repr_pretty_` method to your object and call the methods on the
29 add a `_repr_pretty_` method to your object and call the methods on the
30 pretty printer passed::
30 pretty printer passed::
31
31
32 class MyObject(object):
32 class MyObject(object):
33
33
34 def _repr_pretty_(self, p, cycle):
34 def _repr_pretty_(self, p, cycle):
35 ...
35 ...
36
36
37 Here's an example for a class with a simple constructor::
37 Here's an example for a class with a simple constructor::
38
38
39 class MySimpleObject:
39 class MySimpleObject:
40
40
41 def __init__(self, a, b, *, c=None):
41 def __init__(self, a, b, *, c=None):
42 self.a = a
42 self.a = a
43 self.b = b
43 self.b = b
44 self.c = c
44 self.c = c
45
45
46 def _repr_pretty_(self, p, cycle):
46 def _repr_pretty_(self, p, cycle):
47 ctor = CallExpression.factory(self.__class__.__name__)
47 ctor = CallExpression.factory(self.__class__.__name__)
48 if self.c is None:
48 if self.c is None:
49 p.pretty(ctor(a, b))
49 p.pretty(ctor(a, b))
50 else:
50 else:
51 p.pretty(ctor(a, b, c=c))
51 p.pretty(ctor(a, b, c=c))
52
52
53 Here is an example implementation of a `_repr_pretty_` method for a list
53 Here is an example implementation of a `_repr_pretty_` method for a list
54 subclass::
54 subclass::
55
55
56 class MyList(list):
56 class MyList(list):
57
57
58 def _repr_pretty_(self, p, cycle):
58 def _repr_pretty_(self, p, cycle):
59 if cycle:
59 if cycle:
60 p.text('MyList(...)')
60 p.text('MyList(...)')
61 else:
61 else:
62 with p.group(8, 'MyList([', '])'):
62 with p.group(8, 'MyList([', '])'):
63 for idx, item in enumerate(self):
63 for idx, item in enumerate(self):
64 if idx:
64 if idx:
65 p.text(',')
65 p.text(',')
66 p.breakable()
66 p.breakable()
67 p.pretty(item)
67 p.pretty(item)
68
68
69 The `cycle` parameter is `True` if pretty detected a cycle. You *have* to
69 The `cycle` parameter is `True` if pretty detected a cycle. You *have* to
70 react to that or the result is an infinite loop. `p.text()` just adds
70 react to that or the result is an infinite loop. `p.text()` just adds
71 non breaking text to the output, `p.breakable()` either adds a whitespace
71 non breaking text to the output, `p.breakable()` either adds a whitespace
72 or breaks here. If you pass it an argument it's used instead of the
72 or breaks here. If you pass it an argument it's used instead of the
73 default space. `p.pretty` prettyprints another object using the pretty print
73 default space. `p.pretty` prettyprints another object using the pretty print
74 method.
74 method.
75
75
76 The first parameter to the `group` function specifies the extra indentation
76 The first parameter to the `group` function specifies the extra indentation
77 of the next line. In this example the next item will either be on the same
77 of the next line. In this example the next item will either be on the same
78 line (if the items are short enough) or aligned with the right edge of the
78 line (if the items are short enough) or aligned with the right edge of the
79 opening bracket of `MyList`.
79 opening bracket of `MyList`.
80
80
81 If you just want to indent something you can use the group function
81 If you just want to indent something you can use the group function
82 without open / close parameters. You can also use this code::
82 without open / close parameters. You can also use this code::
83
83
84 with p.indent(2):
84 with p.indent(2):
85 ...
85 ...
86
86
87 Inheritance diagram:
87 Inheritance diagram:
88
88
89 .. inheritance-diagram:: IPython.lib.pretty
89 .. inheritance-diagram:: IPython.lib.pretty
90 :parts: 3
90 :parts: 3
91
91
92 :copyright: 2007 by Armin Ronacher.
92 :copyright: 2007 by Armin Ronacher.
93 Portions (c) 2009 by Robert Kern.
93 Portions (c) 2009 by Robert Kern.
94 :license: BSD License.
94 :license: BSD License.
95 """
95 """
96
96
97 from contextlib import contextmanager
97 from contextlib import contextmanager
98 import datetime
98 import datetime
99 import os
99 import os
100 import re
100 import re
101 import sys
101 import sys
102 import types
102 import types
103 from collections import deque
103 from collections import deque
104 from inspect import signature
104 from inspect import signature
105 from io import StringIO
105 from io import StringIO
106 from warnings import warn
106 from warnings import warn
107
107
108 from IPython.utils.decorators import undoc
108 from IPython.utils.decorators import undoc
109 from IPython.utils.py3compat import PYPY
109 from IPython.utils.py3compat import PYPY
110
110
111 __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter',
111 __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter',
112 'for_type', 'for_type_by_name', 'RawText', 'RawStringLiteral', 'CallExpression']
112 'for_type', 'for_type_by_name', 'RawText', 'RawStringLiteral', 'CallExpression']
113
113
114
114
115 MAX_SEQ_LENGTH = 1000
115 MAX_SEQ_LENGTH = 1000
116 _re_pattern_type = type(re.compile(''))
116 _re_pattern_type = type(re.compile(''))
117
117
118 def _safe_getattr(obj, attr, default=None):
118 def _safe_getattr(obj, attr, default=None):
119 """Safe version of getattr.
119 """Safe version of getattr.
120
120
121 Same as getattr, but will return ``default`` on any Exception,
121 Same as getattr, but will return ``default`` on any Exception,
122 rather than raising.
122 rather than raising.
123 """
123 """
124 try:
124 try:
125 return getattr(obj, attr, default)
125 return getattr(obj, attr, default)
126 except Exception:
126 except Exception:
127 return default
127 return default
128
128
129 @undoc
129 @undoc
130 class CUnicodeIO(StringIO):
130 class CUnicodeIO(StringIO):
131 def __init__(self, *args, **kwargs):
131 def __init__(self, *args, **kwargs):
132 super().__init__(*args, **kwargs)
132 super().__init__(*args, **kwargs)
133 warn(("CUnicodeIO is deprecated since IPython 6.0. "
133 warn(("CUnicodeIO is deprecated since IPython 6.0. "
134 "Please use io.StringIO instead."),
134 "Please use io.StringIO instead."),
135 DeprecationWarning, stacklevel=2)
135 DeprecationWarning, stacklevel=2)
136
136
137 def _sorted_for_pprint(items):
137 def _sorted_for_pprint(items):
138 """
138 """
139 Sort the given items for pretty printing. Since some predictable
139 Sort the given items for pretty printing. Since some predictable
140 sorting is better than no sorting at all, we sort on the string
140 sorting is better than no sorting at all, we sort on the string
141 representation if normal sorting fails.
141 representation if normal sorting fails.
142 """
142 """
143 items = list(items)
143 items = list(items)
144 try:
144 try:
145 return sorted(items)
145 return sorted(items)
146 except Exception:
146 except Exception:
147 try:
147 try:
148 return sorted(items, key=str)
148 return sorted(items, key=str)
149 except Exception:
149 except Exception:
150 return items
150 return items
151
151
152 def pretty(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
152 def pretty(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
153 """
153 """
154 Pretty print the object's representation.
154 Pretty print the object's representation.
155 """
155 """
156 stream = StringIO()
156 stream = StringIO()
157 printer = RepresentationPrinter(stream, verbose, max_width, newline, max_seq_length=max_seq_length)
157 printer = RepresentationPrinter(stream, verbose, max_width, newline, max_seq_length=max_seq_length)
158 printer.pretty(obj)
158 printer.pretty(obj)
159 printer.flush()
159 printer.flush()
160 return stream.getvalue()
160 return stream.getvalue()
161
161
162
162
163 def pprint(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
163 def pprint(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
164 """
164 """
165 Like `pretty` but print to stdout.
165 Like `pretty` but print to stdout.
166 """
166 """
167 printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline, max_seq_length=max_seq_length)
167 printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline, max_seq_length=max_seq_length)
168 printer.pretty(obj)
168 printer.pretty(obj)
169 printer.flush()
169 printer.flush()
170 sys.stdout.write(newline)
170 sys.stdout.write(newline)
171 sys.stdout.flush()
171 sys.stdout.flush()
172
172
173 class _PrettyPrinterBase(object):
173 class _PrettyPrinterBase(object):
174
174
175 @contextmanager
175 @contextmanager
176 def indent(self, indent):
176 def indent(self, indent):
177 """with statement support for indenting/dedenting."""
177 """with statement support for indenting/dedenting."""
178 self.indentation += indent
178 self.indentation += indent
179 try:
179 try:
180 yield
180 yield
181 finally:
181 finally:
182 self.indentation -= indent
182 self.indentation -= indent
183
183
184 @contextmanager
184 @contextmanager
185 def group(self, indent=0, open='', close=''):
185 def group(self, indent=0, open='', close=''):
186 """like begin_group / end_group but for the with statement."""
186 """like begin_group / end_group but for the with statement."""
187 self.begin_group(indent, open)
187 self.begin_group(indent, open)
188 try:
188 try:
189 yield
189 yield
190 finally:
190 finally:
191 self.end_group(indent, close)
191 self.end_group(indent, close)
192
192
193 class PrettyPrinter(_PrettyPrinterBase):
193 class PrettyPrinter(_PrettyPrinterBase):
194 """
194 """
195 Baseclass for the `RepresentationPrinter` prettyprinter that is used to
195 Baseclass for the `RepresentationPrinter` prettyprinter that is used to
196 generate pretty reprs of objects. Contrary to the `RepresentationPrinter`
196 generate pretty reprs of objects. Contrary to the `RepresentationPrinter`
197 this printer knows nothing about the default pprinters or the `_repr_pretty_`
197 this printer knows nothing about the default pprinters or the `_repr_pretty_`
198 callback method.
198 callback method.
199 """
199 """
200
200
201 def __init__(self, output, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
201 def __init__(self, output, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
202 self.output = output
202 self.output = output
203 self.max_width = max_width
203 self.max_width = max_width
204 self.newline = newline
204 self.newline = newline
205 self.max_seq_length = max_seq_length
205 self.max_seq_length = max_seq_length
206 self.output_width = 0
206 self.output_width = 0
207 self.buffer_width = 0
207 self.buffer_width = 0
208 self.buffer = deque()
208 self.buffer = deque()
209
209
210 root_group = Group(0)
210 root_group = Group(0)
211 self.group_stack = [root_group]
211 self.group_stack = [root_group]
212 self.group_queue = GroupQueue(root_group)
212 self.group_queue = GroupQueue(root_group)
213 self.indentation = 0
213 self.indentation = 0
214
214
215 def _break_one_group(self, group):
215 def _break_one_group(self, group):
216 while group.breakables:
216 while group.breakables:
217 x = self.buffer.popleft()
217 x = self.buffer.popleft()
218 self.output_width = x.output(self.output, self.output_width)
218 self.output_width = x.output(self.output, self.output_width)
219 self.buffer_width -= x.width
219 self.buffer_width -= x.width
220 while self.buffer and isinstance(self.buffer[0], Text):
220 while self.buffer and isinstance(self.buffer[0], Text):
221 x = self.buffer.popleft()
221 x = self.buffer.popleft()
222 self.output_width = x.output(self.output, self.output_width)
222 self.output_width = x.output(self.output, self.output_width)
223 self.buffer_width -= x.width
223 self.buffer_width -= x.width
224
224
225 def _break_outer_groups(self):
225 def _break_outer_groups(self):
226 while self.max_width < self.output_width + self.buffer_width:
226 while self.max_width < self.output_width + self.buffer_width:
227 group = self.group_queue.deq()
227 group = self.group_queue.deq()
228 if not group:
228 if not group:
229 return
229 return
230 self._break_one_group(group)
230 self._break_one_group(group)
231
231
232 def text(self, obj):
232 def text(self, obj):
233 """Add literal text to the output."""
233 """Add literal text to the output."""
234 width = len(obj)
234 width = len(obj)
235 if self.buffer:
235 if self.buffer:
236 text = self.buffer[-1]
236 text = self.buffer[-1]
237 if not isinstance(text, Text):
237 if not isinstance(text, Text):
238 text = Text()
238 text = Text()
239 self.buffer.append(text)
239 self.buffer.append(text)
240 text.add(obj, width)
240 text.add(obj, width)
241 self.buffer_width += width
241 self.buffer_width += width
242 self._break_outer_groups()
242 self._break_outer_groups()
243 else:
243 else:
244 self.output.write(obj)
244 self.output.write(obj)
245 self.output_width += width
245 self.output_width += width
246
246
247 def breakable(self, sep=' '):
247 def breakable(self, sep=' '):
248 """
248 """
249 Add a breakable separator to the output. This does not mean that it
249 Add a breakable separator to the output. This does not mean that it
250 will automatically break here. If no breaking on this position takes
250 will automatically break here. If no breaking on this position takes
251 place the `sep` is inserted which default to one space.
251 place the `sep` is inserted which default to one space.
252 """
252 """
253 width = len(sep)
253 width = len(sep)
254 group = self.group_stack[-1]
254 group = self.group_stack[-1]
255 if group.want_break:
255 if group.want_break:
256 self.flush()
256 self.flush()
257 self.output.write(self.newline)
257 self.output.write(self.newline)
258 self.output.write(' ' * self.indentation)
258 self.output.write(' ' * self.indentation)
259 self.output_width = self.indentation
259 self.output_width = self.indentation
260 self.buffer_width = 0
260 self.buffer_width = 0
261 else:
261 else:
262 self.buffer.append(Breakable(sep, width, self))
262 self.buffer.append(Breakable(sep, width, self))
263 self.buffer_width += width
263 self.buffer_width += width
264 self._break_outer_groups()
264 self._break_outer_groups()
265
265
266 def break_(self):
266 def break_(self):
267 """
267 """
268 Explicitly insert a newline into the output, maintaining correct indentation.
268 Explicitly insert a newline into the output, maintaining correct indentation.
269 """
269 """
270 group = self.group_queue.deq()
270 group = self.group_queue.deq()
271 if group:
271 if group:
272 self._break_one_group(group)
272 self._break_one_group(group)
273 self.flush()
273 self.flush()
274 self.output.write(self.newline)
274 self.output.write(self.newline)
275 self.output.write(' ' * self.indentation)
275 self.output.write(' ' * self.indentation)
276 self.output_width = self.indentation
276 self.output_width = self.indentation
277 self.buffer_width = 0
277 self.buffer_width = 0
278
278
279
279
280 def begin_group(self, indent=0, open=''):
280 def begin_group(self, indent=0, open=''):
281 """
281 """
282 Begin a group.
282 Begin a group.
283 The first parameter specifies the indentation for the next line (usually
283 The first parameter specifies the indentation for the next line (usually
284 the width of the opening text), the second the opening text. All
284 the width of the opening text), the second the opening text. All
285 parameters are optional.
285 parameters are optional.
286 """
286 """
287 if open:
287 if open:
288 self.text(open)
288 self.text(open)
289 group = Group(self.group_stack[-1].depth + 1)
289 group = Group(self.group_stack[-1].depth + 1)
290 self.group_stack.append(group)
290 self.group_stack.append(group)
291 self.group_queue.enq(group)
291 self.group_queue.enq(group)
292 self.indentation += indent
292 self.indentation += indent
293
293
294 def _enumerate(self, seq):
294 def _enumerate(self, seq):
295 """like enumerate, but with an upper limit on the number of items"""
295 """like enumerate, but with an upper limit on the number of items"""
296 for idx, x in enumerate(seq):
296 for idx, x in enumerate(seq):
297 if self.max_seq_length and idx >= self.max_seq_length:
297 if self.max_seq_length and idx >= self.max_seq_length:
298 self.text(',')
298 self.text(',')
299 self.breakable()
299 self.breakable()
300 self.text('...')
300 self.text('...')
301 return
301 return
302 yield idx, x
302 yield idx, x
303
303
304 def end_group(self, dedent=0, close=''):
304 def end_group(self, dedent=0, close=''):
305 """End a group. See `begin_group` for more details."""
305 """End a group. See `begin_group` for more details."""
306 self.indentation -= dedent
306 self.indentation -= dedent
307 group = self.group_stack.pop()
307 group = self.group_stack.pop()
308 if not group.breakables:
308 if not group.breakables:
309 self.group_queue.remove(group)
309 self.group_queue.remove(group)
310 if close:
310 if close:
311 self.text(close)
311 self.text(close)
312
312
313 def flush(self):
313 def flush(self):
314 """Flush data that is left in the buffer."""
314 """Flush data that is left in the buffer."""
315 for data in self.buffer:
315 for data in self.buffer:
316 self.output_width += data.output(self.output, self.output_width)
316 self.output_width += data.output(self.output, self.output_width)
317 self.buffer.clear()
317 self.buffer.clear()
318 self.buffer_width = 0
318 self.buffer_width = 0
319
319
320
320
321 def _get_mro(obj_class):
321 def _get_mro(obj_class):
322 """ Get a reasonable method resolution order of a class and its superclasses
322 """ Get a reasonable method resolution order of a class and its superclasses
323 for both old-style and new-style classes.
323 for both old-style and new-style classes.
324 """
324 """
325 if not hasattr(obj_class, '__mro__'):
325 if not hasattr(obj_class, '__mro__'):
326 # Old-style class. Mix in object to make a fake new-style class.
326 # Old-style class. Mix in object to make a fake new-style class.
327 try:
327 try:
328 obj_class = type(obj_class.__name__, (obj_class, object), {})
328 obj_class = type(obj_class.__name__, (obj_class, object), {})
329 except TypeError:
329 except TypeError:
330 # Old-style extension type that does not descend from object.
330 # Old-style extension type that does not descend from object.
331 # FIXME: try to construct a more thorough MRO.
331 # FIXME: try to construct a more thorough MRO.
332 mro = [obj_class]
332 mro = [obj_class]
333 else:
333 else:
334 mro = obj_class.__mro__[1:-1]
334 mro = obj_class.__mro__[1:-1]
335 else:
335 else:
336 mro = obj_class.__mro__
336 mro = obj_class.__mro__
337 return mro
337 return mro
338
338
339
339
340 class RepresentationPrinter(PrettyPrinter):
340 class RepresentationPrinter(PrettyPrinter):
341 """
341 """
342 Special pretty printer that has a `pretty` method that calls the pretty
342 Special pretty printer that has a `pretty` method that calls the pretty
343 printer for a python object.
343 printer for a python object.
344
344
345 This class stores processing data on `self` so you must *never* use
345 This class stores processing data on `self` so you must *never* use
346 this class in a threaded environment. Always lock it or reinstanciate
346 this class in a threaded environment. Always lock it or reinstanciate
347 it.
347 it.
348
348
349 Instances also have a verbose flag callbacks can access to control their
349 Instances also have a verbose flag callbacks can access to control their
350 output. For example the default instance repr prints all attributes and
350 output. For example the default instance repr prints all attributes and
351 methods that are not prefixed by an underscore if the printer is in
351 methods that are not prefixed by an underscore if the printer is in
352 verbose mode.
352 verbose mode.
353 """
353 """
354
354
355 def __init__(self, output, verbose=False, max_width=79, newline='\n',
355 def __init__(self, output, verbose=False, max_width=79, newline='\n',
356 singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None,
356 singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None,
357 max_seq_length=MAX_SEQ_LENGTH):
357 max_seq_length=MAX_SEQ_LENGTH):
358
358
359 PrettyPrinter.__init__(self, output, max_width, newline, max_seq_length=max_seq_length)
359 PrettyPrinter.__init__(self, output, max_width, newline, max_seq_length=max_seq_length)
360 self.verbose = verbose
360 self.verbose = verbose
361 self.stack = []
361 self.stack = []
362 if singleton_pprinters is None:
362 if singleton_pprinters is None:
363 singleton_pprinters = _singleton_pprinters.copy()
363 singleton_pprinters = _singleton_pprinters.copy()
364 self.singleton_pprinters = singleton_pprinters
364 self.singleton_pprinters = singleton_pprinters
365 if type_pprinters is None:
365 if type_pprinters is None:
366 type_pprinters = _type_pprinters.copy()
366 type_pprinters = _type_pprinters.copy()
367 self.type_pprinters = type_pprinters
367 self.type_pprinters = type_pprinters
368 if deferred_pprinters is None:
368 if deferred_pprinters is None:
369 deferred_pprinters = _deferred_type_pprinters.copy()
369 deferred_pprinters = _deferred_type_pprinters.copy()
370 self.deferred_pprinters = deferred_pprinters
370 self.deferred_pprinters = deferred_pprinters
371
371
372 def pretty(self, obj):
372 def pretty(self, obj):
373 """Pretty print the given object."""
373 """Pretty print the given object."""
374 obj_id = id(obj)
374 obj_id = id(obj)
375 cycle = obj_id in self.stack
375 cycle = obj_id in self.stack
376 self.stack.append(obj_id)
376 self.stack.append(obj_id)
377 self.begin_group()
377 self.begin_group()
378 try:
378 try:
379 obj_class = _safe_getattr(obj, '__class__', None) or type(obj)
379 obj_class = _safe_getattr(obj, '__class__', None) or type(obj)
380 # First try to find registered singleton printers for the type.
380 # First try to find registered singleton printers for the type.
381 try:
381 try:
382 printer = self.singleton_pprinters[obj_id]
382 printer = self.singleton_pprinters[obj_id]
383 except (TypeError, KeyError):
383 except (TypeError, KeyError):
384 pass
384 pass
385 else:
385 else:
386 return printer(obj, self, cycle)
386 return printer(obj, self, cycle)
387 # Next walk the mro and check for either:
387 # Next walk the mro and check for either:
388 # 1) a registered printer
388 # 1) a registered printer
389 # 2) a _repr_pretty_ method
389 # 2) a _repr_pretty_ method
390 for cls in _get_mro(obj_class):
390 for cls in _get_mro(obj_class):
391 if cls in self.type_pprinters:
391 if cls in self.type_pprinters:
392 # printer registered in self.type_pprinters
392 # printer registered in self.type_pprinters
393 return self.type_pprinters[cls](obj, self, cycle)
393 return self.type_pprinters[cls](obj, self, cycle)
394 else:
394 else:
395 # deferred printer
395 # deferred printer
396 printer = self._in_deferred_types(cls)
396 printer = self._in_deferred_types(cls)
397 if printer is not None:
397 if printer is not None:
398 return printer(obj, self, cycle)
398 return printer(obj, self, cycle)
399 else:
399 else:
400 # Finally look for special method names.
400 # Finally look for special method names.
401 # Some objects automatically create any requested
401 # Some objects automatically create any requested
402 # attribute. Try to ignore most of them by checking for
402 # attribute. Try to ignore most of them by checking for
403 # callability.
403 # callability.
404 if '_repr_pretty_' in cls.__dict__:
404 if '_repr_pretty_' in cls.__dict__:
405 meth = cls._repr_pretty_
405 meth = cls._repr_pretty_
406 if callable(meth):
406 if callable(meth):
407 return meth(obj, self, cycle)
407 return meth(obj, self, cycle)
408 if cls is not object \
408 if cls is not object \
409 and callable(cls.__dict__.get('__repr__')):
409 and callable(cls.__dict__.get('__repr__')):
410 return _repr_pprint(obj, self, cycle)
410 return _repr_pprint(obj, self, cycle)
411
411
412 return _default_pprint(obj, self, cycle)
412 return _default_pprint(obj, self, cycle)
413 finally:
413 finally:
414 self.end_group()
414 self.end_group()
415 self.stack.pop()
415 self.stack.pop()
416
416
417 def _in_deferred_types(self, cls):
417 def _in_deferred_types(self, cls):
418 """
418 """
419 Check if the given class is specified in the deferred type registry.
419 Check if the given class is specified in the deferred type registry.
420
420
421 Returns the printer from the registry if it exists, and None if the
421 Returns the printer from the registry if it exists, and None if the
422 class is not in the registry. Successful matches will be moved to the
422 class is not in the registry. Successful matches will be moved to the
423 regular type registry for future use.
423 regular type registry for future use.
424 """
424 """
425 mod = _safe_getattr(cls, '__module__', None)
425 mod = _safe_getattr(cls, '__module__', None)
426 name = _safe_getattr(cls, '__name__', None)
426 name = _safe_getattr(cls, '__name__', None)
427 key = (mod, name)
427 key = (mod, name)
428 printer = None
428 printer = None
429 if key in self.deferred_pprinters:
429 if key in self.deferred_pprinters:
430 # Move the printer over to the regular registry.
430 # Move the printer over to the regular registry.
431 printer = self.deferred_pprinters.pop(key)
431 printer = self.deferred_pprinters.pop(key)
432 self.type_pprinters[cls] = printer
432 self.type_pprinters[cls] = printer
433 return printer
433 return printer
434
434
435
435
436 class Printable(object):
436 class Printable(object):
437
437
438 def output(self, stream, output_width):
438 def output(self, stream, output_width):
439 return output_width
439 return output_width
440
440
441
441
442 class Text(Printable):
442 class Text(Printable):
443
443
444 def __init__(self):
444 def __init__(self):
445 self.objs = []
445 self.objs = []
446 self.width = 0
446 self.width = 0
447
447
448 def output(self, stream, output_width):
448 def output(self, stream, output_width):
449 for obj in self.objs:
449 for obj in self.objs:
450 stream.write(obj)
450 stream.write(obj)
451 return output_width + self.width
451 return output_width + self.width
452
452
453 def add(self, obj, width):
453 def add(self, obj, width):
454 self.objs.append(obj)
454 self.objs.append(obj)
455 self.width += width
455 self.width += width
456
456
457
457
458 class Breakable(Printable):
458 class Breakable(Printable):
459
459
460 def __init__(self, seq, width, pretty):
460 def __init__(self, seq, width, pretty):
461 self.obj = seq
461 self.obj = seq
462 self.width = width
462 self.width = width
463 self.pretty = pretty
463 self.pretty = pretty
464 self.indentation = pretty.indentation
464 self.indentation = pretty.indentation
465 self.group = pretty.group_stack[-1]
465 self.group = pretty.group_stack[-1]
466 self.group.breakables.append(self)
466 self.group.breakables.append(self)
467
467
468 def output(self, stream, output_width):
468 def output(self, stream, output_width):
469 self.group.breakables.popleft()
469 self.group.breakables.popleft()
470 if self.group.want_break:
470 if self.group.want_break:
471 stream.write(self.pretty.newline)
471 stream.write(self.pretty.newline)
472 stream.write(' ' * self.indentation)
472 stream.write(' ' * self.indentation)
473 return self.indentation
473 return self.indentation
474 if not self.group.breakables:
474 if not self.group.breakables:
475 self.pretty.group_queue.remove(self.group)
475 self.pretty.group_queue.remove(self.group)
476 stream.write(self.obj)
476 stream.write(self.obj)
477 return output_width + self.width
477 return output_width + self.width
478
478
479
479
480 class Group(Printable):
480 class Group(Printable):
481
481
482 def __init__(self, depth):
482 def __init__(self, depth):
483 self.depth = depth
483 self.depth = depth
484 self.breakables = deque()
484 self.breakables = deque()
485 self.want_break = False
485 self.want_break = False
486
486
487
487
488 class GroupQueue(object):
488 class GroupQueue(object):
489
489
490 def __init__(self, *groups):
490 def __init__(self, *groups):
491 self.queue = []
491 self.queue = []
492 for group in groups:
492 for group in groups:
493 self.enq(group)
493 self.enq(group)
494
494
495 def enq(self, group):
495 def enq(self, group):
496 depth = group.depth
496 depth = group.depth
497 while depth > len(self.queue) - 1:
497 while depth > len(self.queue) - 1:
498 self.queue.append([])
498 self.queue.append([])
499 self.queue[depth].append(group)
499 self.queue[depth].append(group)
500
500
501 def deq(self):
501 def deq(self):
502 for stack in self.queue:
502 for stack in self.queue:
503 for idx, group in enumerate(reversed(stack)):
503 for idx, group in enumerate(reversed(stack)):
504 if group.breakables:
504 if group.breakables:
505 del stack[idx]
505 del stack[idx]
506 group.want_break = True
506 group.want_break = True
507 return group
507 return group
508 for group in stack:
508 for group in stack:
509 group.want_break = True
509 group.want_break = True
510 del stack[:]
510 del stack[:]
511
511
512 def remove(self, group):
512 def remove(self, group):
513 try:
513 try:
514 self.queue[group.depth].remove(group)
514 self.queue[group.depth].remove(group)
515 except ValueError:
515 except ValueError:
516 pass
516 pass
517
517
518
518
519 class RawText:
519 class RawText:
520 """ Object such that ``p.pretty(RawText(value))`` is the same as ``p.text(value)``.
520 """ Object such that ``p.pretty(RawText(value))`` is the same as ``p.text(value)``.
521
521
522 An example usage of this would be to show a list as binary numbers, using
522 An example usage of this would be to show a list as binary numbers, using
523 ``p.pretty([RawText(bin(i)) for i in integers])``.
523 ``p.pretty([RawText(bin(i)) for i in integers])``.
524 """
524 """
525 def __init__(self, value):
525 def __init__(self, value):
526 self.value = value
526 self.value = value
527
527
528 def _repr_pretty_(self, p, cycle):
528 def _repr_pretty_(self, p, cycle):
529 p.text(self.value)
529 p.text(self.value)
530
530
531
531
532 class CallExpression:
532 class CallExpression:
533 """ Object which emits a line-wrapped call expression in the form `__name(*args, **kwargs)` """
533 """ Object which emits a line-wrapped call expression in the form `__name(*args, **kwargs)` """
534 def __init__(__self, __name, *args, **kwargs):
534 def __init__(__self, __name, *args, **kwargs):
535 # dunders are to avoid clashes with kwargs, as python's name manging
535 # dunders are to avoid clashes with kwargs, as python's name manging
536 # will kick in.
536 # will kick in.
537 self = __self
537 self = __self
538 self.name = __name
538 self.name = __name
539 self.args = args
539 self.args = args
540 self.kwargs = kwargs
540 self.kwargs = kwargs
541
541
542 @classmethod
542 @classmethod
543 def factory(cls, name):
543 def factory(cls, name):
544 def inner(*args, **kwargs):
544 def inner(*args, **kwargs):
545 return cls(name, *args, **kwargs)
545 return cls(name, *args, **kwargs)
546 return inner
546 return inner
547
547
548 def _repr_pretty_(self, p, cycle):
548 def _repr_pretty_(self, p, cycle):
549 # dunders are to avoid clashes with kwargs, as python's name manging
549 # dunders are to avoid clashes with kwargs, as python's name manging
550 # will kick in.
550 # will kick in.
551
551
552 started = False
552 started = False
553 def new_item():
553 def new_item():
554 nonlocal started
554 nonlocal started
555 if started:
555 if started:
556 p.text(",")
556 p.text(",")
557 p.breakable()
557 p.breakable()
558 started = True
558 started = True
559
559
560 prefix = self.name + "("
560 prefix = self.name + "("
561 with p.group(len(prefix), prefix, ")"):
561 with p.group(len(prefix), prefix, ")"):
562 for arg in self.args:
562 for arg in self.args:
563 new_item()
563 new_item()
564 p.pretty(arg)
564 p.pretty(arg)
565 for arg_name, arg in self.kwargs.items():
565 for arg_name, arg in self.kwargs.items():
566 new_item()
566 new_item()
567 arg_prefix = arg_name + "="
567 arg_prefix = arg_name + "="
568 with p.group(len(arg_prefix), arg_prefix):
568 with p.group(len(arg_prefix), arg_prefix):
569 p.pretty(arg)
569 p.pretty(arg)
570
570
571
571
572 class RawStringLiteral:
572 class RawStringLiteral:
573 """ Wrapper that shows a string with a `r` prefix """
573 """ Wrapper that shows a string with a `r` prefix """
574 def __init__(self, value):
574 def __init__(self, value):
575 self.value = value
575 self.value = value
576
576
577 def _repr_pretty_(self, p, cycle):
577 def _repr_pretty_(self, p, cycle):
578 base_repr = repr(self.value)
578 base_repr = repr(self.value)
579 if base_repr[:1] in 'uU':
579 if base_repr[:1] in 'uU':
580 base_repr = base_repr[1:]
580 base_repr = base_repr[1:]
581 prefix = 'ur'
581 prefix = 'ur'
582 else:
582 else:
583 prefix = 'r'
583 prefix = 'r'
584 base_repr = prefix + base_repr.replace('\\\\', '\\')
584 base_repr = prefix + base_repr.replace('\\\\', '\\')
585 p.text(base_repr)
585 p.text(base_repr)
586
586
587
587
588 def _default_pprint(obj, p, cycle):
588 def _default_pprint(obj, p, cycle):
589 """
589 """
590 The default print function. Used if an object does not provide one and
590 The default print function. Used if an object does not provide one and
591 it's none of the builtin objects.
591 it's none of the builtin objects.
592 """
592 """
593 klass = _safe_getattr(obj, '__class__', None) or type(obj)
593 klass = _safe_getattr(obj, '__class__', None) or type(obj)
594 if _safe_getattr(klass, '__repr__', None) is not object.__repr__:
594 if _safe_getattr(klass, '__repr__', None) is not object.__repr__:
595 # A user-provided repr. Find newlines and replace them with p.break_()
595 # A user-provided repr. Find newlines and replace them with p.break_()
596 _repr_pprint(obj, p, cycle)
596 _repr_pprint(obj, p, cycle)
597 return
597 return
598 p.begin_group(1, '<')
598 p.begin_group(1, '<')
599 p.pretty(klass)
599 p.pretty(klass)
600 p.text(' at 0x%x' % id(obj))
600 p.text(' at 0x%x' % id(obj))
601 if cycle:
601 if cycle:
602 p.text(' ...')
602 p.text(' ...')
603 elif p.verbose:
603 elif p.verbose:
604 first = True
604 first = True
605 for key in dir(obj):
605 for key in dir(obj):
606 if not key.startswith('_'):
606 if not key.startswith('_'):
607 try:
607 try:
608 value = getattr(obj, key)
608 value = getattr(obj, key)
609 except AttributeError:
609 except AttributeError:
610 continue
610 continue
611 if isinstance(value, types.MethodType):
611 if isinstance(value, types.MethodType):
612 continue
612 continue
613 if not first:
613 if not first:
614 p.text(',')
614 p.text(',')
615 p.breakable()
615 p.breakable()
616 p.text(key)
616 p.text(key)
617 p.text('=')
617 p.text('=')
618 step = len(key) + 1
618 step = len(key) + 1
619 p.indentation += step
619 p.indentation += step
620 p.pretty(value)
620 p.pretty(value)
621 p.indentation -= step
621 p.indentation -= step
622 first = False
622 first = False
623 p.end_group(1, '>')
623 p.end_group(1, '>')
624
624
625
625
626 def _seq_pprinter_factory(start, end):
626 def _seq_pprinter_factory(start, end):
627 """
627 """
628 Factory that returns a pprint function useful for sequences. Used by
628 Factory that returns a pprint function useful for sequences. Used by
629 the default pprint for tuples and lists.
629 the default pprint for tuples and lists.
630 """
630 """
631 def inner(obj, p, cycle):
631 def inner(obj, p, cycle):
632 if cycle:
632 if cycle:
633 return p.text(start + '...' + end)
633 return p.text(start + '...' + end)
634 step = len(start)
634 step = len(start)
635 p.begin_group(step, start)
635 p.begin_group(step, start)
636 for idx, x in p._enumerate(obj):
636 for idx, x in p._enumerate(obj):
637 if idx:
637 if idx:
638 p.text(',')
638 p.text(',')
639 p.breakable()
639 p.breakable()
640 p.pretty(x)
640 p.pretty(x)
641 if len(obj) == 1 and isinstance(obj, tuple):
641 if len(obj) == 1 and isinstance(obj, tuple):
642 # Special case for 1-item tuples.
642 # Special case for 1-item tuples.
643 p.text(',')
643 p.text(',')
644 p.end_group(step, end)
644 p.end_group(step, end)
645 return inner
645 return inner
646
646
647
647
648 def _set_pprinter_factory(start, end):
648 def _set_pprinter_factory(start, end):
649 """
649 """
650 Factory that returns a pprint function useful for sets and frozensets.
650 Factory that returns a pprint function useful for sets and frozensets.
651 """
651 """
652 def inner(obj, p, cycle):
652 def inner(obj, p, cycle):
653 if cycle:
653 if cycle:
654 return p.text(start + '...' + end)
654 return p.text(start + '...' + end)
655 if len(obj) == 0:
655 if len(obj) == 0:
656 # Special case.
656 # Special case.
657 p.text(type(obj).__name__ + '()')
657 p.text(type(obj).__name__ + '()')
658 else:
658 else:
659 step = len(start)
659 step = len(start)
660 p.begin_group(step, start)
660 p.begin_group(step, start)
661 # Like dictionary keys, we will try to sort the items if there aren't too many
661 # Like dictionary keys, we will try to sort the items if there aren't too many
662 if not (p.max_seq_length and len(obj) >= p.max_seq_length):
662 if not (p.max_seq_length and len(obj) >= p.max_seq_length):
663 items = _sorted_for_pprint(obj)
663 items = _sorted_for_pprint(obj)
664 else:
664 else:
665 items = obj
665 items = obj
666 for idx, x in p._enumerate(items):
666 for idx, x in p._enumerate(items):
667 if idx:
667 if idx:
668 p.text(',')
668 p.text(',')
669 p.breakable()
669 p.breakable()
670 p.pretty(x)
670 p.pretty(x)
671 p.end_group(step, end)
671 p.end_group(step, end)
672 return inner
672 return inner
673
673
674
674
675 def _dict_pprinter_factory(start, end):
675 def _dict_pprinter_factory(start, end):
676 """
676 """
677 Factory that returns a pprint function used by the default pprint of
677 Factory that returns a pprint function used by the default pprint of
678 dicts and dict proxies.
678 dicts and dict proxies.
679 """
679 """
680 def inner(obj, p, cycle):
680 def inner(obj, p, cycle):
681 if cycle:
681 if cycle:
682 return p.text('{...}')
682 return p.text('{...}')
683 step = len(start)
683 step = len(start)
684 p.begin_group(step, start)
684 p.begin_group(step, start)
685 keys = obj.keys()
685 keys = obj.keys()
686 for idx, key in p._enumerate(keys):
686 for idx, key in p._enumerate(keys):
687 if idx:
687 if idx:
688 p.text(',')
688 p.text(',')
689 p.breakable()
689 p.breakable()
690 p.pretty(key)
690 p.pretty(key)
691 p.text(': ')
691 p.text(': ')
692 p.pretty(obj[key])
692 p.pretty(obj[key])
693 p.end_group(step, end)
693 p.end_group(step, end)
694 return inner
694 return inner
695
695
696
696
697 def _super_pprint(obj, p, cycle):
697 def _super_pprint(obj, p, cycle):
698 """The pprint for the super type."""
698 """The pprint for the super type."""
699 p.begin_group(8, '<super: ')
699 p.begin_group(8, '<super: ')
700 p.pretty(obj.__thisclass__)
700 p.pretty(obj.__thisclass__)
701 p.text(',')
701 p.text(',')
702 p.breakable()
702 p.breakable()
703 if PYPY: # In PyPy, super() objects don't have __self__ attributes
703 if PYPY: # In PyPy, super() objects don't have __self__ attributes
704 dself = obj.__repr__.__self__
704 dself = obj.__repr__.__self__
705 p.pretty(None if dself is obj else dself)
705 p.pretty(None if dself is obj else dself)
706 else:
706 else:
707 p.pretty(obj.__self__)
707 p.pretty(obj.__self__)
708 p.end_group(8, '>')
708 p.end_group(8, '>')
709
709
710
710
711
711
712 class _ReFlags:
712 class _ReFlags:
713 def __init__(self, value):
713 def __init__(self, value):
714 self.value = value
714 self.value = value
715
715
716 def _repr_pretty_(self, p, cycle):
716 def _repr_pretty_(self, p, cycle):
717 done_one = False
717 done_one = False
718 for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL',
718 for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL',
719 'UNICODE', 'VERBOSE', 'DEBUG'):
719 'UNICODE', 'VERBOSE', 'DEBUG'):
720 if self.value & getattr(re, flag):
720 if self.value & getattr(re, flag):
721 if done_one:
721 if done_one:
722 p.text('|')
722 p.text('|')
723 p.text('re.' + flag)
723 p.text('re.' + flag)
724 done_one = True
724 done_one = True
725
725
726
726
727 def _re_pattern_pprint(obj, p, cycle):
727 def _re_pattern_pprint(obj, p, cycle):
728 """The pprint function for regular expression patterns."""
728 """The pprint function for regular expression patterns."""
729 re_compile = CallExpression.factory('re.compile')
729 re_compile = CallExpression.factory('re.compile')
730 if obj.flags:
730 if obj.flags:
731 p.pretty(re_compile(RawStringLiteral(obj.pattern), _ReFlags(obj.flags)))
731 p.pretty(re_compile(RawStringLiteral(obj.pattern), _ReFlags(obj.flags)))
732 else:
732 else:
733 p.pretty(re_compile(RawStringLiteral(obj.pattern)))
733 p.pretty(re_compile(RawStringLiteral(obj.pattern)))
734
734
735
735
736 def _types_simplenamespace_pprint(obj, p, cycle):
736 def _types_simplenamespace_pprint(obj, p, cycle):
737 """The pprint function for types.SimpleNamespace."""
737 """The pprint function for types.SimpleNamespace."""
738 namespace = CallExpression.factory('namespace')
738 namespace = CallExpression.factory('namespace')
739 if cycle:
739 if cycle:
740 p.pretty(namespace(RawText("...")))
740 p.pretty(namespace(RawText("...")))
741 else:
741 else:
742 p.pretty(namespace(**obj.__dict__))
742 p.pretty(namespace(**obj.__dict__))
743
743
744
744
745 def _type_pprint(obj, p, cycle):
745 def _type_pprint(obj, p, cycle):
746 """The pprint for classes and types."""
746 """The pprint for classes and types."""
747 # Heap allocated types might not have the module attribute,
747 # Heap allocated types might not have the module attribute,
748 # and others may set it to None.
748 # and others may set it to None.
749
749
750 # Checks for a __repr__ override in the metaclass. Can't compare the
750 # Checks for a __repr__ override in the metaclass. Can't compare the
751 # type(obj).__repr__ directly because in PyPy the representation function
751 # type(obj).__repr__ directly because in PyPy the representation function
752 # inherited from type isn't the same type.__repr__
752 # inherited from type isn't the same type.__repr__
753 if [m for m in _get_mro(type(obj)) if "__repr__" in vars(m)][:1] != [type]:
753 if [m for m in _get_mro(type(obj)) if "__repr__" in vars(m)][:1] != [type]:
754 _repr_pprint(obj, p, cycle)
754 _repr_pprint(obj, p, cycle)
755 return
755 return
756
756
757 mod = _safe_getattr(obj, '__module__', None)
757 mod = _safe_getattr(obj, '__module__', None)
758 try:
758 try:
759 name = obj.__qualname__
759 name = obj.__qualname__
760 if not isinstance(name, str):
760 if not isinstance(name, str):
761 # This can happen if the type implements __qualname__ as a property
761 # This can happen if the type implements __qualname__ as a property
762 # or other descriptor in Python 2.
762 # or other descriptor in Python 2.
763 raise Exception("Try __name__")
763 raise Exception("Try __name__")
764 except Exception:
764 except Exception:
765 name = obj.__name__
765 name = obj.__name__
766 if not isinstance(name, str):
766 if not isinstance(name, str):
767 name = '<unknown type>'
767 name = '<unknown type>'
768
768
769 if mod in (None, '__builtin__', 'builtins', 'exceptions'):
769 if mod in (None, '__builtin__', 'builtins', 'exceptions'):
770 p.text(name)
770 p.text(name)
771 else:
771 else:
772 p.text(mod + '.' + name)
772 p.text(mod + '.' + name)
773
773
774
774
775 def _repr_pprint(obj, p, cycle):
775 def _repr_pprint(obj, p, cycle):
776 """A pprint that just redirects to the normal repr function."""
776 """A pprint that just redirects to the normal repr function."""
777 # Find newlines and replace them with p.break_()
777 # Find newlines and replace them with p.break_()
778 output = repr(obj)
778 output = repr(obj)
779 lines = output.splitlines()
779 lines = output.splitlines()
780 with p.group():
780 with p.group():
781 for idx, output_line in enumerate(lines):
781 for idx, output_line in enumerate(lines):
782 if idx:
782 if idx:
783 p.break_()
783 p.break_()
784 p.text(output_line)
784 p.text(output_line)
785
785
786
786
787 def _function_pprint(obj, p, cycle):
787 def _function_pprint(obj, p, cycle):
788 """Base pprint for all functions and builtin functions."""
788 """Base pprint for all functions and builtin functions."""
789 name = _safe_getattr(obj, '__qualname__', obj.__name__)
789 name = _safe_getattr(obj, '__qualname__', obj.__name__)
790 mod = obj.__module__
790 mod = obj.__module__
791 if mod and mod not in ('__builtin__', 'builtins', 'exceptions'):
791 if mod and mod not in ('__builtin__', 'builtins', 'exceptions'):
792 name = mod + '.' + name
792 name = mod + '.' + name
793 try:
793 try:
794 func_def = name + str(signature(obj))
794 func_def = name + str(signature(obj))
795 except ValueError:
795 except ValueError:
796 func_def = name
796 func_def = name
797 p.text('<function %s>' % func_def)
797 p.text('<function %s>' % func_def)
798
798
799
799
800 def _exception_pprint(obj, p, cycle):
800 def _exception_pprint(obj, p, cycle):
801 """Base pprint for all exceptions."""
801 """Base pprint for all exceptions."""
802 name = getattr(obj.__class__, '__qualname__', obj.__class__.__name__)
802 name = getattr(obj.__class__, '__qualname__', obj.__class__.__name__)
803 if obj.__class__.__module__ not in ('exceptions', 'builtins'):
803 if obj.__class__.__module__ not in ('exceptions', 'builtins'):
804 name = '%s.%s' % (obj.__class__.__module__, name)
804 name = '%s.%s' % (obj.__class__.__module__, name)
805
805
806 p.pretty(CallExpression(name, *getattr(obj, 'args', ())))
806 p.pretty(CallExpression(name, *getattr(obj, 'args', ())))
807
807
808
808
809 #: the exception base
809 #: the exception base
810 try:
810 try:
811 _exception_base = BaseException
811 _exception_base = BaseException
812 except NameError:
812 except NameError:
813 _exception_base = Exception
813 _exception_base = Exception
814
814
815
815
816 #: printers for builtin types
816 #: printers for builtin types
817 _type_pprinters = {
817 _type_pprinters = {
818 int: _repr_pprint,
818 int: _repr_pprint,
819 float: _repr_pprint,
819 float: _repr_pprint,
820 str: _repr_pprint,
820 str: _repr_pprint,
821 tuple: _seq_pprinter_factory('(', ')'),
821 tuple: _seq_pprinter_factory('(', ')'),
822 list: _seq_pprinter_factory('[', ']'),
822 list: _seq_pprinter_factory('[', ']'),
823 dict: _dict_pprinter_factory('{', '}'),
823 dict: _dict_pprinter_factory('{', '}'),
824 set: _set_pprinter_factory('{', '}'),
824 set: _set_pprinter_factory('{', '}'),
825 frozenset: _set_pprinter_factory('frozenset({', '})'),
825 frozenset: _set_pprinter_factory('frozenset({', '})'),
826 super: _super_pprint,
826 super: _super_pprint,
827 _re_pattern_type: _re_pattern_pprint,
827 _re_pattern_type: _re_pattern_pprint,
828 type: _type_pprint,
828 type: _type_pprint,
829 types.FunctionType: _function_pprint,
829 types.FunctionType: _function_pprint,
830 types.BuiltinFunctionType: _function_pprint,
830 types.BuiltinFunctionType: _function_pprint,
831 types.MethodType: _repr_pprint,
831 types.MethodType: _repr_pprint,
832 types.SimpleNamespace: _types_simplenamespace_pprint,
832 types.SimpleNamespace: _types_simplenamespace_pprint,
833 datetime.datetime: _repr_pprint,
833 datetime.datetime: _repr_pprint,
834 datetime.timedelta: _repr_pprint,
834 datetime.timedelta: _repr_pprint,
835 _exception_base: _exception_pprint
835 _exception_base: _exception_pprint
836 }
836 }
837
837
838 # render os.environ like a dict
838 # render os.environ like a dict
839 _env_type = type(os.environ)
839 _env_type = type(os.environ)
840 # future-proof in case os.environ becomes a plain dict?
840 # future-proof in case os.environ becomes a plain dict?
841 if _env_type is not dict:
841 if _env_type is not dict:
842 _type_pprinters[_env_type] = _dict_pprinter_factory('environ{', '}')
842 _type_pprinters[_env_type] = _dict_pprinter_factory('environ{', '}')
843
843
844 try:
844 try:
845 # In PyPy, types.DictProxyType is dict, setting the dictproxy printer
845 # In PyPy, types.DictProxyType is dict, setting the dictproxy printer
846 # using dict.setdefault avoids overwriting the dict printer
846 # using dict.setdefault avoids overwriting the dict printer
847 _type_pprinters.setdefault(types.DictProxyType,
847 _type_pprinters.setdefault(types.DictProxyType,
848 _dict_pprinter_factory('dict_proxy({', '})'))
848 _dict_pprinter_factory('dict_proxy({', '})'))
849 _type_pprinters[types.ClassType] = _type_pprint
849 _type_pprinters[types.ClassType] = _type_pprint
850 _type_pprinters[types.SliceType] = _repr_pprint
850 _type_pprinters[types.SliceType] = _repr_pprint
851 except AttributeError: # Python 3
851 except AttributeError: # Python 3
852 _type_pprinters[types.MappingProxyType] = \
852 _type_pprinters[types.MappingProxyType] = \
853 _dict_pprinter_factory('mappingproxy({', '})')
853 _dict_pprinter_factory('mappingproxy({', '})')
854 _type_pprinters[slice] = _repr_pprint
854 _type_pprinters[slice] = _repr_pprint
855
855
856 _type_pprinters[range] = _repr_pprint
856 _type_pprinters[range] = _repr_pprint
857 _type_pprinters[bytes] = _repr_pprint
857 _type_pprinters[bytes] = _repr_pprint
858
858
859 #: printers for types specified by name
859 #: printers for types specified by name
860 _deferred_type_pprinters = {
860 _deferred_type_pprinters = {
861 }
861 }
862
862
863 def for_type(typ, func):
863 def for_type(typ, func):
864 """
864 """
865 Add a pretty printer for a given type.
865 Add a pretty printer for a given type.
866 """
866 """
867 oldfunc = _type_pprinters.get(typ, None)
867 oldfunc = _type_pprinters.get(typ, None)
868 if func is not None:
868 if func is not None:
869 # To support easy restoration of old pprinters, we need to ignore Nones.
869 # To support easy restoration of old pprinters, we need to ignore Nones.
870 _type_pprinters[typ] = func
870 _type_pprinters[typ] = func
871 return oldfunc
871 return oldfunc
872
872
873 def for_type_by_name(type_module, type_name, func):
873 def for_type_by_name(type_module, type_name, func):
874 """
874 """
875 Add a pretty printer for a type specified by the module and name of a type
875 Add a pretty printer for a type specified by the module and name of a type
876 rather than the type object itself.
876 rather than the type object itself.
877 """
877 """
878 key = (type_module, type_name)
878 key = (type_module, type_name)
879 oldfunc = _deferred_type_pprinters.get(key, None)
879 oldfunc = _deferred_type_pprinters.get(key, None)
880 if func is not None:
880 if func is not None:
881 # To support easy restoration of old pprinters, we need to ignore Nones.
881 # To support easy restoration of old pprinters, we need to ignore Nones.
882 _deferred_type_pprinters[key] = func
882 _deferred_type_pprinters[key] = func
883 return oldfunc
883 return oldfunc
884
884
885
885
886 #: printers for the default singletons
886 #: printers for the default singletons
887 _singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis,
887 _singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis,
888 NotImplemented]), _repr_pprint)
888 NotImplemented]), _repr_pprint)
889
889
890
890
891 def _defaultdict_pprint(obj, p, cycle):
891 def _defaultdict_pprint(obj, p, cycle):
892 cls_ctor = CallExpression.factory(obj.__class__.__name__)
892 cls_ctor = CallExpression.factory(obj.__class__.__name__)
893 if cycle:
893 if cycle:
894 p.pretty(cls_ctor(RawText("...")))
894 p.pretty(cls_ctor(RawText("...")))
895 else:
895 else:
896 p.pretty(cls_ctor(obj.default_factory, dict(obj)))
896 p.pretty(cls_ctor(obj.default_factory, dict(obj)))
897
897
898 def _ordereddict_pprint(obj, p, cycle):
898 def _ordereddict_pprint(obj, p, cycle):
899 cls_ctor = CallExpression.factory(obj.__class__.__name__)
899 cls_ctor = CallExpression.factory(obj.__class__.__name__)
900 if cycle:
900 if cycle:
901 p.pretty(cls_ctor(RawText("...")))
901 p.pretty(cls_ctor(RawText("...")))
902 elif len(obj):
902 elif len(obj):
903 p.pretty(cls_ctor(list(obj.items())))
903 p.pretty(cls_ctor(list(obj.items())))
904 else:
904 else:
905 p.pretty(cls_ctor())
905 p.pretty(cls_ctor())
906
906
907 def _deque_pprint(obj, p, cycle):
907 def _deque_pprint(obj, p, cycle):
908 cls_ctor = CallExpression.factory(obj.__class__.__name__)
908 cls_ctor = CallExpression.factory(obj.__class__.__name__)
909 if cycle:
909 if cycle:
910 p.pretty(cls_ctor(RawText("...")))
910 p.pretty(cls_ctor(RawText("...")))
911 elif obj.maxlen is not None:
912 p.pretty(cls_ctor(list(obj), maxlen=obj.maxlen))
911 else:
913 else:
912 p.pretty(cls_ctor(list(obj)))
914 p.pretty(cls_ctor(list(obj)))
913
915
914 def _counter_pprint(obj, p, cycle):
916 def _counter_pprint(obj, p, cycle):
915 cls_ctor = CallExpression.factory(obj.__class__.__name__)
917 cls_ctor = CallExpression.factory(obj.__class__.__name__)
916 if cycle:
918 if cycle:
917 p.pretty(cls_ctor(RawText("...")))
919 p.pretty(cls_ctor(RawText("...")))
918 elif len(obj):
920 elif len(obj):
919 p.pretty(cls_ctor(dict(obj)))
921 p.pretty(cls_ctor(dict(obj)))
920 else:
922 else:
921 p.pretty(cls_ctor())
923 p.pretty(cls_ctor())
922
924
923
925
924 def _userlist_pprint(obj, p, cycle):
926 def _userlist_pprint(obj, p, cycle):
925 cls_ctor = CallExpression.factory(obj.__class__.__name__)
927 cls_ctor = CallExpression.factory(obj.__class__.__name__)
926 if cycle:
928 if cycle:
927 p.pretty(cls_ctor(RawText("...")))
929 p.pretty(cls_ctor(RawText("...")))
928 else:
930 else:
929 p.pretty(cls_ctor(obj.data))
931 p.pretty(cls_ctor(obj.data))
930
932
931
933
932 for_type_by_name('collections', 'defaultdict', _defaultdict_pprint)
934 for_type_by_name('collections', 'defaultdict', _defaultdict_pprint)
933 for_type_by_name('collections', 'OrderedDict', _ordereddict_pprint)
935 for_type_by_name('collections', 'OrderedDict', _ordereddict_pprint)
934 for_type_by_name('collections', 'deque', _deque_pprint)
936 for_type_by_name('collections', 'deque', _deque_pprint)
935 for_type_by_name('collections', 'Counter', _counter_pprint)
937 for_type_by_name('collections', 'Counter', _counter_pprint)
936 for_type_by_name("collections", "UserList", _userlist_pprint)
938 for_type_by_name("collections", "UserList", _userlist_pprint)
937
939
938 if __name__ == '__main__':
940 if __name__ == '__main__':
939 from random import randrange
941 from random import randrange
940 class Foo(object):
942 class Foo(object):
941 def __init__(self):
943 def __init__(self):
942 self.foo = 1
944 self.foo = 1
943 self.bar = re.compile(r'\s+')
945 self.bar = re.compile(r'\s+')
944 self.blub = dict.fromkeys(range(30), randrange(1, 40))
946 self.blub = dict.fromkeys(range(30), randrange(1, 40))
945 self.hehe = 23424.234234
947 self.hehe = 23424.234234
946 self.list = ["blub", "blah", self]
948 self.list = ["blub", "blah", self]
947
949
948 def get_foo(self):
950 def get_foo(self):
949 print("foo")
951 print("foo")
950
952
951 pprint(Foo(), verbose=True)
953 pprint(Foo(), verbose=True)
@@ -1,19 +1,20 b''
1 from IPython.core.error import TryNext
1 from IPython.core.error import TryNext
2 from IPython.lib.clipboard import ClipboardEmpty
2 from IPython.lib.clipboard import ClipboardEmpty
3 from IPython.testing.decorators import skip_if_no_x11
3 from IPython.testing.decorators import skip_if_no_x11
4
4
5
5 @skip_if_no_x11
6 @skip_if_no_x11
6 def test_clipboard_get():
7 def test_clipboard_get():
7 # Smoketest for clipboard access - we can't easily guarantee that the
8 # Smoketest for clipboard access - we can't easily guarantee that the
8 # clipboard is accessible and has something on it, but this tries to
9 # clipboard is accessible and has something on it, but this tries to
9 # exercise the relevant code anyway.
10 # exercise the relevant code anyway.
10 try:
11 try:
11 a = get_ipython().hooks.clipboard_get()
12 a = get_ipython().hooks.clipboard_get()
12 except ClipboardEmpty:
13 except ClipboardEmpty:
13 # Nothing in clipboard to get
14 # Nothing in clipboard to get
14 pass
15 pass
15 except TryNext:
16 except TryNext:
16 # No clipboard access API available
17 # No clipboard access API available
17 pass
18 pass
18 else:
19 else:
19 assert isinstance(a, str)
20 assert isinstance(a, str)
@@ -1,769 +1,774 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import asyncio
3 import asyncio
4 import os
4 import os
5 import sys
5 import sys
6 from warnings import warn
6 from warnings import warn
7
7
8 from IPython.core.async_helpers import get_asyncio_loop
8 from IPython.core.async_helpers import get_asyncio_loop
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 from IPython.utils.py3compat import input
10 from IPython.utils.py3compat import input
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
11 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
12 from IPython.utils.process import abbrev_cwd
12 from IPython.utils.process import abbrev_cwd
13 from traitlets import (
13 from traitlets import (
14 Bool,
14 Bool,
15 Unicode,
15 Unicode,
16 Dict,
16 Dict,
17 Integer,
17 Integer,
18 observe,
18 observe,
19 Instance,
19 Instance,
20 Type,
20 Type,
21 default,
21 default,
22 Enum,
22 Enum,
23 Union,
23 Union,
24 Any,
24 Any,
25 validate,
25 validate,
26 Float,
26 Float,
27 )
27 )
28
28
29 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
29 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
30 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
30 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
31 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
31 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
32 from prompt_toolkit.formatted_text import PygmentsTokens
32 from prompt_toolkit.formatted_text import PygmentsTokens
33 from prompt_toolkit.history import History
33 from prompt_toolkit.history import History
34 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
34 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
35 from prompt_toolkit.output import ColorDepth
35 from prompt_toolkit.output import ColorDepth
36 from prompt_toolkit.patch_stdout import patch_stdout
36 from prompt_toolkit.patch_stdout import patch_stdout
37 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
37 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
38 from prompt_toolkit.styles import DynamicStyle, merge_styles
38 from prompt_toolkit.styles import DynamicStyle, merge_styles
39 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
39 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
40 from prompt_toolkit import __version__ as ptk_version
40 from prompt_toolkit import __version__ as ptk_version
41
41
42 from pygments.styles import get_style_by_name
42 from pygments.styles import get_style_by_name
43 from pygments.style import Style
43 from pygments.style import Style
44 from pygments.token import Token
44 from pygments.token import Token
45
45
46 from .debugger import TerminalPdb, Pdb
46 from .debugger import TerminalPdb, Pdb
47 from .magics import TerminalMagics
47 from .magics import TerminalMagics
48 from .pt_inputhooks import get_inputhook_name_and_func
48 from .pt_inputhooks import get_inputhook_name_and_func
49 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
49 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
50 from .ptutils import IPythonPTCompleter, IPythonPTLexer
50 from .ptutils import IPythonPTCompleter, IPythonPTLexer
51 from .shortcuts import create_ipython_shortcuts
51 from .shortcuts import create_ipython_shortcuts
52
52
53 PTK3 = ptk_version.startswith('3.')
53 PTK3 = ptk_version.startswith('3.')
54
54
55
55
56 class _NoStyle(Style): pass
56 class _NoStyle(Style): pass
57
57
58
58
59
59
60 _style_overrides_light_bg = {
60 _style_overrides_light_bg = {
61 Token.Prompt: '#ansibrightblue',
61 Token.Prompt: '#ansibrightblue',
62 Token.PromptNum: '#ansiblue bold',
62 Token.PromptNum: '#ansiblue bold',
63 Token.OutPrompt: '#ansibrightred',
63 Token.OutPrompt: '#ansibrightred',
64 Token.OutPromptNum: '#ansired bold',
64 Token.OutPromptNum: '#ansired bold',
65 }
65 }
66
66
67 _style_overrides_linux = {
67 _style_overrides_linux = {
68 Token.Prompt: '#ansibrightgreen',
68 Token.Prompt: '#ansibrightgreen',
69 Token.PromptNum: '#ansigreen bold',
69 Token.PromptNum: '#ansigreen bold',
70 Token.OutPrompt: '#ansibrightred',
70 Token.OutPrompt: '#ansibrightred',
71 Token.OutPromptNum: '#ansired bold',
71 Token.OutPromptNum: '#ansired bold',
72 }
72 }
73
73
74 def get_default_editor():
74 def get_default_editor():
75 try:
75 try:
76 return os.environ['EDITOR']
76 return os.environ['EDITOR']
77 except KeyError:
77 except KeyError:
78 pass
78 pass
79 except UnicodeError:
79 except UnicodeError:
80 warn("$EDITOR environment variable is not pure ASCII. Using platform "
80 warn("$EDITOR environment variable is not pure ASCII. Using platform "
81 "default editor.")
81 "default editor.")
82
82
83 if os.name == 'posix':
83 if os.name == 'posix':
84 return 'vi' # the only one guaranteed to be there!
84 return 'vi' # the only one guaranteed to be there!
85 else:
85 else:
86 return 'notepad' # same in Windows!
86 return 'notepad' # same in Windows!
87
87
88 # conservatively check for tty
88 # conservatively check for tty
89 # overridden streams can result in things like:
89 # overridden streams can result in things like:
90 # - sys.stdin = None
90 # - sys.stdin = None
91 # - no isatty method
91 # - no isatty method
92 for _name in ('stdin', 'stdout', 'stderr'):
92 for _name in ('stdin', 'stdout', 'stderr'):
93 _stream = getattr(sys, _name)
93 _stream = getattr(sys, _name)
94 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
94 try:
95 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
96 _is_tty = False
97 break
98 except ValueError:
99 # stream is closed
95 _is_tty = False
100 _is_tty = False
96 break
101 break
97 else:
102 else:
98 _is_tty = True
103 _is_tty = True
99
104
100
105
101 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
106 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
102
107
103 def black_reformat_handler(text_before_cursor):
108 def black_reformat_handler(text_before_cursor):
104 """
109 """
105 We do not need to protect against error,
110 We do not need to protect against error,
106 this is taken care at a higher level where any reformat error is ignored.
111 this is taken care at a higher level where any reformat error is ignored.
107 Indeed we may call reformatting on incomplete code.
112 Indeed we may call reformatting on incomplete code.
108 """
113 """
109 import black
114 import black
110
115
111 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
116 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
112 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
117 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
113 formatted_text = formatted_text[:-1]
118 formatted_text = formatted_text[:-1]
114 return formatted_text
119 return formatted_text
115
120
116
121
117 def yapf_reformat_handler(text_before_cursor):
122 def yapf_reformat_handler(text_before_cursor):
118 from yapf.yapflib import file_resources
123 from yapf.yapflib import file_resources
119 from yapf.yapflib import yapf_api
124 from yapf.yapflib import yapf_api
120
125
121 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
126 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
122 formatted_text, was_formatted = yapf_api.FormatCode(
127 formatted_text, was_formatted = yapf_api.FormatCode(
123 text_before_cursor, style_config=style_config
128 text_before_cursor, style_config=style_config
124 )
129 )
125 if was_formatted:
130 if was_formatted:
126 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
131 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
127 formatted_text = formatted_text[:-1]
132 formatted_text = formatted_text[:-1]
128 return formatted_text
133 return formatted_text
129 else:
134 else:
130 return text_before_cursor
135 return text_before_cursor
131
136
132
137
133 class PtkHistoryAdapter(History):
138 class PtkHistoryAdapter(History):
134 """
139 """
135 Prompt toolkit has it's own way of handling history, Where it assumes it can
140 Prompt toolkit has it's own way of handling history, Where it assumes it can
136 Push/pull from history.
141 Push/pull from history.
137
142
138 """
143 """
139
144
140 def __init__(self, shell):
145 def __init__(self, shell):
141 super().__init__()
146 super().__init__()
142 self.shell = shell
147 self.shell = shell
143 self._refresh()
148 self._refresh()
144
149
145 def append_string(self, string):
150 def append_string(self, string):
146 # we rely on sql for that.
151 # we rely on sql for that.
147 self._loaded = False
152 self._loaded = False
148 self._refresh()
153 self._refresh()
149
154
150 def _refresh(self):
155 def _refresh(self):
151 if not self._loaded:
156 if not self._loaded:
152 self._loaded_strings = list(self.load_history_strings())
157 self._loaded_strings = list(self.load_history_strings())
153
158
154 def load_history_strings(self):
159 def load_history_strings(self):
155 last_cell = ""
160 last_cell = ""
156 res = []
161 res = []
157 for __, ___, cell in self.shell.history_manager.get_tail(
162 for __, ___, cell in self.shell.history_manager.get_tail(
158 self.shell.history_load_length, include_latest=True
163 self.shell.history_load_length, include_latest=True
159 ):
164 ):
160 # Ignore blank lines and consecutive duplicates
165 # Ignore blank lines and consecutive duplicates
161 cell = cell.rstrip()
166 cell = cell.rstrip()
162 if cell and (cell != last_cell):
167 if cell and (cell != last_cell):
163 res.append(cell)
168 res.append(cell)
164 last_cell = cell
169 last_cell = cell
165 yield from res[::-1]
170 yield from res[::-1]
166
171
167 def store_string(self, string: str) -> None:
172 def store_string(self, string: str) -> None:
168 pass
173 pass
169
174
170 class TerminalInteractiveShell(InteractiveShell):
175 class TerminalInteractiveShell(InteractiveShell):
171 mime_renderers = Dict().tag(config=True)
176 mime_renderers = Dict().tag(config=True)
172
177
173 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
178 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
174 'to reserve for the tab completion menu, '
179 'to reserve for the tab completion menu, '
175 'search history, ...etc, the height of '
180 'search history, ...etc, the height of '
176 'these menus will at most this value. '
181 'these menus will at most this value. '
177 'Increase it is you prefer long and skinny '
182 'Increase it is you prefer long and skinny '
178 'menus, decrease for short and wide.'
183 'menus, decrease for short and wide.'
179 ).tag(config=True)
184 ).tag(config=True)
180
185
181 pt_app = None
186 pt_app = None
182 debugger_history = None
187 debugger_history = None
183
188
184 debugger_history_file = Unicode(
189 debugger_history_file = Unicode(
185 "~/.pdbhistory", help="File in which to store and read history"
190 "~/.pdbhistory", help="File in which to store and read history"
186 ).tag(config=True)
191 ).tag(config=True)
187
192
188 simple_prompt = Bool(_use_simple_prompt,
193 simple_prompt = Bool(_use_simple_prompt,
189 help="""Use `raw_input` for the REPL, without completion and prompt colors.
194 help="""Use `raw_input` for the REPL, without completion and prompt colors.
190
195
191 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
196 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
192 IPython own testing machinery, and emacs inferior-shell integration through elpy.
197 IPython own testing machinery, and emacs inferior-shell integration through elpy.
193
198
194 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
199 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
195 environment variable is set, or the current terminal is not a tty."""
200 environment variable is set, or the current terminal is not a tty."""
196 ).tag(config=True)
201 ).tag(config=True)
197
202
198 @property
203 @property
199 def debugger_cls(self):
204 def debugger_cls(self):
200 return Pdb if self.simple_prompt else TerminalPdb
205 return Pdb if self.simple_prompt else TerminalPdb
201
206
202 confirm_exit = Bool(True,
207 confirm_exit = Bool(True,
203 help="""
208 help="""
204 Set to confirm when you try to exit IPython with an EOF (Control-D
209 Set to confirm when you try to exit IPython with an EOF (Control-D
205 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
210 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
206 you can force a direct exit without any confirmation.""",
211 you can force a direct exit without any confirmation.""",
207 ).tag(config=True)
212 ).tag(config=True)
208
213
209 editing_mode = Unicode('emacs',
214 editing_mode = Unicode('emacs',
210 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
215 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
211 ).tag(config=True)
216 ).tag(config=True)
212
217
213 emacs_bindings_in_vi_insert_mode = Bool(
218 emacs_bindings_in_vi_insert_mode = Bool(
214 True,
219 True,
215 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
220 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
216 ).tag(config=True)
221 ).tag(config=True)
217
222
218 modal_cursor = Bool(
223 modal_cursor = Bool(
219 True,
224 True,
220 help="""
225 help="""
221 Cursor shape changes depending on vi mode: beam in vi insert mode,
226 Cursor shape changes depending on vi mode: beam in vi insert mode,
222 block in nav mode, underscore in replace mode.""",
227 block in nav mode, underscore in replace mode.""",
223 ).tag(config=True)
228 ).tag(config=True)
224
229
225 ttimeoutlen = Float(
230 ttimeoutlen = Float(
226 0.01,
231 0.01,
227 help="""The time in milliseconds that is waited for a key code
232 help="""The time in milliseconds that is waited for a key code
228 to complete.""",
233 to complete.""",
229 ).tag(config=True)
234 ).tag(config=True)
230
235
231 timeoutlen = Float(
236 timeoutlen = Float(
232 0.5,
237 0.5,
233 help="""The time in milliseconds that is waited for a mapped key
238 help="""The time in milliseconds that is waited for a mapped key
234 sequence to complete.""",
239 sequence to complete.""",
235 ).tag(config=True)
240 ).tag(config=True)
236
241
237 autoformatter = Unicode(
242 autoformatter = Unicode(
238 None,
243 None,
239 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
244 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
240 allow_none=True
245 allow_none=True
241 ).tag(config=True)
246 ).tag(config=True)
242
247
243 auto_match = Bool(
248 auto_match = Bool(
244 False,
249 False,
245 help="""
250 help="""
246 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
251 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
247 Brackets: (), [], {}
252 Brackets: (), [], {}
248 Quotes: '', \"\"
253 Quotes: '', \"\"
249 """,
254 """,
250 ).tag(config=True)
255 ).tag(config=True)
251
256
252 mouse_support = Bool(False,
257 mouse_support = Bool(False,
253 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
258 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
254 ).tag(config=True)
259 ).tag(config=True)
255
260
256 # We don't load the list of styles for the help string, because loading
261 # We don't load the list of styles for the help string, because loading
257 # Pygments plugins takes time and can cause unexpected errors.
262 # Pygments plugins takes time and can cause unexpected errors.
258 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
263 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
259 help="""The name or class of a Pygments style to use for syntax
264 help="""The name or class of a Pygments style to use for syntax
260 highlighting. To see available styles, run `pygmentize -L styles`."""
265 highlighting. To see available styles, run `pygmentize -L styles`."""
261 ).tag(config=True)
266 ).tag(config=True)
262
267
263 @validate('editing_mode')
268 @validate('editing_mode')
264 def _validate_editing_mode(self, proposal):
269 def _validate_editing_mode(self, proposal):
265 if proposal['value'].lower() == 'vim':
270 if proposal['value'].lower() == 'vim':
266 proposal['value']= 'vi'
271 proposal['value']= 'vi'
267 elif proposal['value'].lower() == 'default':
272 elif proposal['value'].lower() == 'default':
268 proposal['value']= 'emacs'
273 proposal['value']= 'emacs'
269
274
270 if hasattr(EditingMode, proposal['value'].upper()):
275 if hasattr(EditingMode, proposal['value'].upper()):
271 return proposal['value'].lower()
276 return proposal['value'].lower()
272
277
273 return self.editing_mode
278 return self.editing_mode
274
279
275
280
276 @observe('editing_mode')
281 @observe('editing_mode')
277 def _editing_mode(self, change):
282 def _editing_mode(self, change):
278 if self.pt_app:
283 if self.pt_app:
279 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
284 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
280
285
281 def _set_formatter(self, formatter):
286 def _set_formatter(self, formatter):
282 if formatter is None:
287 if formatter is None:
283 self.reformat_handler = lambda x:x
288 self.reformat_handler = lambda x:x
284 elif formatter == 'black':
289 elif formatter == 'black':
285 self.reformat_handler = black_reformat_handler
290 self.reformat_handler = black_reformat_handler
286 elif formatter == "yapf":
291 elif formatter == "yapf":
287 self.reformat_handler = yapf_reformat_handler
292 self.reformat_handler = yapf_reformat_handler
288 else:
293 else:
289 raise ValueError
294 raise ValueError
290
295
291 @observe("autoformatter")
296 @observe("autoformatter")
292 def _autoformatter_changed(self, change):
297 def _autoformatter_changed(self, change):
293 formatter = change.new
298 formatter = change.new
294 self._set_formatter(formatter)
299 self._set_formatter(formatter)
295
300
296 @observe('highlighting_style')
301 @observe('highlighting_style')
297 @observe('colors')
302 @observe('colors')
298 def _highlighting_style_changed(self, change):
303 def _highlighting_style_changed(self, change):
299 self.refresh_style()
304 self.refresh_style()
300
305
301 def refresh_style(self):
306 def refresh_style(self):
302 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
307 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
303
308
304
309
305 highlighting_style_overrides = Dict(
310 highlighting_style_overrides = Dict(
306 help="Override highlighting format for specific tokens"
311 help="Override highlighting format for specific tokens"
307 ).tag(config=True)
312 ).tag(config=True)
308
313
309 true_color = Bool(False,
314 true_color = Bool(False,
310 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
315 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
311 If your terminal supports true color, the following command should
316 If your terminal supports true color, the following command should
312 print ``TRUECOLOR`` in orange::
317 print ``TRUECOLOR`` in orange::
313
318
314 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
319 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
315 """,
320 """,
316 ).tag(config=True)
321 ).tag(config=True)
317
322
318 editor = Unicode(get_default_editor(),
323 editor = Unicode(get_default_editor(),
319 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
324 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
320 ).tag(config=True)
325 ).tag(config=True)
321
326
322 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
327 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
323
328
324 prompts = Instance(Prompts)
329 prompts = Instance(Prompts)
325
330
326 @default('prompts')
331 @default('prompts')
327 def _prompts_default(self):
332 def _prompts_default(self):
328 return self.prompts_class(self)
333 return self.prompts_class(self)
329
334
330 # @observe('prompts')
335 # @observe('prompts')
331 # def _(self, change):
336 # def _(self, change):
332 # self._update_layout()
337 # self._update_layout()
333
338
334 @default('displayhook_class')
339 @default('displayhook_class')
335 def _displayhook_class_default(self):
340 def _displayhook_class_default(self):
336 return RichPromptDisplayHook
341 return RichPromptDisplayHook
337
342
338 term_title = Bool(True,
343 term_title = Bool(True,
339 help="Automatically set the terminal title"
344 help="Automatically set the terminal title"
340 ).tag(config=True)
345 ).tag(config=True)
341
346
342 term_title_format = Unicode("IPython: {cwd}",
347 term_title_format = Unicode("IPython: {cwd}",
343 help="Customize the terminal title format. This is a python format string. " +
348 help="Customize the terminal title format. This is a python format string. " +
344 "Available substitutions are: {cwd}."
349 "Available substitutions are: {cwd}."
345 ).tag(config=True)
350 ).tag(config=True)
346
351
347 display_completions = Enum(('column', 'multicolumn','readlinelike'),
352 display_completions = Enum(('column', 'multicolumn','readlinelike'),
348 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
353 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
349 "'readlinelike'. These options are for `prompt_toolkit`, see "
354 "'readlinelike'. These options are for `prompt_toolkit`, see "
350 "`prompt_toolkit` documentation for more information."
355 "`prompt_toolkit` documentation for more information."
351 ),
356 ),
352 default_value='multicolumn').tag(config=True)
357 default_value='multicolumn').tag(config=True)
353
358
354 highlight_matching_brackets = Bool(True,
359 highlight_matching_brackets = Bool(True,
355 help="Highlight matching brackets.",
360 help="Highlight matching brackets.",
356 ).tag(config=True)
361 ).tag(config=True)
357
362
358 extra_open_editor_shortcuts = Bool(False,
363 extra_open_editor_shortcuts = Bool(False,
359 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
364 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
360 "This is in addition to the F2 binding, which is always enabled."
365 "This is in addition to the F2 binding, which is always enabled."
361 ).tag(config=True)
366 ).tag(config=True)
362
367
363 handle_return = Any(None,
368 handle_return = Any(None,
364 help="Provide an alternative handler to be called when the user presses "
369 help="Provide an alternative handler to be called when the user presses "
365 "Return. This is an advanced option intended for debugging, which "
370 "Return. This is an advanced option intended for debugging, which "
366 "may be changed or removed in later releases."
371 "may be changed or removed in later releases."
367 ).tag(config=True)
372 ).tag(config=True)
368
373
369 enable_history_search = Bool(True,
374 enable_history_search = Bool(True,
370 help="Allows to enable/disable the prompt toolkit history search"
375 help="Allows to enable/disable the prompt toolkit history search"
371 ).tag(config=True)
376 ).tag(config=True)
372
377
373 autosuggestions_provider = Unicode(
378 autosuggestions_provider = Unicode(
374 "AutoSuggestFromHistory",
379 "AutoSuggestFromHistory",
375 help="Specifies from which source automatic suggestions are provided. "
380 help="Specifies from which source automatic suggestions are provided. "
376 "Can be set to `'AutoSuggestFromHistory`' or `None` to disable"
381 "Can be set to `'AutoSuggestFromHistory`' or `None` to disable"
377 "automatic suggestions. Default is `'AutoSuggestFromHistory`'.",
382 "automatic suggestions. Default is `'AutoSuggestFromHistory`'.",
378 allow_none=True,
383 allow_none=True,
379 ).tag(config=True)
384 ).tag(config=True)
380
385
381 def _set_autosuggestions(self, provider):
386 def _set_autosuggestions(self, provider):
382 if provider is None:
387 if provider is None:
383 self.auto_suggest = None
388 self.auto_suggest = None
384 elif provider == "AutoSuggestFromHistory":
389 elif provider == "AutoSuggestFromHistory":
385 self.auto_suggest = AutoSuggestFromHistory()
390 self.auto_suggest = AutoSuggestFromHistory()
386 else:
391 else:
387 raise ValueError("No valid provider.")
392 raise ValueError("No valid provider.")
388 if self.pt_app:
393 if self.pt_app:
389 self.pt_app.auto_suggest = self.auto_suggest
394 self.pt_app.auto_suggest = self.auto_suggest
390
395
391 @observe("autosuggestions_provider")
396 @observe("autosuggestions_provider")
392 def _autosuggestions_provider_changed(self, change):
397 def _autosuggestions_provider_changed(self, change):
393 provider = change.new
398 provider = change.new
394 self._set_autosuggestions(provider)
399 self._set_autosuggestions(provider)
395
400
396 prompt_includes_vi_mode = Bool(True,
401 prompt_includes_vi_mode = Bool(True,
397 help="Display the current vi mode (when using vi editing mode)."
402 help="Display the current vi mode (when using vi editing mode)."
398 ).tag(config=True)
403 ).tag(config=True)
399
404
400 @observe('term_title')
405 @observe('term_title')
401 def init_term_title(self, change=None):
406 def init_term_title(self, change=None):
402 # Enable or disable the terminal title.
407 # Enable or disable the terminal title.
403 if self.term_title:
408 if self.term_title:
404 toggle_set_term_title(True)
409 toggle_set_term_title(True)
405 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
410 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
406 else:
411 else:
407 toggle_set_term_title(False)
412 toggle_set_term_title(False)
408
413
409 def restore_term_title(self):
414 def restore_term_title(self):
410 if self.term_title:
415 if self.term_title:
411 restore_term_title()
416 restore_term_title()
412
417
413 def init_display_formatter(self):
418 def init_display_formatter(self):
414 super(TerminalInteractiveShell, self).init_display_formatter()
419 super(TerminalInteractiveShell, self).init_display_formatter()
415 # terminal only supports plain text
420 # terminal only supports plain text
416 self.display_formatter.active_types = ["text/plain"]
421 self.display_formatter.active_types = ["text/plain"]
417
422
418 def init_prompt_toolkit_cli(self):
423 def init_prompt_toolkit_cli(self):
419 if self.simple_prompt:
424 if self.simple_prompt:
420 # Fall back to plain non-interactive output for tests.
425 # Fall back to plain non-interactive output for tests.
421 # This is very limited.
426 # This is very limited.
422 def prompt():
427 def prompt():
423 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
428 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
424 lines = [input(prompt_text)]
429 lines = [input(prompt_text)]
425 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
430 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
426 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
431 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
427 lines.append( input(prompt_continuation) )
432 lines.append( input(prompt_continuation) )
428 return '\n'.join(lines)
433 return '\n'.join(lines)
429 self.prompt_for_code = prompt
434 self.prompt_for_code = prompt
430 return
435 return
431
436
432 # Set up keyboard shortcuts
437 # Set up keyboard shortcuts
433 key_bindings = create_ipython_shortcuts(self)
438 key_bindings = create_ipython_shortcuts(self)
434
439
435
440
436 # Pre-populate history from IPython's history database
441 # Pre-populate history from IPython's history database
437 history = PtkHistoryAdapter(self)
442 history = PtkHistoryAdapter(self)
438
443
439 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
444 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
440 self.style = DynamicStyle(lambda: self._style)
445 self.style = DynamicStyle(lambda: self._style)
441
446
442 editing_mode = getattr(EditingMode, self.editing_mode.upper())
447 editing_mode = getattr(EditingMode, self.editing_mode.upper())
443
448
444 self.pt_loop = asyncio.new_event_loop()
449 self.pt_loop = asyncio.new_event_loop()
445 self.pt_app = PromptSession(
450 self.pt_app = PromptSession(
446 auto_suggest=self.auto_suggest,
451 auto_suggest=self.auto_suggest,
447 editing_mode=editing_mode,
452 editing_mode=editing_mode,
448 key_bindings=key_bindings,
453 key_bindings=key_bindings,
449 history=history,
454 history=history,
450 completer=IPythonPTCompleter(shell=self),
455 completer=IPythonPTCompleter(shell=self),
451 enable_history_search=self.enable_history_search,
456 enable_history_search=self.enable_history_search,
452 style=self.style,
457 style=self.style,
453 include_default_pygments_style=False,
458 include_default_pygments_style=False,
454 mouse_support=self.mouse_support,
459 mouse_support=self.mouse_support,
455 enable_open_in_editor=self.extra_open_editor_shortcuts,
460 enable_open_in_editor=self.extra_open_editor_shortcuts,
456 color_depth=self.color_depth,
461 color_depth=self.color_depth,
457 tempfile_suffix=".py",
462 tempfile_suffix=".py",
458 **self._extra_prompt_options()
463 **self._extra_prompt_options()
459 )
464 )
460
465
461 def _make_style_from_name_or_cls(self, name_or_cls):
466 def _make_style_from_name_or_cls(self, name_or_cls):
462 """
467 """
463 Small wrapper that make an IPython compatible style from a style name
468 Small wrapper that make an IPython compatible style from a style name
464
469
465 We need that to add style for prompt ... etc.
470 We need that to add style for prompt ... etc.
466 """
471 """
467 style_overrides = {}
472 style_overrides = {}
468 if name_or_cls == 'legacy':
473 if name_or_cls == 'legacy':
469 legacy = self.colors.lower()
474 legacy = self.colors.lower()
470 if legacy == 'linux':
475 if legacy == 'linux':
471 style_cls = get_style_by_name('monokai')
476 style_cls = get_style_by_name('monokai')
472 style_overrides = _style_overrides_linux
477 style_overrides = _style_overrides_linux
473 elif legacy == 'lightbg':
478 elif legacy == 'lightbg':
474 style_overrides = _style_overrides_light_bg
479 style_overrides = _style_overrides_light_bg
475 style_cls = get_style_by_name('pastie')
480 style_cls = get_style_by_name('pastie')
476 elif legacy == 'neutral':
481 elif legacy == 'neutral':
477 # The default theme needs to be visible on both a dark background
482 # The default theme needs to be visible on both a dark background
478 # and a light background, because we can't tell what the terminal
483 # and a light background, because we can't tell what the terminal
479 # looks like. These tweaks to the default theme help with that.
484 # looks like. These tweaks to the default theme help with that.
480 style_cls = get_style_by_name('default')
485 style_cls = get_style_by_name('default')
481 style_overrides.update({
486 style_overrides.update({
482 Token.Number: '#ansigreen',
487 Token.Number: '#ansigreen',
483 Token.Operator: 'noinherit',
488 Token.Operator: 'noinherit',
484 Token.String: '#ansiyellow',
489 Token.String: '#ansiyellow',
485 Token.Name.Function: '#ansiblue',
490 Token.Name.Function: '#ansiblue',
486 Token.Name.Class: 'bold #ansiblue',
491 Token.Name.Class: 'bold #ansiblue',
487 Token.Name.Namespace: 'bold #ansiblue',
492 Token.Name.Namespace: 'bold #ansiblue',
488 Token.Name.Variable.Magic: '#ansiblue',
493 Token.Name.Variable.Magic: '#ansiblue',
489 Token.Prompt: '#ansigreen',
494 Token.Prompt: '#ansigreen',
490 Token.PromptNum: '#ansibrightgreen bold',
495 Token.PromptNum: '#ansibrightgreen bold',
491 Token.OutPrompt: '#ansired',
496 Token.OutPrompt: '#ansired',
492 Token.OutPromptNum: '#ansibrightred bold',
497 Token.OutPromptNum: '#ansibrightred bold',
493 })
498 })
494
499
495 # Hack: Due to limited color support on the Windows console
500 # Hack: Due to limited color support on the Windows console
496 # the prompt colors will be wrong without this
501 # the prompt colors will be wrong without this
497 if os.name == 'nt':
502 if os.name == 'nt':
498 style_overrides.update({
503 style_overrides.update({
499 Token.Prompt: '#ansidarkgreen',
504 Token.Prompt: '#ansidarkgreen',
500 Token.PromptNum: '#ansigreen bold',
505 Token.PromptNum: '#ansigreen bold',
501 Token.OutPrompt: '#ansidarkred',
506 Token.OutPrompt: '#ansidarkred',
502 Token.OutPromptNum: '#ansired bold',
507 Token.OutPromptNum: '#ansired bold',
503 })
508 })
504 elif legacy =='nocolor':
509 elif legacy =='nocolor':
505 style_cls=_NoStyle
510 style_cls=_NoStyle
506 style_overrides = {}
511 style_overrides = {}
507 else :
512 else :
508 raise ValueError('Got unknown colors: ', legacy)
513 raise ValueError('Got unknown colors: ', legacy)
509 else :
514 else :
510 if isinstance(name_or_cls, str):
515 if isinstance(name_or_cls, str):
511 style_cls = get_style_by_name(name_or_cls)
516 style_cls = get_style_by_name(name_or_cls)
512 else:
517 else:
513 style_cls = name_or_cls
518 style_cls = name_or_cls
514 style_overrides = {
519 style_overrides = {
515 Token.Prompt: '#ansigreen',
520 Token.Prompt: '#ansigreen',
516 Token.PromptNum: '#ansibrightgreen bold',
521 Token.PromptNum: '#ansibrightgreen bold',
517 Token.OutPrompt: '#ansired',
522 Token.OutPrompt: '#ansired',
518 Token.OutPromptNum: '#ansibrightred bold',
523 Token.OutPromptNum: '#ansibrightred bold',
519 }
524 }
520 style_overrides.update(self.highlighting_style_overrides)
525 style_overrides.update(self.highlighting_style_overrides)
521 style = merge_styles([
526 style = merge_styles([
522 style_from_pygments_cls(style_cls),
527 style_from_pygments_cls(style_cls),
523 style_from_pygments_dict(style_overrides),
528 style_from_pygments_dict(style_overrides),
524 ])
529 ])
525
530
526 return style
531 return style
527
532
528 @property
533 @property
529 def pt_complete_style(self):
534 def pt_complete_style(self):
530 return {
535 return {
531 'multicolumn': CompleteStyle.MULTI_COLUMN,
536 'multicolumn': CompleteStyle.MULTI_COLUMN,
532 'column': CompleteStyle.COLUMN,
537 'column': CompleteStyle.COLUMN,
533 'readlinelike': CompleteStyle.READLINE_LIKE,
538 'readlinelike': CompleteStyle.READLINE_LIKE,
534 }[self.display_completions]
539 }[self.display_completions]
535
540
536 @property
541 @property
537 def color_depth(self):
542 def color_depth(self):
538 return (ColorDepth.TRUE_COLOR if self.true_color else None)
543 return (ColorDepth.TRUE_COLOR if self.true_color else None)
539
544
540 def _extra_prompt_options(self):
545 def _extra_prompt_options(self):
541 """
546 """
542 Return the current layout option for the current Terminal InteractiveShell
547 Return the current layout option for the current Terminal InteractiveShell
543 """
548 """
544 def get_message():
549 def get_message():
545 return PygmentsTokens(self.prompts.in_prompt_tokens())
550 return PygmentsTokens(self.prompts.in_prompt_tokens())
546
551
547 if self.editing_mode == 'emacs':
552 if self.editing_mode == 'emacs':
548 # with emacs mode the prompt is (usually) static, so we call only
553 # with emacs mode the prompt is (usually) static, so we call only
549 # the function once. With VI mode it can toggle between [ins] and
554 # the function once. With VI mode it can toggle between [ins] and
550 # [nor] so we can't precompute.
555 # [nor] so we can't precompute.
551 # here I'm going to favor the default keybinding which almost
556 # here I'm going to favor the default keybinding which almost
552 # everybody uses to decrease CPU usage.
557 # everybody uses to decrease CPU usage.
553 # if we have issues with users with custom Prompts we can see how to
558 # if we have issues with users with custom Prompts we can see how to
554 # work around this.
559 # work around this.
555 get_message = get_message()
560 get_message = get_message()
556
561
557 options = {
562 options = {
558 'complete_in_thread': False,
563 'complete_in_thread': False,
559 'lexer':IPythonPTLexer(),
564 'lexer':IPythonPTLexer(),
560 'reserve_space_for_menu':self.space_for_menu,
565 'reserve_space_for_menu':self.space_for_menu,
561 'message': get_message,
566 'message': get_message,
562 'prompt_continuation': (
567 'prompt_continuation': (
563 lambda width, lineno, is_soft_wrap:
568 lambda width, lineno, is_soft_wrap:
564 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
569 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
565 'multiline': True,
570 'multiline': True,
566 'complete_style': self.pt_complete_style,
571 'complete_style': self.pt_complete_style,
567
572
568 # Highlight matching brackets, but only when this setting is
573 # Highlight matching brackets, but only when this setting is
569 # enabled, and only when the DEFAULT_BUFFER has the focus.
574 # enabled, and only when the DEFAULT_BUFFER has the focus.
570 'input_processors': [ConditionalProcessor(
575 'input_processors': [ConditionalProcessor(
571 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
576 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
572 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
577 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
573 Condition(lambda: self.highlight_matching_brackets))],
578 Condition(lambda: self.highlight_matching_brackets))],
574 }
579 }
575 if not PTK3:
580 if not PTK3:
576 options['inputhook'] = self.inputhook
581 options['inputhook'] = self.inputhook
577
582
578 return options
583 return options
579
584
580 def prompt_for_code(self):
585 def prompt_for_code(self):
581 if self.rl_next_input:
586 if self.rl_next_input:
582 default = self.rl_next_input
587 default = self.rl_next_input
583 self.rl_next_input = None
588 self.rl_next_input = None
584 else:
589 else:
585 default = ''
590 default = ''
586
591
587 # In order to make sure that asyncio code written in the
592 # In order to make sure that asyncio code written in the
588 # interactive shell doesn't interfere with the prompt, we run the
593 # interactive shell doesn't interfere with the prompt, we run the
589 # prompt in a different event loop.
594 # prompt in a different event loop.
590 # If we don't do this, people could spawn coroutine with a
595 # If we don't do this, people could spawn coroutine with a
591 # while/true inside which will freeze the prompt.
596 # while/true inside which will freeze the prompt.
592
597
593 policy = asyncio.get_event_loop_policy()
598 policy = asyncio.get_event_loop_policy()
594 old_loop = get_asyncio_loop()
599 old_loop = get_asyncio_loop()
595
600
596 # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop`
601 # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop`
597 # to get the current event loop.
602 # to get the current event loop.
598 # This will probably be replaced by an attribute or input argument,
603 # This will probably be replaced by an attribute or input argument,
599 # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here.
604 # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here.
600 if old_loop is not self.pt_loop:
605 if old_loop is not self.pt_loop:
601 policy.set_event_loop(self.pt_loop)
606 policy.set_event_loop(self.pt_loop)
602 try:
607 try:
603 with patch_stdout(raw=True):
608 with patch_stdout(raw=True):
604 text = self.pt_app.prompt(
609 text = self.pt_app.prompt(
605 default=default,
610 default=default,
606 **self._extra_prompt_options())
611 **self._extra_prompt_options())
607 finally:
612 finally:
608 # Restore the original event loop.
613 # Restore the original event loop.
609 if old_loop is not None and old_loop is not self.pt_loop:
614 if old_loop is not None and old_loop is not self.pt_loop:
610 policy.set_event_loop(old_loop)
615 policy.set_event_loop(old_loop)
611
616
612 return text
617 return text
613
618
614 def enable_win_unicode_console(self):
619 def enable_win_unicode_console(self):
615 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
620 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
616 # console by default, so WUC shouldn't be needed.
621 # console by default, so WUC shouldn't be needed.
617 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
622 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
618 DeprecationWarning,
623 DeprecationWarning,
619 stacklevel=2)
624 stacklevel=2)
620
625
621 def init_io(self):
626 def init_io(self):
622 if sys.platform not in {'win32', 'cli'}:
627 if sys.platform not in {'win32', 'cli'}:
623 return
628 return
624
629
625 import colorama
630 import colorama
626 colorama.init()
631 colorama.init()
627
632
628 def init_magics(self):
633 def init_magics(self):
629 super(TerminalInteractiveShell, self).init_magics()
634 super(TerminalInteractiveShell, self).init_magics()
630 self.register_magics(TerminalMagics)
635 self.register_magics(TerminalMagics)
631
636
632 def init_alias(self):
637 def init_alias(self):
633 # The parent class defines aliases that can be safely used with any
638 # The parent class defines aliases that can be safely used with any
634 # frontend.
639 # frontend.
635 super(TerminalInteractiveShell, self).init_alias()
640 super(TerminalInteractiveShell, self).init_alias()
636
641
637 # Now define aliases that only make sense on the terminal, because they
642 # Now define aliases that only make sense on the terminal, because they
638 # need direct access to the console in a way that we can't emulate in
643 # need direct access to the console in a way that we can't emulate in
639 # GUI or web frontend
644 # GUI or web frontend
640 if os.name == 'posix':
645 if os.name == 'posix':
641 for cmd in ('clear', 'more', 'less', 'man'):
646 for cmd in ('clear', 'more', 'less', 'man'):
642 self.alias_manager.soft_define_alias(cmd, cmd)
647 self.alias_manager.soft_define_alias(cmd, cmd)
643
648
644
649
645 def __init__(self, *args, **kwargs):
650 def __init__(self, *args, **kwargs):
646 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
651 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
647 self._set_autosuggestions(self.autosuggestions_provider)
652 self._set_autosuggestions(self.autosuggestions_provider)
648 self.init_prompt_toolkit_cli()
653 self.init_prompt_toolkit_cli()
649 self.init_term_title()
654 self.init_term_title()
650 self.keep_running = True
655 self.keep_running = True
651 self._set_formatter(self.autoformatter)
656 self._set_formatter(self.autoformatter)
652
657
653
658
654 def ask_exit(self):
659 def ask_exit(self):
655 self.keep_running = False
660 self.keep_running = False
656
661
657 rl_next_input = None
662 rl_next_input = None
658
663
659 def interact(self):
664 def interact(self):
660 self.keep_running = True
665 self.keep_running = True
661 while self.keep_running:
666 while self.keep_running:
662 print(self.separate_in, end='')
667 print(self.separate_in, end='')
663
668
664 try:
669 try:
665 code = self.prompt_for_code()
670 code = self.prompt_for_code()
666 except EOFError:
671 except EOFError:
667 if (not self.confirm_exit) \
672 if (not self.confirm_exit) \
668 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
673 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
669 self.ask_exit()
674 self.ask_exit()
670
675
671 else:
676 else:
672 if code:
677 if code:
673 self.run_cell(code, store_history=True)
678 self.run_cell(code, store_history=True)
674
679
675 def mainloop(self):
680 def mainloop(self):
676 # An extra layer of protection in case someone mashing Ctrl-C breaks
681 # An extra layer of protection in case someone mashing Ctrl-C breaks
677 # out of our internal code.
682 # out of our internal code.
678 while True:
683 while True:
679 try:
684 try:
680 self.interact()
685 self.interact()
681 break
686 break
682 except KeyboardInterrupt as e:
687 except KeyboardInterrupt as e:
683 print("\n%s escaped interact()\n" % type(e).__name__)
688 print("\n%s escaped interact()\n" % type(e).__name__)
684 finally:
689 finally:
685 # An interrupt during the eventloop will mess up the
690 # An interrupt during the eventloop will mess up the
686 # internal state of the prompt_toolkit library.
691 # internal state of the prompt_toolkit library.
687 # Stopping the eventloop fixes this, see
692 # Stopping the eventloop fixes this, see
688 # https://github.com/ipython/ipython/pull/9867
693 # https://github.com/ipython/ipython/pull/9867
689 if hasattr(self, '_eventloop'):
694 if hasattr(self, '_eventloop'):
690 self._eventloop.stop()
695 self._eventloop.stop()
691
696
692 self.restore_term_title()
697 self.restore_term_title()
693
698
694 # try to call some at-exit operation optimistically as some things can't
699 # try to call some at-exit operation optimistically as some things can't
695 # be done during interpreter shutdown. this is technically inaccurate as
700 # be done during interpreter shutdown. this is technically inaccurate as
696 # this make mainlool not re-callable, but that should be a rare if not
701 # this make mainlool not re-callable, but that should be a rare if not
697 # in existent use case.
702 # in existent use case.
698
703
699 self._atexit_once()
704 self._atexit_once()
700
705
701
706
702 _inputhook = None
707 _inputhook = None
703 def inputhook(self, context):
708 def inputhook(self, context):
704 if self._inputhook is not None:
709 if self._inputhook is not None:
705 self._inputhook(context)
710 self._inputhook(context)
706
711
707 active_eventloop = None
712 active_eventloop = None
708 def enable_gui(self, gui=None):
713 def enable_gui(self, gui=None):
709 if gui and (gui != 'inline') :
714 if gui and (gui != 'inline') :
710 self.active_eventloop, self._inputhook =\
715 self.active_eventloop, self._inputhook =\
711 get_inputhook_name_and_func(gui)
716 get_inputhook_name_and_func(gui)
712 else:
717 else:
713 self.active_eventloop = self._inputhook = None
718 self.active_eventloop = self._inputhook = None
714
719
715 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
720 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
716 # this inputhook.
721 # this inputhook.
717 if PTK3:
722 if PTK3:
718 import asyncio
723 import asyncio
719 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
724 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
720
725
721 if gui == 'asyncio':
726 if gui == 'asyncio':
722 # When we integrate the asyncio event loop, run the UI in the
727 # When we integrate the asyncio event loop, run the UI in the
723 # same event loop as the rest of the code. don't use an actual
728 # same event loop as the rest of the code. don't use an actual
724 # input hook. (Asyncio is not made for nesting event loops.)
729 # input hook. (Asyncio is not made for nesting event loops.)
725 self.pt_loop = get_asyncio_loop()
730 self.pt_loop = get_asyncio_loop()
726
731
727 elif self._inputhook:
732 elif self._inputhook:
728 # If an inputhook was set, create a new asyncio event loop with
733 # If an inputhook was set, create a new asyncio event loop with
729 # this inputhook for the prompt.
734 # this inputhook for the prompt.
730 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
735 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
731 else:
736 else:
732 # When there's no inputhook, run the prompt in a separate
737 # When there's no inputhook, run the prompt in a separate
733 # asyncio event loop.
738 # asyncio event loop.
734 self.pt_loop = asyncio.new_event_loop()
739 self.pt_loop = asyncio.new_event_loop()
735
740
736 # Run !system commands directly, not through pipes, so terminal programs
741 # Run !system commands directly, not through pipes, so terminal programs
737 # work correctly.
742 # work correctly.
738 system = InteractiveShell.system_raw
743 system = InteractiveShell.system_raw
739
744
740 def auto_rewrite_input(self, cmd):
745 def auto_rewrite_input(self, cmd):
741 """Overridden from the parent class to use fancy rewriting prompt"""
746 """Overridden from the parent class to use fancy rewriting prompt"""
742 if not self.show_rewritten_input:
747 if not self.show_rewritten_input:
743 return
748 return
744
749
745 tokens = self.prompts.rewrite_prompt_tokens()
750 tokens = self.prompts.rewrite_prompt_tokens()
746 if self.pt_app:
751 if self.pt_app:
747 print_formatted_text(PygmentsTokens(tokens), end='',
752 print_formatted_text(PygmentsTokens(tokens), end='',
748 style=self.pt_app.app.style)
753 style=self.pt_app.app.style)
749 print(cmd)
754 print(cmd)
750 else:
755 else:
751 prompt = ''.join(s for t, s in tokens)
756 prompt = ''.join(s for t, s in tokens)
752 print(prompt, cmd, sep='')
757 print(prompt, cmd, sep='')
753
758
754 _prompts_before = None
759 _prompts_before = None
755 def switch_doctest_mode(self, mode):
760 def switch_doctest_mode(self, mode):
756 """Switch prompts to classic for %doctest_mode"""
761 """Switch prompts to classic for %doctest_mode"""
757 if mode:
762 if mode:
758 self._prompts_before = self.prompts
763 self._prompts_before = self.prompts
759 self.prompts = ClassicPrompts(self)
764 self.prompts = ClassicPrompts(self)
760 elif self._prompts_before:
765 elif self._prompts_before:
761 self.prompts = self._prompts_before
766 self.prompts = self._prompts_before
762 self._prompts_before = None
767 self._prompts_before = None
763 # self._update_layout()
768 # self._update_layout()
764
769
765
770
766 InteractiveShellABC.register(TerminalInteractiveShell)
771 InteractiveShellABC.register(TerminalInteractiveShell)
767
772
768 if __name__ == '__main__':
773 if __name__ == '__main__':
769 TerminalInteractiveShell.instance().interact()
774 TerminalInteractiveShell.instance().interact()
@@ -1,197 +1,204 b''
1 """prompt-toolkit utilities
1 """prompt-toolkit utilities
2
2
3 Everything in this module is a private API,
3 Everything in this module is a private API,
4 not to be used outside IPython.
4 not to be used outside IPython.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import unicodedata
10 import unicodedata
11 from wcwidth import wcwidth
11 from wcwidth import wcwidth
12
12
13 from IPython.core.completer import (
13 from IPython.core.completer import (
14 provisionalcompleter, cursor_to_position,
14 provisionalcompleter, cursor_to_position,
15 _deduplicate_completions)
15 _deduplicate_completions)
16 from prompt_toolkit.completion import Completer, Completion
16 from prompt_toolkit.completion import Completer, Completion
17 from prompt_toolkit.lexers import Lexer
17 from prompt_toolkit.lexers import Lexer
18 from prompt_toolkit.lexers import PygmentsLexer
18 from prompt_toolkit.lexers import PygmentsLexer
19 from prompt_toolkit.patch_stdout import patch_stdout
19 from prompt_toolkit.patch_stdout import patch_stdout
20
20
21 import pygments.lexers as pygments_lexers
21 import pygments.lexers as pygments_lexers
22 import os
22 import os
23 import sys
23 import sys
24 import traceback
24 import traceback
25
25
26 _completion_sentinel = object()
26 _completion_sentinel = object()
27
27
28 def _elide_point(string:str, *, min_elide=30)->str:
28 def _elide_point(string:str, *, min_elide=30)->str:
29 """
29 """
30 If a string is long enough, and has at least 3 dots,
30 If a string is long enough, and has at least 3 dots,
31 replace the middle part with ellipses.
31 replace the middle part with ellipses.
32
32
33 If a string naming a file is long enough, and has at least 3 slashes,
33 If a string naming a file is long enough, and has at least 3 slashes,
34 replace the middle part with ellipses.
34 replace the middle part with ellipses.
35
35
36 If three consecutive dots, or two consecutive dots are encountered these are
36 If three consecutive dots, or two consecutive dots are encountered these are
37 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
37 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
38 equivalents
38 equivalents
39 """
39 """
40 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
40 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
41 string = string.replace('..','\N{TWO DOT LEADER}')
41 string = string.replace('..','\N{TWO DOT LEADER}')
42 if len(string) < min_elide:
42 if len(string) < min_elide:
43 return string
43 return string
44
44
45 object_parts = string.split('.')
45 object_parts = string.split('.')
46 file_parts = string.split(os.sep)
46 file_parts = string.split(os.sep)
47 if file_parts[-1] == '':
47 if file_parts[-1] == '':
48 file_parts.pop()
48 file_parts.pop()
49
49
50 if len(object_parts) > 3:
50 if len(object_parts) > 3:
51 return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1])
51 return "{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}".format(
52 object_parts[0],
53 object_parts[1][:1],
54 object_parts[-2][-1:],
55 object_parts[-1],
56 )
52
57
53 elif len(file_parts) > 3:
58 elif len(file_parts) > 3:
54 return ('{}' + os.sep + '{}\N{HORIZONTAL ELLIPSIS}{}' + os.sep + '{}').format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1])
59 return ("{}" + os.sep + "{}\N{HORIZONTAL ELLIPSIS}{}" + os.sep + "{}").format(
60 file_parts[0], file_parts[1][:1], file_parts[-2][-1:], file_parts[-1]
61 )
55
62
56 return string
63 return string
57
64
58 def _elide_typed(string:str, typed:str, *, min_elide:int=30)->str:
65 def _elide_typed(string:str, typed:str, *, min_elide:int=30)->str:
59 """
66 """
60 Elide the middle of a long string if the beginning has already been typed.
67 Elide the middle of a long string if the beginning has already been typed.
61 """
68 """
62
69
63 if len(string) < min_elide:
70 if len(string) < min_elide:
64 return string
71 return string
65 cut_how_much = len(typed)-3
72 cut_how_much = len(typed)-3
66 if cut_how_much < 7:
73 if cut_how_much < 7:
67 return string
74 return string
68 if string.startswith(typed) and len(string)> len(typed):
75 if string.startswith(typed) and len(string)> len(typed):
69 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
76 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
70 return string
77 return string
71
78
72 def _elide(string:str, typed:str, min_elide=30)->str:
79 def _elide(string:str, typed:str, min_elide=30)->str:
73 return _elide_typed(
80 return _elide_typed(
74 _elide_point(string, min_elide=min_elide),
81 _elide_point(string, min_elide=min_elide),
75 typed, min_elide=min_elide)
82 typed, min_elide=min_elide)
76
83
77
84
78
85
79 def _adjust_completion_text_based_on_context(text, body, offset):
86 def _adjust_completion_text_based_on_context(text, body, offset):
80 if text.endswith('=') and len(body) > offset and body[offset] == '=':
87 if text.endswith('=') and len(body) > offset and body[offset] == '=':
81 return text[:-1]
88 return text[:-1]
82 else:
89 else:
83 return text
90 return text
84
91
85
92
86 class IPythonPTCompleter(Completer):
93 class IPythonPTCompleter(Completer):
87 """Adaptor to provide IPython completions to prompt_toolkit"""
94 """Adaptor to provide IPython completions to prompt_toolkit"""
88 def __init__(self, ipy_completer=None, shell=None):
95 def __init__(self, ipy_completer=None, shell=None):
89 if shell is None and ipy_completer is None:
96 if shell is None and ipy_completer is None:
90 raise TypeError("Please pass shell=an InteractiveShell instance.")
97 raise TypeError("Please pass shell=an InteractiveShell instance.")
91 self._ipy_completer = ipy_completer
98 self._ipy_completer = ipy_completer
92 self.shell = shell
99 self.shell = shell
93
100
94 @property
101 @property
95 def ipy_completer(self):
102 def ipy_completer(self):
96 if self._ipy_completer:
103 if self._ipy_completer:
97 return self._ipy_completer
104 return self._ipy_completer
98 else:
105 else:
99 return self.shell.Completer
106 return self.shell.Completer
100
107
101 def get_completions(self, document, complete_event):
108 def get_completions(self, document, complete_event):
102 if not document.current_line.strip():
109 if not document.current_line.strip():
103 return
110 return
104 # Some bits of our completion system may print stuff (e.g. if a module
111 # Some bits of our completion system may print stuff (e.g. if a module
105 # is imported). This context manager ensures that doesn't interfere with
112 # is imported). This context manager ensures that doesn't interfere with
106 # the prompt.
113 # the prompt.
107
114
108 with patch_stdout(), provisionalcompleter():
115 with patch_stdout(), provisionalcompleter():
109 body = document.text
116 body = document.text
110 cursor_row = document.cursor_position_row
117 cursor_row = document.cursor_position_row
111 cursor_col = document.cursor_position_col
118 cursor_col = document.cursor_position_col
112 cursor_position = document.cursor_position
119 cursor_position = document.cursor_position
113 offset = cursor_to_position(body, cursor_row, cursor_col)
120 offset = cursor_to_position(body, cursor_row, cursor_col)
114 try:
121 try:
115 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
122 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
116 except Exception as e:
123 except Exception as e:
117 try:
124 try:
118 exc_type, exc_value, exc_tb = sys.exc_info()
125 exc_type, exc_value, exc_tb = sys.exc_info()
119 traceback.print_exception(exc_type, exc_value, exc_tb)
126 traceback.print_exception(exc_type, exc_value, exc_tb)
120 except AttributeError:
127 except AttributeError:
121 print('Unrecoverable Error in completions')
128 print('Unrecoverable Error in completions')
122
129
123 @staticmethod
130 @staticmethod
124 def _get_completions(body, offset, cursor_position, ipyc):
131 def _get_completions(body, offset, cursor_position, ipyc):
125 """
132 """
126 Private equivalent of get_completions() use only for unit_testing.
133 Private equivalent of get_completions() use only for unit_testing.
127 """
134 """
128 debug = getattr(ipyc, 'debug', False)
135 debug = getattr(ipyc, 'debug', False)
129 completions = _deduplicate_completions(
136 completions = _deduplicate_completions(
130 body, ipyc.completions(body, offset))
137 body, ipyc.completions(body, offset))
131 for c in completions:
138 for c in completions:
132 if not c.text:
139 if not c.text:
133 # Guard against completion machinery giving us an empty string.
140 # Guard against completion machinery giving us an empty string.
134 continue
141 continue
135 text = unicodedata.normalize('NFC', c.text)
142 text = unicodedata.normalize('NFC', c.text)
136 # When the first character of the completion has a zero length,
143 # When the first character of the completion has a zero length,
137 # then it's probably a decomposed unicode character. E.g. caused by
144 # then it's probably a decomposed unicode character. E.g. caused by
138 # the "\dot" completion. Try to compose again with the previous
145 # the "\dot" completion. Try to compose again with the previous
139 # character.
146 # character.
140 if wcwidth(text[0]) == 0:
147 if wcwidth(text[0]) == 0:
141 if cursor_position + c.start > 0:
148 if cursor_position + c.start > 0:
142 char_before = body[c.start - 1]
149 char_before = body[c.start - 1]
143 fixed_text = unicodedata.normalize(
150 fixed_text = unicodedata.normalize(
144 'NFC', char_before + text)
151 'NFC', char_before + text)
145
152
146 # Yield the modified completion instead, if this worked.
153 # Yield the modified completion instead, if this worked.
147 if wcwidth(text[0:1]) == 1:
154 if wcwidth(text[0:1]) == 1:
148 yield Completion(fixed_text, start_position=c.start - offset - 1)
155 yield Completion(fixed_text, start_position=c.start - offset - 1)
149 continue
156 continue
150
157
151 # TODO: Use Jedi to determine meta_text
158 # TODO: Use Jedi to determine meta_text
152 # (Jedi currently has a bug that results in incorrect information.)
159 # (Jedi currently has a bug that results in incorrect information.)
153 # meta_text = ''
160 # meta_text = ''
154 # yield Completion(m, start_position=start_pos,
161 # yield Completion(m, start_position=start_pos,
155 # display_meta=meta_text)
162 # display_meta=meta_text)
156 display_text = c.text
163 display_text = c.text
157
164
158 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
165 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
159 if c.type == 'function':
166 if c.type == 'function':
160 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
167 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
161 else:
168 else:
162 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
169 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
163
170
164 class IPythonPTLexer(Lexer):
171 class IPythonPTLexer(Lexer):
165 """
172 """
166 Wrapper around PythonLexer and BashLexer.
173 Wrapper around PythonLexer and BashLexer.
167 """
174 """
168 def __init__(self):
175 def __init__(self):
169 l = pygments_lexers
176 l = pygments_lexers
170 self.python_lexer = PygmentsLexer(l.Python3Lexer)
177 self.python_lexer = PygmentsLexer(l.Python3Lexer)
171 self.shell_lexer = PygmentsLexer(l.BashLexer)
178 self.shell_lexer = PygmentsLexer(l.BashLexer)
172
179
173 self.magic_lexers = {
180 self.magic_lexers = {
174 'HTML': PygmentsLexer(l.HtmlLexer),
181 'HTML': PygmentsLexer(l.HtmlLexer),
175 'html': PygmentsLexer(l.HtmlLexer),
182 'html': PygmentsLexer(l.HtmlLexer),
176 'javascript': PygmentsLexer(l.JavascriptLexer),
183 'javascript': PygmentsLexer(l.JavascriptLexer),
177 'js': PygmentsLexer(l.JavascriptLexer),
184 'js': PygmentsLexer(l.JavascriptLexer),
178 'perl': PygmentsLexer(l.PerlLexer),
185 'perl': PygmentsLexer(l.PerlLexer),
179 'ruby': PygmentsLexer(l.RubyLexer),
186 'ruby': PygmentsLexer(l.RubyLexer),
180 'latex': PygmentsLexer(l.TexLexer),
187 'latex': PygmentsLexer(l.TexLexer),
181 }
188 }
182
189
183 def lex_document(self, document):
190 def lex_document(self, document):
184 text = document.text.lstrip()
191 text = document.text.lstrip()
185
192
186 lexer = self.python_lexer
193 lexer = self.python_lexer
187
194
188 if text.startswith('!') or text.startswith('%%bash'):
195 if text.startswith('!') or text.startswith('%%bash'):
189 lexer = self.shell_lexer
196 lexer = self.shell_lexer
190
197
191 elif text.startswith('%%'):
198 elif text.startswith('%%'):
192 for magic, l in self.magic_lexers.items():
199 for magic, l in self.magic_lexers.items():
193 if text.startswith('%%' + magic):
200 if text.startswith('%%' + magic):
194 lexer = l
201 lexer = l
195 break
202 break
196
203
197 return lexer.lex_document(document)
204 return lexer.lex_document(document)
@@ -1,551 +1,585 b''
1 """
1 """
2 Module to define and register Terminal IPython shortcuts with
2 Module to define and register Terminal IPython shortcuts with
3 :mod:`prompt_toolkit`
3 :mod:`prompt_toolkit`
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import warnings
9 import warnings
10 import signal
10 import signal
11 import sys
11 import sys
12 import re
12 import re
13 import os
13 import os
14 from typing import Callable
14 from typing import Callable
15
15
16
16
17 from prompt_toolkit.application.current import get_app
17 from prompt_toolkit.application.current import get_app
18 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
18 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
19 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
19 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
20 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
20 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
21 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
21 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
22 from prompt_toolkit.key_binding import KeyBindings
22 from prompt_toolkit.key_binding import KeyBindings
23 from prompt_toolkit.key_binding.bindings import named_commands as nc
23 from prompt_toolkit.key_binding.bindings import named_commands as nc
24 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
24 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
25
25
26 from IPython.utils.decorators import undoc
26 from IPython.utils.decorators import undoc
27
27
28 @undoc
28 @undoc
29 @Condition
29 @Condition
30 def cursor_in_leading_ws():
30 def cursor_in_leading_ws():
31 before = get_app().current_buffer.document.current_line_before_cursor
31 before = get_app().current_buffer.document.current_line_before_cursor
32 return (not before) or before.isspace()
32 return (not before) or before.isspace()
33
33
34
34
35 # Needed for to accept autosuggestions in vi insert mode
35 # Needed for to accept autosuggestions in vi insert mode
36 def _apply_autosuggest(event):
36 def _apply_autosuggest(event):
37 """
37 """
38 Apply autosuggestion if at end of line.
38 Apply autosuggestion if at end of line.
39 """
39 """
40 b = event.current_buffer
40 b = event.current_buffer
41 d = b.document
41 d = b.document
42 after_cursor = d.text[d.cursor_position :]
42 after_cursor = d.text[d.cursor_position :]
43 lines = after_cursor.split("\n")
43 lines = after_cursor.split("\n")
44 end_of_current_line = lines[0].strip()
44 end_of_current_line = lines[0].strip()
45 suggestion = b.suggestion
45 suggestion = b.suggestion
46 if (suggestion is not None) and (suggestion.text) and (end_of_current_line == ""):
46 if (suggestion is not None) and (suggestion.text) and (end_of_current_line == ""):
47 b.insert_text(suggestion.text)
47 b.insert_text(suggestion.text)
48 else:
48 else:
49 nc.end_of_line(event)
49 nc.end_of_line(event)
50
50
51 def create_ipython_shortcuts(shell):
51 def create_ipython_shortcuts(shell):
52 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
52 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
53
53
54 kb = KeyBindings()
54 kb = KeyBindings()
55 insert_mode = vi_insert_mode | emacs_insert_mode
55 insert_mode = vi_insert_mode | emacs_insert_mode
56
56
57 if getattr(shell, 'handle_return', None):
57 if getattr(shell, 'handle_return', None):
58 return_handler = shell.handle_return(shell)
58 return_handler = shell.handle_return(shell)
59 else:
59 else:
60 return_handler = newline_or_execute_outer(shell)
60 return_handler = newline_or_execute_outer(shell)
61
61
62 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
62 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
63 & ~has_selection
63 & ~has_selection
64 & insert_mode
64 & insert_mode
65 ))(return_handler)
65 ))(return_handler)
66
66
67 def reformat_and_execute(event):
67 def reformat_and_execute(event):
68 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
68 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
69 event.current_buffer.validate_and_handle()
69 event.current_buffer.validate_and_handle()
70
70
71 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
71 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
72 & ~has_selection
72 & ~has_selection
73 & insert_mode
73 & insert_mode
74 ))(reformat_and_execute)
74 ))(reformat_and_execute)
75
75
76 kb.add("c-\\")(quit)
76 kb.add("c-\\")(quit)
77
77
78 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
78 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
79 )(previous_history_or_previous_completion)
79 )(previous_history_or_previous_completion)
80
80
81 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
81 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
82 )(next_history_or_next_completion)
82 )(next_history_or_next_completion)
83
83
84 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
84 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
85 )(dismiss_completion)
85 )(dismiss_completion)
86
86
87 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
87 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
88
88
89 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
89 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
90
90
91 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
91 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
92 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
92 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
93
93
94 # Ctrl+I == Tab
94 # Ctrl+I == Tab
95 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
95 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
96 & ~has_selection
96 & ~has_selection
97 & insert_mode
97 & insert_mode
98 & cursor_in_leading_ws
98 & cursor_in_leading_ws
99 ))(indent_buffer)
99 ))(indent_buffer)
100 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
100 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
101 )(newline_autoindent_outer(shell.input_transformer_manager))
101 )(newline_autoindent_outer(shell.input_transformer_manager))
102
102
103 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
103 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
104
104
105 @Condition
105 @Condition
106 def auto_match():
106 def auto_match():
107 return shell.auto_match
107 return shell.auto_match
108
108
109 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
109 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
110 _preceding_text_cache = {}
110 _preceding_text_cache = {}
111 _following_text_cache = {}
111 _following_text_cache = {}
112
112
113 def preceding_text(pattern):
113 def preceding_text(pattern):
114 try:
114 try:
115 return _preceding_text_cache[pattern]
115 return _preceding_text_cache[pattern]
116 except KeyError:
116 except KeyError:
117 pass
117 pass
118 m = re.compile(pattern)
118 m = re.compile(pattern)
119
119
120 def _preceding_text():
120 def _preceding_text():
121 app = get_app()
121 app = get_app()
122 return bool(m.match(app.current_buffer.document.current_line_before_cursor))
122 return bool(m.match(app.current_buffer.document.current_line_before_cursor))
123
123
124 condition = Condition(_preceding_text)
124 condition = Condition(_preceding_text)
125 _preceding_text_cache[pattern] = condition
125 _preceding_text_cache[pattern] = condition
126 return condition
126 return condition
127
127
128 def following_text(pattern):
128 def following_text(pattern):
129 try:
129 try:
130 return _following_text_cache[pattern]
130 return _following_text_cache[pattern]
131 except KeyError:
131 except KeyError:
132 pass
132 pass
133 m = re.compile(pattern)
133 m = re.compile(pattern)
134
134
135 def _following_text():
135 def _following_text():
136 app = get_app()
136 app = get_app()
137 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
137 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
138
138
139 condition = Condition(_following_text)
139 condition = Condition(_following_text)
140 _following_text_cache[pattern] = condition
140 _following_text_cache[pattern] = condition
141 return condition
141 return condition
142
142
143 @Condition
144 def not_inside_unclosed_string():
145 app = get_app()
146 s = app.current_buffer.document.text_before_cursor
147 # remove escaped quotes
148 s = s.replace('\\"', "").replace("\\'", "")
149 # remove triple-quoted string literals
150 s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s)
151 # remove single-quoted string literals
152 s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s)
153 return not ('"' in s or "'" in s)
154
143 # auto match
155 # auto match
144 @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
156 @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
145 def _(event):
157 def _(event):
146 event.current_buffer.insert_text("()")
158 event.current_buffer.insert_text("()")
147 event.current_buffer.cursor_left()
159 event.current_buffer.cursor_left()
148
160
149 @kb.add("[", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
161 @kb.add("[", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
150 def _(event):
162 def _(event):
151 event.current_buffer.insert_text("[]")
163 event.current_buffer.insert_text("[]")
152 event.current_buffer.cursor_left()
164 event.current_buffer.cursor_left()
153
165
154 @kb.add("{", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
166 @kb.add("{", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
155 def _(event):
167 def _(event):
156 event.current_buffer.insert_text("{}")
168 event.current_buffer.insert_text("{}")
157 event.current_buffer.cursor_left()
169 event.current_buffer.cursor_left()
158
170
159 @kb.add(
171 @kb.add(
160 '"',
172 '"',
161 filter=focused_insert
173 filter=focused_insert
162 & auto_match
174 & auto_match
163 & preceding_text(r'^([^"]+|"[^"]*")*$')
175 & not_inside_unclosed_string
164 & following_text(r"[,)}\]]|$"),
176 & following_text(r"[,)}\]]|$"),
165 )
177 )
166 def _(event):
178 def _(event):
167 event.current_buffer.insert_text('""')
179 event.current_buffer.insert_text('""')
168 event.current_buffer.cursor_left()
180 event.current_buffer.cursor_left()
169
181
170 @kb.add(
182 @kb.add(
171 "'",
183 "'",
172 filter=focused_insert
184 filter=focused_insert
173 & auto_match
185 & auto_match
174 & preceding_text(r"^([^']+|'[^']*')*$")
186 & not_inside_unclosed_string
175 & following_text(r"[,)}\]]|$"),
187 & following_text(r"[,)}\]]|$"),
176 )
188 )
177 def _(event):
189 def _(event):
178 event.current_buffer.insert_text("''")
190 event.current_buffer.insert_text("''")
179 event.current_buffer.cursor_left()
191 event.current_buffer.cursor_left()
180
192
193 @kb.add(
194 '"',
195 filter=focused_insert
196 & auto_match
197 & not_inside_unclosed_string
198 & preceding_text(r'^.*""$'),
199 )
200 def _(event):
201 event.current_buffer.insert_text('""""')
202 event.current_buffer.cursor_left(3)
203
204 @kb.add(
205 "'",
206 filter=focused_insert
207 & auto_match
208 & not_inside_unclosed_string
209 & preceding_text(r"^.*''$"),
210 )
211 def _(event):
212 event.current_buffer.insert_text("''''")
213 event.current_buffer.cursor_left(3)
214
181 # raw string
215 # raw string
182 @kb.add(
216 @kb.add(
183 "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
217 "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
184 )
218 )
185 def _(event):
219 def _(event):
186 matches = re.match(
220 matches = re.match(
187 r".*(r|R)[\"'](-*)",
221 r".*(r|R)[\"'](-*)",
188 event.current_buffer.document.current_line_before_cursor,
222 event.current_buffer.document.current_line_before_cursor,
189 )
223 )
190 dashes = matches.group(2) or ""
224 dashes = matches.group(2) or ""
191 event.current_buffer.insert_text("()" + dashes)
225 event.current_buffer.insert_text("()" + dashes)
192 event.current_buffer.cursor_left(len(dashes) + 1)
226 event.current_buffer.cursor_left(len(dashes) + 1)
193
227
194 @kb.add(
228 @kb.add(
195 "[", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
229 "[", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
196 )
230 )
197 def _(event):
231 def _(event):
198 matches = re.match(
232 matches = re.match(
199 r".*(r|R)[\"'](-*)",
233 r".*(r|R)[\"'](-*)",
200 event.current_buffer.document.current_line_before_cursor,
234 event.current_buffer.document.current_line_before_cursor,
201 )
235 )
202 dashes = matches.group(2) or ""
236 dashes = matches.group(2) or ""
203 event.current_buffer.insert_text("[]" + dashes)
237 event.current_buffer.insert_text("[]" + dashes)
204 event.current_buffer.cursor_left(len(dashes) + 1)
238 event.current_buffer.cursor_left(len(dashes) + 1)
205
239
206 @kb.add(
240 @kb.add(
207 "{", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
241 "{", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
208 )
242 )
209 def _(event):
243 def _(event):
210 matches = re.match(
244 matches = re.match(
211 r".*(r|R)[\"'](-*)",
245 r".*(r|R)[\"'](-*)",
212 event.current_buffer.document.current_line_before_cursor,
246 event.current_buffer.document.current_line_before_cursor,
213 )
247 )
214 dashes = matches.group(2) or ""
248 dashes = matches.group(2) or ""
215 event.current_buffer.insert_text("{}" + dashes)
249 event.current_buffer.insert_text("{}" + dashes)
216 event.current_buffer.cursor_left(len(dashes) + 1)
250 event.current_buffer.cursor_left(len(dashes) + 1)
217
251
218 # just move cursor
252 # just move cursor
219 @kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))
253 @kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))
220 @kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))
254 @kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))
221 @kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))
255 @kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))
222 @kb.add('"', filter=focused_insert & auto_match & following_text('^"'))
256 @kb.add('"', filter=focused_insert & auto_match & following_text('^"'))
223 @kb.add("'", filter=focused_insert & auto_match & following_text("^'"))
257 @kb.add("'", filter=focused_insert & auto_match & following_text("^'"))
224 def _(event):
258 def _(event):
225 event.current_buffer.cursor_right()
259 event.current_buffer.cursor_right()
226
260
227 @kb.add(
261 @kb.add(
228 "backspace",
262 "backspace",
229 filter=focused_insert
263 filter=focused_insert
230 & preceding_text(r".*\($")
264 & preceding_text(r".*\($")
231 & auto_match
265 & auto_match
232 & following_text(r"^\)"),
266 & following_text(r"^\)"),
233 )
267 )
234 @kb.add(
268 @kb.add(
235 "backspace",
269 "backspace",
236 filter=focused_insert
270 filter=focused_insert
237 & preceding_text(r".*\[$")
271 & preceding_text(r".*\[$")
238 & auto_match
272 & auto_match
239 & following_text(r"^\]"),
273 & following_text(r"^\]"),
240 )
274 )
241 @kb.add(
275 @kb.add(
242 "backspace",
276 "backspace",
243 filter=focused_insert
277 filter=focused_insert
244 & preceding_text(r".*\{$")
278 & preceding_text(r".*\{$")
245 & auto_match
279 & auto_match
246 & following_text(r"^\}"),
280 & following_text(r"^\}"),
247 )
281 )
248 @kb.add(
282 @kb.add(
249 "backspace",
283 "backspace",
250 filter=focused_insert
284 filter=focused_insert
251 & preceding_text('.*"$')
285 & preceding_text('.*"$')
252 & auto_match
286 & auto_match
253 & following_text('^"'),
287 & following_text('^"'),
254 )
288 )
255 @kb.add(
289 @kb.add(
256 "backspace",
290 "backspace",
257 filter=focused_insert
291 filter=focused_insert
258 & preceding_text(r".*'$")
292 & preceding_text(r".*'$")
259 & auto_match
293 & auto_match
260 & following_text(r"^'"),
294 & following_text(r"^'"),
261 )
295 )
262 def _(event):
296 def _(event):
263 event.current_buffer.delete()
297 event.current_buffer.delete()
264 event.current_buffer.delete_before_cursor()
298 event.current_buffer.delete_before_cursor()
265
299
266 if shell.display_completions == "readlinelike":
300 if shell.display_completions == "readlinelike":
267 kb.add(
301 kb.add(
268 "c-i",
302 "c-i",
269 filter=(
303 filter=(
270 has_focus(DEFAULT_BUFFER)
304 has_focus(DEFAULT_BUFFER)
271 & ~has_selection
305 & ~has_selection
272 & insert_mode
306 & insert_mode
273 & ~cursor_in_leading_ws
307 & ~cursor_in_leading_ws
274 ),
308 ),
275 )(display_completions_like_readline)
309 )(display_completions_like_readline)
276
310
277 if sys.platform == "win32":
311 if sys.platform == "win32":
278 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
312 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
279
313
280 @Condition
314 @Condition
281 def ebivim():
315 def ebivim():
282 return shell.emacs_bindings_in_vi_insert_mode
316 return shell.emacs_bindings_in_vi_insert_mode
283
317
284 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
318 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
285
319
286 @kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))
320 @kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))
287 def _(event):
321 def _(event):
288 _apply_autosuggest(event)
322 _apply_autosuggest(event)
289
323
290 @kb.add("c-e", filter=focused_insert_vi & ebivim)
324 @kb.add("c-e", filter=focused_insert_vi & ebivim)
291 def _(event):
325 def _(event):
292 _apply_autosuggest(event)
326 _apply_autosuggest(event)
293
327
294 @kb.add("c-f", filter=focused_insert_vi)
328 @kb.add("c-f", filter=focused_insert_vi)
295 def _(event):
329 def _(event):
296 b = event.current_buffer
330 b = event.current_buffer
297 suggestion = b.suggestion
331 suggestion = b.suggestion
298 if suggestion:
332 if suggestion:
299 b.insert_text(suggestion.text)
333 b.insert_text(suggestion.text)
300 else:
334 else:
301 nc.forward_char(event)
335 nc.forward_char(event)
302
336
303 @kb.add("escape", "f", filter=focused_insert_vi & ebivim)
337 @kb.add("escape", "f", filter=focused_insert_vi & ebivim)
304 def _(event):
338 def _(event):
305 b = event.current_buffer
339 b = event.current_buffer
306 suggestion = b.suggestion
340 suggestion = b.suggestion
307 if suggestion:
341 if suggestion:
308 t = re.split(r"(\S+\s+)", suggestion.text)
342 t = re.split(r"(\S+\s+)", suggestion.text)
309 b.insert_text(next((x for x in t if x), ""))
343 b.insert_text(next((x for x in t if x), ""))
310 else:
344 else:
311 nc.forward_word(event)
345 nc.forward_word(event)
312
346
313 # Simple Control keybindings
347 # Simple Control keybindings
314 key_cmd_dict = {
348 key_cmd_dict = {
315 "c-a": nc.beginning_of_line,
349 "c-a": nc.beginning_of_line,
316 "c-b": nc.backward_char,
350 "c-b": nc.backward_char,
317 "c-k": nc.kill_line,
351 "c-k": nc.kill_line,
318 "c-w": nc.backward_kill_word,
352 "c-w": nc.backward_kill_word,
319 "c-y": nc.yank,
353 "c-y": nc.yank,
320 "c-_": nc.undo,
354 "c-_": nc.undo,
321 }
355 }
322
356
323 for key, cmd in key_cmd_dict.items():
357 for key, cmd in key_cmd_dict.items():
324 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
358 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
325
359
326 # Alt and Combo Control keybindings
360 # Alt and Combo Control keybindings
327 keys_cmd_dict = {
361 keys_cmd_dict = {
328 # Control Combos
362 # Control Combos
329 ("c-x", "c-e"): nc.edit_and_execute,
363 ("c-x", "c-e"): nc.edit_and_execute,
330 ("c-x", "e"): nc.edit_and_execute,
364 ("c-x", "e"): nc.edit_and_execute,
331 # Alt
365 # Alt
332 ("escape", "b"): nc.backward_word,
366 ("escape", "b"): nc.backward_word,
333 ("escape", "c"): nc.capitalize_word,
367 ("escape", "c"): nc.capitalize_word,
334 ("escape", "d"): nc.kill_word,
368 ("escape", "d"): nc.kill_word,
335 ("escape", "h"): nc.backward_kill_word,
369 ("escape", "h"): nc.backward_kill_word,
336 ("escape", "l"): nc.downcase_word,
370 ("escape", "l"): nc.downcase_word,
337 ("escape", "u"): nc.uppercase_word,
371 ("escape", "u"): nc.uppercase_word,
338 ("escape", "y"): nc.yank_pop,
372 ("escape", "y"): nc.yank_pop,
339 ("escape", "."): nc.yank_last_arg,
373 ("escape", "."): nc.yank_last_arg,
340 }
374 }
341
375
342 for keys, cmd in keys_cmd_dict.items():
376 for keys, cmd in keys_cmd_dict.items():
343 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
377 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
344
378
345 def get_input_mode(self):
379 def get_input_mode(self):
346 app = get_app()
380 app = get_app()
347 app.ttimeoutlen = shell.ttimeoutlen
381 app.ttimeoutlen = shell.ttimeoutlen
348 app.timeoutlen = shell.timeoutlen
382 app.timeoutlen = shell.timeoutlen
349
383
350 return self._input_mode
384 return self._input_mode
351
385
352 def set_input_mode(self, mode):
386 def set_input_mode(self, mode):
353 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
387 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
354 cursor = "\x1b[{} q".format(shape)
388 cursor = "\x1b[{} q".format(shape)
355
389
356 sys.stdout.write(cursor)
390 sys.stdout.write(cursor)
357 sys.stdout.flush()
391 sys.stdout.flush()
358
392
359 self._input_mode = mode
393 self._input_mode = mode
360
394
361 if shell.editing_mode == "vi" and shell.modal_cursor:
395 if shell.editing_mode == "vi" and shell.modal_cursor:
362 ViState._input_mode = InputMode.INSERT
396 ViState._input_mode = InputMode.INSERT
363 ViState.input_mode = property(get_input_mode, set_input_mode)
397 ViState.input_mode = property(get_input_mode, set_input_mode)
364
398
365 return kb
399 return kb
366
400
367
401
368 def reformat_text_before_cursor(buffer, document, shell):
402 def reformat_text_before_cursor(buffer, document, shell):
369 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
403 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
370 try:
404 try:
371 formatted_text = shell.reformat_handler(text)
405 formatted_text = shell.reformat_handler(text)
372 buffer.insert_text(formatted_text)
406 buffer.insert_text(formatted_text)
373 except Exception as e:
407 except Exception as e:
374 buffer.insert_text(text)
408 buffer.insert_text(text)
375
409
376
410
377 def newline_or_execute_outer(shell):
411 def newline_or_execute_outer(shell):
378
412
379 def newline_or_execute(event):
413 def newline_or_execute(event):
380 """When the user presses return, insert a newline or execute the code."""
414 """When the user presses return, insert a newline or execute the code."""
381 b = event.current_buffer
415 b = event.current_buffer
382 d = b.document
416 d = b.document
383
417
384 if b.complete_state:
418 if b.complete_state:
385 cc = b.complete_state.current_completion
419 cc = b.complete_state.current_completion
386 if cc:
420 if cc:
387 b.apply_completion(cc)
421 b.apply_completion(cc)
388 else:
422 else:
389 b.cancel_completion()
423 b.cancel_completion()
390 return
424 return
391
425
392 # If there's only one line, treat it as if the cursor is at the end.
426 # If there's only one line, treat it as if the cursor is at the end.
393 # See https://github.com/ipython/ipython/issues/10425
427 # See https://github.com/ipython/ipython/issues/10425
394 if d.line_count == 1:
428 if d.line_count == 1:
395 check_text = d.text
429 check_text = d.text
396 else:
430 else:
397 check_text = d.text[:d.cursor_position]
431 check_text = d.text[:d.cursor_position]
398 status, indent = shell.check_complete(check_text)
432 status, indent = shell.check_complete(check_text)
399
433
400 # if all we have after the cursor is whitespace: reformat current text
434 # if all we have after the cursor is whitespace: reformat current text
401 # before cursor
435 # before cursor
402 after_cursor = d.text[d.cursor_position:]
436 after_cursor = d.text[d.cursor_position:]
403 reformatted = False
437 reformatted = False
404 if not after_cursor.strip():
438 if not after_cursor.strip():
405 reformat_text_before_cursor(b, d, shell)
439 reformat_text_before_cursor(b, d, shell)
406 reformatted = True
440 reformatted = True
407 if not (d.on_last_line or
441 if not (d.on_last_line or
408 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
442 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
409 ):
443 ):
410 if shell.autoindent:
444 if shell.autoindent:
411 b.insert_text('\n' + indent)
445 b.insert_text('\n' + indent)
412 else:
446 else:
413 b.insert_text('\n')
447 b.insert_text('\n')
414 return
448 return
415
449
416 if (status != 'incomplete') and b.accept_handler:
450 if (status != 'incomplete') and b.accept_handler:
417 if not reformatted:
451 if not reformatted:
418 reformat_text_before_cursor(b, d, shell)
452 reformat_text_before_cursor(b, d, shell)
419 b.validate_and_handle()
453 b.validate_and_handle()
420 else:
454 else:
421 if shell.autoindent:
455 if shell.autoindent:
422 b.insert_text('\n' + indent)
456 b.insert_text('\n' + indent)
423 else:
457 else:
424 b.insert_text('\n')
458 b.insert_text('\n')
425 return newline_or_execute
459 return newline_or_execute
426
460
427
461
428 def previous_history_or_previous_completion(event):
462 def previous_history_or_previous_completion(event):
429 """
463 """
430 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
464 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
431
465
432 If completer is open this still select previous completion.
466 If completer is open this still select previous completion.
433 """
467 """
434 event.current_buffer.auto_up()
468 event.current_buffer.auto_up()
435
469
436
470
437 def next_history_or_next_completion(event):
471 def next_history_or_next_completion(event):
438 """
472 """
439 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
473 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
440
474
441 If completer is open this still select next completion.
475 If completer is open this still select next completion.
442 """
476 """
443 event.current_buffer.auto_down()
477 event.current_buffer.auto_down()
444
478
445
479
446 def dismiss_completion(event):
480 def dismiss_completion(event):
447 b = event.current_buffer
481 b = event.current_buffer
448 if b.complete_state:
482 if b.complete_state:
449 b.cancel_completion()
483 b.cancel_completion()
450
484
451
485
452 def reset_buffer(event):
486 def reset_buffer(event):
453 b = event.current_buffer
487 b = event.current_buffer
454 if b.complete_state:
488 if b.complete_state:
455 b.cancel_completion()
489 b.cancel_completion()
456 else:
490 else:
457 b.reset()
491 b.reset()
458
492
459
493
460 def reset_search_buffer(event):
494 def reset_search_buffer(event):
461 if event.current_buffer.document.text:
495 if event.current_buffer.document.text:
462 event.current_buffer.reset()
496 event.current_buffer.reset()
463 else:
497 else:
464 event.app.layout.focus(DEFAULT_BUFFER)
498 event.app.layout.focus(DEFAULT_BUFFER)
465
499
466 def suspend_to_bg(event):
500 def suspend_to_bg(event):
467 event.app.suspend_to_background()
501 event.app.suspend_to_background()
468
502
469 def quit(event):
503 def quit(event):
470 """
504 """
471 On platforms that support SIGQUIT, send SIGQUIT to the current process.
505 On platforms that support SIGQUIT, send SIGQUIT to the current process.
472 On other platforms, just exit the process with a message.
506 On other platforms, just exit the process with a message.
473 """
507 """
474 sigquit = getattr(signal, "SIGQUIT", None)
508 sigquit = getattr(signal, "SIGQUIT", None)
475 if sigquit is not None:
509 if sigquit is not None:
476 os.kill(0, signal.SIGQUIT)
510 os.kill(0, signal.SIGQUIT)
477 else:
511 else:
478 sys.exit("Quit")
512 sys.exit("Quit")
479
513
480 def indent_buffer(event):
514 def indent_buffer(event):
481 event.current_buffer.insert_text(' ' * 4)
515 event.current_buffer.insert_text(' ' * 4)
482
516
483 @undoc
517 @undoc
484 def newline_with_copy_margin(event):
518 def newline_with_copy_margin(event):
485 """
519 """
486 DEPRECATED since IPython 6.0
520 DEPRECATED since IPython 6.0
487
521
488 See :any:`newline_autoindent_outer` for a replacement.
522 See :any:`newline_autoindent_outer` for a replacement.
489
523
490 Preserve margin and cursor position when using
524 Preserve margin and cursor position when using
491 Control-O to insert a newline in EMACS mode
525 Control-O to insert a newline in EMACS mode
492 """
526 """
493 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
527 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
494 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
528 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
495 DeprecationWarning, stacklevel=2)
529 DeprecationWarning, stacklevel=2)
496
530
497 b = event.current_buffer
531 b = event.current_buffer
498 cursor_start_pos = b.document.cursor_position_col
532 cursor_start_pos = b.document.cursor_position_col
499 b.newline(copy_margin=True)
533 b.newline(copy_margin=True)
500 b.cursor_up(count=1)
534 b.cursor_up(count=1)
501 cursor_end_pos = b.document.cursor_position_col
535 cursor_end_pos = b.document.cursor_position_col
502 if cursor_start_pos != cursor_end_pos:
536 if cursor_start_pos != cursor_end_pos:
503 pos_diff = cursor_start_pos - cursor_end_pos
537 pos_diff = cursor_start_pos - cursor_end_pos
504 b.cursor_right(count=pos_diff)
538 b.cursor_right(count=pos_diff)
505
539
506 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
540 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
507 """
541 """
508 Return a function suitable for inserting a indented newline after the cursor.
542 Return a function suitable for inserting a indented newline after the cursor.
509
543
510 Fancier version of deprecated ``newline_with_copy_margin`` which should
544 Fancier version of deprecated ``newline_with_copy_margin`` which should
511 compute the correct indentation of the inserted line. That is to say, indent
545 compute the correct indentation of the inserted line. That is to say, indent
512 by 4 extra space after a function definition, class definition, context
546 by 4 extra space after a function definition, class definition, context
513 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
547 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
514 """
548 """
515
549
516 def newline_autoindent(event):
550 def newline_autoindent(event):
517 """insert a newline after the cursor indented appropriately."""
551 """insert a newline after the cursor indented appropriately."""
518 b = event.current_buffer
552 b = event.current_buffer
519 d = b.document
553 d = b.document
520
554
521 if b.complete_state:
555 if b.complete_state:
522 b.cancel_completion()
556 b.cancel_completion()
523 text = d.text[:d.cursor_position] + '\n'
557 text = d.text[:d.cursor_position] + '\n'
524 _, indent = inputsplitter.check_complete(text)
558 _, indent = inputsplitter.check_complete(text)
525 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
559 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
526
560
527 return newline_autoindent
561 return newline_autoindent
528
562
529
563
530 def open_input_in_editor(event):
564 def open_input_in_editor(event):
531 event.app.current_buffer.open_in_editor()
565 event.app.current_buffer.open_in_editor()
532
566
533
567
534 if sys.platform == 'win32':
568 if sys.platform == 'win32':
535 from IPython.core.error import TryNext
569 from IPython.core.error import TryNext
536 from IPython.lib.clipboard import (ClipboardEmpty,
570 from IPython.lib.clipboard import (ClipboardEmpty,
537 win32_clipboard_get,
571 win32_clipboard_get,
538 tkinter_clipboard_get)
572 tkinter_clipboard_get)
539
573
540 @undoc
574 @undoc
541 def win_paste(event):
575 def win_paste(event):
542 try:
576 try:
543 text = win32_clipboard_get()
577 text = win32_clipboard_get()
544 except TryNext:
578 except TryNext:
545 try:
579 try:
546 text = tkinter_clipboard_get()
580 text = tkinter_clipboard_get()
547 except (TryNext, ClipboardEmpty):
581 except (TryNext, ClipboardEmpty):
548 return
582 return
549 except ClipboardEmpty:
583 except ClipboardEmpty:
550 return
584 return
551 event.current_buffer.insert_text(text.replace("\t", " " * 4))
585 event.current_buffer.insert_text(text.replace("\t", " " * 4))
@@ -1,60 +1,61 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Miscellaneous context managers.
2 """Miscellaneous context managers.
3 """
3 """
4
4
5 import warnings
5 import warnings
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10
10 class preserve_keys(object):
11 class preserve_keys(object):
11 """Preserve a set of keys in a dictionary.
12 """Preserve a set of keys in a dictionary.
12
13
13 Upon entering the context manager the current values of the keys
14 Upon entering the context manager the current values of the keys
14 will be saved. Upon exiting, the dictionary will be updated to
15 will be saved. Upon exiting, the dictionary will be updated to
15 restore the original value of the preserved keys. Preserved keys
16 restore the original value of the preserved keys. Preserved keys
16 which did not exist when entering the context manager will be
17 which did not exist when entering the context manager will be
17 deleted.
18 deleted.
18
19
19 Examples
20 Examples
20 --------
21 --------
21
22
22 >>> d = {'a': 1, 'b': 2, 'c': 3}
23 >>> d = {'a': 1, 'b': 2, 'c': 3}
23 >>> with preserve_keys(d, 'b', 'c', 'd'):
24 >>> with preserve_keys(d, 'b', 'c', 'd'):
24 ... del d['a']
25 ... del d['a']
25 ... del d['b'] # will be reset to 2
26 ... del d['b'] # will be reset to 2
26 ... d['c'] = None # will be reset to 3
27 ... d['c'] = None # will be reset to 3
27 ... d['d'] = 4 # will be deleted
28 ... d['d'] = 4 # will be deleted
28 ... d['e'] = 5
29 ... d['e'] = 5
29 ... print(sorted(d.items()))
30 ... print(sorted(d.items()))
30 ...
31 ...
31 [('c', None), ('d', 4), ('e', 5)]
32 [('c', None), ('d', 4), ('e', 5)]
32 >>> print(sorted(d.items()))
33 >>> print(sorted(d.items()))
33 [('b', 2), ('c', 3), ('e', 5)]
34 [('b', 2), ('c', 3), ('e', 5)]
34 """
35 """
35
36
36 def __init__(self, dictionary, *keys):
37 def __init__(self, dictionary, *keys):
37 self.dictionary = dictionary
38 self.dictionary = dictionary
38 self.keys = keys
39 self.keys = keys
39
40
40 def __enter__(self):
41 def __enter__(self):
41 # Actions to perform upon exiting.
42 # Actions to perform upon exiting.
42 to_delete = []
43 to_delete = []
43 to_update = {}
44 to_update = {}
44
45
45 d = self.dictionary
46 d = self.dictionary
46 for k in self.keys:
47 for k in self.keys:
47 if k in d:
48 if k in d:
48 to_update[k] = d[k]
49 to_update[k] = d[k]
49 else:
50 else:
50 to_delete.append(k)
51 to_delete.append(k)
51
52
52 self.to_delete = to_delete
53 self.to_delete = to_delete
53 self.to_update = to_update
54 self.to_update = to_update
54
55
55 def __exit__(self, *exc_info):
56 def __exit__(self, *exc_info):
56 d = self.dictionary
57 d = self.dictionary
57
58
58 for k in self.to_delete:
59 for k in self.to_delete:
59 d.pop(k, None)
60 d.pop(k, None)
60 d.update(self.to_update)
61 d.update(self.to_update)
@@ -1,6 +1,5 b''
1
2 from warnings import warn
1 from warnings import warn
3
2
4 warn("IPython.utils.eventful has moved to traitlets.eventful", stacklevel=2)
3 warn("IPython.utils.eventful has moved to traitlets.eventful", stacklevel=2)
5
4
6 from traitlets.eventful import *
5 from traitlets.eventful import *
@@ -1,6 +1,5 b''
1
2 from warnings import warn
1 from warnings import warn
3
2
4 warn("IPython.utils.log has moved to traitlets.log", stacklevel=2)
3 warn("IPython.utils.log has moved to traitlets.log", stacklevel=2)
5
4
6 from traitlets.log import *
5 from traitlets.log import *
@@ -1,392 +1,393 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for path handling.
3 Utilities for path handling.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import os
9 import os
10 import sys
10 import sys
11 import errno
11 import errno
12 import shutil
12 import shutil
13 import random
13 import random
14 import glob
14 import glob
15 from warnings import warn
15 from warnings import warn
16
16
17 from IPython.utils.process import system
17 from IPython.utils.process import system
18 from IPython.utils.decorators import undoc
18 from IPython.utils.decorators import undoc
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Code
21 # Code
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 fs_encoding = sys.getfilesystemencoding()
23 fs_encoding = sys.getfilesystemencoding()
24
24
25 def _writable_dir(path):
25 def _writable_dir(path):
26 """Whether `path` is a directory, to which the user has write access."""
26 """Whether `path` is a directory, to which the user has write access."""
27 return os.path.isdir(path) and os.access(path, os.W_OK)
27 return os.path.isdir(path) and os.access(path, os.W_OK)
28
28
29 if sys.platform == 'win32':
29 if sys.platform == 'win32':
30 def _get_long_path_name(path):
30 def _get_long_path_name(path):
31 """Get a long path name (expand ~) on Windows using ctypes.
31 """Get a long path name (expand ~) on Windows using ctypes.
32
32
33 Examples
33 Examples
34 --------
34 --------
35
35
36 >>> get_long_path_name('c:\\\\docume~1')
36 >>> get_long_path_name('c:\\\\docume~1')
37 'c:\\\\Documents and Settings'
37 'c:\\\\Documents and Settings'
38
38
39 """
39 """
40 try:
40 try:
41 import ctypes
41 import ctypes
42 except ImportError as e:
42 except ImportError as e:
43 raise ImportError('you need to have ctypes installed for this to work') from e
43 raise ImportError('you need to have ctypes installed for this to work') from e
44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
46 ctypes.c_uint ]
46 ctypes.c_uint ]
47
47
48 buf = ctypes.create_unicode_buffer(260)
48 buf = ctypes.create_unicode_buffer(260)
49 rv = _GetLongPathName(path, buf, 260)
49 rv = _GetLongPathName(path, buf, 260)
50 if rv == 0 or rv > 260:
50 if rv == 0 or rv > 260:
51 return path
51 return path
52 else:
52 else:
53 return buf.value
53 return buf.value
54 else:
54 else:
55 def _get_long_path_name(path):
55 def _get_long_path_name(path):
56 """Dummy no-op."""
56 """Dummy no-op."""
57 return path
57 return path
58
58
59
59
60
60
61 def get_long_path_name(path):
61 def get_long_path_name(path):
62 """Expand a path into its long form.
62 """Expand a path into its long form.
63
63
64 On Windows this expands any ~ in the paths. On other platforms, it is
64 On Windows this expands any ~ in the paths. On other platforms, it is
65 a null operation.
65 a null operation.
66 """
66 """
67 return _get_long_path_name(path)
67 return _get_long_path_name(path)
68
68
69
69
70 def compress_user(path):
70 def compress_user(path):
71 """Reverse of :func:`os.path.expanduser`
71 """Reverse of :func:`os.path.expanduser`
72 """
72 """
73 home = os.path.expanduser('~')
73 home = os.path.expanduser('~')
74 if path.startswith(home):
74 if path.startswith(home):
75 path = "~" + path[len(home):]
75 path = "~" + path[len(home):]
76 return path
76 return path
77
77
78 def get_py_filename(name):
78 def get_py_filename(name):
79 """Return a valid python filename in the current directory.
79 """Return a valid python filename in the current directory.
80
80
81 If the given name is not a file, it adds '.py' and searches again.
81 If the given name is not a file, it adds '.py' and searches again.
82 Raises IOError with an informative message if the file isn't found.
82 Raises IOError with an informative message if the file isn't found.
83 """
83 """
84
84
85 name = os.path.expanduser(name)
85 name = os.path.expanduser(name)
86 if not os.path.isfile(name) and not name.endswith('.py'):
87 name += '.py'
88 if os.path.isfile(name):
86 if os.path.isfile(name):
89 return name
87 return name
90 else:
88 if not name.endswith(".py"):
91 raise IOError('File `%r` not found.' % name)
89 py_name = name + ".py"
90 if os.path.isfile(py_name):
91 return py_name
92 raise IOError("File `%r` not found." % name)
92
93
93
94
94 def filefind(filename: str, path_dirs=None) -> str:
95 def filefind(filename: str, path_dirs=None) -> str:
95 """Find a file by looking through a sequence of paths.
96 """Find a file by looking through a sequence of paths.
96
97
97 This iterates through a sequence of paths looking for a file and returns
98 This iterates through a sequence of paths looking for a file and returns
98 the full, absolute path of the first occurrence of the file. If no set of
99 the full, absolute path of the first occurrence of the file. If no set of
99 path dirs is given, the filename is tested as is, after running through
100 path dirs is given, the filename is tested as is, after running through
100 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
101 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
101
102
102 filefind('myfile.txt')
103 filefind('myfile.txt')
103
104
104 will find the file in the current working dir, but::
105 will find the file in the current working dir, but::
105
106
106 filefind('~/myfile.txt')
107 filefind('~/myfile.txt')
107
108
108 Will find the file in the users home directory. This function does not
109 Will find the file in the users home directory. This function does not
109 automatically try any paths, such as the cwd or the user's home directory.
110 automatically try any paths, such as the cwd or the user's home directory.
110
111
111 Parameters
112 Parameters
112 ----------
113 ----------
113 filename : str
114 filename : str
114 The filename to look for.
115 The filename to look for.
115 path_dirs : str, None or sequence of str
116 path_dirs : str, None or sequence of str
116 The sequence of paths to look for the file in. If None, the filename
117 The sequence of paths to look for the file in. If None, the filename
117 need to be absolute or be in the cwd. If a string, the string is
118 need to be absolute or be in the cwd. If a string, the string is
118 put into a sequence and the searched. If a sequence, walk through
119 put into a sequence and the searched. If a sequence, walk through
119 each element and join with ``filename``, calling :func:`expandvars`
120 each element and join with ``filename``, calling :func:`expandvars`
120 and :func:`expanduser` before testing for existence.
121 and :func:`expanduser` before testing for existence.
121
122
122 Returns
123 Returns
123 -------
124 -------
124 path : str
125 path : str
125 returns absolute path to file.
126 returns absolute path to file.
126
127
127 Raises
128 Raises
128 ------
129 ------
129 IOError
130 IOError
130 """
131 """
131
132
132 # If paths are quoted, abspath gets confused, strip them...
133 # If paths are quoted, abspath gets confused, strip them...
133 filename = filename.strip('"').strip("'")
134 filename = filename.strip('"').strip("'")
134 # If the input is an absolute path, just check it exists
135 # If the input is an absolute path, just check it exists
135 if os.path.isabs(filename) and os.path.isfile(filename):
136 if os.path.isabs(filename) and os.path.isfile(filename):
136 return filename
137 return filename
137
138
138 if path_dirs is None:
139 if path_dirs is None:
139 path_dirs = ("",)
140 path_dirs = ("",)
140 elif isinstance(path_dirs, str):
141 elif isinstance(path_dirs, str):
141 path_dirs = (path_dirs,)
142 path_dirs = (path_dirs,)
142
143
143 for path in path_dirs:
144 for path in path_dirs:
144 if path == '.': path = os.getcwd()
145 if path == '.': path = os.getcwd()
145 testname = expand_path(os.path.join(path, filename))
146 testname = expand_path(os.path.join(path, filename))
146 if os.path.isfile(testname):
147 if os.path.isfile(testname):
147 return os.path.abspath(testname)
148 return os.path.abspath(testname)
148
149
149 raise IOError("File %r does not exist in any of the search paths: %r" %
150 raise IOError("File %r does not exist in any of the search paths: %r" %
150 (filename, path_dirs) )
151 (filename, path_dirs) )
151
152
152
153
153 class HomeDirError(Exception):
154 class HomeDirError(Exception):
154 pass
155 pass
155
156
156
157
157 def get_home_dir(require_writable=False) -> str:
158 def get_home_dir(require_writable=False) -> str:
158 """Return the 'home' directory, as a unicode string.
159 """Return the 'home' directory, as a unicode string.
159
160
160 Uses os.path.expanduser('~'), and checks for writability.
161 Uses os.path.expanduser('~'), and checks for writability.
161
162
162 See stdlib docs for how this is determined.
163 See stdlib docs for how this is determined.
163 For Python <3.8, $HOME is first priority on *ALL* platforms.
164 For Python <3.8, $HOME is first priority on *ALL* platforms.
164 For Python >=3.8 on Windows, %HOME% is no longer considered.
165 For Python >=3.8 on Windows, %HOME% is no longer considered.
165
166
166 Parameters
167 Parameters
167 ----------
168 ----------
168 require_writable : bool [default: False]
169 require_writable : bool [default: False]
169 if True:
170 if True:
170 guarantees the return value is a writable directory, otherwise
171 guarantees the return value is a writable directory, otherwise
171 raises HomeDirError
172 raises HomeDirError
172 if False:
173 if False:
173 The path is resolved, but it is not guaranteed to exist or be writable.
174 The path is resolved, but it is not guaranteed to exist or be writable.
174 """
175 """
175
176
176 homedir = os.path.expanduser('~')
177 homedir = os.path.expanduser('~')
177 # Next line will make things work even when /home/ is a symlink to
178 # Next line will make things work even when /home/ is a symlink to
178 # /usr/home as it is on FreeBSD, for example
179 # /usr/home as it is on FreeBSD, for example
179 homedir = os.path.realpath(homedir)
180 homedir = os.path.realpath(homedir)
180
181
181 if not _writable_dir(homedir) and os.name == 'nt':
182 if not _writable_dir(homedir) and os.name == 'nt':
182 # expanduser failed, use the registry to get the 'My Documents' folder.
183 # expanduser failed, use the registry to get the 'My Documents' folder.
183 try:
184 try:
184 import winreg as wreg
185 import winreg as wreg
185 with wreg.OpenKey(
186 with wreg.OpenKey(
186 wreg.HKEY_CURRENT_USER,
187 wreg.HKEY_CURRENT_USER,
187 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
188 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
188 ) as key:
189 ) as key:
189 homedir = wreg.QueryValueEx(key,'Personal')[0]
190 homedir = wreg.QueryValueEx(key,'Personal')[0]
190 except:
191 except:
191 pass
192 pass
192
193
193 if (not require_writable) or _writable_dir(homedir):
194 if (not require_writable) or _writable_dir(homedir):
194 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
195 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
195 return homedir
196 return homedir
196 else:
197 else:
197 raise HomeDirError('%s is not a writable dir, '
198 raise HomeDirError('%s is not a writable dir, '
198 'set $HOME environment variable to override' % homedir)
199 'set $HOME environment variable to override' % homedir)
199
200
200 def get_xdg_dir():
201 def get_xdg_dir():
201 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
202 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
202
203
203 This is only for non-OS X posix (Linux,Unix,etc.) systems.
204 This is only for non-OS X posix (Linux,Unix,etc.) systems.
204 """
205 """
205
206
206 env = os.environ
207 env = os.environ
207
208
208 if os.name == "posix":
209 if os.name == "posix":
209 # Linux, Unix, AIX, etc.
210 # Linux, Unix, AIX, etc.
210 # use ~/.config if empty OR not set
211 # use ~/.config if empty OR not set
211 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
212 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
212 if xdg and _writable_dir(xdg):
213 if xdg and _writable_dir(xdg):
213 assert isinstance(xdg, str)
214 assert isinstance(xdg, str)
214 return xdg
215 return xdg
215
216
216 return None
217 return None
217
218
218
219
219 def get_xdg_cache_dir():
220 def get_xdg_cache_dir():
220 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
221 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
221
222
222 This is only for non-OS X posix (Linux,Unix,etc.) systems.
223 This is only for non-OS X posix (Linux,Unix,etc.) systems.
223 """
224 """
224
225
225 env = os.environ
226 env = os.environ
226
227
227 if os.name == "posix":
228 if os.name == "posix":
228 # Linux, Unix, AIX, etc.
229 # Linux, Unix, AIX, etc.
229 # use ~/.cache if empty OR not set
230 # use ~/.cache if empty OR not set
230 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
231 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
231 if xdg and _writable_dir(xdg):
232 if xdg and _writable_dir(xdg):
232 assert isinstance(xdg, str)
233 assert isinstance(xdg, str)
233 return xdg
234 return xdg
234
235
235 return None
236 return None
236
237
237
238
238 def expand_path(s):
239 def expand_path(s):
239 """Expand $VARS and ~names in a string, like a shell
240 """Expand $VARS and ~names in a string, like a shell
240
241
241 :Examples:
242 :Examples:
242
243
243 In [2]: os.environ['FOO']='test'
244 In [2]: os.environ['FOO']='test'
244
245
245 In [3]: expand_path('variable FOO is $FOO')
246 In [3]: expand_path('variable FOO is $FOO')
246 Out[3]: 'variable FOO is test'
247 Out[3]: 'variable FOO is test'
247 """
248 """
248 # This is a pretty subtle hack. When expand user is given a UNC path
249 # This is a pretty subtle hack. When expand user is given a UNC path
249 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
250 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
250 # the $ to get (\\server\share\%username%). I think it considered $
251 # the $ to get (\\server\share\%username%). I think it considered $
251 # alone an empty var. But, we need the $ to remains there (it indicates
252 # alone an empty var. But, we need the $ to remains there (it indicates
252 # a hidden share).
253 # a hidden share).
253 if os.name=='nt':
254 if os.name=='nt':
254 s = s.replace('$\\', 'IPYTHON_TEMP')
255 s = s.replace('$\\', 'IPYTHON_TEMP')
255 s = os.path.expandvars(os.path.expanduser(s))
256 s = os.path.expandvars(os.path.expanduser(s))
256 if os.name=='nt':
257 if os.name=='nt':
257 s = s.replace('IPYTHON_TEMP', '$\\')
258 s = s.replace('IPYTHON_TEMP', '$\\')
258 return s
259 return s
259
260
260
261
261 def unescape_glob(string):
262 def unescape_glob(string):
262 """Unescape glob pattern in `string`."""
263 """Unescape glob pattern in `string`."""
263 def unescape(s):
264 def unescape(s):
264 for pattern in '*[]!?':
265 for pattern in '*[]!?':
265 s = s.replace(r'\{0}'.format(pattern), pattern)
266 s = s.replace(r'\{0}'.format(pattern), pattern)
266 return s
267 return s
267 return '\\'.join(map(unescape, string.split('\\\\')))
268 return '\\'.join(map(unescape, string.split('\\\\')))
268
269
269
270
270 def shellglob(args):
271 def shellglob(args):
271 """
272 """
272 Do glob expansion for each element in `args` and return a flattened list.
273 Do glob expansion for each element in `args` and return a flattened list.
273
274
274 Unmatched glob pattern will remain as-is in the returned list.
275 Unmatched glob pattern will remain as-is in the returned list.
275
276
276 """
277 """
277 expanded = []
278 expanded = []
278 # Do not unescape backslash in Windows as it is interpreted as
279 # Do not unescape backslash in Windows as it is interpreted as
279 # path separator:
280 # path separator:
280 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
281 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
281 for a in args:
282 for a in args:
282 expanded.extend(glob.glob(a) or [unescape(a)])
283 expanded.extend(glob.glob(a) or [unescape(a)])
283 return expanded
284 return expanded
284
285
285
286
286 def target_outdated(target,deps):
287 def target_outdated(target,deps):
287 """Determine whether a target is out of date.
288 """Determine whether a target is out of date.
288
289
289 target_outdated(target,deps) -> 1/0
290 target_outdated(target,deps) -> 1/0
290
291
291 deps: list of filenames which MUST exist.
292 deps: list of filenames which MUST exist.
292 target: single filename which may or may not exist.
293 target: single filename which may or may not exist.
293
294
294 If target doesn't exist or is older than any file listed in deps, return
295 If target doesn't exist or is older than any file listed in deps, return
295 true, otherwise return false.
296 true, otherwise return false.
296 """
297 """
297 try:
298 try:
298 target_time = os.path.getmtime(target)
299 target_time = os.path.getmtime(target)
299 except os.error:
300 except os.error:
300 return 1
301 return 1
301 for dep in deps:
302 for dep in deps:
302 dep_time = os.path.getmtime(dep)
303 dep_time = os.path.getmtime(dep)
303 if dep_time > target_time:
304 if dep_time > target_time:
304 #print "For target",target,"Dep failed:",dep # dbg
305 #print "For target",target,"Dep failed:",dep # dbg
305 #print "times (dep,tar):",dep_time,target_time # dbg
306 #print "times (dep,tar):",dep_time,target_time # dbg
306 return 1
307 return 1
307 return 0
308 return 0
308
309
309
310
310 def target_update(target,deps,cmd):
311 def target_update(target,deps,cmd):
311 """Update a target with a given command given a list of dependencies.
312 """Update a target with a given command given a list of dependencies.
312
313
313 target_update(target,deps,cmd) -> runs cmd if target is outdated.
314 target_update(target,deps,cmd) -> runs cmd if target is outdated.
314
315
315 This is just a wrapper around target_outdated() which calls the given
316 This is just a wrapper around target_outdated() which calls the given
316 command if target is outdated."""
317 command if target is outdated."""
317
318
318 if target_outdated(target,deps):
319 if target_outdated(target,deps):
319 system(cmd)
320 system(cmd)
320
321
321
322
322 ENOLINK = 1998
323 ENOLINK = 1998
323
324
324 def link(src, dst):
325 def link(src, dst):
325 """Hard links ``src`` to ``dst``, returning 0 or errno.
326 """Hard links ``src`` to ``dst``, returning 0 or errno.
326
327
327 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
328 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
328 supported by the operating system.
329 supported by the operating system.
329 """
330 """
330
331
331 if not hasattr(os, "link"):
332 if not hasattr(os, "link"):
332 return ENOLINK
333 return ENOLINK
333 link_errno = 0
334 link_errno = 0
334 try:
335 try:
335 os.link(src, dst)
336 os.link(src, dst)
336 except OSError as e:
337 except OSError as e:
337 link_errno = e.errno
338 link_errno = e.errno
338 return link_errno
339 return link_errno
339
340
340
341
341 def link_or_copy(src, dst):
342 def link_or_copy(src, dst):
342 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
343 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
343
344
344 Attempts to maintain the semantics of ``shutil.copy``.
345 Attempts to maintain the semantics of ``shutil.copy``.
345
346
346 Because ``os.link`` does not overwrite files, a unique temporary file
347 Because ``os.link`` does not overwrite files, a unique temporary file
347 will be used if the target already exists, then that file will be moved
348 will be used if the target already exists, then that file will be moved
348 into place.
349 into place.
349 """
350 """
350
351
351 if os.path.isdir(dst):
352 if os.path.isdir(dst):
352 dst = os.path.join(dst, os.path.basename(src))
353 dst = os.path.join(dst, os.path.basename(src))
353
354
354 link_errno = link(src, dst)
355 link_errno = link(src, dst)
355 if link_errno == errno.EEXIST:
356 if link_errno == errno.EEXIST:
356 if os.stat(src).st_ino == os.stat(dst).st_ino:
357 if os.stat(src).st_ino == os.stat(dst).st_ino:
357 # dst is already a hard link to the correct file, so we don't need
358 # dst is already a hard link to the correct file, so we don't need
358 # to do anything else. If we try to link and rename the file
359 # to do anything else. If we try to link and rename the file
359 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
360 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
360 return
361 return
361
362
362 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
363 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
363 try:
364 try:
364 link_or_copy(src, new_dst)
365 link_or_copy(src, new_dst)
365 except:
366 except:
366 try:
367 try:
367 os.remove(new_dst)
368 os.remove(new_dst)
368 except OSError:
369 except OSError:
369 pass
370 pass
370 raise
371 raise
371 os.rename(new_dst, dst)
372 os.rename(new_dst, dst)
372 elif link_errno != 0:
373 elif link_errno != 0:
373 # Either link isn't supported, or the filesystem doesn't support
374 # Either link isn't supported, or the filesystem doesn't support
374 # linking, or 'src' and 'dst' are on different filesystems.
375 # linking, or 'src' and 'dst' are on different filesystems.
375 shutil.copy(src, dst)
376 shutil.copy(src, dst)
376
377
377 def ensure_dir_exists(path, mode=0o755):
378 def ensure_dir_exists(path, mode=0o755):
378 """ensure that a directory exists
379 """ensure that a directory exists
379
380
380 If it doesn't exist, try to create it and protect against a race condition
381 If it doesn't exist, try to create it and protect against a race condition
381 if another process is doing the same.
382 if another process is doing the same.
382
383
383 The default permissions are 755, which differ from os.makedirs default of 777.
384 The default permissions are 755, which differ from os.makedirs default of 777.
384 """
385 """
385 if not os.path.exists(path):
386 if not os.path.exists(path):
386 try:
387 try:
387 os.makedirs(path, mode=mode)
388 os.makedirs(path, mode=mode)
388 except OSError as e:
389 except OSError as e:
389 if e.errno != errno.EEXIST:
390 if e.errno != errno.EEXIST:
390 raise
391 raise
391 elif not os.path.isdir(path):
392 elif not os.path.isdir(path):
392 raise IOError("%r exists but is not a directory" % path)
393 raise IOError("%r exists but is not a directory" % path)
@@ -1,58 +1,59 b''
1 """ This module contains classes - NamedFileInTemporaryDirectory, TemporaryWorkingDirectory.
1 """ This module contains classes - NamedFileInTemporaryDirectory, TemporaryWorkingDirectory.
2
2
3 These classes add extra features such as creating a named file in temporary directory and
3 These classes add extra features such as creating a named file in temporary directory and
4 creating a context manager for the working directory which is also temporary.
4 creating a context manager for the working directory which is also temporary.
5 """
5 """
6
6
7 import os as _os
7 import os as _os
8 from pathlib import Path
8 from pathlib import Path
9 from tempfile import TemporaryDirectory
9 from tempfile import TemporaryDirectory
10
10
11
11
12 class NamedFileInTemporaryDirectory(object):
12 class NamedFileInTemporaryDirectory(object):
13 def __init__(self, filename, mode="w+b", bufsize=-1, add_to_syspath=False, **kwds):
13 def __init__(self, filename, mode="w+b", bufsize=-1, add_to_syspath=False, **kwds):
14 """
14 """
15 Open a file named `filename` in a temporary directory.
15 Open a file named `filename` in a temporary directory.
16
16
17 This context manager is preferred over `NamedTemporaryFile` in
17 This context manager is preferred over `NamedTemporaryFile` in
18 stdlib `tempfile` when one needs to reopen the file.
18 stdlib `tempfile` when one needs to reopen the file.
19
19
20 Arguments `mode` and `bufsize` are passed to `open`.
20 Arguments `mode` and `bufsize` are passed to `open`.
21 Rest of the arguments are passed to `TemporaryDirectory`.
21 Rest of the arguments are passed to `TemporaryDirectory`.
22
22
23 """
23 """
24 self._tmpdir = TemporaryDirectory(**kwds)
24 self._tmpdir = TemporaryDirectory(**kwds)
25 path = Path(self._tmpdir.name) / filename
25 path = Path(self._tmpdir.name) / filename
26 encoding = None if "b" in mode else "utf-8"
26 encoding = None if "b" in mode else "utf-8"
27 self.file = open(path, mode, bufsize, encoding=encoding)
27 self.file = open(path, mode, bufsize, encoding=encoding)
28
28
29 def cleanup(self):
29 def cleanup(self):
30 self.file.close()
30 self.file.close()
31 self._tmpdir.cleanup()
31 self._tmpdir.cleanup()
32
32
33 __del__ = cleanup
33 __del__ = cleanup
34
34
35 def __enter__(self):
35 def __enter__(self):
36 return self.file
36 return self.file
37
37
38 def __exit__(self, type, value, traceback):
38 def __exit__(self, type, value, traceback):
39 self.cleanup()
39 self.cleanup()
40
40
41
41
42 class TemporaryWorkingDirectory(TemporaryDirectory):
42 class TemporaryWorkingDirectory(TemporaryDirectory):
43 """
43 """
44 Creates a temporary directory and sets the cwd to that directory.
44 Creates a temporary directory and sets the cwd to that directory.
45 Automatically reverts to previous cwd upon cleanup.
45 Automatically reverts to previous cwd upon cleanup.
46 Usage example:
46 Usage example:
47
47
48 with TemporaryWorkingDirectory() as tmpdir:
48 with TemporaryWorkingDirectory() as tmpdir:
49 ...
49 ...
50 """
50 """
51
51 def __enter__(self):
52 def __enter__(self):
52 self.old_wd = Path.cwd()
53 self.old_wd = Path.cwd()
53 _os.chdir(self.name)
54 _os.chdir(self.name)
54 return super(TemporaryWorkingDirectory, self).__enter__()
55 return super(TemporaryWorkingDirectory, self).__enter__()
55
56
56 def __exit__(self, exc, value, tb):
57 def __exit__(self, exc, value, tb):
57 _os.chdir(self.old_wd)
58 _os.chdir(self.old_wd)
58 return super(TemporaryWorkingDirectory, self).__exit__(exc, value, tb)
59 return super(TemporaryWorkingDirectory, self).__exit__(exc, value, tb)
@@ -1,33 +1,34 b''
1 =====================
1 =====================
2 Development version
2 Development version
3 =====================
3 =====================
4
4
5 This document describes in-flight development work.
5 This document describes in-flight development work.
6
6
7 .. warning::
7 .. warning::
8
8
9 Please do not edit this file by hand (doing so will likely cause merge
9 Please do not edit this file by hand (doing so will likely cause merge
10 conflicts for other Pull Requests). Instead, create a new file in the
10 conflicts for other Pull Requests). Instead, create a new file in the
11 `docs/source/whatsnew/pr` folder
11 `docs/source/whatsnew/pr` folder
12
12
13
13
14 Released .... ...., 2019
14 Released .... ...., 2019
15
15
16
16
17 Need to be updated:
17 Need to be updated:
18
18
19 .. toctree::
19 .. toctree::
20 :maxdepth: 2
20 :maxdepth: 2
21 :glob:
21 :glob:
22
22
23 pr/*
23 pr/*
24
24
25
25
26
26
27
27
28
28 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
29 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
29
30
30 Backwards incompatible changes
31 Backwards incompatible changes
31 ------------------------------
32 ------------------------------
32
33
33 .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT.
34 .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT.
@@ -1,1080 +1,1196 b''
1 ============
1 ============
2 8.x Series
2 8.x Series
3 ============
3 ============
4
4
5 .. _version 8.5.0:
6
7 IPython 8.5.0
8 -------------
9
10 First release since a couple of month due to various reasons and timing preventing
11 me for sticking to the usual monthly release the last Friday of each month. This
12 is of non negligible size as it has more than two dozen PRs with various fixes
13 an bug fixes.
14
15 Many thanks to everybody who contributed PRs for your patience in review and
16 merges.
17
18 Here is a non exhaustive list of changes that have been implemented for IPython
19 8.5.0. As usual you can find the full list of issues and PRs tagged with `the
20 8.5 milestone
21 <https://github.com/ipython/ipython/pulls?q=is%3Aclosed+milestone%3A8.5+>`__.
22
23 - Added shortcut for accepting auto suggestion. The End key shortcut for
24 accepting auto-suggestion This binding works in Vi mode too, provided
25 ``TerminalInteractiveShell.emacs_bindings_in_vi_insert_mode`` is set to be
26 ``True`` :ghpull:`13566`.
27
28 - No popup in window for latex generation w hen generating latex (e.g. via
29 `_latex_repr_`) no popup window is shows under Windows. :ghpull:`13679`
30
31 - Fixed error raised when attempting to tab-complete an input string with
32 consecutive periods or forward slashes (such as "file:///var/log/...").
33 :ghpull:`13675`
34
35 - Relative filenames in Latex rendering :
36 The `latex_to_png_dvipng` command internally generates input and output file
37 arguments to `latex` and `dvipis`. These arguments are now generated as
38 relative files to the current working directory instead of absolute file
39 paths. This solves a problem where the current working directory contains
40 characters that are not handled properly by `latex` and `dvips`. There are
41 no changes to the user API. :ghpull:`13680`
42
43 - Stripping decorators bug: Fixed bug which meant that ipython code blocks in
44 restructured text documents executed with the ipython-sphinx extension
45 skipped any lines of code containing python decorators. :ghpull:`13612`
46
47 - Allow some modules with frozen dataclasses to be reloaded. :ghpull:`13732`
48 - Fix paste magic on wayland. :ghpull:`13671`
49 - show maxlen in deque's repr. :ghpull:`13648`
50
51 Restore line numbers for Input
52 ------------------------------
53
54 Line number information in tracebacks from input are restored.
55 Line numbers from input were removed during the transition to v8 enhanced traceback reporting.
56
57 So, instead of::
58
59 ---------------------------------------------------------------------------
60 ZeroDivisionError Traceback (most recent call last)
61 Input In [3], in <cell line: 1>()
62 ----> 1 myfunc(2)
63
64 Input In [2], in myfunc(z)
65 1 def myfunc(z):
66 ----> 2 foo.boo(z-1)
67
68 File ~/code/python/ipython/foo.py:3, in boo(x)
69 2 def boo(x):
70 ----> 3 return 1/(1-x)
71
72 ZeroDivisionError: division by zero
73
74 The error traceback now looks like::
75
76 ---------------------------------------------------------------------------
77 ZeroDivisionError Traceback (most recent call last)
78 Cell In [3], line 1
79 ----> 1 myfunc(2)
80
81 Cell In [2], line 2, in myfunc(z)
82 1 def myfunc(z):
83 ----> 2 foo.boo(z-1)
84
85 File ~/code/python/ipython/foo.py:3, in boo(x)
86 2 def boo(x):
87 ----> 3 return 1/(1-x)
88
89 ZeroDivisionError: division by zero
90
91 or, with xmode=Plain::
92
93 Traceback (most recent call last):
94 Cell In [12], line 1
95 myfunc(2)
96 Cell In [6], line 2 in myfunc
97 foo.boo(z-1)
98 File ~/code/python/ipython/foo.py:3 in boo
99 return 1/(1-x)
100 ZeroDivisionError: division by zero
101
102 :ghpull:`13560`
103
104 New setting to silence warning if working inside a virtual environment
105 ----------------------------------------------------------------------
106
107 Previously, when starting IPython in a virtual environment without IPython installed (so IPython from the global environment is used), the following warning was printed:
108
109 Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv.
110
111 This warning can be permanently silenced by setting ``c.InteractiveShell.warn_venv`` to ``False`` (the default is ``True``).
112
113 :ghpull:`13706`
114
115 -------
116
117 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
118 work on IPython and related libraries.
119
120
5 .. _version 8.4.0:
121 .. _version 8.4.0:
6
122
7 IPython 8.4.0
123 IPython 8.4.0
8 -------------
124 -------------
9
125
10 As for 7.34, this version contains a single fix: fix uncaught BdbQuit exceptions on ipdb
126 As for 7.34, this version contains a single fix: fix uncaught BdbQuit exceptions on ipdb
11 exit :ghpull:`13668`, and a single typo fix in documentation: :ghpull:`13682`
127 exit :ghpull:`13668`, and a single typo fix in documentation: :ghpull:`13682`
12
128
13 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
129 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
14 work on IPython and related libraries.
130 work on IPython and related libraries.
15
131
16
132
17 .. _version 8.3.0:
133 .. _version 8.3.0:
18
134
19 IPython 8.3.0
135 IPython 8.3.0
20 -------------
136 -------------
21
137
22 - :ghpull:`13625`, using ``?``, ``??``, ``*?`` will not call
138 - :ghpull:`13625`, using ``?``, ``??``, ``*?`` will not call
23 ``set_next_input`` as most frontend allow proper multiline editing and it was
139 ``set_next_input`` as most frontend allow proper multiline editing and it was
24 causing issues for many users of multi-cell frontends. This has been backported to 7.33
140 causing issues for many users of multi-cell frontends. This has been backported to 7.33
25
141
26
142
27 - :ghpull:`13600`, ``pre_run_*``-hooks will now have a ``cell_id`` attribute on
143 - :ghpull:`13600`, ``pre_run_*``-hooks will now have a ``cell_id`` attribute on
28 the info object when frontend provide it. This has been backported to 7.33
144 the info object when frontend provide it. This has been backported to 7.33
29
145
30 - :ghpull:`13624`, fixed :kbd:`End` key being broken after accepting an
146 - :ghpull:`13624`, fixed :kbd:`End` key being broken after accepting an
31 auto-suggestion.
147 auto-suggestion.
32
148
33 - :ghpull:`13657` fix issue where history from different sessions would be mixed.
149 - :ghpull:`13657` fix issue where history from different sessions would be mixed.
34
150
35 .. _version 8.2.0:
151 .. _version 8.2.0:
36
152
37 IPython 8.2.0
153 IPython 8.2.0
38 -------------
154 -------------
39
155
40 IPython 8.2 mostly bring bugfixes to IPython.
156 IPython 8.2 mostly bring bugfixes to IPython.
41
157
42 - Auto-suggestion can now be elected with the ``end`` key. :ghpull:`13566`
158 - Auto-suggestion can now be elected with the ``end`` key. :ghpull:`13566`
43 - Some traceback issues with ``assert etb is not None`` have been fixed. :ghpull:`13588`
159 - Some traceback issues with ``assert etb is not None`` have been fixed. :ghpull:`13588`
44 - History is now pulled from the sqitel database and not from in-memory.
160 - History is now pulled from the sqitel database and not from in-memory.
45 In particular when using the ``%paste`` magic, the content of the pasted text will
161 In particular when using the ``%paste`` magic, the content of the pasted text will
46 be part of the history and not the verbatim text ``%paste`` anymore. :ghpull:`13592`
162 be part of the history and not the verbatim text ``%paste`` anymore. :ghpull:`13592`
47 - Fix ``Ctrl-\\`` exit cleanup :ghpull:`13603`
163 - Fix ``Ctrl-\\`` exit cleanup :ghpull:`13603`
48 - Fixes to ``ultratb`` ipdb support when used outside of IPython. :ghpull:`13498`
164 - Fixes to ``ultratb`` ipdb support when used outside of IPython. :ghpull:`13498`
49
165
50
166
51 I am still trying to fix and investigate :ghissue:`13598`, which seem to be
167 I am still trying to fix and investigate :ghissue:`13598`, which seem to be
52 random, and would appreciate help if you find reproducible minimal case. I've
168 random, and would appreciate help if you find reproducible minimal case. I've
53 tried to make various changes to the codebase to mitigate it, but a proper fix
169 tried to make various changes to the codebase to mitigate it, but a proper fix
54 will be difficult without understanding the cause.
170 will be difficult without understanding the cause.
55
171
56
172
57 All the issues on pull-requests for this release can be found in the `8.2
173 All the issues on pull-requests for this release can be found in the `8.2
58 milestone. <https://github.com/ipython/ipython/milestone/100>`__ . And some
174 milestone. <https://github.com/ipython/ipython/milestone/100>`__ . And some
59 documentation only PR can be found as part of the `7.33 milestone
175 documentation only PR can be found as part of the `7.33 milestone
60 <https://github.com/ipython/ipython/milestone/101>`__ (currently not released).
176 <https://github.com/ipython/ipython/milestone/101>`__ (currently not released).
61
177
62 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
178 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
63 work on IPython and related libraries.
179 work on IPython and related libraries.
64
180
65 .. _version 8.1.1:
181 .. _version 8.1.1:
66
182
67 IPython 8.1.1
183 IPython 8.1.1
68 -------------
184 -------------
69
185
70 Fix an issue with virtualenv and Python 3.8 introduced in 8.1
186 Fix an issue with virtualenv and Python 3.8 introduced in 8.1
71
187
72 Revert :ghpull:`13537` (fix an issue with symlinks in virtualenv) that raises an
188 Revert :ghpull:`13537` (fix an issue with symlinks in virtualenv) that raises an
73 error in Python 3.8, and fixed in a different way in :ghpull:`13559`.
189 error in Python 3.8, and fixed in a different way in :ghpull:`13559`.
74
190
75 .. _version 8.1:
191 .. _version 8.1:
76
192
77 IPython 8.1.0
193 IPython 8.1.0
78 -------------
194 -------------
79
195
80 IPython 8.1 is the first minor release after 8.0 and fixes a number of bugs and
196 IPython 8.1 is the first minor release after 8.0 and fixes a number of bugs and
81 Update a few behavior that were problematic with the 8.0 as with many new major
197 Update a few behavior that were problematic with the 8.0 as with many new major
82 release.
198 release.
83
199
84 Note that beyond the changes listed here, IPython 8.1.0 also contains all the
200 Note that beyond the changes listed here, IPython 8.1.0 also contains all the
85 features listed in :ref:`version 7.32`.
201 features listed in :ref:`version 7.32`.
86
202
87 - Misc and multiple fixes around quotation auto-closing. It is now disabled by
203 - Misc and multiple fixes around quotation auto-closing. It is now disabled by
88 default. Run with ``TerminalInteractiveShell.auto_match=True`` to re-enabled
204 default. Run with ``TerminalInteractiveShell.auto_match=True`` to re-enabled
89 - Require pygments>=2.4.0 :ghpull:`13459`, this was implicit in the code, but
205 - Require pygments>=2.4.0 :ghpull:`13459`, this was implicit in the code, but
90 is now explicit in ``setup.cfg``/``setup.py``
206 is now explicit in ``setup.cfg``/``setup.py``
91 - Docs improvement of ``core.magic_arguments`` examples. :ghpull:`13433`
207 - Docs improvement of ``core.magic_arguments`` examples. :ghpull:`13433`
92 - Multi-line edit executes too early with await. :ghpull:`13424`
208 - Multi-line edit executes too early with await. :ghpull:`13424`
93
209
94 - ``black`` is back as an optional dependency, and autoformatting disabled by
210 - ``black`` is back as an optional dependency, and autoformatting disabled by
95 default until some fixes are implemented (black improperly reformat magics).
211 default until some fixes are implemented (black improperly reformat magics).
96 :ghpull:`13471` Additionally the ability to use ``yapf`` as a code
212 :ghpull:`13471` Additionally the ability to use ``yapf`` as a code
97 reformatter has been added :ghpull:`13528` . You can use
213 reformatter has been added :ghpull:`13528` . You can use
98 ``TerminalInteractiveShell.autoformatter="black"``,
214 ``TerminalInteractiveShell.autoformatter="black"``,
99 ``TerminalInteractiveShell.autoformatter="yapf"`` to re-enable auto formating
215 ``TerminalInteractiveShell.autoformatter="yapf"`` to re-enable auto formating
100 with black, or switch to yapf.
216 with black, or switch to yapf.
101
217
102 - Fix and issue where ``display`` was not defined.
218 - Fix and issue where ``display`` was not defined.
103
219
104 - Auto suggestions are now configurable. Currently only
220 - Auto suggestions are now configurable. Currently only
105 ``AutoSuggestFromHistory`` (default) and ``None``. new provider contribution
221 ``AutoSuggestFromHistory`` (default) and ``None``. new provider contribution
106 welcomed. :ghpull:`13475`
222 welcomed. :ghpull:`13475`
107
223
108 - multiple packaging/testing improvement to simplify downstream packaging
224 - multiple packaging/testing improvement to simplify downstream packaging
109 (xfail with reasons, try to not access network...).
225 (xfail with reasons, try to not access network...).
110
226
111 - Update deprecation. ``InteractiveShell.magic`` internal method has been
227 - Update deprecation. ``InteractiveShell.magic`` internal method has been
112 deprecated for many years but did not emit a warning until now.
228 deprecated for many years but did not emit a warning until now.
113
229
114 - internal ``appended_to_syspath`` context manager has been deprecated.
230 - internal ``appended_to_syspath`` context manager has been deprecated.
115
231
116 - fix an issue with symlinks in virtualenv :ghpull:`13537` (Reverted in 8.1.1)
232 - fix an issue with symlinks in virtualenv :ghpull:`13537` (Reverted in 8.1.1)
117
233
118 - Fix an issue with vim mode, where cursor would not be reset on exit :ghpull:`13472`
234 - Fix an issue with vim mode, where cursor would not be reset on exit :ghpull:`13472`
119
235
120 - ipython directive now remove only known pseudo-decorators :ghpull:`13532`
236 - ipython directive now remove only known pseudo-decorators :ghpull:`13532`
121
237
122 - ``IPython/lib/security`` which used to be used for jupyter notebook has been
238 - ``IPython/lib/security`` which used to be used for jupyter notebook has been
123 removed.
239 removed.
124
240
125 - Fix an issue where ``async with`` would execute on new lines. :ghpull:`13436`
241 - Fix an issue where ``async with`` would execute on new lines. :ghpull:`13436`
126
242
127
243
128 We want to remind users that IPython is part of the Jupyter organisations, and
244 We want to remind users that IPython is part of the Jupyter organisations, and
129 thus governed by a Code of Conduct. Some of the behavior we have seen on GitHub is not acceptable.
245 thus governed by a Code of Conduct. Some of the behavior we have seen on GitHub is not acceptable.
130 Abuse and non-respectful comments on discussion will not be tolerated.
246 Abuse and non-respectful comments on discussion will not be tolerated.
131
247
132 Many thanks to all the contributors to this release, many of the above fixed issue and
248 Many thanks to all the contributors to this release, many of the above fixed issue and
133 new features where done by first time contributors, showing there is still
249 new features where done by first time contributors, showing there is still
134 plenty of easy contribution possible in IPython
250 plenty of easy contribution possible in IPython
135 . You can find all individual contributions
251 . You can find all individual contributions
136 to this milestone `on github <https://github.com/ipython/ipython/milestone/91>`__.
252 to this milestone `on github <https://github.com/ipython/ipython/milestone/91>`__.
137
253
138 Thanks as well to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
254 Thanks as well to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
139 work on IPython and related libraries. In particular the Lazy autoloading of
255 work on IPython and related libraries. In particular the Lazy autoloading of
140 magics that you will find described in the 7.32 release notes.
256 magics that you will find described in the 7.32 release notes.
141
257
142
258
143 .. _version 8.0.1:
259 .. _version 8.0.1:
144
260
145 IPython 8.0.1 (CVE-2022-21699)
261 IPython 8.0.1 (CVE-2022-21699)
146 ------------------------------
262 ------------------------------
147
263
148 IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default
264 IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default
149 values in order to prevent potential Execution with Unnecessary Privileges.
265 values in order to prevent potential Execution with Unnecessary Privileges.
150
266
151 Almost all version of IPython looks for configuration and profiles in current
267 Almost all version of IPython looks for configuration and profiles in current
152 working directory. Since IPython was developed before pip and environments
268 working directory. Since IPython was developed before pip and environments
153 existed it was used a convenient way to load code/packages in a project
269 existed it was used a convenient way to load code/packages in a project
154 dependant way.
270 dependant way.
155
271
156 In 2022, it is not necessary anymore, and can lead to confusing behavior where
272 In 2022, it is not necessary anymore, and can lead to confusing behavior where
157 for example cloning a repository and starting IPython or loading a notebook from
273 for example cloning a repository and starting IPython or loading a notebook from
158 any Jupyter-Compatible interface that has ipython set as a kernel can lead to
274 any Jupyter-Compatible interface that has ipython set as a kernel can lead to
159 code execution.
275 code execution.
160
276
161
277
162 I did not find any standard way for packaged to advertise CVEs they fix, I'm
278 I did not find any standard way for packaged to advertise CVEs they fix, I'm
163 thus trying to add a ``__patched_cves__`` attribute to the IPython module that
279 thus trying to add a ``__patched_cves__`` attribute to the IPython module that
164 list the CVEs that should have been fixed. This attribute is informational only
280 list the CVEs that should have been fixed. This attribute is informational only
165 as if a executable has a flaw, this value can always be changed by an attacker.
281 as if a executable has a flaw, this value can always be changed by an attacker.
166
282
167 .. code::
283 .. code::
168
284
169 In [1]: import IPython
285 In [1]: import IPython
170
286
171 In [2]: IPython.__patched_cves__
287 In [2]: IPython.__patched_cves__
172 Out[2]: {'CVE-2022-21699'}
288 Out[2]: {'CVE-2022-21699'}
173
289
174 In [3]: 'CVE-2022-21699' in IPython.__patched_cves__
290 In [3]: 'CVE-2022-21699' in IPython.__patched_cves__
175 Out[3]: True
291 Out[3]: True
176
292
177 Thus starting with this version:
293 Thus starting with this version:
178
294
179 - The current working directory is not searched anymore for profiles or
295 - The current working directory is not searched anymore for profiles or
180 configurations files.
296 configurations files.
181 - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain
297 - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain
182 the list of fixed CVE. This is informational only.
298 the list of fixed CVE. This is informational only.
183
299
184 Further details can be read on the `GitHub Advisory <https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5x>`__
300 Further details can be read on the `GitHub Advisory <https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5x>`__
185
301
186
302
187 .. _version 8.0:
303 .. _version 8.0:
188
304
189 IPython 8.0
305 IPython 8.0
190 -----------
306 -----------
191
307
192 IPython 8.0 is bringing a large number of new features and improvements to both the
308 IPython 8.0 is bringing a large number of new features and improvements to both the
193 user of the terminal and of the kernel via Jupyter. The removal of compatibility
309 user of the terminal and of the kernel via Jupyter. The removal of compatibility
194 with older version of Python is also the opportunity to do a couple of
310 with older version of Python is also the opportunity to do a couple of
195 performance improvements in particular with respect to startup time.
311 performance improvements in particular with respect to startup time.
196 The 8.x branch started diverging from its predecessor around IPython 7.12
312 The 8.x branch started diverging from its predecessor around IPython 7.12
197 (January 2020).
313 (January 2020).
198
314
199 This release contains 250+ pull requests, in addition to many of the features
315 This release contains 250+ pull requests, in addition to many of the features
200 and backports that have made it to the 7.x branch. Please see the
316 and backports that have made it to the 7.x branch. Please see the
201 `8.0 milestone <https://github.com/ipython/ipython/milestone/73?closed=1>`__ for the full list of pull requests.
317 `8.0 milestone <https://github.com/ipython/ipython/milestone/73?closed=1>`__ for the full list of pull requests.
202
318
203 Please feel free to send pull requests to updates those notes after release,
319 Please feel free to send pull requests to updates those notes after release,
204 I have likely forgotten a few things reviewing 250+ PRs.
320 I have likely forgotten a few things reviewing 250+ PRs.
205
321
206 Dependencies changes/downstream packaging
322 Dependencies changes/downstream packaging
207 -----------------------------------------
323 -----------------------------------------
208
324
209 Most of our building steps have been changed to be (mostly) declarative
325 Most of our building steps have been changed to be (mostly) declarative
210 and follow PEP 517. We are trying to completely remove ``setup.py`` (:ghpull:`13238`) and are
326 and follow PEP 517. We are trying to completely remove ``setup.py`` (:ghpull:`13238`) and are
211 looking for help to do so.
327 looking for help to do so.
212
328
213 - minimum supported ``traitlets`` version is now 5+
329 - minimum supported ``traitlets`` version is now 5+
214 - we now require ``stack_data``
330 - we now require ``stack_data``
215 - minimal Python is now 3.8
331 - minimal Python is now 3.8
216 - ``nose`` is not a testing requirement anymore
332 - ``nose`` is not a testing requirement anymore
217 - ``pytest`` replaces nose.
333 - ``pytest`` replaces nose.
218 - ``iptest``/``iptest3`` cli entrypoints do not exists anymore.
334 - ``iptest``/``iptest3`` cli entrypoints do not exists anymore.
219 - minimum officially support ``numpy`` version has been bumped, but this should
335 - minimum officially support ``numpy`` version has been bumped, but this should
220 not have much effect on packaging.
336 not have much effect on packaging.
221
337
222
338
223 Deprecation and removal
339 Deprecation and removal
224 -----------------------
340 -----------------------
225
341
226 We removed almost all features, arguments, functions, and modules that were
342 We removed almost all features, arguments, functions, and modules that were
227 marked as deprecated between IPython 1.0 and 5.0. As a reminder, 5.0 was released
343 marked as deprecated between IPython 1.0 and 5.0. As a reminder, 5.0 was released
228 in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in May 2020.
344 in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in May 2020.
229 The few remaining deprecated features we left have better deprecation warnings
345 The few remaining deprecated features we left have better deprecation warnings
230 or have been turned into explicit errors for better error messages.
346 or have been turned into explicit errors for better error messages.
231
347
232 I will use this occasion to add the following requests to anyone emitting a
348 I will use this occasion to add the following requests to anyone emitting a
233 deprecation warning:
349 deprecation warning:
234
350
235 - Please add at least ``stacklevel=2`` so that the warning is emitted into the
351 - Please add at least ``stacklevel=2`` so that the warning is emitted into the
236 caller context, and not the callee one.
352 caller context, and not the callee one.
237 - Please add **since which version** something is deprecated.
353 - Please add **since which version** something is deprecated.
238
354
239 As a side note, it is much easier to conditionally compare version
355 As a side note, it is much easier to conditionally compare version
240 numbers rather than using ``try/except`` when functionality changes with a version.
356 numbers rather than using ``try/except`` when functionality changes with a version.
241
357
242 I won't list all the removed features here, but modules like ``IPython.kernel``,
358 I won't list all the removed features here, but modules like ``IPython.kernel``,
243 which was just a shim module around ``ipykernel`` for the past 8 years, have been
359 which was just a shim module around ``ipykernel`` for the past 8 years, have been
244 removed, and so many other similar things that pre-date the name **Jupyter**
360 removed, and so many other similar things that pre-date the name **Jupyter**
245 itself.
361 itself.
246
362
247 We no longer need to add ``IPython.extensions`` to the PYTHONPATH because that is being
363 We no longer need to add ``IPython.extensions`` to the PYTHONPATH because that is being
248 handled by ``load_extension``.
364 handled by ``load_extension``.
249
365
250 We are also removing ``Cythonmagic``, ``sympyprinting`` and ``rmagic`` as they are now in
366 We are also removing ``Cythonmagic``, ``sympyprinting`` and ``rmagic`` as they are now in
251 other packages and no longer need to be inside IPython.
367 other packages and no longer need to be inside IPython.
252
368
253
369
254 Documentation
370 Documentation
255 -------------
371 -------------
256
372
257 The majority of our docstrings have now been reformatted and automatically fixed by
373 The majority of our docstrings have now been reformatted and automatically fixed by
258 the experimental `Vélin <https://pypi.org/project/velin/>`_ project to conform
374 the experimental `Vélin <https://pypi.org/project/velin/>`_ project to conform
259 to numpydoc.
375 to numpydoc.
260
376
261 Type annotations
377 Type annotations
262 ----------------
378 ----------------
263
379
264 While IPython itself is highly dynamic and can't be completely typed, many of
380 While IPython itself is highly dynamic and can't be completely typed, many of
265 the functions now have type annotations, and part of the codebase is now checked
381 the functions now have type annotations, and part of the codebase is now checked
266 by mypy.
382 by mypy.
267
383
268
384
269 Featured changes
385 Featured changes
270 ----------------
386 ----------------
271
387
272 Here is a features list of changes in IPython 8.0. This is of course non-exhaustive.
388 Here is a features list of changes in IPython 8.0. This is of course non-exhaustive.
273 Please note as well that many features have been added in the 7.x branch as well
389 Please note as well that many features have been added in the 7.x branch as well
274 (and hence why you want to read the 7.x what's new notes), in particular
390 (and hence why you want to read the 7.x what's new notes), in particular
275 features contributed by QuantStack (with respect to debugger protocol and Xeus
391 features contributed by QuantStack (with respect to debugger protocol and Xeus
276 Python), as well as many debugger features that I was pleased to implement as
392 Python), as well as many debugger features that I was pleased to implement as
277 part of my work at QuanSight and sponsored by DE Shaw.
393 part of my work at QuanSight and sponsored by DE Shaw.
278
394
279 Traceback improvements
395 Traceback improvements
280 ~~~~~~~~~~~~~~~~~~~~~~
396 ~~~~~~~~~~~~~~~~~~~~~~
281
397
282 Previously, error tracebacks for errors happening in code cells were showing a
398 Previously, error tracebacks for errors happening in code cells were showing a
283 hash, the one used for compiling the Python AST::
399 hash, the one used for compiling the Python AST::
284
400
285 In [1]: def foo():
401 In [1]: def foo():
286 ...: return 3 / 0
402 ...: return 3 / 0
287 ...:
403 ...:
288
404
289 In [2]: foo()
405 In [2]: foo()
290 ---------------------------------------------------------------------------
406 ---------------------------------------------------------------------------
291 ZeroDivisionError Traceback (most recent call last)
407 ZeroDivisionError Traceback (most recent call last)
292 <ipython-input-2-c19b6d9633cf> in <module>
408 <ipython-input-2-c19b6d9633cf> in <module>
293 ----> 1 foo()
409 ----> 1 foo()
294
410
295 <ipython-input-1-1595a74c32d5> in foo()
411 <ipython-input-1-1595a74c32d5> in foo()
296 1 def foo():
412 1 def foo():
297 ----> 2 return 3 / 0
413 ----> 2 return 3 / 0
298 3
414 3
299
415
300 ZeroDivisionError: division by zero
416 ZeroDivisionError: division by zero
301
417
302 The error traceback is now correctly formatted, showing the cell number in which the error happened::
418 The error traceback is now correctly formatted, showing the cell number in which the error happened::
303
419
304 In [1]: def foo():
420 In [1]: def foo():
305 ...: return 3 / 0
421 ...: return 3 / 0
306 ...:
422 ...:
307
423
308 Input In [2]: foo()
424 Input In [2]: foo()
309 ---------------------------------------------------------------------------
425 ---------------------------------------------------------------------------
310 ZeroDivisionError Traceback (most recent call last)
426 ZeroDivisionError Traceback (most recent call last)
311 input In [2], in <module>
427 input In [2], in <module>
312 ----> 1 foo()
428 ----> 1 foo()
313
429
314 Input In [1], in foo()
430 Input In [1], in foo()
315 1 def foo():
431 1 def foo():
316 ----> 2 return 3 / 0
432 ----> 2 return 3 / 0
317
433
318 ZeroDivisionError: division by zero
434 ZeroDivisionError: division by zero
319
435
320 The ``stack_data`` package has been integrated, which provides smarter information in the traceback;
436 The ``stack_data`` package has been integrated, which provides smarter information in the traceback;
321 in particular it will highlight the AST node where an error occurs which can help to quickly narrow down errors.
437 in particular it will highlight the AST node where an error occurs which can help to quickly narrow down errors.
322
438
323 For example in the following snippet::
439 For example in the following snippet::
324
440
325 def foo(i):
441 def foo(i):
326 x = [[[0]]]
442 x = [[[0]]]
327 return x[0][i][0]
443 return x[0][i][0]
328
444
329
445
330 def bar():
446 def bar():
331 return foo(0) + foo(
447 return foo(0) + foo(
332 1
448 1
333 ) + foo(2)
449 ) + foo(2)
334
450
335
451
336 calling ``bar()`` would raise an ``IndexError`` on the return line of ``foo``,
452 calling ``bar()`` would raise an ``IndexError`` on the return line of ``foo``,
337 and IPython 8.0 is capable of telling you where the index error occurs::
453 and IPython 8.0 is capable of telling you where the index error occurs::
338
454
339
455
340 IndexError
456 IndexError
341 Input In [2], in <module>
457 Input In [2], in <module>
342 ----> 1 bar()
458 ----> 1 bar()
343 ^^^^^
459 ^^^^^
344
460
345 Input In [1], in bar()
461 Input In [1], in bar()
346 6 def bar():
462 6 def bar():
347 ----> 7 return foo(0) + foo(
463 ----> 7 return foo(0) + foo(
348 ^^^^
464 ^^^^
349 8 1
465 8 1
350 ^^^^^^^^
466 ^^^^^^^^
351 9 ) + foo(2)
467 9 ) + foo(2)
352 ^^^^
468 ^^^^
353
469
354 Input In [1], in foo(i)
470 Input In [1], in foo(i)
355 1 def foo(i):
471 1 def foo(i):
356 2 x = [[[0]]]
472 2 x = [[[0]]]
357 ----> 3 return x[0][i][0]
473 ----> 3 return x[0][i][0]
358 ^^^^^^^
474 ^^^^^^^
359
475
360 The corresponding locations marked here with ``^`` will show up highlighted in
476 The corresponding locations marked here with ``^`` will show up highlighted in
361 the terminal and notebooks.
477 the terminal and notebooks.
362
478
363 Finally, a colon ``::`` and line number is appended after a filename in
479 Finally, a colon ``::`` and line number is appended after a filename in
364 traceback::
480 traceback::
365
481
366
482
367 ZeroDivisionError Traceback (most recent call last)
483 ZeroDivisionError Traceback (most recent call last)
368 File ~/error.py:4, in <module>
484 File ~/error.py:4, in <module>
369 1 def f():
485 1 def f():
370 2 1/0
486 2 1/0
371 ----> 4 f()
487 ----> 4 f()
372
488
373 File ~/error.py:2, in f()
489 File ~/error.py:2, in f()
374 1 def f():
490 1 def f():
375 ----> 2 1/0
491 ----> 2 1/0
376
492
377 Many terminals and editors have integrations enabling you to directly jump to the
493 Many terminals and editors have integrations enabling you to directly jump to the
378 relevant file/line when this syntax is used, so this small addition may have a high
494 relevant file/line when this syntax is used, so this small addition may have a high
379 impact on productivity.
495 impact on productivity.
380
496
381
497
382 Autosuggestions
498 Autosuggestions
383 ~~~~~~~~~~~~~~~
499 ~~~~~~~~~~~~~~~
384
500
385 Autosuggestion is a very useful feature available in `fish <https://fishshell.com/>`__, `zsh <https://en.wikipedia.org/wiki/Z_shell>`__, and `prompt-toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html#auto-suggestion>`__.
501 Autosuggestion is a very useful feature available in `fish <https://fishshell.com/>`__, `zsh <https://en.wikipedia.org/wiki/Z_shell>`__, and `prompt-toolkit <https://python-prompt-toolkit.readthedocs.io/en/master/pages/asking_for_input.html#auto-suggestion>`__.
386
502
387 `Ptpython <https://github.com/prompt-toolkit/ptpython#ptpython>`__ allows users to enable this feature in
503 `Ptpython <https://github.com/prompt-toolkit/ptpython#ptpython>`__ allows users to enable this feature in
388 `ptpython/config.py <https://github.com/prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py#L90>`__.
504 `ptpython/config.py <https://github.com/prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py#L90>`__.
389
505
390 This feature allows users to accept autosuggestions with ctrl e, ctrl f,
506 This feature allows users to accept autosuggestions with ctrl e, ctrl f,
391 or right arrow as described below.
507 or right arrow as described below.
392
508
393 1. Start ipython
509 1. Start ipython
394
510
395 .. image:: ../_images/8.0/auto_suggest_1_prompt_no_text.png
511 .. image:: ../_images/8.0/auto_suggest_1_prompt_no_text.png
396
512
397 2. Run ``print("hello")``
513 2. Run ``print("hello")``
398
514
399 .. image:: ../_images/8.0/auto_suggest_2_print_hello_suggest.png
515 .. image:: ../_images/8.0/auto_suggest_2_print_hello_suggest.png
400
516
401 3. start typing ``print`` again to see the autosuggestion
517 3. start typing ``print`` again to see the autosuggestion
402
518
403 .. image:: ../_images/8.0/auto_suggest_3_print_hello_suggest.png
519 .. image:: ../_images/8.0/auto_suggest_3_print_hello_suggest.png
404
520
405 4. Press ``ctrl-f``, or ``ctrl-e``, or ``right-arrow`` to accept the suggestion
521 4. Press ``ctrl-f``, or ``ctrl-e``, or ``right-arrow`` to accept the suggestion
406
522
407 .. image:: ../_images/8.0/auto_suggest_4_print_hello.png
523 .. image:: ../_images/8.0/auto_suggest_4_print_hello.png
408
524
409 You can also complete word by word:
525 You can also complete word by word:
410
526
411 1. Run ``def say_hello(): print("hello")``
527 1. Run ``def say_hello(): print("hello")``
412
528
413 .. image:: ../_images/8.0/auto_suggest_second_prompt.png
529 .. image:: ../_images/8.0/auto_suggest_second_prompt.png
414
530
415 2. Start typing the first letter if ``def`` to see the autosuggestion
531 2. Start typing the first letter if ``def`` to see the autosuggestion
416
532
417 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
533 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
418
534
419 3. Press ``alt-f`` (or ``escape`` followed by ``f``), to accept the first word of the suggestion
535 3. Press ``alt-f`` (or ``escape`` followed by ``f``), to accept the first word of the suggestion
420
536
421 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
537 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
422
538
423 Importantly, this feature does not interfere with tab completion:
539 Importantly, this feature does not interfere with tab completion:
424
540
425 1. After running ``def say_hello(): print("hello")``, press d
541 1. After running ``def say_hello(): print("hello")``, press d
426
542
427 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
543 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
428
544
429 2. Press Tab to start tab completion
545 2. Press Tab to start tab completion
430
546
431 .. image:: ../_images/8.0/auto_suggest_d_completions.png
547 .. image:: ../_images/8.0/auto_suggest_d_completions.png
432
548
433 3A. Press Tab again to select the first option
549 3A. Press Tab again to select the first option
434
550
435 .. image:: ../_images/8.0/auto_suggest_def_completions.png
551 .. image:: ../_images/8.0/auto_suggest_def_completions.png
436
552
437 3B. Press ``alt f`` (``escape``, ``f``) to accept to accept the first word of the suggestion
553 3B. Press ``alt f`` (``escape``, ``f``) to accept to accept the first word of the suggestion
438
554
439 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
555 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
440
556
441 3C. Press ``ctrl-f`` or ``ctrl-e`` to accept the entire suggestion
557 3C. Press ``ctrl-f`` or ``ctrl-e`` to accept the entire suggestion
442
558
443 .. image:: ../_images/8.0/auto_suggest_match_parens.png
559 .. image:: ../_images/8.0/auto_suggest_match_parens.png
444
560
445
561
446 Currently, autosuggestions are only shown in the emacs or vi insert editing modes:
562 Currently, autosuggestions are only shown in the emacs or vi insert editing modes:
447
563
448 - The ctrl e, ctrl f, and alt f shortcuts work by default in emacs mode.
564 - The ctrl e, ctrl f, and alt f shortcuts work by default in emacs mode.
449 - To use these shortcuts in vi insert mode, you will have to create `custom keybindings in your config.py <https://github.com/mskar/setup/commit/2892fcee46f9f80ef7788f0749edc99daccc52f4/>`__.
565 - To use these shortcuts in vi insert mode, you will have to create `custom keybindings in your config.py <https://github.com/mskar/setup/commit/2892fcee46f9f80ef7788f0749edc99daccc52f4/>`__.
450
566
451
567
452 Show pinfo information in ipdb using "?" and "??"
568 Show pinfo information in ipdb using "?" and "??"
453 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
569 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
454
570
455 In IPDB, it is now possible to show the information about an object using "?"
571 In IPDB, it is now possible to show the information about an object using "?"
456 and "??", in much the same way that it can be done when using the IPython prompt::
572 and "??", in much the same way that it can be done when using the IPython prompt::
457
573
458 ipdb> partial?
574 ipdb> partial?
459 Init signature: partial(self, /, *args, **kwargs)
575 Init signature: partial(self, /, *args, **kwargs)
460 Docstring:
576 Docstring:
461 partial(func, *args, **keywords) - new function with partial application
577 partial(func, *args, **keywords) - new function with partial application
462 of the given arguments and keywords.
578 of the given arguments and keywords.
463 File: ~/.pyenv/versions/3.8.6/lib/python3.8/functools.py
579 File: ~/.pyenv/versions/3.8.6/lib/python3.8/functools.py
464 Type: type
580 Type: type
465 Subclasses:
581 Subclasses:
466
582
467 Previously, ``pinfo`` or ``pinfo2`` command had to be used for this purpose.
583 Previously, ``pinfo`` or ``pinfo2`` command had to be used for this purpose.
468
584
469
585
470 Autoreload 3 feature
586 Autoreload 3 feature
471 ~~~~~~~~~~~~~~~~~~~~
587 ~~~~~~~~~~~~~~~~~~~~
472
588
473 Example: When an IPython session is run with the 'autoreload' extension loaded,
589 Example: When an IPython session is run with the 'autoreload' extension loaded,
474 you will now have the option '3' to select, which means the following:
590 you will now have the option '3' to select, which means the following:
475
591
476 1. replicate all functionality from option 2
592 1. replicate all functionality from option 2
477 2. autoload all new funcs/classes/enums/globals from the module when they are added
593 2. autoload all new funcs/classes/enums/globals from the module when they are added
478 3. autoload all newly imported funcs/classes/enums/globals from external modules
594 3. autoload all newly imported funcs/classes/enums/globals from external modules
479
595
480 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``.
596 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``.
481
597
482 For more information please see the following unit test : ``extensions/tests/test_autoreload.py:test_autoload_newly_added_objects``
598 For more information please see the following unit test : ``extensions/tests/test_autoreload.py:test_autoload_newly_added_objects``
483
599
484 Auto formatting with black in the CLI
600 Auto formatting with black in the CLI
485 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
601 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
486
602
487 This feature was present in 7.x, but disabled by default.
603 This feature was present in 7.x, but disabled by default.
488
604
489 In 8.0, input was automatically reformatted with Black when black was installed.
605 In 8.0, input was automatically reformatted with Black when black was installed.
490 This feature has been reverted for the time being.
606 This feature has been reverted for the time being.
491 You can re-enable it by setting ``TerminalInteractiveShell.autoformatter`` to ``"black"``
607 You can re-enable it by setting ``TerminalInteractiveShell.autoformatter`` to ``"black"``
492
608
493 History Range Glob feature
609 History Range Glob feature
494 ~~~~~~~~~~~~~~~~~~~~~~~~~~
610 ~~~~~~~~~~~~~~~~~~~~~~~~~~
495
611
496 Previously, when using ``%history``, users could specify either
612 Previously, when using ``%history``, users could specify either
497 a range of sessions and lines, for example:
613 a range of sessions and lines, for example:
498
614
499 .. code-block:: python
615 .. code-block:: python
500
616
501 ~8/1-~6/5 # see history from the first line of 8 sessions ago,
617 ~8/1-~6/5 # see history from the first line of 8 sessions ago,
502 # to the fifth line of 6 sessions ago.``
618 # to the fifth line of 6 sessions ago.``
503
619
504 Or users could specify a glob pattern:
620 Or users could specify a glob pattern:
505
621
506 .. code-block:: python
622 .. code-block:: python
507
623
508 -g <pattern> # glob ALL history for the specified pattern.
624 -g <pattern> # glob ALL history for the specified pattern.
509
625
510 However users could *not* specify both.
626 However users could *not* specify both.
511
627
512 If a user *did* specify both a range and a glob pattern,
628 If a user *did* specify both a range and a glob pattern,
513 then the glob pattern would be used (globbing *all* history) *and the range would be ignored*.
629 then the glob pattern would be used (globbing *all* history) *and the range would be ignored*.
514
630
515 With this enhancement, if a user specifies both a range and a glob pattern, then the glob pattern will be applied to the specified range of history.
631 With this enhancement, if a user specifies both a range and a glob pattern, then the glob pattern will be applied to the specified range of history.
516
632
517 Don't start a multi-line cell with sunken parenthesis
633 Don't start a multi-line cell with sunken parenthesis
518 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
634 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
519
635
520 From now on, IPython will not ask for the next line of input when given a single
636 From now on, IPython will not ask for the next line of input when given a single
521 line with more closing than opening brackets. For example, this means that if
637 line with more closing than opening brackets. For example, this means that if
522 you (mis)type ``]]`` instead of ``[]``, a ``SyntaxError`` will show up, instead of
638 you (mis)type ``]]`` instead of ``[]``, a ``SyntaxError`` will show up, instead of
523 the ``...:`` prompt continuation.
639 the ``...:`` prompt continuation.
524
640
525 IPython shell for ipdb interact
641 IPython shell for ipdb interact
526 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
642 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
527
643
528 The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``.
644 The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``.
529
645
530 Automatic Vi prompt stripping
646 Automatic Vi prompt stripping
531 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
647 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
532
648
533 When pasting code into IPython, it will strip the leading prompt characters if
649 When pasting code into IPython, it will strip the leading prompt characters if
534 there are any. For example, you can paste the following code into the console -
650 there are any. For example, you can paste the following code into the console -
535 it will still work, even though each line is prefixed with prompts (``In``,
651 it will still work, even though each line is prefixed with prompts (``In``,
536 ``Out``)::
652 ``Out``)::
537
653
538 In [1]: 2 * 2 == 4
654 In [1]: 2 * 2 == 4
539 Out[1]: True
655 Out[1]: True
540
656
541 In [2]: print("This still works as pasted")
657 In [2]: print("This still works as pasted")
542
658
543
659
544 Previously, this was not the case for the Vi-mode prompts::
660 Previously, this was not the case for the Vi-mode prompts::
545
661
546 In [1]: [ins] In [13]: 2 * 2 == 4
662 In [1]: [ins] In [13]: 2 * 2 == 4
547 ...: Out[13]: True
663 ...: Out[13]: True
548 ...:
664 ...:
549 File "<ipython-input-1-727bb88eaf33>", line 1
665 File "<ipython-input-1-727bb88eaf33>", line 1
550 [ins] In [13]: 2 * 2 == 4
666 [ins] In [13]: 2 * 2 == 4
551 ^
667 ^
552 SyntaxError: invalid syntax
668 SyntaxError: invalid syntax
553
669
554 This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are
670 This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are
555 skipped just as the normal ``In`` would be.
671 skipped just as the normal ``In`` would be.
556
672
557 IPython shell can be started in the Vi mode using ``ipython --TerminalInteractiveShell.editing_mode=vi``,
673 IPython shell can be started in the Vi mode using ``ipython --TerminalInteractiveShell.editing_mode=vi``,
558 You should be able to change mode dynamically with ``%config TerminalInteractiveShell.editing_mode='vi'``
674 You should be able to change mode dynamically with ``%config TerminalInteractiveShell.editing_mode='vi'``
559
675
560 Empty History Ranges
676 Empty History Ranges
561 ~~~~~~~~~~~~~~~~~~~~
677 ~~~~~~~~~~~~~~~~~~~~
562
678
563 A number of magics that take history ranges can now be used with an empty
679 A number of magics that take history ranges can now be used with an empty
564 range. These magics are:
680 range. These magics are:
565
681
566 * ``%save``
682 * ``%save``
567 * ``%load``
683 * ``%load``
568 * ``%pastebin``
684 * ``%pastebin``
569 * ``%pycat``
685 * ``%pycat``
570
686
571 Using them this way will make them take the history of the current session up
687 Using them this way will make them take the history of the current session up
572 to the point of the magic call (such that the magic itself will not be
688 to the point of the magic call (such that the magic itself will not be
573 included).
689 included).
574
690
575 Therefore it is now possible to save the whole history to a file using
691 Therefore it is now possible to save the whole history to a file using
576 ``%save <filename>``, load and edit it using ``%load`` (makes for a nice usage
692 ``%save <filename>``, load and edit it using ``%load`` (makes for a nice usage
577 when followed with :kbd:`F2`), send it to `dpaste.org <http://dpast.org>`_ using
693 when followed with :kbd:`F2`), send it to `dpaste.org <http://dpast.org>`_ using
578 ``%pastebin``, or view the whole thing syntax-highlighted with a single
694 ``%pastebin``, or view the whole thing syntax-highlighted with a single
579 ``%pycat``.
695 ``%pycat``.
580
696
581
697
582 Windows timing implementation: Switch to process_time
698 Windows timing implementation: Switch to process_time
583 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
699 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
584 Timing on Windows, for example with ``%%time``, was changed from being based on ``time.perf_counter``
700 Timing on Windows, for example with ``%%time``, was changed from being based on ``time.perf_counter``
585 (which counted time even when the process was sleeping) to being based on ``time.process_time`` instead
701 (which counted time even when the process was sleeping) to being based on ``time.process_time`` instead
586 (which only counts CPU time). This brings it closer to the behavior on Linux. See :ghpull:`12984`.
702 (which only counts CPU time). This brings it closer to the behavior on Linux. See :ghpull:`12984`.
587
703
588 Miscellaneous
704 Miscellaneous
589 ~~~~~~~~~~~~~
705 ~~~~~~~~~~~~~
590 - Non-text formatters are not disabled in the terminal, which should simplify
706 - Non-text formatters are not disabled in the terminal, which should simplify
591 writing extensions displaying images or other mimetypes in supporting terminals.
707 writing extensions displaying images or other mimetypes in supporting terminals.
592 :ghpull:`12315`
708 :ghpull:`12315`
593 - It is now possible to automatically insert matching brackets in Terminal IPython using the
709 - It is now possible to automatically insert matching brackets in Terminal IPython using the
594 ``TerminalInteractiveShell.auto_match=True`` option. :ghpull:`12586`
710 ``TerminalInteractiveShell.auto_match=True`` option. :ghpull:`12586`
595 - We are thinking of deprecating the current ``%%javascript`` magic in favor of a better replacement. See :ghpull:`13376`.
711 - We are thinking of deprecating the current ``%%javascript`` magic in favor of a better replacement. See :ghpull:`13376`.
596 - ``~`` is now expanded when part of a path in most magics :ghpull:`13385`
712 - ``~`` is now expanded when part of a path in most magics :ghpull:`13385`
597 - ``%/%%timeit`` magic now adds a comma every thousands to make reading a long number easier :ghpull:`13379`
713 - ``%/%%timeit`` magic now adds a comma every thousands to make reading a long number easier :ghpull:`13379`
598 - ``"info"`` messages can now be customised to hide some fields :ghpull:`13343`
714 - ``"info"`` messages can now be customised to hide some fields :ghpull:`13343`
599 - ``collections.UserList`` now pretty-prints :ghpull:`13320`
715 - ``collections.UserList`` now pretty-prints :ghpull:`13320`
600 - The debugger now has a persistent history, which should make it less
716 - The debugger now has a persistent history, which should make it less
601 annoying to retype commands :ghpull:`13246`
717 annoying to retype commands :ghpull:`13246`
602 - ``!pip`` ``!conda`` ``!cd`` or ``!ls`` are likely doing the wrong thing. We
718 - ``!pip`` ``!conda`` ``!cd`` or ``!ls`` are likely doing the wrong thing. We
603 now warn users if they use one of those commands. :ghpull:`12954`
719 now warn users if they use one of those commands. :ghpull:`12954`
604 - Make ``%precision`` work for ``numpy.float64`` type :ghpull:`12902`
720 - Make ``%precision`` work for ``numpy.float64`` type :ghpull:`12902`
605
721
606 Re-added support for XDG config directories
722 Re-added support for XDG config directories
607 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
723 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
608
724
609 XDG support through the years comes and goes. There is a tension between having
725 XDG support through the years comes and goes. There is a tension between having
610 an identical location for configuration in all platforms versus having simple instructions.
726 an identical location for configuration in all platforms versus having simple instructions.
611 After initial failures a couple of years ago, IPython was modified to automatically migrate XDG
727 After initial failures a couple of years ago, IPython was modified to automatically migrate XDG
612 config files back into ``~/.ipython``. That migration code has now been removed.
728 config files back into ``~/.ipython``. That migration code has now been removed.
613 IPython now checks the XDG locations, so if you _manually_ move your config
729 IPython now checks the XDG locations, so if you _manually_ move your config
614 files to your preferred location, IPython will not move them back.
730 files to your preferred location, IPython will not move them back.
615
731
616
732
617 Preparing for Python 3.10
733 Preparing for Python 3.10
618 -------------------------
734 -------------------------
619
735
620 To prepare for Python 3.10, we have started working on removing reliance and
736 To prepare for Python 3.10, we have started working on removing reliance and
621 any dependency that is not compatible with Python 3.10. This includes migrating our
737 any dependency that is not compatible with Python 3.10. This includes migrating our
622 test suite to pytest and starting to remove nose. This also means that the
738 test suite to pytest and starting to remove nose. This also means that the
623 ``iptest`` command is now gone and all testing is via pytest.
739 ``iptest`` command is now gone and all testing is via pytest.
624
740
625 This was in large part thanks to the NumFOCUS Small Developer grant, which enabled us to
741 This was in large part thanks to the NumFOCUS Small Developer grant, which enabled us to
626 allocate \$4000 to hire `Nikita Kniazev (@Kojoley) <https://github.com/Kojoley>`_,
742 allocate \$4000 to hire `Nikita Kniazev (@Kojoley) <https://github.com/Kojoley>`_,
627 who did a fantastic job at updating our code base, migrating to pytest, pushing
743 who did a fantastic job at updating our code base, migrating to pytest, pushing
628 our coverage, and fixing a large number of bugs. I highly recommend contacting
744 our coverage, and fixing a large number of bugs. I highly recommend contacting
629 them if you need help with C++ and Python projects.
745 them if you need help with C++ and Python projects.
630
746
631 You can find all relevant issues and PRs with `the SDG 2021 tag <https://github.com/ipython/ipython/issues?q=label%3A%22Numfocus+SDG+2021%22+>`__
747 You can find all relevant issues and PRs with `the SDG 2021 tag <https://github.com/ipython/ipython/issues?q=label%3A%22Numfocus+SDG+2021%22+>`__
632
748
633 Removing support for older Python versions
749 Removing support for older Python versions
634 ------------------------------------------
750 ------------------------------------------
635
751
636
752
637 We are removing support for Python up through 3.7, allowing internal code to use the more
753 We are removing support for Python up through 3.7, allowing internal code to use the more
638 efficient ``pathlib`` and to make better use of type annotations.
754 efficient ``pathlib`` and to make better use of type annotations.
639
755
640 .. image:: ../_images/8.0/pathlib_pathlib_everywhere.jpg
756 .. image:: ../_images/8.0/pathlib_pathlib_everywhere.jpg
641 :alt: "Meme image of Toy Story with Woody and Buzz, with the text 'pathlib, pathlib everywhere'"
757 :alt: "Meme image of Toy Story with Woody and Buzz, with the text 'pathlib, pathlib everywhere'"
642
758
643
759
644 We had about 34 PRs only to update some logic to update some functions from managing strings to
760 We had about 34 PRs only to update some logic to update some functions from managing strings to
645 using Pathlib.
761 using Pathlib.
646
762
647 The completer has also seen significant updates and now makes use of newer Jedi APIs,
763 The completer has also seen significant updates and now makes use of newer Jedi APIs,
648 offering faster and more reliable tab completion.
764 offering faster and more reliable tab completion.
649
765
650 Misc Statistics
766 Misc Statistics
651 ---------------
767 ---------------
652
768
653 Here are some numbers::
769 Here are some numbers::
654
770
655 7.x: 296 files, 12561 blank lines, 20282 comments, 35142 line of code.
771 7.x: 296 files, 12561 blank lines, 20282 comments, 35142 line of code.
656 8.0: 252 files, 12053 blank lines, 19232 comments, 34505 line of code.
772 8.0: 252 files, 12053 blank lines, 19232 comments, 34505 line of code.
657
773
658 $ git diff --stat 7.x...master | tail -1
774 $ git diff --stat 7.x...master | tail -1
659 340 files changed, 13399 insertions(+), 12421 deletions(-)
775 340 files changed, 13399 insertions(+), 12421 deletions(-)
660
776
661 We have commits from 162 authors, who contributed 1916 commits in 23 month, excluding merges (to not bias toward
777 We have commits from 162 authors, who contributed 1916 commits in 23 month, excluding merges (to not bias toward
662 maintainers pushing buttons).::
778 maintainers pushing buttons).::
663
779
664 $ git shortlog -s --no-merges 7.x...master | sort -nr
780 $ git shortlog -s --no-merges 7.x...master | sort -nr
665 535 Matthias Bussonnier
781 535 Matthias Bussonnier
666 86 Nikita Kniazev
782 86 Nikita Kniazev
667 69 Blazej Michalik
783 69 Blazej Michalik
668 49 Samuel Gaist
784 49 Samuel Gaist
669 27 Itamar Turner-Trauring
785 27 Itamar Turner-Trauring
670 18 Spas Kalaydzhisyki
786 18 Spas Kalaydzhisyki
671 17 Thomas Kluyver
787 17 Thomas Kluyver
672 17 Quentin Peter
788 17 Quentin Peter
673 17 James Morris
789 17 James Morris
674 17 Artur Svistunov
790 17 Artur Svistunov
675 15 Bart Skowron
791 15 Bart Skowron
676 14 Alex Hall
792 14 Alex Hall
677 13 rushabh-v
793 13 rushabh-v
678 13 Terry Davis
794 13 Terry Davis
679 13 Benjamin Ragan-Kelley
795 13 Benjamin Ragan-Kelley
680 8 martinRenou
796 8 martinRenou
681 8 farisachugthai
797 8 farisachugthai
682 7 dswij
798 7 dswij
683 7 Gal B
799 7 Gal B
684 7 Corentin Cadiou
800 7 Corentin Cadiou
685 6 yuji96
801 6 yuji96
686 6 Martin Skarzynski
802 6 Martin Skarzynski
687 6 Justin Palmer
803 6 Justin Palmer
688 6 Daniel Goldfarb
804 6 Daniel Goldfarb
689 6 Ben Greiner
805 6 Ben Greiner
690 5 Sammy Al Hashemi
806 5 Sammy Al Hashemi
691 5 Paul Ivanov
807 5 Paul Ivanov
692 5 Inception95
808 5 Inception95
693 5 Eyenpi
809 5 Eyenpi
694 5 Douglas Blank
810 5 Douglas Blank
695 5 Coco Mishra
811 5 Coco Mishra
696 5 Bibo Hao
812 5 Bibo Hao
697 5 André A. Gomes
813 5 André A. Gomes
698 5 Ahmed Fasih
814 5 Ahmed Fasih
699 4 takuya fujiwara
815 4 takuya fujiwara
700 4 palewire
816 4 palewire
701 4 Thomas A Caswell
817 4 Thomas A Caswell
702 4 Talley Lambert
818 4 Talley Lambert
703 4 Scott Sanderson
819 4 Scott Sanderson
704 4 Ram Rachum
820 4 Ram Rachum
705 4 Nick Muoh
821 4 Nick Muoh
706 4 Nathan Goldbaum
822 4 Nathan Goldbaum
707 4 Mithil Poojary
823 4 Mithil Poojary
708 4 Michael T
824 4 Michael T
709 4 Jakub Klus
825 4 Jakub Klus
710 4 Ian Castleden
826 4 Ian Castleden
711 4 Eli Rykoff
827 4 Eli Rykoff
712 4 Ashwin Vishnu
828 4 Ashwin Vishnu
713 3 谭九鼎
829 3 谭九鼎
714 3 sleeping
830 3 sleeping
715 3 Sylvain Corlay
831 3 Sylvain Corlay
716 3 Peter Corke
832 3 Peter Corke
717 3 Paul Bissex
833 3 Paul Bissex
718 3 Matthew Feickert
834 3 Matthew Feickert
719 3 Fernando Perez
835 3 Fernando Perez
720 3 Eric Wieser
836 3 Eric Wieser
721 3 Daniel Mietchen
837 3 Daniel Mietchen
722 3 Aditya Sathe
838 3 Aditya Sathe
723 3 007vedant
839 3 007vedant
724 2 rchiodo
840 2 rchiodo
725 2 nicolaslazo
841 2 nicolaslazo
726 2 luttik
842 2 luttik
727 2 gorogoroumaru
843 2 gorogoroumaru
728 2 foobarbyte
844 2 foobarbyte
729 2 bar-hen
845 2 bar-hen
730 2 Theo Ouzhinski
846 2 Theo Ouzhinski
731 2 Strawkage
847 2 Strawkage
732 2 Samreen Zarroug
848 2 Samreen Zarroug
733 2 Pete Blois
849 2 Pete Blois
734 2 Meysam Azad
850 2 Meysam Azad
735 2 Matthieu Ancellin
851 2 Matthieu Ancellin
736 2 Mark Schmitz
852 2 Mark Schmitz
737 2 Maor Kleinberger
853 2 Maor Kleinberger
738 2 MRCWirtz
854 2 MRCWirtz
739 2 Lumir Balhar
855 2 Lumir Balhar
740 2 Julien Rabinow
856 2 Julien Rabinow
741 2 Juan Luis Cano Rodríguez
857 2 Juan Luis Cano Rodríguez
742 2 Joyce Er
858 2 Joyce Er
743 2 Jakub
859 2 Jakub
744 2 Faris A Chugthai
860 2 Faris A Chugthai
745 2 Ethan Madden
861 2 Ethan Madden
746 2 Dimitri Papadopoulos
862 2 Dimitri Papadopoulos
747 2 Diego Fernandez
863 2 Diego Fernandez
748 2 Daniel Shimon
864 2 Daniel Shimon
749 2 Coco Bennett
865 2 Coco Bennett
750 2 Carlos Cordoba
866 2 Carlos Cordoba
751 2 Boyuan Liu
867 2 Boyuan Liu
752 2 BaoGiang HoangVu
868 2 BaoGiang HoangVu
753 2 Augusto
869 2 Augusto
754 2 Arthur Svistunov
870 2 Arthur Svistunov
755 2 Arthur Moreira
871 2 Arthur Moreira
756 2 Ali Nabipour
872 2 Ali Nabipour
757 2 Adam Hackbarth
873 2 Adam Hackbarth
758 1 richard
874 1 richard
759 1 linar-jether
875 1 linar-jether
760 1 lbennett
876 1 lbennett
761 1 juacrumar
877 1 juacrumar
762 1 gpotter2
878 1 gpotter2
763 1 digitalvirtuoso
879 1 digitalvirtuoso
764 1 dalthviz
880 1 dalthviz
765 1 Yonatan Goldschmidt
881 1 Yonatan Goldschmidt
766 1 Tomasz Kłoczko
882 1 Tomasz Kłoczko
767 1 Tobias Bengfort
883 1 Tobias Bengfort
768 1 Timur Kushukov
884 1 Timur Kushukov
769 1 Thomas
885 1 Thomas
770 1 Snir Broshi
886 1 Snir Broshi
771 1 Shao Yang Hong
887 1 Shao Yang Hong
772 1 Sanjana-03
888 1 Sanjana-03
773 1 Romulo Filho
889 1 Romulo Filho
774 1 Rodolfo Carvalho
890 1 Rodolfo Carvalho
775 1 Richard Shadrach
891 1 Richard Shadrach
776 1 Reilly Tucker Siemens
892 1 Reilly Tucker Siemens
777 1 Rakessh Roshan
893 1 Rakessh Roshan
778 1 Piers Titus van der Torren
894 1 Piers Titus van der Torren
779 1 PhanatosZou
895 1 PhanatosZou
780 1 Pavel Safronov
896 1 Pavel Safronov
781 1 Paulo S. Costa
897 1 Paulo S. Costa
782 1 Paul McCarthy
898 1 Paul McCarthy
783 1 NotWearingPants
899 1 NotWearingPants
784 1 Naelson Douglas
900 1 Naelson Douglas
785 1 Michael Tiemann
901 1 Michael Tiemann
786 1 Matt Wozniski
902 1 Matt Wozniski
787 1 Markus Wageringel
903 1 Markus Wageringel
788 1 Marcus Wirtz
904 1 Marcus Wirtz
789 1 Marcio Mazza
905 1 Marcio Mazza
790 1 Lumír 'Frenzy' Balhar
906 1 Lumír 'Frenzy' Balhar
791 1 Lightyagami1
907 1 Lightyagami1
792 1 Leon Anavi
908 1 Leon Anavi
793 1 LeafyLi
909 1 LeafyLi
794 1 L0uisJ0shua
910 1 L0uisJ0shua
795 1 Kyle Cutler
911 1 Kyle Cutler
796 1 Krzysztof Cybulski
912 1 Krzysztof Cybulski
797 1 Kevin Kirsche
913 1 Kevin Kirsche
798 1 KIU Shueng Chuan
914 1 KIU Shueng Chuan
799 1 Jonathan Slenders
915 1 Jonathan Slenders
800 1 Jay Qi
916 1 Jay Qi
801 1 Jake VanderPlas
917 1 Jake VanderPlas
802 1 Iwan Briquemont
918 1 Iwan Briquemont
803 1 Hussaina Begum Nandyala
919 1 Hussaina Begum Nandyala
804 1 Gordon Ball
920 1 Gordon Ball
805 1 Gabriel Simonetto
921 1 Gabriel Simonetto
806 1 Frank Tobia
922 1 Frank Tobia
807 1 Erik
923 1 Erik
808 1 Elliott Sales de Andrade
924 1 Elliott Sales de Andrade
809 1 Daniel Hahler
925 1 Daniel Hahler
810 1 Dan Green-Leipciger
926 1 Dan Green-Leipciger
811 1 Dan Green
927 1 Dan Green
812 1 Damian Yurzola
928 1 Damian Yurzola
813 1 Coon, Ethan T
929 1 Coon, Ethan T
814 1 Carol Willing
930 1 Carol Willing
815 1 Brian Lee
931 1 Brian Lee
816 1 Brendan Gerrity
932 1 Brendan Gerrity
817 1 Blake Griffin
933 1 Blake Griffin
818 1 Bastian Ebeling
934 1 Bastian Ebeling
819 1 Bartosz Telenczuk
935 1 Bartosz Telenczuk
820 1 Ankitsingh6299
936 1 Ankitsingh6299
821 1 Andrew Port
937 1 Andrew Port
822 1 Andrew J. Hesford
938 1 Andrew J. Hesford
823 1 Albert Zhang
939 1 Albert Zhang
824 1 Adam Johnson
940 1 Adam Johnson
825
941
826 This does not, of course, represent non-code contributions, for which we are also grateful.
942 This does not, of course, represent non-code contributions, for which we are also grateful.
827
943
828
944
829 API Changes using Frappuccino
945 API Changes using Frappuccino
830 -----------------------------
946 -----------------------------
831
947
832 This is an experimental exhaustive API difference using `Frappuccino <https://pypi.org/project/frappuccino/>`_
948 This is an experimental exhaustive API difference using `Frappuccino <https://pypi.org/project/frappuccino/>`_
833
949
834
950
835 The following items are new in IPython 8.0 ::
951 The following items are new in IPython 8.0 ::
836
952
837 + IPython.core.async_helpers.get_asyncio_loop()
953 + IPython.core.async_helpers.get_asyncio_loop()
838 + IPython.core.completer.Dict
954 + IPython.core.completer.Dict
839 + IPython.core.completer.Pattern
955 + IPython.core.completer.Pattern
840 + IPython.core.completer.Sequence
956 + IPython.core.completer.Sequence
841 + IPython.core.completer.__skip_doctest__
957 + IPython.core.completer.__skip_doctest__
842 + IPython.core.debugger.Pdb.precmd(self, line)
958 + IPython.core.debugger.Pdb.precmd(self, line)
843 + IPython.core.debugger.__skip_doctest__
959 + IPython.core.debugger.__skip_doctest__
844 + IPython.core.display.__getattr__(name)
960 + IPython.core.display.__getattr__(name)
845 + IPython.core.display.warn
961 + IPython.core.display.warn
846 + IPython.core.display_functions
962 + IPython.core.display_functions
847 + IPython.core.display_functions.DisplayHandle
963 + IPython.core.display_functions.DisplayHandle
848 + IPython.core.display_functions.DisplayHandle.display(self, obj, **kwargs)
964 + IPython.core.display_functions.DisplayHandle.display(self, obj, **kwargs)
849 + IPython.core.display_functions.DisplayHandle.update(self, obj, **kwargs)
965 + IPython.core.display_functions.DisplayHandle.update(self, obj, **kwargs)
850 + IPython.core.display_functions.__all__
966 + IPython.core.display_functions.__all__
851 + IPython.core.display_functions.__builtins__
967 + IPython.core.display_functions.__builtins__
852 + IPython.core.display_functions.__cached__
968 + IPython.core.display_functions.__cached__
853 + IPython.core.display_functions.__doc__
969 + IPython.core.display_functions.__doc__
854 + IPython.core.display_functions.__file__
970 + IPython.core.display_functions.__file__
855 + IPython.core.display_functions.__loader__
971 + IPython.core.display_functions.__loader__
856 + IPython.core.display_functions.__name__
972 + IPython.core.display_functions.__name__
857 + IPython.core.display_functions.__package__
973 + IPython.core.display_functions.__package__
858 + IPython.core.display_functions.__spec__
974 + IPython.core.display_functions.__spec__
859 + IPython.core.display_functions.b2a_hex
975 + IPython.core.display_functions.b2a_hex
860 + IPython.core.display_functions.clear_output(wait=False)
976 + IPython.core.display_functions.clear_output(wait=False)
861 + IPython.core.display_functions.display(*objs, include='None', exclude='None', metadata='None', transient='None', display_id='None', raw=False, clear=False, **kwargs)
977 + IPython.core.display_functions.display(*objs, include='None', exclude='None', metadata='None', transient='None', display_id='None', raw=False, clear=False, **kwargs)
862 + IPython.core.display_functions.publish_display_data(data, metadata='None', source='<deprecated>', *, transient='None', **kwargs)
978 + IPython.core.display_functions.publish_display_data(data, metadata='None', source='<deprecated>', *, transient='None', **kwargs)
863 + IPython.core.display_functions.update_display(obj, *, display_id, **kwargs)
979 + IPython.core.display_functions.update_display(obj, *, display_id, **kwargs)
864 + IPython.core.extensions.BUILTINS_EXTS
980 + IPython.core.extensions.BUILTINS_EXTS
865 + IPython.core.inputtransformer2.has_sunken_brackets(tokens)
981 + IPython.core.inputtransformer2.has_sunken_brackets(tokens)
866 + IPython.core.interactiveshell.Callable
982 + IPython.core.interactiveshell.Callable
867 + IPython.core.interactiveshell.__annotations__
983 + IPython.core.interactiveshell.__annotations__
868 + IPython.core.ultratb.List
984 + IPython.core.ultratb.List
869 + IPython.core.ultratb.Tuple
985 + IPython.core.ultratb.Tuple
870 + IPython.lib.pretty.CallExpression
986 + IPython.lib.pretty.CallExpression
871 + IPython.lib.pretty.CallExpression.factory(name)
987 + IPython.lib.pretty.CallExpression.factory(name)
872 + IPython.lib.pretty.RawStringLiteral
988 + IPython.lib.pretty.RawStringLiteral
873 + IPython.lib.pretty.RawText
989 + IPython.lib.pretty.RawText
874 + IPython.terminal.debugger.TerminalPdb.do_interact(self, arg)
990 + IPython.terminal.debugger.TerminalPdb.do_interact(self, arg)
875 + IPython.terminal.embed.Set
991 + IPython.terminal.embed.Set
876
992
877 The following items have been removed (or moved to superclass)::
993 The following items have been removed (or moved to superclass)::
878
994
879 - IPython.core.application.BaseIPythonApplication.initialize_subcommand
995 - IPython.core.application.BaseIPythonApplication.initialize_subcommand
880 - IPython.core.completer.Sentinel
996 - IPython.core.completer.Sentinel
881 - IPython.core.completer.skip_doctest
997 - IPython.core.completer.skip_doctest
882 - IPython.core.debugger.Tracer
998 - IPython.core.debugger.Tracer
883 - IPython.core.display.DisplayHandle
999 - IPython.core.display.DisplayHandle
884 - IPython.core.display.DisplayHandle.display
1000 - IPython.core.display.DisplayHandle.display
885 - IPython.core.display.DisplayHandle.update
1001 - IPython.core.display.DisplayHandle.update
886 - IPython.core.display.b2a_hex
1002 - IPython.core.display.b2a_hex
887 - IPython.core.display.clear_output
1003 - IPython.core.display.clear_output
888 - IPython.core.display.display
1004 - IPython.core.display.display
889 - IPython.core.display.publish_display_data
1005 - IPython.core.display.publish_display_data
890 - IPython.core.display.update_display
1006 - IPython.core.display.update_display
891 - IPython.core.excolors.Deprec
1007 - IPython.core.excolors.Deprec
892 - IPython.core.excolors.ExceptionColors
1008 - IPython.core.excolors.ExceptionColors
893 - IPython.core.history.warn
1009 - IPython.core.history.warn
894 - IPython.core.hooks.late_startup_hook
1010 - IPython.core.hooks.late_startup_hook
895 - IPython.core.hooks.pre_run_code_hook
1011 - IPython.core.hooks.pre_run_code_hook
896 - IPython.core.hooks.shutdown_hook
1012 - IPython.core.hooks.shutdown_hook
897 - IPython.core.interactiveshell.InteractiveShell.init_deprecation_warnings
1013 - IPython.core.interactiveshell.InteractiveShell.init_deprecation_warnings
898 - IPython.core.interactiveshell.InteractiveShell.init_readline
1014 - IPython.core.interactiveshell.InteractiveShell.init_readline
899 - IPython.core.interactiveshell.InteractiveShell.write
1015 - IPython.core.interactiveshell.InteractiveShell.write
900 - IPython.core.interactiveshell.InteractiveShell.write_err
1016 - IPython.core.interactiveshell.InteractiveShell.write_err
901 - IPython.core.interactiveshell.get_default_colors
1017 - IPython.core.interactiveshell.get_default_colors
902 - IPython.core.interactiveshell.removed_co_newlocals
1018 - IPython.core.interactiveshell.removed_co_newlocals
903 - IPython.core.magics.execution.ExecutionMagics.profile_missing_notice
1019 - IPython.core.magics.execution.ExecutionMagics.profile_missing_notice
904 - IPython.core.magics.script.PIPE
1020 - IPython.core.magics.script.PIPE
905 - IPython.core.prefilter.PrefilterManager.init_transformers
1021 - IPython.core.prefilter.PrefilterManager.init_transformers
906 - IPython.core.release.classifiers
1022 - IPython.core.release.classifiers
907 - IPython.core.release.description
1023 - IPython.core.release.description
908 - IPython.core.release.keywords
1024 - IPython.core.release.keywords
909 - IPython.core.release.long_description
1025 - IPython.core.release.long_description
910 - IPython.core.release.name
1026 - IPython.core.release.name
911 - IPython.core.release.platforms
1027 - IPython.core.release.platforms
912 - IPython.core.release.url
1028 - IPython.core.release.url
913 - IPython.core.ultratb.VerboseTB.format_records
1029 - IPython.core.ultratb.VerboseTB.format_records
914 - IPython.core.ultratb.find_recursion
1030 - IPython.core.ultratb.find_recursion
915 - IPython.core.ultratb.findsource
1031 - IPython.core.ultratb.findsource
916 - IPython.core.ultratb.fix_frame_records_filenames
1032 - IPython.core.ultratb.fix_frame_records_filenames
917 - IPython.core.ultratb.inspect_error
1033 - IPython.core.ultratb.inspect_error
918 - IPython.core.ultratb.is_recursion_error
1034 - IPython.core.ultratb.is_recursion_error
919 - IPython.core.ultratb.with_patch_inspect
1035 - IPython.core.ultratb.with_patch_inspect
920 - IPython.external.__all__
1036 - IPython.external.__all__
921 - IPython.external.__builtins__
1037 - IPython.external.__builtins__
922 - IPython.external.__cached__
1038 - IPython.external.__cached__
923 - IPython.external.__doc__
1039 - IPython.external.__doc__
924 - IPython.external.__file__
1040 - IPython.external.__file__
925 - IPython.external.__loader__
1041 - IPython.external.__loader__
926 - IPython.external.__name__
1042 - IPython.external.__name__
927 - IPython.external.__package__
1043 - IPython.external.__package__
928 - IPython.external.__path__
1044 - IPython.external.__path__
929 - IPython.external.__spec__
1045 - IPython.external.__spec__
930 - IPython.kernel.KernelConnectionInfo
1046 - IPython.kernel.KernelConnectionInfo
931 - IPython.kernel.__builtins__
1047 - IPython.kernel.__builtins__
932 - IPython.kernel.__cached__
1048 - IPython.kernel.__cached__
933 - IPython.kernel.__warningregistry__
1049 - IPython.kernel.__warningregistry__
934 - IPython.kernel.pkg
1050 - IPython.kernel.pkg
935 - IPython.kernel.protocol_version
1051 - IPython.kernel.protocol_version
936 - IPython.kernel.protocol_version_info
1052 - IPython.kernel.protocol_version_info
937 - IPython.kernel.src
1053 - IPython.kernel.src
938 - IPython.kernel.version_info
1054 - IPython.kernel.version_info
939 - IPython.kernel.warn
1055 - IPython.kernel.warn
940 - IPython.lib.backgroundjobs
1056 - IPython.lib.backgroundjobs
941 - IPython.lib.backgroundjobs.BackgroundJobBase
1057 - IPython.lib.backgroundjobs.BackgroundJobBase
942 - IPython.lib.backgroundjobs.BackgroundJobBase.run
1058 - IPython.lib.backgroundjobs.BackgroundJobBase.run
943 - IPython.lib.backgroundjobs.BackgroundJobBase.traceback
1059 - IPython.lib.backgroundjobs.BackgroundJobBase.traceback
944 - IPython.lib.backgroundjobs.BackgroundJobExpr
1060 - IPython.lib.backgroundjobs.BackgroundJobExpr
945 - IPython.lib.backgroundjobs.BackgroundJobExpr.call
1061 - IPython.lib.backgroundjobs.BackgroundJobExpr.call
946 - IPython.lib.backgroundjobs.BackgroundJobFunc
1062 - IPython.lib.backgroundjobs.BackgroundJobFunc
947 - IPython.lib.backgroundjobs.BackgroundJobFunc.call
1063 - IPython.lib.backgroundjobs.BackgroundJobFunc.call
948 - IPython.lib.backgroundjobs.BackgroundJobManager
1064 - IPython.lib.backgroundjobs.BackgroundJobManager
949 - IPython.lib.backgroundjobs.BackgroundJobManager.flush
1065 - IPython.lib.backgroundjobs.BackgroundJobManager.flush
950 - IPython.lib.backgroundjobs.BackgroundJobManager.new
1066 - IPython.lib.backgroundjobs.BackgroundJobManager.new
951 - IPython.lib.backgroundjobs.BackgroundJobManager.remove
1067 - IPython.lib.backgroundjobs.BackgroundJobManager.remove
952 - IPython.lib.backgroundjobs.BackgroundJobManager.result
1068 - IPython.lib.backgroundjobs.BackgroundJobManager.result
953 - IPython.lib.backgroundjobs.BackgroundJobManager.status
1069 - IPython.lib.backgroundjobs.BackgroundJobManager.status
954 - IPython.lib.backgroundjobs.BackgroundJobManager.traceback
1070 - IPython.lib.backgroundjobs.BackgroundJobManager.traceback
955 - IPython.lib.backgroundjobs.__builtins__
1071 - IPython.lib.backgroundjobs.__builtins__
956 - IPython.lib.backgroundjobs.__cached__
1072 - IPython.lib.backgroundjobs.__cached__
957 - IPython.lib.backgroundjobs.__doc__
1073 - IPython.lib.backgroundjobs.__doc__
958 - IPython.lib.backgroundjobs.__file__
1074 - IPython.lib.backgroundjobs.__file__
959 - IPython.lib.backgroundjobs.__loader__
1075 - IPython.lib.backgroundjobs.__loader__
960 - IPython.lib.backgroundjobs.__name__
1076 - IPython.lib.backgroundjobs.__name__
961 - IPython.lib.backgroundjobs.__package__
1077 - IPython.lib.backgroundjobs.__package__
962 - IPython.lib.backgroundjobs.__spec__
1078 - IPython.lib.backgroundjobs.__spec__
963 - IPython.lib.kernel.__builtins__
1079 - IPython.lib.kernel.__builtins__
964 - IPython.lib.kernel.__cached__
1080 - IPython.lib.kernel.__cached__
965 - IPython.lib.kernel.__doc__
1081 - IPython.lib.kernel.__doc__
966 - IPython.lib.kernel.__file__
1082 - IPython.lib.kernel.__file__
967 - IPython.lib.kernel.__loader__
1083 - IPython.lib.kernel.__loader__
968 - IPython.lib.kernel.__name__
1084 - IPython.lib.kernel.__name__
969 - IPython.lib.kernel.__package__
1085 - IPython.lib.kernel.__package__
970 - IPython.lib.kernel.__spec__
1086 - IPython.lib.kernel.__spec__
971 - IPython.lib.kernel.__warningregistry__
1087 - IPython.lib.kernel.__warningregistry__
972 - IPython.paths.fs_encoding
1088 - IPython.paths.fs_encoding
973 - IPython.terminal.debugger.DEFAULT_BUFFER
1089 - IPython.terminal.debugger.DEFAULT_BUFFER
974 - IPython.terminal.debugger.cursor_in_leading_ws
1090 - IPython.terminal.debugger.cursor_in_leading_ws
975 - IPython.terminal.debugger.emacs_insert_mode
1091 - IPython.terminal.debugger.emacs_insert_mode
976 - IPython.terminal.debugger.has_selection
1092 - IPython.terminal.debugger.has_selection
977 - IPython.terminal.debugger.vi_insert_mode
1093 - IPython.terminal.debugger.vi_insert_mode
978 - IPython.terminal.interactiveshell.DISPLAY_BANNER_DEPRECATED
1094 - IPython.terminal.interactiveshell.DISPLAY_BANNER_DEPRECATED
979 - IPython.terminal.ipapp.TerminalIPythonApp.parse_command_line
1095 - IPython.terminal.ipapp.TerminalIPythonApp.parse_command_line
980 - IPython.testing.test
1096 - IPython.testing.test
981 - IPython.utils.contexts.NoOpContext
1097 - IPython.utils.contexts.NoOpContext
982 - IPython.utils.io.IOStream
1098 - IPython.utils.io.IOStream
983 - IPython.utils.io.IOStream.close
1099 - IPython.utils.io.IOStream.close
984 - IPython.utils.io.IOStream.write
1100 - IPython.utils.io.IOStream.write
985 - IPython.utils.io.IOStream.writelines
1101 - IPython.utils.io.IOStream.writelines
986 - IPython.utils.io.__warningregistry__
1102 - IPython.utils.io.__warningregistry__
987 - IPython.utils.io.atomic_writing
1103 - IPython.utils.io.atomic_writing
988 - IPython.utils.io.stderr
1104 - IPython.utils.io.stderr
989 - IPython.utils.io.stdin
1105 - IPython.utils.io.stdin
990 - IPython.utils.io.stdout
1106 - IPython.utils.io.stdout
991 - IPython.utils.io.unicode_std_stream
1107 - IPython.utils.io.unicode_std_stream
992 - IPython.utils.path.get_ipython_cache_dir
1108 - IPython.utils.path.get_ipython_cache_dir
993 - IPython.utils.path.get_ipython_dir
1109 - IPython.utils.path.get_ipython_dir
994 - IPython.utils.path.get_ipython_module_path
1110 - IPython.utils.path.get_ipython_module_path
995 - IPython.utils.path.get_ipython_package_dir
1111 - IPython.utils.path.get_ipython_package_dir
996 - IPython.utils.path.locate_profile
1112 - IPython.utils.path.locate_profile
997 - IPython.utils.path.unquote_filename
1113 - IPython.utils.path.unquote_filename
998 - IPython.utils.py3compat.PY2
1114 - IPython.utils.py3compat.PY2
999 - IPython.utils.py3compat.PY3
1115 - IPython.utils.py3compat.PY3
1000 - IPython.utils.py3compat.buffer_to_bytes
1116 - IPython.utils.py3compat.buffer_to_bytes
1001 - IPython.utils.py3compat.builtin_mod_name
1117 - IPython.utils.py3compat.builtin_mod_name
1002 - IPython.utils.py3compat.cast_bytes
1118 - IPython.utils.py3compat.cast_bytes
1003 - IPython.utils.py3compat.getcwd
1119 - IPython.utils.py3compat.getcwd
1004 - IPython.utils.py3compat.isidentifier
1120 - IPython.utils.py3compat.isidentifier
1005 - IPython.utils.py3compat.u_format
1121 - IPython.utils.py3compat.u_format
1006
1122
1007 The following signatures differ between 7.x and 8.0::
1123 The following signatures differ between 7.x and 8.0::
1008
1124
1009 - IPython.core.completer.IPCompleter.unicode_name_matches(self, text)
1125 - IPython.core.completer.IPCompleter.unicode_name_matches(self, text)
1010 + IPython.core.completer.IPCompleter.unicode_name_matches(text)
1126 + IPython.core.completer.IPCompleter.unicode_name_matches(text)
1011
1127
1012 - IPython.core.completer.match_dict_keys(keys, prefix, delims)
1128 - IPython.core.completer.match_dict_keys(keys, prefix, delims)
1013 + IPython.core.completer.match_dict_keys(keys, prefix, delims, extra_prefix='None')
1129 + IPython.core.completer.match_dict_keys(keys, prefix, delims, extra_prefix='None')
1014
1130
1015 - IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0)
1131 - IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0)
1016 + IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0, omit_sections='()')
1132 + IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0, omit_sections='()')
1017
1133
1018 - IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None', _warn_deprecated=True)
1134 - IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None', _warn_deprecated=True)
1019 + IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None')
1135 + IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None')
1020
1136
1021 - IPython.core.oinspect.Inspector.info(self, obj, oname='', formatter='None', info='None', detail_level=0)
1137 - IPython.core.oinspect.Inspector.info(self, obj, oname='', formatter='None', info='None', detail_level=0)
1022 + IPython.core.oinspect.Inspector.info(self, obj, oname='', info='None', detail_level=0)
1138 + IPython.core.oinspect.Inspector.info(self, obj, oname='', info='None', detail_level=0)
1023
1139
1024 - IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True)
1140 - IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True)
1025 + IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True, omit_sections='()')
1141 + IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True, omit_sections='()')
1026
1142
1027 - IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path='None', overwrite=False)
1143 - IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path='None', overwrite=False)
1028 + IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path, overwrite=False)
1144 + IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path, overwrite=False)
1029
1145
1030 - IPython.core.ultratb.VerboseTB.format_record(self, frame, file, lnum, func, lines, index)
1146 - IPython.core.ultratb.VerboseTB.format_record(self, frame, file, lnum, func, lines, index)
1031 + IPython.core.ultratb.VerboseTB.format_record(self, frame_info)
1147 + IPython.core.ultratb.VerboseTB.format_record(self, frame_info)
1032
1148
1033 - IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, display_banner='None', global_ns='None', compile_flags='None')
1149 - IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, display_banner='None', global_ns='None', compile_flags='None')
1034 + IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, compile_flags='None')
1150 + IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, compile_flags='None')
1035
1151
1036 - IPython.terminal.embed.embed(**kwargs)
1152 - IPython.terminal.embed.embed(**kwargs)
1037 + IPython.terminal.embed.embed(*, header='', compile_flags='None', **kwargs)
1153 + IPython.terminal.embed.embed(*, header='', compile_flags='None', **kwargs)
1038
1154
1039 - IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self, display_banner='<object object at 0xffffff>')
1155 - IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self, display_banner='<object object at 0xffffff>')
1040 + IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self)
1156 + IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self)
1041
1157
1042 - IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self, display_banner='<object object at 0xffffff>')
1158 - IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self, display_banner='<object object at 0xffffff>')
1043 + IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self)
1159 + IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self)
1044
1160
1045 - IPython.utils.path.get_py_filename(name, force_win32='None')
1161 - IPython.utils.path.get_py_filename(name, force_win32='None')
1046 + IPython.utils.path.get_py_filename(name)
1162 + IPython.utils.path.get_py_filename(name)
1047
1163
1048 The following are new attributes (that might be inherited)::
1164 The following are new attributes (that might be inherited)::
1049
1165
1050 + IPython.core.completer.IPCompleter.unicode_names
1166 + IPython.core.completer.IPCompleter.unicode_names
1051 + IPython.core.debugger.InterruptiblePdb.precmd
1167 + IPython.core.debugger.InterruptiblePdb.precmd
1052 + IPython.core.debugger.Pdb.precmd
1168 + IPython.core.debugger.Pdb.precmd
1053 + IPython.core.ultratb.AutoFormattedTB.has_colors
1169 + IPython.core.ultratb.AutoFormattedTB.has_colors
1054 + IPython.core.ultratb.ColorTB.has_colors
1170 + IPython.core.ultratb.ColorTB.has_colors
1055 + IPython.core.ultratb.FormattedTB.has_colors
1171 + IPython.core.ultratb.FormattedTB.has_colors
1056 + IPython.core.ultratb.ListTB.has_colors
1172 + IPython.core.ultratb.ListTB.has_colors
1057 + IPython.core.ultratb.SyntaxTB.has_colors
1173 + IPython.core.ultratb.SyntaxTB.has_colors
1058 + IPython.core.ultratb.TBTools.has_colors
1174 + IPython.core.ultratb.TBTools.has_colors
1059 + IPython.core.ultratb.VerboseTB.has_colors
1175 + IPython.core.ultratb.VerboseTB.has_colors
1060 + IPython.terminal.debugger.TerminalPdb.do_interact
1176 + IPython.terminal.debugger.TerminalPdb.do_interact
1061 + IPython.terminal.debugger.TerminalPdb.precmd
1177 + IPython.terminal.debugger.TerminalPdb.precmd
1062
1178
1063 The following attribute/methods have been removed::
1179 The following attribute/methods have been removed::
1064
1180
1065 - IPython.core.application.BaseIPythonApplication.deprecated_subcommands
1181 - IPython.core.application.BaseIPythonApplication.deprecated_subcommands
1066 - IPython.core.ultratb.AutoFormattedTB.format_records
1182 - IPython.core.ultratb.AutoFormattedTB.format_records
1067 - IPython.core.ultratb.ColorTB.format_records
1183 - IPython.core.ultratb.ColorTB.format_records
1068 - IPython.core.ultratb.FormattedTB.format_records
1184 - IPython.core.ultratb.FormattedTB.format_records
1069 - IPython.terminal.embed.InteractiveShellEmbed.init_deprecation_warnings
1185 - IPython.terminal.embed.InteractiveShellEmbed.init_deprecation_warnings
1070 - IPython.terminal.embed.InteractiveShellEmbed.init_readline
1186 - IPython.terminal.embed.InteractiveShellEmbed.init_readline
1071 - IPython.terminal.embed.InteractiveShellEmbed.write
1187 - IPython.terminal.embed.InteractiveShellEmbed.write
1072 - IPython.terminal.embed.InteractiveShellEmbed.write_err
1188 - IPython.terminal.embed.InteractiveShellEmbed.write_err
1073 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_deprecation_warnings
1189 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_deprecation_warnings
1074 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_readline
1190 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_readline
1075 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write
1191 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write
1076 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write_err
1192 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write_err
1077 - IPython.terminal.ipapp.LocateIPythonApp.deprecated_subcommands
1193 - IPython.terminal.ipapp.LocateIPythonApp.deprecated_subcommands
1078 - IPython.terminal.ipapp.LocateIPythonApp.initialize_subcommand
1194 - IPython.terminal.ipapp.LocateIPythonApp.initialize_subcommand
1079 - IPython.terminal.ipapp.TerminalIPythonApp.deprecated_subcommands
1195 - IPython.terminal.ipapp.TerminalIPythonApp.deprecated_subcommands
1080 - IPython.terminal.ipapp.TerminalIPythonApp.initialize_subcommand
1196 - IPython.terminal.ipapp.TerminalIPythonApp.initialize_subcommand
@@ -1,116 +1,115 b''
1 [metadata]
1 [metadata]
2 name = ipython
2 name = ipython
3 version = attr: IPython.core.release.__version__
3 version = attr: IPython.core.release.__version__
4 url = https://ipython.org
4 url = https://ipython.org
5 description = IPython: Productive Interactive Computing
5 description = IPython: Productive Interactive Computing
6 long_description_content_type = text/x-rst
6 long_description_content_type = text/x-rst
7 long_description = file: long_description.rst
7 long_description = file: long_description.rst
8 license_file = LICENSE
8 license_file = LICENSE
9 project_urls =
9 project_urls =
10 Documentation = https://ipython.readthedocs.io/
10 Documentation = https://ipython.readthedocs.io/
11 Funding = https://numfocus.org/
11 Funding = https://numfocus.org/
12 Source = https://github.com/ipython/ipython
12 Source = https://github.com/ipython/ipython
13 Tracker = https://github.com/ipython/ipython/issues
13 Tracker = https://github.com/ipython/ipython/issues
14 keywords = Interactive, Interpreter, Shell, Embedding
14 keywords = Interactive, Interpreter, Shell, Embedding
15 platforms = Linux, Mac OSX, Windows
15 platforms = Linux, Mac OSX, Windows
16 classifiers =
16 classifiers =
17 Framework :: IPython
17 Framework :: IPython
18 Framework :: Jupyter
18 Framework :: Jupyter
19 Intended Audience :: Developers
19 Intended Audience :: Developers
20 Intended Audience :: Science/Research
20 Intended Audience :: Science/Research
21 License :: OSI Approved :: BSD License
21 License :: OSI Approved :: BSD License
22 Programming Language :: Python
22 Programming Language :: Python
23 Programming Language :: Python :: 3
23 Programming Language :: Python :: 3
24 Programming Language :: Python :: 3 :: Only
24 Programming Language :: Python :: 3 :: Only
25 Topic :: System :: Shells
25 Topic :: System :: Shells
26
26
27 [options]
27 [options]
28 packages = find:
28 packages = find:
29 python_requires = >=3.8
29 python_requires = >=3.8
30 zip_safe = False
30 zip_safe = False
31 install_requires =
31 install_requires =
32 appnope; sys_platform == "darwin"
32 appnope; sys_platform == "darwin"
33 backcall
33 backcall
34 colorama; sys_platform == "win32"
34 colorama; sys_platform == "win32"
35 decorator
35 decorator
36 jedi>=0.16
36 jedi>=0.16
37 matplotlib-inline
37 matplotlib-inline
38 pexpect>4.3; sys_platform != "win32"
38 pexpect>4.3; sys_platform != "win32"
39 pickleshare
39 pickleshare
40 prompt_toolkit>3.0.1,<3.1.0
40 prompt_toolkit>3.0.1,<3.1.0
41 pygments>=2.4.0
41 pygments>=2.4.0
42 setuptools>=18.5
43 stack_data
42 stack_data
44 traitlets>=5
43 traitlets>=5
45
44
46 [options.extras_require]
45 [options.extras_require]
47 black =
46 black =
48 black
47 black
49 doc =
48 doc =
50 Sphinx>=1.3
49 Sphinx>=1.3
51 kernel =
50 kernel =
52 ipykernel
51 ipykernel
53 nbconvert =
52 nbconvert =
54 nbconvert
53 nbconvert
55 nbformat =
54 nbformat =
56 nbformat
55 nbformat
57 notebook =
56 notebook =
58 ipywidgets
57 ipywidgets
59 notebook
58 notebook
60 parallel =
59 parallel =
61 ipyparallel
60 ipyparallel
62 qtconsole =
61 qtconsole =
63 qtconsole
62 qtconsole
64 terminal =
63 terminal =
65 test =
64 test =
66 pytest<7.1
65 pytest<7.1
67 pytest-asyncio
66 pytest-asyncio
68 testpath
67 testpath
69 test_extra =
68 test_extra =
70 %(test)s
69 %(test)s
71 curio
70 curio
72 matplotlib!=3.2.0
71 matplotlib!=3.2.0
73 nbformat
72 nbformat
74 numpy>=1.19
73 numpy>=1.19
75 pandas
74 pandas
76 trio
75 trio
77 typing_extensions
76 typing_extensions
78 all =
77 all =
79 %(black)s
78 %(black)s
80 %(doc)s
79 %(doc)s
81 %(kernel)s
80 %(kernel)s
82 %(nbconvert)s
81 %(nbconvert)s
83 %(nbformat)s
82 %(nbformat)s
84 %(notebook)s
83 %(notebook)s
85 %(parallel)s
84 %(parallel)s
86 %(qtconsole)s
85 %(qtconsole)s
87 %(terminal)s
86 %(terminal)s
88 %(test_extra)s
87 %(test_extra)s
89 %(test)s
88 %(test)s
90
89
91 [options.packages.find]
90 [options.packages.find]
92 exclude =
91 exclude =
93 setupext
92 setupext
94
93
95 [options.package_data]
94 [options.package_data]
96 IPython.core = profile/README*
95 IPython.core = profile/README*
97 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
96 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
98 IPython.lib.tests = *.wav
97 IPython.lib.tests = *.wav
99 IPython.testing.plugin = *.txt
98 IPython.testing.plugin = *.txt
100
99
101 [options.entry_points]
100 [options.entry_points]
102 console_scripts =
101 console_scripts =
103 ipython = IPython:start_ipython
102 ipython = IPython:start_ipython
104 ipython3 = IPython:start_ipython
103 ipython3 = IPython:start_ipython
105 pygments.lexers =
104 pygments.lexers =
106 ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer
105 ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer
107 ipython = IPython.lib.lexers:IPythonLexer
106 ipython = IPython.lib.lexers:IPythonLexer
108 ipython3 = IPython.lib.lexers:IPython3Lexer
107 ipython3 = IPython.lib.lexers:IPython3Lexer
109
108
110 [velin]
109 [velin]
111 ignore_patterns =
110 ignore_patterns =
112 IPython/core/tests
111 IPython/core/tests
113 IPython/testing
112 IPython/testing
114
113
115 [tool.black]
114 [tool.black]
116 exclude = 'timing\.py'
115 exclude = 'timing\.py'
@@ -1,230 +1,230 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Simple tools to query github.com and gather stats about issues.
2 """Simple tools to query github.com and gather stats about issues.
3
3
4 To generate a report for IPython 2.0, run:
4 To generate a report for IPython 2.0, run:
5
5
6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Imports
9 # Imports
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12
12
13 import sys
13 import sys
14
14
15 from argparse import ArgumentParser
15 from argparse import ArgumentParser
16 from datetime import datetime, timedelta
16 from datetime import datetime, timedelta
17 from subprocess import check_output
17 from subprocess import check_output
18
18
19 from gh_api import (
19 from gh_api import (
20 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
20 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
21 get_milestone_id, get_issues_list, get_authors,
21 get_milestone_id, get_issues_list, get_authors,
22 )
22 )
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Globals
24 # Globals
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
27 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
28 PER_PAGE = 100
28 PER_PAGE = 100
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Functions
31 # Functions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 def round_hour(dt):
34 def round_hour(dt):
35 return dt.replace(minute=0,second=0,microsecond=0)
35 return dt.replace(minute=0,second=0,microsecond=0)
36
36
37 def _parse_datetime(s):
37 def _parse_datetime(s):
38 """Parse dates in the format returned by the Github API."""
38 """Parse dates in the format returned by the Github API."""
39 if s:
39 if s:
40 return datetime.strptime(s, ISO8601)
40 return datetime.strptime(s, ISO8601)
41 else:
41 else:
42 return datetime.fromtimestamp(0)
42 return datetime.fromtimestamp(0)
43
43
44 def issues2dict(issues):
44 def issues2dict(issues):
45 """Convert a list of issues to a dict, keyed by issue number."""
45 """Convert a list of issues to a dict, keyed by issue number."""
46 idict = {}
46 idict = {}
47 for i in issues:
47 for i in issues:
48 idict[i['number']] = i
48 idict[i['number']] = i
49 return idict
49 return idict
50
50
51 def split_pulls(all_issues, project="ipython/ipython"):
51 def split_pulls(all_issues, project="ipython/ipython"):
52 """split a list of closed issues into non-PR Issues and Pull Requests"""
52 """split a list of closed issues into non-PR Issues and Pull Requests"""
53 pulls = []
53 pulls = []
54 issues = []
54 issues = []
55 for i in all_issues:
55 for i in all_issues:
56 if is_pull_request(i):
56 if is_pull_request(i):
57 pull = get_pull_request(project, i['number'], auth=True)
57 pull = get_pull_request(project, i['number'], auth=True)
58 pulls.append(pull)
58 pulls.append(pull)
59 else:
59 else:
60 issues.append(i)
60 issues.append(i)
61 return issues, pulls
61 return issues, pulls
62
62
63
63
64 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
64 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
65 """Get all issues closed since a particular point in time. period
65 """Get all issues closed since a particular point in time. period
66 can either be a datetime object, or a timedelta object. In the
66 can either be a datetime object, or a timedelta object. In the
67 latter case, it is used as a time before the present.
67 latter case, it is used as a time before the present.
68 """
68 """
69
69
70 which = 'pulls' if pulls else 'issues'
70 which = 'pulls' if pulls else 'issues'
71
71
72 if isinstance(period, timedelta):
72 if isinstance(period, timedelta):
73 since = round_hour(datetime.utcnow() - period)
73 since = round_hour(datetime.utcnow() - period)
74 else:
74 else:
75 since = period
75 since = period
76 url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, since.strftime(ISO8601), PER_PAGE)
76 url = "https://api.github.com/repos/%s/%s?state=closed&sort=updated&since=%s&per_page=%i" % (project, which, since.strftime(ISO8601), PER_PAGE)
77 allclosed = get_paged_request(url, headers=make_auth_header())
77 allclosed = get_paged_request(url, headers=make_auth_header())
78
78
79 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
79 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
80 if pulls:
80 if pulls:
81 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
81 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
82 # filter out PRs not against main (backports)
82 # filter out PRs not against main (backports)
83 filtered = [ i for i in filtered if i['base']['ref'] == 'main' ]
83 filtered = [i for i in filtered if i["base"]["ref"] == "main"]
84 else:
84 else:
85 filtered = [ i for i in filtered if not is_pull_request(i) ]
85 filtered = [ i for i in filtered if not is_pull_request(i) ]
86
86
87 return filtered
87 return filtered
88
88
89
89
90 def sorted_by_field(issues, field='closed_at', reverse=False):
90 def sorted_by_field(issues, field='closed_at', reverse=False):
91 """Return a list of issues sorted by closing date date."""
91 """Return a list of issues sorted by closing date date."""
92 return sorted(issues, key = lambda i:i[field], reverse=reverse)
92 return sorted(issues, key = lambda i:i[field], reverse=reverse)
93
93
94
94
95 def report(issues, show_urls=False):
95 def report(issues, show_urls=False):
96 """Summary report about a list of issues, printing number and title."""
96 """Summary report about a list of issues, printing number and title."""
97 if show_urls:
97 if show_urls:
98 for i in issues:
98 for i in issues:
99 role = 'ghpull' if 'merged_at' in i else 'ghissue'
99 role = 'ghpull' if 'merged_at' in i else 'ghissue'
100 print(u'* :%s:`%d`: %s' % (role, i['number'],
100 print(u'* :%s:`%d`: %s' % (role, i['number'],
101 i['title'].replace(u'`', u'``')))
101 i['title'].replace(u'`', u'``')))
102 else:
102 else:
103 for i in issues:
103 for i in issues:
104 print(u'* %d: %s' % (i['number'], i['title'].replace(u'`', u'``')))
104 print(u'* %d: %s' % (i['number'], i['title'].replace(u'`', u'``')))
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Main script
107 # Main script
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110 if __name__ == "__main__":
110 if __name__ == "__main__":
111
111
112 print("DEPRECATE: backport_pr.py is deprecated and it is now recommended"
112 print("DEPRECATE: backport_pr.py is deprecated and it is now recommended"
113 "to install `ghpro` from PyPI.", file=sys.stderr)
113 "to install `ghpro` from PyPI.", file=sys.stderr)
114
114
115
115
116 # Whether to add reST urls for all issues in printout.
116 # Whether to add reST urls for all issues in printout.
117 show_urls = True
117 show_urls = True
118
118
119 parser = ArgumentParser()
119 parser = ArgumentParser()
120 parser.add_argument('--since-tag', type=str,
120 parser.add_argument('--since-tag', type=str,
121 help="The git tag to use for the starting point (typically the last major release)."
121 help="The git tag to use for the starting point (typically the last major release)."
122 )
122 )
123 parser.add_argument('--milestone', type=str,
123 parser.add_argument('--milestone', type=str,
124 help="The GitHub milestone to use for filtering issues [optional]."
124 help="The GitHub milestone to use for filtering issues [optional]."
125 )
125 )
126 parser.add_argument('--days', type=int,
126 parser.add_argument('--days', type=int,
127 help="The number of days of data to summarize (use this or --since-tag)."
127 help="The number of days of data to summarize (use this or --since-tag)."
128 )
128 )
129 parser.add_argument('--project', type=str, default="ipython/ipython",
129 parser.add_argument('--project', type=str, default="ipython/ipython",
130 help="The project to summarize."
130 help="The project to summarize."
131 )
131 )
132 parser.add_argument('--links', action='store_true', default=False,
132 parser.add_argument('--links', action='store_true', default=False,
133 help="Include links to all closed Issues and PRs in the output."
133 help="Include links to all closed Issues and PRs in the output."
134 )
134 )
135
135
136 opts = parser.parse_args()
136 opts = parser.parse_args()
137 tag = opts.since_tag
137 tag = opts.since_tag
138
138
139 # set `since` from days or git tag
139 # set `since` from days or git tag
140 if opts.days:
140 if opts.days:
141 since = datetime.utcnow() - timedelta(days=opts.days)
141 since = datetime.utcnow() - timedelta(days=opts.days)
142 else:
142 else:
143 if not tag:
143 if not tag:
144 tag = check_output(['git', 'describe', '--abbrev=0']).strip().decode('utf8')
144 tag = check_output(['git', 'describe', '--abbrev=0']).strip().decode('utf8')
145 cmd = ['git', 'log', '-1', '--format=%ai', tag]
145 cmd = ['git', 'log', '-1', '--format=%ai', tag]
146 tagday, tz = check_output(cmd).strip().decode('utf8').rsplit(' ', 1)
146 tagday, tz = check_output(cmd).strip().decode('utf8').rsplit(' ', 1)
147 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
147 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
148 h = int(tz[1:3])
148 h = int(tz[1:3])
149 m = int(tz[3:])
149 m = int(tz[3:])
150 td = timedelta(hours=h, minutes=m)
150 td = timedelta(hours=h, minutes=m)
151 if tz[0] == '-':
151 if tz[0] == '-':
152 since += td
152 since += td
153 else:
153 else:
154 since -= td
154 since -= td
155
155
156 since = round_hour(since)
156 since = round_hour(since)
157
157
158 milestone = opts.milestone
158 milestone = opts.milestone
159 project = opts.project
159 project = opts.project
160
160
161 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
161 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
162 if milestone:
162 if milestone:
163 milestone_id = get_milestone_id(project=project, milestone=milestone,
163 milestone_id = get_milestone_id(project=project, milestone=milestone,
164 auth=True)
164 auth=True)
165 issues_and_pulls = get_issues_list(project=project,
165 issues_and_pulls = get_issues_list(project=project,
166 milestone=milestone_id,
166 milestone=milestone_id,
167 state='closed',
167 state='closed',
168 auth=True,
168 auth=True,
169 )
169 )
170 issues, pulls = split_pulls(issues_and_pulls, project=project)
170 issues, pulls = split_pulls(issues_and_pulls, project=project)
171 else:
171 else:
172 issues = issues_closed_since(since, project=project, pulls=False)
172 issues = issues_closed_since(since, project=project, pulls=False)
173 pulls = issues_closed_since(since, project=project, pulls=True)
173 pulls = issues_closed_since(since, project=project, pulls=True)
174
174
175 # For regular reports, it's nice to show them in reverse chronological order
175 # For regular reports, it's nice to show them in reverse chronological order
176 issues = sorted_by_field(issues, reverse=True)
176 issues = sorted_by_field(issues, reverse=True)
177 pulls = sorted_by_field(pulls, reverse=True)
177 pulls = sorted_by_field(pulls, reverse=True)
178
178
179 n_issues, n_pulls = map(len, (issues, pulls))
179 n_issues, n_pulls = map(len, (issues, pulls))
180 n_total = n_issues + n_pulls
180 n_total = n_issues + n_pulls
181
181
182 # Print summary report we can directly include into release notes.
182 # Print summary report we can directly include into release notes.
183
183
184 print()
184 print()
185 since_day = since.strftime("%Y/%m/%d")
185 since_day = since.strftime("%Y/%m/%d")
186 today = datetime.today().strftime("%Y/%m/%d")
186 today = datetime.today().strftime("%Y/%m/%d")
187 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
187 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
188 print()
188 print()
189 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
189 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
190 print()
190 print()
191
191
192 ncommits = 0
192 ncommits = 0
193 all_authors = []
193 all_authors = []
194 if tag:
194 if tag:
195 # print git info, in addition to GitHub info:
195 # print git info, in addition to GitHub info:
196 since_tag = tag+'..'
196 since_tag = tag+'..'
197 cmd = ['git', 'log', '--oneline', since_tag]
197 cmd = ['git', 'log', '--oneline', since_tag]
198 ncommits += len(check_output(cmd).splitlines())
198 ncommits += len(check_output(cmd).splitlines())
199
199
200 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
200 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
201 all_authors.extend(check_output(author_cmd).decode('utf-8', 'replace').splitlines())
201 all_authors.extend(check_output(author_cmd).decode('utf-8', 'replace').splitlines())
202
202
203 pr_authors = []
203 pr_authors = []
204 for pr in pulls:
204 for pr in pulls:
205 pr_authors.extend(get_authors(pr))
205 pr_authors.extend(get_authors(pr))
206 ncommits = len(pr_authors) + ncommits - len(pulls)
206 ncommits = len(pr_authors) + ncommits - len(pulls)
207 author_cmd = ['git', 'check-mailmap'] + pr_authors
207 author_cmd = ['git', 'check-mailmap'] + pr_authors
208 with_email = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
208 with_email = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
209 all_authors.extend([ u'* ' + a.split(' <')[0] for a in with_email ])
209 all_authors.extend([ u'* ' + a.split(' <')[0] for a in with_email ])
210 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
210 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
211
211
212 print("We closed %d issues and merged %d pull requests." % (n_issues, n_pulls))
212 print("We closed %d issues and merged %d pull requests." % (n_issues, n_pulls))
213 if milestone:
213 if milestone:
214 print("The full list can be seen `on GitHub <https://github.com/{project}/issues?q=milestone%3A{milestone}>`__".format(project=project,milestone=milestone)
214 print("The full list can be seen `on GitHub <https://github.com/{project}/issues?q=milestone%3A{milestone}>`__".format(project=project,milestone=milestone)
215 )
215 )
216
216
217 print()
217 print()
218 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
218 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
219 print()
219 print()
220 print('\n'.join(unique_authors))
220 print('\n'.join(unique_authors))
221
221
222 if opts.links:
222 if opts.links:
223 print()
223 print()
224 print("GitHub issues and pull requests:")
224 print("GitHub issues and pull requests:")
225 print()
225 print()
226 print('Pull Requests (%d):\n' % n_pulls)
226 print('Pull Requests (%d):\n' % n_pulls)
227 report(pulls, show_urls)
227 report(pulls, show_urls)
228 print()
228 print()
229 print('Issues (%d):\n' % n_issues)
229 print('Issues (%d):\n' % n_issues)
230 report(issues, show_urls)
230 report(issues, show_urls)
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now