##// 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 1 name: Build docs
2 2
3 3 on: [push, pull_request]
4 4
5 5 jobs:
6 6 build:
7 7 runs-on: ubuntu-latest
8 8
9 9 steps:
10 - uses: actions/checkout@v2
11 - name: Set up Python 3.8
12 uses: actions/setup-python@v2
10 - uses: actions/checkout@v3
11 - name: Set up Python
12 uses: actions/setup-python@v4
13 13 with:
14 python-version: 3.8
14 python-version: 3.x
15 15 - name: Install Graphviz
16 16 run: |
17 17 sudo apt-get update
18 18 sudo apt-get install graphviz
19 19 - name: Install Python dependencies
20 20 run: |
21 21 python -m pip install --upgrade pip setuptools coverage rstvalidator
22 22 pip install -r docs/requirements.txt
23 23 - name: Build docs
24 24 run: |
25 25 python -m rstvalidator long_description.rst
26 26 python tools/fixup_whats_new_pr.py
27 27 make -C docs/ html SPHINXOPTS="-W" \
28 28 PYTHON="coverage run -a" \
29 29 SPHINXBUILD="coverage run -a -m sphinx.cmd.build"
30 30 - name: Generate coverage xml
31 31 run: |
32 32 coverage combine `find . -name .coverage\*` && coverage xml
33 33 - name: Upload coverage to Codecov
34 34 uses: codecov/codecov-action@v2
35 35 with:
36 36 name: Docs
@@ -1,50 +1,50 b''
1 1 name: Run Downstream tests
2 2
3 3 on:
4 4 push:
5 5 pull_request:
6 6 # Run weekly on Monday at 1:23 UTC
7 7 schedule:
8 8 - cron: '23 1 * * 1'
9 9 workflow_dispatch:
10 10
11 11
12 12 jobs:
13 13 test:
14 14 runs-on: ${{ matrix.os }}
15 15 strategy:
16 16 matrix:
17 17 os: [ubuntu-latest]
18 18 python-version: ["3.9"]
19 19 include:
20 20 - os: macos-latest
21 21 python-version: "3.9"
22 22
23 23 steps:
24 - uses: actions/checkout@v2
24 - uses: actions/checkout@v3
25 25 - name: Set up Python ${{ matrix.python-version }}
26 uses: actions/setup-python@v2
26 uses: actions/setup-python@v4
27 27 with:
28 28 python-version: ${{ matrix.python-version }}
29 29 - name: Update Python installer
30 30 run: |
31 31 python -m pip install --upgrade pip setuptools wheel
32 32 - name: Install ipykernel
33 33 run: |
34 34 cd ..
35 35 git clone https://github.com/ipython/ipykernel
36 36 cd ipykernel
37 37 pip install -e .[test]
38 38 cd ..
39 39 - name: Install and update Python dependencies
40 40 run: |
41 41 python -m pip install --upgrade -e file://$PWD#egg=ipython[test]
42 42 # we must install IPython after ipykernel to get the right versions.
43 43 python -m pip install --upgrade --upgrade-strategy eager flaky ipyparallel
44 44 python -m pip install --upgrade 'pytest<7'
45 45 - name: pytest
46 46 env:
47 47 COLUMNS: 120
48 48 run: |
49 49 cd ../ipykernel
50 50 pytest
@@ -1,34 +1,34 b''
1 1 name: Run MyPy
2 2
3 3 on:
4 4 push:
5 5 branches: [ main, 7.x]
6 6 pull_request:
7 7 branches: [ main, 7.x]
8 8
9 9 jobs:
10 10 build:
11 11
12 12 runs-on: ubuntu-latest
13 13 strategy:
14 14 matrix:
15 15 python-version: [3.8]
16 16
17 17 steps:
18 - uses: actions/checkout@v2
18 - uses: actions/checkout@v3
19 19 - name: Set up Python ${{ matrix.python-version }}
20 uses: actions/setup-python@v2
20 uses: actions/setup-python@v4
21 21 with:
22 22 python-version: ${{ matrix.python-version }}
23 23 - name: Install dependencies
24 24 run: |
25 25 python -m pip install --upgrade pip
26 26 pip install mypy pyflakes flake8
27 27 - name: Lint with mypy
28 28 run: |
29 29 mypy -p IPython.terminal
30 30 mypy -p IPython.core.magics
31 31 - name: Lint with pyflakes
32 32 run: |
33 33 flake8 IPython/core/magics/script.py
34 34 flake8 IPython/core/magics/packaging.py
@@ -1,40 +1,36 b''
1 1 # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2 2 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 3
4 4 name: Python package
5 5
6 6 on:
7 7 push:
8 8 branches: [ main, 7.x ]
9 9 pull_request:
10 10 branches: [ main, 7.x ]
11 11
12 12 jobs:
13 13 formatting:
14 14
15 15 runs-on: ubuntu-latest
16 16 timeout-minutes: 5
17 strategy:
18 matrix:
19 python-version: [3.8]
20
21 17 steps:
22 - uses: actions/checkout@v2
18 - uses: actions/checkout@v3
23 19 with:
24 20 fetch-depth: 0
25 - name: Set up Python ${{ matrix.python-version }}
26 uses: actions/setup-python@v2
21 - name: Set up Python
22 uses: actions/setup-python@v4
27 23 with:
28 python-version: ${{ matrix.python-version }}
24 python-version: 3.x
29 25 - name: Install dependencies
30 26 run: |
31 27 python -m pip install --upgrade pip
32 28 pip install darker black==21.12b0
33 29 - name: Lint with darker
34 30 run: |
35 31 darker -r 60625f241f298b5039cb2debc365db38aa7bb522 --check --diff . || (
36 32 echo "Changes need auto-formatting. Run:"
37 33 echo " darker -r 60625f241f298b5039cb2debc365db38aa7bb522"
38 34 echo "then commit and push changes to fix."
39 35 exit 1
40 36 )
@@ -1,80 +1,80 b''
1 1 name: Run tests
2 2
3 3 on:
4 4 push:
5 5 branches:
6 6 - main
7 7 - '*.x'
8 8 pull_request:
9 9 # Run weekly on Monday at 1:23 UTC
10 10 schedule:
11 11 - cron: '23 1 * * 1'
12 12 workflow_dispatch:
13 13
14 14
15 15 jobs:
16 16 test:
17 17 runs-on: ${{ matrix.os }}
18 18 strategy:
19 19 fail-fast: false
20 20 matrix:
21 21 os: [ubuntu-latest, windows-latest]
22 22 python-version: ["3.8", "3.9", "3.10"]
23 23 deps: [test_extra]
24 24 # Test all on ubuntu, test ends on macos
25 25 include:
26 26 - os: macos-latest
27 27 python-version: "3.8"
28 28 deps: test_extra
29 29 - os: macos-latest
30 30 python-version: "3.10"
31 31 deps: test_extra
32 32 # Tests minimal dependencies set
33 33 - os: ubuntu-latest
34 34 python-version: "3.10"
35 35 deps: test
36 36 # Tests latest development Python version
37 37 - os: ubuntu-latest
38 38 python-version: "3.11-dev"
39 39 deps: test
40 40 # Installing optional dependencies stuff takes ages on PyPy
41 41 - os: ubuntu-latest
42 42 python-version: "pypy-3.8"
43 43 deps: test
44 44 - os: windows-latest
45 45 python-version: "pypy-3.8"
46 46 deps: test
47 47 - os: macos-latest
48 48 python-version: "pypy-3.8"
49 49 deps: test
50 50
51 51 steps:
52 - uses: actions/checkout@v2
52 - uses: actions/checkout@v3
53 53 - name: Set up Python ${{ matrix.python-version }}
54 uses: actions/setup-python@v2
54 uses: actions/setup-python@v4
55 55 with:
56 56 python-version: ${{ matrix.python-version }}
57 57 cache: pip
58 58 - name: Install latex
59 59 if: runner.os == 'Linux' && matrix.deps == 'test_extra'
60 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 61 - name: Install and update Python dependencies
62 62 run: |
63 63 python -m pip install --upgrade pip setuptools wheel build
64 64 python -m pip install --upgrade -e .[${{ matrix.deps }}]
65 65 python -m pip install --upgrade check-manifest pytest-cov
66 66 - name: Try building with Python build
67 67 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
68 68 run: |
69 69 python -m build
70 70 shasum -a 256 dist/*
71 71 - name: Check manifest
72 72 if: runner.os != 'Windows' # setup.py does not support sdist on Windows
73 73 run: check-manifest
74 74 - name: pytest
75 75 env:
76 76 COLUMNS: 120
77 77 run: |
78 78 pytest --color=yes -raXxs ${{ startsWith(matrix.python-version, 'pypy') && ' ' || '--cov --cov-report=xml' }}
79 79 - name: Upload coverage to Codecov
80 80 uses: codecov/codecov-action@v2
@@ -1,2858 +1,2862 b''
1 1 """Completion for IPython.
2 2
3 3 This module started as fork of the rlcompleter module in the Python standard
4 4 library. The original enhancements made to rlcompleter have been sent
5 5 upstream and were accepted as of Python 2.3,
6 6
7 7 This module now support a wide variety of completion mechanism both available
8 8 for normal classic Python code, as well as completer for IPython specific
9 9 Syntax like magics.
10 10
11 11 Latex and Unicode completion
12 12 ============================
13 13
14 14 IPython and compatible frontends not only can complete your code, but can help
15 15 you to input a wide range of characters. In particular we allow you to insert
16 16 a unicode character using the tab completion mechanism.
17 17
18 18 Forward latex/unicode completion
19 19 --------------------------------
20 20
21 21 Forward completion allows you to easily type a unicode character using its latex
22 22 name, or unicode long description. To do so type a backslash follow by the
23 23 relevant name and press tab:
24 24
25 25
26 26 Using latex completion:
27 27
28 28 .. code::
29 29
30 30 \\alpha<tab>
31 31 α
32 32
33 33 or using unicode completion:
34 34
35 35
36 36 .. code::
37 37
38 38 \\GREEK SMALL LETTER ALPHA<tab>
39 39 α
40 40
41 41
42 42 Only valid Python identifiers will complete. Combining characters (like arrow or
43 43 dots) are also available, unlike latex they need to be put after the their
44 44 counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``.
45 45
46 46 Some browsers are known to display combining characters incorrectly.
47 47
48 48 Backward latex completion
49 49 -------------------------
50 50
51 51 It is sometime challenging to know how to type a character, if you are using
52 52 IPython, or any compatible frontend you can prepend backslash to the character
53 53 and press ``<tab>`` to expand it to its latex form.
54 54
55 55 .. code::
56 56
57 57 \\α<tab>
58 58 \\alpha
59 59
60 60
61 61 Both forward and backward completions can be deactivated by setting the
62 62 ``Completer.backslash_combining_completions`` option to ``False``.
63 63
64 64
65 65 Experimental
66 66 ============
67 67
68 68 Starting with IPython 6.0, this module can make use of the Jedi library to
69 69 generate completions both using static analysis of the code, and dynamically
70 70 inspecting multiple namespaces. Jedi is an autocompletion and static analysis
71 71 for Python. The APIs attached to this new mechanism is unstable and will
72 72 raise unless use in an :any:`provisionalcompleter` context manager.
73 73
74 74 You will find that the following are experimental:
75 75
76 76 - :any:`provisionalcompleter`
77 77 - :any:`IPCompleter.completions`
78 78 - :any:`Completion`
79 79 - :any:`rectify_completions`
80 80
81 81 .. note::
82 82
83 83 better name for :any:`rectify_completions` ?
84 84
85 85 We welcome any feedback on these new API, and we also encourage you to try this
86 86 module in debug mode (start IPython with ``--Completer.debug=True``) in order
87 87 to have extra logging information if :any:`jedi` is crashing, or if current
88 88 IPython completer pending deprecations are returning results not yet handled
89 89 by :any:`jedi`
90 90
91 91 Using Jedi for tab completion allow snippets like the following to work without
92 92 having to execute any code:
93 93
94 94 >>> myvar = ['hello', 42]
95 95 ... myvar[1].bi<tab>
96 96
97 97 Tab completion will be able to infer that ``myvar[1]`` is a real number without
98 98 executing any code unlike the previously available ``IPCompleter.greedy``
99 99 option.
100 100
101 101 Be sure to update :any:`jedi` to the latest stable version or to try the
102 102 current development version to get better completions.
103 103
104 104 Matchers
105 105 ========
106 106
107 107 All completions routines are implemented using unified *Matchers* API.
108 108 The matchers API is provisional and subject to change without notice.
109 109
110 110 The built-in matchers include:
111 111
112 112 - ``IPCompleter.dict_key_matcher``: dictionary key completions,
113 113 - ``IPCompleter.magic_matcher``: completions for magics,
114 114 - ``IPCompleter.unicode_name_matcher``, ``IPCompleter.fwd_unicode_matcher`` and ``IPCompleter.latex_matcher``: see `Forward latex/unicode completion`_,
115 115 - ``back_unicode_name_matcher`` and ``back_latex_name_matcher``: see `Backward latex completion`_,
116 116 - ``IPCompleter.file_matcher``: paths to files and directories,
117 117 - ``IPCompleter.python_func_kw_matcher`` - function keywords,
118 118 - ``IPCompleter.python_matches`` - globals and attributes (v1 API),
119 119 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
120 120 - ``IPCompleter.custom_completer_matcher`` - pluggable completer with a default implementation in any:`core.InteractiveShell`
121 121 which uses uses IPython hooks system (`complete_command`) with string dispatch (including regular expressions).
122 122 Differently to other matchers, ``custom_completer_matcher`` will not suppress Jedi results to match
123 123 behaviour in earlier IPython versions.
124 124
125 125 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
126 126
127 127 Suppression of competing matchers
128 128 ---------------------------------
129 129
130 130 By default results from all matchers are combined, in the order determined by
131 131 their priority. Matchers can request to suppress results from subsequent
132 132 matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
133 133
134 134 When multiple matchers simultaneously request surpression, the results from of
135 135 the matcher with higher priority will be returned.
136 136
137 137 Sometimes it is desirable to suppress most but not all other matchers;
138 138 this can be achieved by adding a list of identifiers of matchers which
139 139 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
140 140 """
141 141
142 142
143 143 # Copyright (c) IPython Development Team.
144 144 # Distributed under the terms of the Modified BSD License.
145 145 #
146 146 # Some of this code originated from rlcompleter in the Python standard library
147 147 # Copyright (C) 2001 Python Software Foundation, www.python.org
148 148
149 149
150 150 import builtins as builtin_mod
151 151 import glob
152 152 import inspect
153 153 import itertools
154 154 import keyword
155 155 import os
156 156 import re
157 157 import string
158 158 import sys
159 159 import time
160 160 import unicodedata
161 161 import uuid
162 162 import warnings
163 163 from contextlib import contextmanager
164 164 from functools import lru_cache, partial
165 165 from importlib import import_module
166 166 from types import SimpleNamespace
167 167 from typing import (
168 168 Iterable,
169 169 Iterator,
170 170 List,
171 171 Tuple,
172 172 Union,
173 173 Any,
174 174 Sequence,
175 175 Dict,
176 176 NamedTuple,
177 177 Pattern,
178 178 Optional,
179 179 Callable,
180 180 TYPE_CHECKING,
181 181 Set,
182 182 )
183 183
184 184 from IPython.core.error import TryNext
185 185 from IPython.core.inputtransformer2 import ESC_MAGIC
186 186 from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol
187 187 from IPython.core.oinspect import InspectColors
188 188 from IPython.testing.skipdoctest import skip_doctest
189 189 from IPython.utils import generics
190 190 from IPython.utils.dir2 import dir2, get_real_method
191 191 from IPython.utils.path import ensure_dir_exists
192 192 from IPython.utils.process import arg_split
193 193 from traitlets import (
194 194 Bool,
195 195 Enum,
196 196 Int,
197 197 List as ListTrait,
198 198 Unicode,
199 199 Dict as DictTrait,
200 200 Union as UnionTrait,
201 201 default,
202 202 observe,
203 203 )
204 204 from traitlets.config.configurable import Configurable
205 205
206 206 import __main__
207 207
208 208 # skip module docstests
209 209 __skip_doctest__ = True
210 210
211 211
212 212 try:
213 213 import jedi
214 214 jedi.settings.case_insensitive_completion = False
215 215 import jedi.api.helpers
216 216 import jedi.api.classes
217 217 JEDI_INSTALLED = True
218 218 except ImportError:
219 219 JEDI_INSTALLED = False
220 220
221 221 if TYPE_CHECKING:
222 222 from typing import cast
223 223 from typing_extensions import TypedDict, NotRequired
224 224 else:
225 225
226 226 def cast(obj, _type):
227 227 return obj
228 228
229 229 TypedDict = Dict
230 230 NotRequired = Tuple
231 231
232 232 # -----------------------------------------------------------------------------
233 233 # Globals
234 234 #-----------------------------------------------------------------------------
235 235
236 236 # ranges where we have most of the valid unicode names. We could be more finer
237 237 # grained but is it worth it for performance While unicode have character in the
238 238 # range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I
239 239 # write this). With below range we cover them all, with a density of ~67%
240 240 # biggest next gap we consider only adds up about 1% density and there are 600
241 241 # gaps that would need hard coding.
242 242 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
243 243
244 244 # Public API
245 245 __all__ = ["Completer", "IPCompleter"]
246 246
247 247 if sys.platform == 'win32':
248 248 PROTECTABLES = ' '
249 249 else:
250 250 PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&'
251 251
252 252 # Protect against returning an enormous number of completions which the frontend
253 253 # may have trouble processing.
254 254 MATCHES_LIMIT = 500
255 255
256 256 # Completion type reported when no type can be inferred.
257 257 _UNKNOWN_TYPE = "<unknown>"
258 258
259 259 class ProvisionalCompleterWarning(FutureWarning):
260 260 """
261 261 Exception raise by an experimental feature in this module.
262 262
263 263 Wrap code in :any:`provisionalcompleter` context manager if you
264 264 are certain you want to use an unstable feature.
265 265 """
266 266 pass
267 267
268 268 warnings.filterwarnings('error', category=ProvisionalCompleterWarning)
269 269
270 270
271 271 @skip_doctest
272 272 @contextmanager
273 273 def provisionalcompleter(action='ignore'):
274 274 """
275 275 This context manager has to be used in any place where unstable completer
276 276 behavior and API may be called.
277 277
278 278 >>> with provisionalcompleter():
279 279 ... completer.do_experimental_things() # works
280 280
281 281 >>> completer.do_experimental_things() # raises.
282 282
283 283 .. note::
284 284
285 285 Unstable
286 286
287 287 By using this context manager you agree that the API in use may change
288 288 without warning, and that you won't complain if they do so.
289 289
290 290 You also understand that, if the API is not to your liking, you should report
291 291 a bug to explain your use case upstream.
292 292
293 293 We'll be happy to get your feedback, feature requests, and improvements on
294 294 any of the unstable APIs!
295 295 """
296 296 with warnings.catch_warnings():
297 297 warnings.filterwarnings(action, category=ProvisionalCompleterWarning)
298 298 yield
299 299
300 300
301 301 def has_open_quotes(s):
302 302 """Return whether a string has open quotes.
303 303
304 304 This simply counts whether the number of quote characters of either type in
305 305 the string is odd.
306 306
307 307 Returns
308 308 -------
309 309 If there is an open quote, the quote character is returned. Else, return
310 310 False.
311 311 """
312 312 # We check " first, then ', so complex cases with nested quotes will get
313 313 # the " to take precedence.
314 314 if s.count('"') % 2:
315 315 return '"'
316 316 elif s.count("'") % 2:
317 317 return "'"
318 318 else:
319 319 return False
320 320
321 321
322 322 def protect_filename(s, protectables=PROTECTABLES):
323 323 """Escape a string to protect certain characters."""
324 324 if set(s) & set(protectables):
325 325 if sys.platform == "win32":
326 326 return '"' + s + '"'
327 327 else:
328 328 return "".join(("\\" + c if c in protectables else c) for c in s)
329 329 else:
330 330 return s
331 331
332 332
333 333 def expand_user(path:str) -> Tuple[str, bool, str]:
334 334 """Expand ``~``-style usernames in strings.
335 335
336 336 This is similar to :func:`os.path.expanduser`, but it computes and returns
337 337 extra information that will be useful if the input was being used in
338 338 computing completions, and you wish to return the completions with the
339 339 original '~' instead of its expanded value.
340 340
341 341 Parameters
342 342 ----------
343 343 path : str
344 344 String to be expanded. If no ~ is present, the output is the same as the
345 345 input.
346 346
347 347 Returns
348 348 -------
349 349 newpath : str
350 350 Result of ~ expansion in the input path.
351 351 tilde_expand : bool
352 352 Whether any expansion was performed or not.
353 353 tilde_val : str
354 354 The value that ~ was replaced with.
355 355 """
356 356 # Default values
357 357 tilde_expand = False
358 358 tilde_val = ''
359 359 newpath = path
360 360
361 361 if path.startswith('~'):
362 362 tilde_expand = True
363 363 rest = len(path)-1
364 364 newpath = os.path.expanduser(path)
365 365 if rest:
366 366 tilde_val = newpath[:-rest]
367 367 else:
368 368 tilde_val = newpath
369 369
370 370 return newpath, tilde_expand, tilde_val
371 371
372 372
373 373 def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str:
374 374 """Does the opposite of expand_user, with its outputs.
375 375 """
376 376 if tilde_expand:
377 377 return path.replace(tilde_val, '~')
378 378 else:
379 379 return path
380 380
381 381
382 382 def completions_sorting_key(word):
383 383 """key for sorting completions
384 384
385 385 This does several things:
386 386
387 387 - Demote any completions starting with underscores to the end
388 388 - Insert any %magic and %%cellmagic completions in the alphabetical order
389 389 by their name
390 390 """
391 391 prio1, prio2 = 0, 0
392 392
393 393 if word.startswith('__'):
394 394 prio1 = 2
395 395 elif word.startswith('_'):
396 396 prio1 = 1
397 397
398 398 if word.endswith('='):
399 399 prio1 = -1
400 400
401 401 if word.startswith('%%'):
402 402 # If there's another % in there, this is something else, so leave it alone
403 403 if not "%" in word[2:]:
404 404 word = word[2:]
405 405 prio2 = 2
406 406 elif word.startswith('%'):
407 407 if not "%" in word[1:]:
408 408 word = word[1:]
409 409 prio2 = 1
410 410
411 411 return prio1, word, prio2
412 412
413 413
414 414 class _FakeJediCompletion:
415 415 """
416 416 This is a workaround to communicate to the UI that Jedi has crashed and to
417 417 report a bug. Will be used only id :any:`IPCompleter.debug` is set to true.
418 418
419 419 Added in IPython 6.0 so should likely be removed for 7.0
420 420
421 421 """
422 422
423 423 def __init__(self, name):
424 424
425 425 self.name = name
426 426 self.complete = name
427 427 self.type = 'crashed'
428 428 self.name_with_symbols = name
429 429 self.signature = ''
430 430 self._origin = 'fake'
431 431
432 432 def __repr__(self):
433 433 return '<Fake completion object jedi has crashed>'
434 434
435 435
436 436 _JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion]
437 437
438 438
439 439 class Completion:
440 440 """
441 441 Completion object used and returned by IPython completers.
442 442
443 443 .. warning::
444 444
445 445 Unstable
446 446
447 447 This function is unstable, API may change without warning.
448 448 It will also raise unless use in proper context manager.
449 449
450 450 This act as a middle ground :any:`Completion` object between the
451 451 :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion
452 452 object. While Jedi need a lot of information about evaluator and how the
453 453 code should be ran/inspected, PromptToolkit (and other frontend) mostly
454 454 need user facing information.
455 455
456 456 - Which range should be replaced replaced by what.
457 457 - Some metadata (like completion type), or meta information to displayed to
458 458 the use user.
459 459
460 460 For debugging purpose we can also store the origin of the completion (``jedi``,
461 461 ``IPython.python_matches``, ``IPython.magics_matches``...).
462 462 """
463 463
464 464 __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin']
465 465
466 466 def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None:
467 467 warnings.warn("``Completion`` is a provisional API (as of IPython 6.0). "
468 468 "It may change without warnings. "
469 469 "Use in corresponding context manager.",
470 470 category=ProvisionalCompleterWarning, stacklevel=2)
471 471
472 472 self.start = start
473 473 self.end = end
474 474 self.text = text
475 475 self.type = type
476 476 self.signature = signature
477 477 self._origin = _origin
478 478
479 479 def __repr__(self):
480 480 return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \
481 481 (self.start, self.end, self.text, self.type or '?', self.signature or '?')
482 482
483 483 def __eq__(self, other)->Bool:
484 484 """
485 485 Equality and hash do not hash the type (as some completer may not be
486 486 able to infer the type), but are use to (partially) de-duplicate
487 487 completion.
488 488
489 489 Completely de-duplicating completion is a bit tricker that just
490 490 comparing as it depends on surrounding text, which Completions are not
491 491 aware of.
492 492 """
493 493 return self.start == other.start and \
494 494 self.end == other.end and \
495 495 self.text == other.text
496 496
497 497 def __hash__(self):
498 498 return hash((self.start, self.end, self.text))
499 499
500 500
501 501 class SimpleCompletion:
502 502 """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
503 503
504 504 .. warning::
505 505
506 506 Provisional
507 507
508 508 This class is used to describe the currently supported attributes of
509 509 simple completion items, and any additional implementation details
510 510 should not be relied on. Additional attributes may be included in
511 511 future versions, and meaning of text disambiguated from the current
512 512 dual meaning of "text to insert" and "text to used as a label".
513 513 """
514 514
515 515 __slots__ = ["text", "type"]
516 516
517 517 def __init__(self, text: str, *, type: str = None):
518 518 self.text = text
519 519 self.type = type
520 520
521 521 def __repr__(self):
522 522 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
523 523
524 524
525 525 class MatcherResultBase(TypedDict):
526 526 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
527 527
528 528 #: suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
529 529 matched_fragment: NotRequired[str]
530 530
531 531 #: whether to suppress results from all other matchers (True), some
532 532 #: matchers (set of identifiers) or none (False); default is False.
533 533 suppress: NotRequired[Union[bool, Set[str]]]
534 534
535 535 #: identifiers of matchers which should NOT be suppressed
536 536 do_not_suppress: NotRequired[Set[str]]
537 537
538 538 #: are completions already ordered and should be left as-is? default is False.
539 539 ordered: NotRequired[bool]
540 540
541 541
542 542 class SimpleMatcherResult(MatcherResultBase):
543 543 """Result of new-style completion matcher."""
544 544
545 545 #: list of candidate completions
546 546 completions: Sequence[SimpleCompletion]
547 547
548 548
549 549 class _JediMatcherResult(MatcherResultBase):
550 550 """Matching result returned by Jedi (will be processed differently)"""
551 551
552 552 #: list of candidate completions
553 553 completions: Iterable[_JediCompletionLike]
554 554
555 555
556 556 class CompletionContext(NamedTuple):
557 557 """Completion context provided as an argument to matchers in the Matcher API v2."""
558 558
559 559 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
560 560 # which was not explicitly visible as an argument of the matcher, making any refactor
561 561 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
562 562 # from the completer, and make substituting them in sub-classes easier.
563 563
564 564 #: Relevant fragment of code directly preceding the cursor.
565 565 #: The extraction of token is implemented via splitter heuristic
566 566 #: (following readline behaviour for legacy reasons), which is user configurable
567 567 #: (by switching the greedy mode).
568 568 token: str
569 569
570 570 #: The full available content of the editor or buffer
571 571 full_text: str
572 572
573 573 #: Cursor position in the line (the same for ``full_text`` and ``text``).
574 574 cursor_position: int
575 575
576 576 #: Cursor line in ``full_text``.
577 577 cursor_line: int
578 578
579 579 #: The maximum number of completions that will be used downstream.
580 580 #: Matchers can use this information to abort early.
581 581 #: The built-in Jedi matcher is currently excepted from this limit.
582 582 limit: int
583 583
584 584 @property
585 585 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
586 586 def text_until_cursor(self) -> str:
587 587 return self.line_with_cursor[: self.cursor_position]
588 588
589 589 @property
590 590 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
591 591 def line_with_cursor(self) -> str:
592 592 return self.full_text.split("\n")[self.cursor_line]
593 593
594 594
595 595 MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult]
596 596
597 597 MatcherAPIv1 = Callable[[str], List[str]]
598 598 MatcherAPIv2 = Callable[[CompletionContext], MatcherResult]
599 599 Matcher = Union[MatcherAPIv1, MatcherAPIv2]
600 600
601 601
602 602 def completion_matcher(
603 603 *, priority: float = None, identifier: str = None, api_version: int = 1
604 604 ):
605 605 """Adds attributes describing the matcher.
606 606
607 607 Parameters
608 608 ----------
609 609 priority : Optional[float]
610 610 The priority of the matcher, determines the order of execution of matchers.
611 611 Higher priority means that the matcher will be executed first. Defaults to 0.
612 612 identifier : Optional[str]
613 613 identifier of the matcher allowing users to modify the behaviour via traitlets,
614 614 and also used to for debugging (will be passed as ``origin`` with the completions).
615 615 Defaults to matcher function ``__qualname__``.
616 616 api_version: Optional[int]
617 617 version of the Matcher API used by this matcher.
618 618 Currently supported values are 1 and 2.
619 619 Defaults to 1.
620 620 """
621 621
622 622 def wrapper(func: Matcher):
623 623 func.matcher_priority = priority or 0
624 624 func.matcher_identifier = identifier or func.__qualname__
625 625 func.matcher_api_version = api_version
626 626 if TYPE_CHECKING:
627 627 if api_version == 1:
628 628 func = cast(func, MatcherAPIv1)
629 629 elif api_version == 2:
630 630 func = cast(func, MatcherAPIv2)
631 631 return func
632 632
633 633 return wrapper
634 634
635 635
636 636 def _get_matcher_priority(matcher: Matcher):
637 637 return getattr(matcher, "matcher_priority", 0)
638 638
639 639
640 640 def _get_matcher_id(matcher: Matcher):
641 641 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
642 642
643 643
644 644 def _get_matcher_api_version(matcher):
645 645 return getattr(matcher, "matcher_api_version", 1)
646 646
647 647
648 648 context_matcher = partial(completion_matcher, api_version=2)
649 649
650 650
651 651 _IC = Iterable[Completion]
652 652
653 653
654 654 def _deduplicate_completions(text: str, completions: _IC)-> _IC:
655 655 """
656 656 Deduplicate a set of completions.
657 657
658 658 .. warning::
659 659
660 660 Unstable
661 661
662 662 This function is unstable, API may change without warning.
663 663
664 664 Parameters
665 665 ----------
666 666 text : str
667 667 text that should be completed.
668 668 completions : Iterator[Completion]
669 669 iterator over the completions to deduplicate
670 670
671 671 Yields
672 672 ------
673 673 `Completions` objects
674 674 Completions coming from multiple sources, may be different but end up having
675 675 the same effect when applied to ``text``. If this is the case, this will
676 676 consider completions as equal and only emit the first encountered.
677 677 Not folded in `completions()` yet for debugging purpose, and to detect when
678 678 the IPython completer does return things that Jedi does not, but should be
679 679 at some point.
680 680 """
681 681 completions = list(completions)
682 682 if not completions:
683 683 return
684 684
685 685 new_start = min(c.start for c in completions)
686 686 new_end = max(c.end for c in completions)
687 687
688 688 seen = set()
689 689 for c in completions:
690 690 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
691 691 if new_text not in seen:
692 692 yield c
693 693 seen.add(new_text)
694 694
695 695
696 696 def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC:
697 697 """
698 698 Rectify a set of completions to all have the same ``start`` and ``end``
699 699
700 700 .. warning::
701 701
702 702 Unstable
703 703
704 704 This function is unstable, API may change without warning.
705 705 It will also raise unless use in proper context manager.
706 706
707 707 Parameters
708 708 ----------
709 709 text : str
710 710 text that should be completed.
711 711 completions : Iterator[Completion]
712 712 iterator over the completions to rectify
713 713 _debug : bool
714 714 Log failed completion
715 715
716 716 Notes
717 717 -----
718 718 :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though
719 719 the Jupyter Protocol requires them to behave like so. This will readjust
720 720 the completion to have the same ``start`` and ``end`` by padding both
721 721 extremities with surrounding text.
722 722
723 723 During stabilisation should support a ``_debug`` option to log which
724 724 completion are return by the IPython completer and not found in Jedi in
725 725 order to make upstream bug report.
726 726 """
727 727 warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). "
728 728 "It may change without warnings. "
729 729 "Use in corresponding context manager.",
730 730 category=ProvisionalCompleterWarning, stacklevel=2)
731 731
732 732 completions = list(completions)
733 733 if not completions:
734 734 return
735 735 starts = (c.start for c in completions)
736 736 ends = (c.end for c in completions)
737 737
738 738 new_start = min(starts)
739 739 new_end = max(ends)
740 740
741 741 seen_jedi = set()
742 742 seen_python_matches = set()
743 743 for c in completions:
744 744 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
745 745 if c._origin == 'jedi':
746 746 seen_jedi.add(new_text)
747 747 elif c._origin == 'IPCompleter.python_matches':
748 748 seen_python_matches.add(new_text)
749 749 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
750 750 diff = seen_python_matches.difference(seen_jedi)
751 751 if diff and _debug:
752 752 print('IPython.python matches have extras:', diff)
753 753
754 754
755 755 if sys.platform == 'win32':
756 756 DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?'
757 757 else:
758 758 DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?'
759 759
760 760 GREEDY_DELIMS = ' =\r\n'
761 761
762 762
763 763 class CompletionSplitter(object):
764 764 """An object to split an input line in a manner similar to readline.
765 765
766 766 By having our own implementation, we can expose readline-like completion in
767 767 a uniform manner to all frontends. This object only needs to be given the
768 768 line of text to be split and the cursor position on said line, and it
769 769 returns the 'word' to be completed on at the cursor after splitting the
770 770 entire line.
771 771
772 772 What characters are used as splitting delimiters can be controlled by
773 773 setting the ``delims`` attribute (this is a property that internally
774 774 automatically builds the necessary regular expression)"""
775 775
776 776 # Private interface
777 777
778 778 # A string of delimiter characters. The default value makes sense for
779 779 # IPython's most typical usage patterns.
780 780 _delims = DELIMS
781 781
782 782 # The expression (a normal string) to be compiled into a regular expression
783 783 # for actual splitting. We store it as an attribute mostly for ease of
784 784 # debugging, since this type of code can be so tricky to debug.
785 785 _delim_expr = None
786 786
787 787 # The regular expression that does the actual splitting
788 788 _delim_re = None
789 789
790 790 def __init__(self, delims=None):
791 791 delims = CompletionSplitter._delims if delims is None else delims
792 792 self.delims = delims
793 793
794 794 @property
795 795 def delims(self):
796 796 """Return the string of delimiter characters."""
797 797 return self._delims
798 798
799 799 @delims.setter
800 800 def delims(self, delims):
801 801 """Set the delimiters for line splitting."""
802 802 expr = '[' + ''.join('\\'+ c for c in delims) + ']'
803 803 self._delim_re = re.compile(expr)
804 804 self._delims = delims
805 805 self._delim_expr = expr
806 806
807 807 def split_line(self, line, cursor_pos=None):
808 808 """Split a line of text with a cursor at the given position.
809 809 """
810 810 l = line if cursor_pos is None else line[:cursor_pos]
811 811 return self._delim_re.split(l)[-1]
812 812
813 813
814 814
815 815 class Completer(Configurable):
816 816
817 817 greedy = Bool(False,
818 818 help="""Activate greedy completion
819 819 PENDING DEPRECATION. this is now mostly taken care of with Jedi.
820 820
821 821 This will enable completion on elements of lists, results of function calls, etc.,
822 822 but can be unsafe because the code is actually evaluated on TAB.
823 823 """,
824 824 ).tag(config=True)
825 825
826 826 use_jedi = Bool(default_value=JEDI_INSTALLED,
827 827 help="Experimental: Use Jedi to generate autocompletions. "
828 828 "Default to True if jedi is installed.").tag(config=True)
829 829
830 830 jedi_compute_type_timeout = Int(default_value=400,
831 831 help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types.
832 832 Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt
833 833 performance by preventing jedi to build its cache.
834 834 """).tag(config=True)
835 835
836 836 debug = Bool(default_value=False,
837 837 help='Enable debug for the Completer. Mostly print extra '
838 838 'information for experimental jedi integration.')\
839 839 .tag(config=True)
840 840
841 841 backslash_combining_completions = Bool(True,
842 842 help="Enable unicode completions, e.g. \\alpha<tab> . "
843 843 "Includes completion of latex commands, unicode names, and expanding "
844 844 "unicode characters back to latex commands.").tag(config=True)
845 845
846 846 def __init__(self, namespace=None, global_namespace=None, **kwargs):
847 847 """Create a new completer for the command line.
848 848
849 849 Completer(namespace=ns, global_namespace=ns2) -> completer instance.
850 850
851 851 If unspecified, the default namespace where completions are performed
852 852 is __main__ (technically, __main__.__dict__). Namespaces should be
853 853 given as dictionaries.
854 854
855 855 An optional second namespace can be given. This allows the completer
856 856 to handle cases where both the local and global scopes need to be
857 857 distinguished.
858 858 """
859 859
860 860 # Don't bind to namespace quite yet, but flag whether the user wants a
861 861 # specific namespace or to use __main__.__dict__. This will allow us
862 862 # to bind to __main__.__dict__ at completion time, not now.
863 863 if namespace is None:
864 864 self.use_main_ns = True
865 865 else:
866 866 self.use_main_ns = False
867 867 self.namespace = namespace
868 868
869 869 # The global namespace, if given, can be bound directly
870 870 if global_namespace is None:
871 871 self.global_namespace = {}
872 872 else:
873 873 self.global_namespace = global_namespace
874 874
875 875 self.custom_matchers = []
876 876
877 877 super(Completer, self).__init__(**kwargs)
878 878
879 879 def complete(self, text, state):
880 880 """Return the next possible completion for 'text'.
881 881
882 882 This is called successively with state == 0, 1, 2, ... until it
883 883 returns None. The completion should begin with 'text'.
884 884
885 885 """
886 886 if self.use_main_ns:
887 887 self.namespace = __main__.__dict__
888 888
889 889 if state == 0:
890 890 if "." in text:
891 891 self.matches = self.attr_matches(text)
892 892 else:
893 893 self.matches = self.global_matches(text)
894 894 try:
895 895 return self.matches[state]
896 896 except IndexError:
897 897 return None
898 898
899 899 def global_matches(self, text):
900 900 """Compute matches when text is a simple name.
901 901
902 902 Return a list of all keywords, built-in functions and names currently
903 903 defined in self.namespace or self.global_namespace that match.
904 904
905 905 """
906 906 matches = []
907 907 match_append = matches.append
908 908 n = len(text)
909 for lst in [keyword.kwlist,
910 builtin_mod.__dict__.keys(),
911 self.namespace.keys(),
912 self.global_namespace.keys()]:
909 for lst in [
910 keyword.kwlist,
911 builtin_mod.__dict__.keys(),
912 list(self.namespace.keys()),
913 list(self.global_namespace.keys()),
914 ]:
913 915 for word in lst:
914 916 if word[:n] == text and word != "__builtins__":
915 917 match_append(word)
916 918
917 919 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
918 for lst in [self.namespace.keys(),
919 self.global_namespace.keys()]:
920 shortened = {"_".join([sub[0] for sub in word.split('_')]) : word
921 for word in lst if snake_case_re.match(word)}
920 for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]:
921 shortened = {
922 "_".join([sub[0] for sub in word.split("_")]): word
923 for word in lst
924 if snake_case_re.match(word)
925 }
922 926 for word in shortened.keys():
923 927 if word[:n] == text and word != "__builtins__":
924 928 match_append(shortened[word])
925 929 return matches
926 930
927 931 def attr_matches(self, text):
928 932 """Compute matches when text contains a dot.
929 933
930 934 Assuming the text is of the form NAME.NAME....[NAME], and is
931 935 evaluatable in self.namespace or self.global_namespace, it will be
932 936 evaluated and its attributes (as revealed by dir()) are used as
933 937 possible completions. (For class instances, class members are
934 938 also considered.)
935 939
936 940 WARNING: this can still invoke arbitrary C code, if an object
937 941 with a __getattr__ hook is evaluated.
938 942
939 943 """
940 944
941 945 # Another option, seems to work great. Catches things like ''.<tab>
942 946 m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text)
943 947
944 948 if m:
945 949 expr, attr = m.group(1, 3)
946 950 elif self.greedy:
947 951 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
948 952 if not m2:
949 953 return []
950 954 expr, attr = m2.group(1,2)
951 955 else:
952 956 return []
953 957
954 958 try:
955 959 obj = eval(expr, self.namespace)
956 960 except:
957 961 try:
958 962 obj = eval(expr, self.global_namespace)
959 963 except:
960 964 return []
961 965
962 966 if self.limit_to__all__ and hasattr(obj, '__all__'):
963 967 words = get__all__entries(obj)
964 968 else:
965 969 words = dir2(obj)
966 970
967 971 try:
968 972 words = generics.complete_object(obj, words)
969 973 except TryNext:
970 974 pass
971 975 except AssertionError:
972 976 raise
973 977 except Exception:
974 978 # Silence errors from completion function
975 979 #raise # dbg
976 980 pass
977 981 # Build match list to return
978 982 n = len(attr)
979 983 return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ]
980 984
981 985
982 986 def get__all__entries(obj):
983 987 """returns the strings in the __all__ attribute"""
984 988 try:
985 989 words = getattr(obj, '__all__')
986 990 except:
987 991 return []
988 992
989 993 return [w for w in words if isinstance(w, str)]
990 994
991 995
992 996 def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str,
993 997 extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]:
994 998 """Used by dict_key_matches, matching the prefix to a list of keys
995 999
996 1000 Parameters
997 1001 ----------
998 1002 keys
999 1003 list of keys in dictionary currently being completed.
1000 1004 prefix
1001 1005 Part of the text already typed by the user. E.g. `mydict[b'fo`
1002 1006 delims
1003 1007 String of delimiters to consider when finding the current key.
1004 1008 extra_prefix : optional
1005 1009 Part of the text already typed in multi-key index cases. E.g. for
1006 1010 `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`.
1007 1011
1008 1012 Returns
1009 1013 -------
1010 1014 A tuple of three elements: ``quote``, ``token_start``, ``matched``, with
1011 1015 ``quote`` being the quote that need to be used to close current string.
1012 1016 ``token_start`` the position where the replacement should start occurring,
1013 1017 ``matches`` a list of replacement/completion
1014 1018
1015 1019 """
1016 1020 prefix_tuple = extra_prefix if extra_prefix else ()
1017 1021 Nprefix = len(prefix_tuple)
1018 1022 def filter_prefix_tuple(key):
1019 1023 # Reject too short keys
1020 1024 if len(key) <= Nprefix:
1021 1025 return False
1022 1026 # Reject keys with non str/bytes in it
1023 1027 for k in key:
1024 1028 if not isinstance(k, (str, bytes)):
1025 1029 return False
1026 1030 # Reject keys that do not match the prefix
1027 1031 for k, pt in zip(key, prefix_tuple):
1028 1032 if k != pt:
1029 1033 return False
1030 1034 # All checks passed!
1031 1035 return True
1032 1036
1033 1037 filtered_keys:List[Union[str,bytes]] = []
1034 1038 def _add_to_filtered_keys(key):
1035 1039 if isinstance(key, (str, bytes)):
1036 1040 filtered_keys.append(key)
1037 1041
1038 1042 for k in keys:
1039 1043 if isinstance(k, tuple):
1040 1044 if filter_prefix_tuple(k):
1041 1045 _add_to_filtered_keys(k[Nprefix])
1042 1046 else:
1043 1047 _add_to_filtered_keys(k)
1044 1048
1045 1049 if not prefix:
1046 1050 return '', 0, [repr(k) for k in filtered_keys]
1047 1051 quote_match = re.search('["\']', prefix)
1048 1052 assert quote_match is not None # silence mypy
1049 1053 quote = quote_match.group()
1050 1054 try:
1051 1055 prefix_str = eval(prefix + quote, {})
1052 1056 except Exception:
1053 1057 return '', 0, []
1054 1058
1055 1059 pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$'
1056 1060 token_match = re.search(pattern, prefix, re.UNICODE)
1057 1061 assert token_match is not None # silence mypy
1058 1062 token_start = token_match.start()
1059 1063 token_prefix = token_match.group()
1060 1064
1061 1065 matched:List[str] = []
1062 1066 for key in filtered_keys:
1063 1067 try:
1064 1068 if not key.startswith(prefix_str):
1065 1069 continue
1066 1070 except (AttributeError, TypeError, UnicodeError):
1067 1071 # Python 3+ TypeError on b'a'.startswith('a') or vice-versa
1068 1072 continue
1069 1073
1070 1074 # reformat remainder of key to begin with prefix
1071 1075 rem = key[len(prefix_str):]
1072 1076 # force repr wrapped in '
1073 1077 rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"')
1074 1078 rem_repr = rem_repr[1 + rem_repr.index("'"):-2]
1075 1079 if quote == '"':
1076 1080 # The entered prefix is quoted with ",
1077 1081 # but the match is quoted with '.
1078 1082 # A contained " hence needs escaping for comparison:
1079 1083 rem_repr = rem_repr.replace('"', '\\"')
1080 1084
1081 1085 # then reinsert prefix from start of token
1082 1086 matched.append('%s%s' % (token_prefix, rem_repr))
1083 1087 return quote, token_start, matched
1084 1088
1085 1089
1086 1090 def cursor_to_position(text:str, line:int, column:int)->int:
1087 1091 """
1088 1092 Convert the (line,column) position of the cursor in text to an offset in a
1089 1093 string.
1090 1094
1091 1095 Parameters
1092 1096 ----------
1093 1097 text : str
1094 1098 The text in which to calculate the cursor offset
1095 1099 line : int
1096 1100 Line of the cursor; 0-indexed
1097 1101 column : int
1098 1102 Column of the cursor 0-indexed
1099 1103
1100 1104 Returns
1101 1105 -------
1102 1106 Position of the cursor in ``text``, 0-indexed.
1103 1107
1104 1108 See Also
1105 1109 --------
1106 1110 position_to_cursor : reciprocal of this function
1107 1111
1108 1112 """
1109 1113 lines = text.split('\n')
1110 1114 assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines)))
1111 1115
1112 1116 return sum(len(l) + 1 for l in lines[:line]) + column
1113 1117
1114 1118 def position_to_cursor(text:str, offset:int)->Tuple[int, int]:
1115 1119 """
1116 1120 Convert the position of the cursor in text (0 indexed) to a line
1117 1121 number(0-indexed) and a column number (0-indexed) pair
1118 1122
1119 1123 Position should be a valid position in ``text``.
1120 1124
1121 1125 Parameters
1122 1126 ----------
1123 1127 text : str
1124 1128 The text in which to calculate the cursor offset
1125 1129 offset : int
1126 1130 Position of the cursor in ``text``, 0-indexed.
1127 1131
1128 1132 Returns
1129 1133 -------
1130 1134 (line, column) : (int, int)
1131 1135 Line of the cursor; 0-indexed, column of the cursor 0-indexed
1132 1136
1133 1137 See Also
1134 1138 --------
1135 1139 cursor_to_position : reciprocal of this function
1136 1140
1137 1141 """
1138 1142
1139 1143 assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text))
1140 1144
1141 1145 before = text[:offset]
1142 1146 blines = before.split('\n') # ! splitnes trim trailing \n
1143 1147 line = before.count('\n')
1144 1148 col = len(blines[-1])
1145 1149 return line, col
1146 1150
1147 1151
1148 1152 def _safe_isinstance(obj, module, class_name):
1149 1153 """Checks if obj is an instance of module.class_name if loaded
1150 1154 """
1151 1155 return (module in sys.modules and
1152 1156 isinstance(obj, getattr(import_module(module), class_name)))
1153 1157
1154 1158
1155 1159 @context_matcher()
1156 1160 def back_unicode_name_matcher(context):
1157 1161 """Match Unicode characters back to Unicode name
1158 1162
1159 1163 Same as ``back_unicode_name_matches``, but adopted to new Matcher API.
1160 1164 """
1161 1165 fragment, matches = back_unicode_name_matches(context.token)
1162 1166 return _convert_matcher_v1_result_to_v2(
1163 1167 matches, type="unicode", fragment=fragment, suppress_if_matches=True
1164 1168 )
1165 1169
1166 1170
1167 1171 def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1168 1172 """Match Unicode characters back to Unicode name
1169 1173
1170 1174 This does ``☃`` -> ``\\snowman``
1171 1175
1172 1176 Note that snowman is not a valid python3 combining character but will be expanded.
1173 1177 Though it will not recombine back to the snowman character by the completion machinery.
1174 1178
1175 1179 This will not either back-complete standard sequences like \\n, \\b ...
1176 1180
1177 1181 Returns
1178 1182 =======
1179 1183
1180 1184 Return a tuple with two elements:
1181 1185
1182 1186 - The Unicode character that was matched (preceded with a backslash), or
1183 1187 empty string,
1184 1188 - a sequence (of 1), name for the match Unicode character, preceded by
1185 1189 backslash, or empty if no match.
1186 1190
1187 1191 """
1188 1192 if len(text)<2:
1189 1193 return '', ()
1190 1194 maybe_slash = text[-2]
1191 1195 if maybe_slash != '\\':
1192 1196 return '', ()
1193 1197
1194 1198 char = text[-1]
1195 1199 # no expand on quote for completion in strings.
1196 1200 # nor backcomplete standard ascii keys
1197 1201 if char in string.ascii_letters or char in ('"',"'"):
1198 1202 return '', ()
1199 1203 try :
1200 1204 unic = unicodedata.name(char)
1201 1205 return '\\'+char,('\\'+unic,)
1202 1206 except KeyError:
1203 1207 pass
1204 1208 return '', ()
1205 1209
1206 1210
1207 1211 @context_matcher()
1208 1212 def back_latex_name_matcher(context):
1209 1213 """Match latex characters back to unicode name
1210 1214
1211 1215 Same as ``back_latex_name_matches``, but adopted to new Matcher API.
1212 1216 """
1213 1217 fragment, matches = back_latex_name_matches(context.token)
1214 1218 return _convert_matcher_v1_result_to_v2(
1215 1219 matches, type="latex", fragment=fragment, suppress_if_matches=True
1216 1220 )
1217 1221
1218 1222
1219 1223 def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:
1220 1224 """Match latex characters back to unicode name
1221 1225
1222 1226 This does ``\\ℵ`` -> ``\\aleph``
1223 1227
1224 1228 """
1225 1229 if len(text)<2:
1226 1230 return '', ()
1227 1231 maybe_slash = text[-2]
1228 1232 if maybe_slash != '\\':
1229 1233 return '', ()
1230 1234
1231 1235
1232 1236 char = text[-1]
1233 1237 # no expand on quote for completion in strings.
1234 1238 # nor backcomplete standard ascii keys
1235 1239 if char in string.ascii_letters or char in ('"',"'"):
1236 1240 return '', ()
1237 1241 try :
1238 1242 latex = reverse_latex_symbol[char]
1239 1243 # '\\' replace the \ as well
1240 1244 return '\\'+char,[latex]
1241 1245 except KeyError:
1242 1246 pass
1243 1247 return '', ()
1244 1248
1245 1249
1246 1250 def _formatparamchildren(parameter) -> str:
1247 1251 """
1248 1252 Get parameter name and value from Jedi Private API
1249 1253
1250 1254 Jedi does not expose a simple way to get `param=value` from its API.
1251 1255
1252 1256 Parameters
1253 1257 ----------
1254 1258 parameter
1255 1259 Jedi's function `Param`
1256 1260
1257 1261 Returns
1258 1262 -------
1259 1263 A string like 'a', 'b=1', '*args', '**kwargs'
1260 1264
1261 1265 """
1262 1266 description = parameter.description
1263 1267 if not description.startswith('param '):
1264 1268 raise ValueError('Jedi function parameter description have change format.'
1265 1269 'Expected "param ...", found %r".' % description)
1266 1270 return description[6:]
1267 1271
1268 1272 def _make_signature(completion)-> str:
1269 1273 """
1270 1274 Make the signature from a jedi completion
1271 1275
1272 1276 Parameters
1273 1277 ----------
1274 1278 completion : jedi.Completion
1275 1279 object does not complete a function type
1276 1280
1277 1281 Returns
1278 1282 -------
1279 1283 a string consisting of the function signature, with the parenthesis but
1280 1284 without the function name. example:
1281 1285 `(a, *args, b=1, **kwargs)`
1282 1286
1283 1287 """
1284 1288
1285 1289 # it looks like this might work on jedi 0.17
1286 1290 if hasattr(completion, 'get_signatures'):
1287 1291 signatures = completion.get_signatures()
1288 1292 if not signatures:
1289 1293 return '(?)'
1290 1294
1291 1295 c0 = completion.get_signatures()[0]
1292 1296 return '('+c0.to_string().split('(', maxsplit=1)[1]
1293 1297
1294 1298 return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures()
1295 1299 for p in signature.defined_names()) if f])
1296 1300
1297 1301
1298 1302 _CompleteResult = Dict[str, MatcherResult]
1299 1303
1300 1304
1301 1305 def _convert_matcher_v1_result_to_v2(
1302 1306 matches: Sequence[str],
1303 1307 type: str,
1304 1308 fragment: str = None,
1305 1309 suppress_if_matches: bool = False,
1306 1310 ) -> SimpleMatcherResult:
1307 1311 """Utility to help with transition"""
1308 1312 result = {
1309 1313 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1310 1314 "suppress": (True if matches else False) if suppress_if_matches else False,
1311 1315 }
1312 1316 if fragment is not None:
1313 1317 result["matched_fragment"] = fragment
1314 1318 return result
1315 1319
1316 1320
1317 1321 class IPCompleter(Completer):
1318 1322 """Extension of the completer class with IPython-specific features"""
1319 1323
1320 1324 __dict_key_regexps: Optional[Dict[bool,Pattern]] = None
1321 1325
1322 1326 @observe('greedy')
1323 1327 def _greedy_changed(self, change):
1324 1328 """update the splitter and readline delims when greedy is changed"""
1325 1329 if change['new']:
1326 1330 self.splitter.delims = GREEDY_DELIMS
1327 1331 else:
1328 1332 self.splitter.delims = DELIMS
1329 1333
1330 1334 dict_keys_only = Bool(
1331 1335 False,
1332 1336 help="""
1333 1337 Whether to show dict key matches only.
1334 1338
1335 1339 (disables all matchers except for `IPCompleter.dict_key_matcher`).
1336 1340 """,
1337 1341 )
1338 1342
1339 1343 suppress_competing_matchers = UnionTrait(
1340 1344 [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))],
1341 1345 default_value=None,
1342 1346 help="""
1343 1347 Whether to suppress completions from other *Matchers*.
1344 1348
1345 1349 When set to ``None`` (default) the matchers will attempt to auto-detect
1346 1350 whether suppression of other matchers is desirable. For example, at
1347 1351 the beginning of a line followed by `%` we expect a magic completion
1348 1352 to be the only applicable option, and after ``my_dict['`` we usually
1349 1353 expect a completion with an existing dictionary key.
1350 1354
1351 1355 If you want to disable this heuristic and see completions from all matchers,
1352 1356 set ``IPCompleter.suppress_competing_matchers = False``.
1353 1357 To disable the heuristic for specific matchers provide a dictionary mapping:
1354 1358 ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``.
1355 1359
1356 1360 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1357 1361 completions to the set of matchers with the highest priority;
1358 1362 this is equivalent to ``IPCompleter.merge_completions`` and
1359 1363 can be beneficial for performance, but will sometimes omit relevant
1360 1364 candidates from matchers further down the priority list.
1361 1365 """,
1362 1366 ).tag(config=True)
1363 1367
1364 1368 merge_completions = Bool(
1365 1369 True,
1366 1370 help="""Whether to merge completion results into a single list
1367 1371
1368 1372 If False, only the completion results from the first non-empty
1369 1373 completer will be returned.
1370 1374
1371 1375 As of version 8.6.0, setting the value to ``False`` is an alias for:
1372 1376 ``IPCompleter.suppress_competing_matchers = True.``.
1373 1377 """,
1374 1378 ).tag(config=True)
1375 1379
1376 1380 disable_matchers = ListTrait(
1377 1381 Unicode(), help="""List of matchers to disable."""
1378 1382 ).tag(config=True)
1379 1383
1380 1384 omit__names = Enum(
1381 1385 (0, 1, 2),
1382 1386 default_value=2,
1383 1387 help="""Instruct the completer to omit private method names
1384 1388
1385 1389 Specifically, when completing on ``object.<tab>``.
1386 1390
1387 1391 When 2 [default]: all names that start with '_' will be excluded.
1388 1392
1389 1393 When 1: all 'magic' names (``__foo__``) will be excluded.
1390 1394
1391 1395 When 0: nothing will be excluded.
1392 1396 """
1393 1397 ).tag(config=True)
1394 1398 limit_to__all__ = Bool(False,
1395 1399 help="""
1396 1400 DEPRECATED as of version 5.0.
1397 1401
1398 1402 Instruct the completer to use __all__ for the completion
1399 1403
1400 1404 Specifically, when completing on ``object.<tab>``.
1401 1405
1402 1406 When True: only those names in obj.__all__ will be included.
1403 1407
1404 1408 When False [default]: the __all__ attribute is ignored
1405 1409 """,
1406 1410 ).tag(config=True)
1407 1411
1408 1412 profile_completions = Bool(
1409 1413 default_value=False,
1410 1414 help="If True, emit profiling data for completion subsystem using cProfile."
1411 1415 ).tag(config=True)
1412 1416
1413 1417 profiler_output_dir = Unicode(
1414 1418 default_value=".completion_profiles",
1415 1419 help="Template for path at which to output profile data for completions."
1416 1420 ).tag(config=True)
1417 1421
1418 1422 @observe('limit_to__all__')
1419 1423 def _limit_to_all_changed(self, change):
1420 1424 warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration '
1421 1425 'value has been deprecated since IPython 5.0, will be made to have '
1422 1426 'no effects and then removed in future version of IPython.',
1423 1427 UserWarning)
1424 1428
1425 1429 def __init__(
1426 1430 self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs
1427 1431 ):
1428 1432 """IPCompleter() -> completer
1429 1433
1430 1434 Return a completer object.
1431 1435
1432 1436 Parameters
1433 1437 ----------
1434 1438 shell
1435 1439 a pointer to the ipython shell itself. This is needed
1436 1440 because this completer knows about magic functions, and those can
1437 1441 only be accessed via the ipython instance.
1438 1442 namespace : dict, optional
1439 1443 an optional dict where completions are performed.
1440 1444 global_namespace : dict, optional
1441 1445 secondary optional dict for completions, to
1442 1446 handle cases (such as IPython embedded inside functions) where
1443 1447 both Python scopes are visible.
1444 1448 config : Config
1445 1449 traitlet's config object
1446 1450 **kwargs
1447 1451 passed to super class unmodified.
1448 1452 """
1449 1453
1450 1454 self.magic_escape = ESC_MAGIC
1451 1455 self.splitter = CompletionSplitter()
1452 1456
1453 1457 # _greedy_changed() depends on splitter and readline being defined:
1454 1458 super().__init__(
1455 1459 namespace=namespace,
1456 1460 global_namespace=global_namespace,
1457 1461 config=config,
1458 1462 **kwargs,
1459 1463 )
1460 1464
1461 1465 # List where completion matches will be stored
1462 1466 self.matches = []
1463 1467 self.shell = shell
1464 1468 # Regexp to split filenames with spaces in them
1465 1469 self.space_name_re = re.compile(r'([^\\] )')
1466 1470 # Hold a local ref. to glob.glob for speed
1467 1471 self.glob = glob.glob
1468 1472
1469 1473 # Determine if we are running on 'dumb' terminals, like (X)Emacs
1470 1474 # buffers, to avoid completion problems.
1471 1475 term = os.environ.get('TERM','xterm')
1472 1476 self.dumb_terminal = term in ['dumb','emacs']
1473 1477
1474 1478 # Special handling of backslashes needed in win32 platforms
1475 1479 if sys.platform == "win32":
1476 1480 self.clean_glob = self._clean_glob_win32
1477 1481 else:
1478 1482 self.clean_glob = self._clean_glob
1479 1483
1480 1484 #regexp to parse docstring for function signature
1481 1485 self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1482 1486 self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1483 1487 #use this if positional argument name is also needed
1484 1488 #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)')
1485 1489
1486 1490 self.magic_arg_matchers = [
1487 1491 self.magic_config_matcher,
1488 1492 self.magic_color_matcher,
1489 1493 ]
1490 1494
1491 1495 # This is set externally by InteractiveShell
1492 1496 self.custom_completers = None
1493 1497
1494 1498 # This is a list of names of unicode characters that can be completed
1495 1499 # into their corresponding unicode value. The list is large, so we
1496 1500 # lazily initialize it on first use. Consuming code should access this
1497 1501 # attribute through the `@unicode_names` property.
1498 1502 self._unicode_names = None
1499 1503
1500 1504 self._backslash_combining_matchers = [
1501 1505 self.latex_name_matcher,
1502 1506 self.unicode_name_matcher,
1503 1507 back_latex_name_matcher,
1504 1508 back_unicode_name_matcher,
1505 1509 self.fwd_unicode_matcher,
1506 1510 ]
1507 1511
1508 1512 if not self.backslash_combining_completions:
1509 1513 for matcher in self._backslash_combining_matchers:
1510 1514 self.disable_matchers.append(matcher.matcher_identifier)
1511 1515
1512 1516 if not self.merge_completions:
1513 1517 self.suppress_competing_matchers = True
1514 1518
1515 1519 @property
1516 1520 def matchers(self) -> List[Matcher]:
1517 1521 """All active matcher routines for completion"""
1518 1522 if self.dict_keys_only:
1519 1523 return [self.dict_key_matcher]
1520 1524
1521 1525 if self.use_jedi:
1522 1526 return [
1523 1527 *self.custom_matchers,
1524 1528 *self._backslash_combining_matchers,
1525 1529 *self.magic_arg_matchers,
1526 1530 self.custom_completer_matcher,
1527 1531 self.magic_matcher,
1528 1532 self._jedi_matcher,
1529 1533 self.dict_key_matcher,
1530 1534 self.file_matcher,
1531 1535 ]
1532 1536 else:
1533 1537 return [
1534 1538 *self.custom_matchers,
1535 1539 *self._backslash_combining_matchers,
1536 1540 *self.magic_arg_matchers,
1537 1541 self.custom_completer_matcher,
1538 1542 self.dict_key_matcher,
1539 1543 # TODO: convert python_matches to v2 API
1540 1544 self.magic_matcher,
1541 1545 self.python_matches,
1542 1546 self.file_matcher,
1543 1547 self.python_func_kw_matcher,
1544 1548 ]
1545 1549
1546 1550 def all_completions(self, text:str) -> List[str]:
1547 1551 """
1548 1552 Wrapper around the completion methods for the benefit of emacs.
1549 1553 """
1550 1554 prefix = text.rpartition('.')[0]
1551 1555 with provisionalcompleter():
1552 1556 return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text
1553 1557 for c in self.completions(text, len(text))]
1554 1558
1555 1559 return self.complete(text)[1]
1556 1560
1557 1561 def _clean_glob(self, text:str):
1558 1562 return self.glob("%s*" % text)
1559 1563
1560 1564 def _clean_glob_win32(self, text:str):
1561 1565 return [f.replace("\\","/")
1562 1566 for f in self.glob("%s*" % text)]
1563 1567
1564 1568 @context_matcher()
1565 1569 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1566 1570 """Same as ``file_matches``, but adopted to new Matcher API."""
1567 1571 matches = self.file_matches(context.token)
1568 1572 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1569 1573 # starts with `/home/`, `C:\`, etc)
1570 1574 return _convert_matcher_v1_result_to_v2(matches, type="path")
1571 1575
1572 1576 def file_matches(self, text: str) -> List[str]:
1573 1577 """Match filenames, expanding ~USER type strings.
1574 1578
1575 1579 Most of the seemingly convoluted logic in this completer is an
1576 1580 attempt to handle filenames with spaces in them. And yet it's not
1577 1581 quite perfect, because Python's readline doesn't expose all of the
1578 1582 GNU readline details needed for this to be done correctly.
1579 1583
1580 1584 For a filename with a space in it, the printed completions will be
1581 1585 only the parts after what's already been typed (instead of the
1582 1586 full completions, as is normally done). I don't think with the
1583 1587 current (as of Python 2.3) Python readline it's possible to do
1584 1588 better.
1585 1589
1586 1590 DEPRECATED: Deprecated since 8.6. Use ``file_matcher`` instead.
1587 1591 """
1588 1592
1589 1593 # chars that require escaping with backslash - i.e. chars
1590 1594 # that readline treats incorrectly as delimiters, but we
1591 1595 # don't want to treat as delimiters in filename matching
1592 1596 # when escaped with backslash
1593 1597 if text.startswith('!'):
1594 1598 text = text[1:]
1595 1599 text_prefix = u'!'
1596 1600 else:
1597 1601 text_prefix = u''
1598 1602
1599 1603 text_until_cursor = self.text_until_cursor
1600 1604 # track strings with open quotes
1601 1605 open_quotes = has_open_quotes(text_until_cursor)
1602 1606
1603 1607 if '(' in text_until_cursor or '[' in text_until_cursor:
1604 1608 lsplit = text
1605 1609 else:
1606 1610 try:
1607 1611 # arg_split ~ shlex.split, but with unicode bugs fixed by us
1608 1612 lsplit = arg_split(text_until_cursor)[-1]
1609 1613 except ValueError:
1610 1614 # typically an unmatched ", or backslash without escaped char.
1611 1615 if open_quotes:
1612 1616 lsplit = text_until_cursor.split(open_quotes)[-1]
1613 1617 else:
1614 1618 return []
1615 1619 except IndexError:
1616 1620 # tab pressed on empty line
1617 1621 lsplit = ""
1618 1622
1619 1623 if not open_quotes and lsplit != protect_filename(lsplit):
1620 1624 # if protectables are found, do matching on the whole escaped name
1621 1625 has_protectables = True
1622 1626 text0,text = text,lsplit
1623 1627 else:
1624 1628 has_protectables = False
1625 1629 text = os.path.expanduser(text)
1626 1630
1627 1631 if text == "":
1628 1632 return [text_prefix + protect_filename(f) for f in self.glob("*")]
1629 1633
1630 1634 # Compute the matches from the filesystem
1631 1635 if sys.platform == 'win32':
1632 1636 m0 = self.clean_glob(text)
1633 1637 else:
1634 1638 m0 = self.clean_glob(text.replace('\\', ''))
1635 1639
1636 1640 if has_protectables:
1637 1641 # If we had protectables, we need to revert our changes to the
1638 1642 # beginning of filename so that we don't double-write the part
1639 1643 # of the filename we have so far
1640 1644 len_lsplit = len(lsplit)
1641 1645 matches = [text_prefix + text0 +
1642 1646 protect_filename(f[len_lsplit:]) for f in m0]
1643 1647 else:
1644 1648 if open_quotes:
1645 1649 # if we have a string with an open quote, we don't need to
1646 1650 # protect the names beyond the quote (and we _shouldn't_, as
1647 1651 # it would cause bugs when the filesystem call is made).
1648 1652 matches = m0 if sys.platform == "win32" else\
1649 1653 [protect_filename(f, open_quotes) for f in m0]
1650 1654 else:
1651 1655 matches = [text_prefix +
1652 1656 protect_filename(f) for f in m0]
1653 1657
1654 1658 # Mark directories in input list by appending '/' to their names.
1655 1659 return [x+'/' if os.path.isdir(x) else x for x in matches]
1656 1660
1657 1661 @context_matcher()
1658 1662 def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1659 1663 text = context.token
1660 1664 matches = self.magic_matches(text)
1661 1665 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
1662 1666 is_magic_prefix = len(text) > 0 and text[0] == "%"
1663 1667 result["suppress"] = is_magic_prefix and bool(result["completions"])
1664 1668 return result
1665 1669
1666 1670 def magic_matches(self, text: str):
1667 1671 """Match magics.
1668 1672
1669 1673 DEPRECATED: Deprecated since 8.6. Use ``magic_matcher`` instead.
1670 1674 """
1671 1675 # Get all shell magics now rather than statically, so magics loaded at
1672 1676 # runtime show up too.
1673 1677 lsm = self.shell.magics_manager.lsmagic()
1674 1678 line_magics = lsm['line']
1675 1679 cell_magics = lsm['cell']
1676 1680 pre = self.magic_escape
1677 1681 pre2 = pre+pre
1678 1682
1679 1683 explicit_magic = text.startswith(pre)
1680 1684
1681 1685 # Completion logic:
1682 1686 # - user gives %%: only do cell magics
1683 1687 # - user gives %: do both line and cell magics
1684 1688 # - no prefix: do both
1685 1689 # In other words, line magics are skipped if the user gives %% explicitly
1686 1690 #
1687 1691 # We also exclude magics that match any currently visible names:
1688 1692 # https://github.com/ipython/ipython/issues/4877, unless the user has
1689 1693 # typed a %:
1690 1694 # https://github.com/ipython/ipython/issues/10754
1691 1695 bare_text = text.lstrip(pre)
1692 1696 global_matches = self.global_matches(bare_text)
1693 1697 if not explicit_magic:
1694 1698 def matches(magic):
1695 1699 """
1696 1700 Filter magics, in particular remove magics that match
1697 1701 a name present in global namespace.
1698 1702 """
1699 1703 return ( magic.startswith(bare_text) and
1700 1704 magic not in global_matches )
1701 1705 else:
1702 1706 def matches(magic):
1703 1707 return magic.startswith(bare_text)
1704 1708
1705 1709 comp = [ pre2+m for m in cell_magics if matches(m)]
1706 1710 if not text.startswith(pre2):
1707 1711 comp += [ pre+m for m in line_magics if matches(m)]
1708 1712
1709 1713 return comp
1710 1714
1711 1715 @context_matcher()
1712 1716 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1713 1717 """Match class names and attributes for %config magic."""
1714 1718 # NOTE: uses `line_buffer` equivalent for compatibility
1715 1719 matches = self.magic_config_matches(context.line_with_cursor)
1716 1720 return _convert_matcher_v1_result_to_v2(matches, type="param")
1717 1721
1718 1722 def magic_config_matches(self, text: str) -> List[str]:
1719 1723 """Match class names and attributes for %config magic.
1720 1724
1721 1725 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1722 1726 """
1723 1727 texts = text.strip().split()
1724 1728
1725 1729 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
1726 1730 # get all configuration classes
1727 1731 classes = sorted(set([ c for c in self.shell.configurables
1728 1732 if c.__class__.class_traits(config=True)
1729 1733 ]), key=lambda x: x.__class__.__name__)
1730 1734 classnames = [ c.__class__.__name__ for c in classes ]
1731 1735
1732 1736 # return all classnames if config or %config is given
1733 1737 if len(texts) == 1:
1734 1738 return classnames
1735 1739
1736 1740 # match classname
1737 1741 classname_texts = texts[1].split('.')
1738 1742 classname = classname_texts[0]
1739 1743 classname_matches = [ c for c in classnames
1740 1744 if c.startswith(classname) ]
1741 1745
1742 1746 # return matched classes or the matched class with attributes
1743 1747 if texts[1].find('.') < 0:
1744 1748 return classname_matches
1745 1749 elif len(classname_matches) == 1 and \
1746 1750 classname_matches[0] == classname:
1747 1751 cls = classes[classnames.index(classname)].__class__
1748 1752 help = cls.class_get_help()
1749 1753 # strip leading '--' from cl-args:
1750 1754 help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
1751 1755 return [ attr.split('=')[0]
1752 1756 for attr in help.strip().splitlines()
1753 1757 if attr.startswith(texts[1]) ]
1754 1758 return []
1755 1759
1756 1760 @context_matcher()
1757 1761 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1758 1762 """Match color schemes for %colors magic."""
1759 1763 # NOTE: uses `line_buffer` equivalent for compatibility
1760 1764 matches = self.magic_color_matches(context.line_with_cursor)
1761 1765 return _convert_matcher_v1_result_to_v2(matches, type="param")
1762 1766
1763 1767 def magic_color_matches(self, text: str) -> List[str]:
1764 1768 """Match color schemes for %colors magic.
1765 1769
1766 1770 DEPRECATED: Deprecated since 8.6. Use ``magic_color_matcher`` instead.
1767 1771 """
1768 1772 texts = text.split()
1769 1773 if text.endswith(' '):
1770 1774 # .split() strips off the trailing whitespace. Add '' back
1771 1775 # so that: '%colors ' -> ['%colors', '']
1772 1776 texts.append('')
1773 1777
1774 1778 if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'):
1775 1779 prefix = texts[1]
1776 1780 return [ color for color in InspectColors.keys()
1777 1781 if color.startswith(prefix) ]
1778 1782 return []
1779 1783
1780 1784 @context_matcher(identifier="IPCompleter.jedi_matcher")
1781 1785 def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult:
1782 1786 matches = self._jedi_matches(
1783 1787 cursor_column=context.cursor_position,
1784 1788 cursor_line=context.cursor_line,
1785 1789 text=context.full_text,
1786 1790 )
1787 1791 return {
1788 1792 "completions": matches,
1789 1793 # static analysis should not suppress other matchers
1790 1794 "suppress": False,
1791 1795 }
1792 1796
1793 1797 def _jedi_matches(
1794 1798 self, cursor_column: int, cursor_line: int, text: str
1795 1799 ) -> Iterable[_JediCompletionLike]:
1796 1800 """
1797 1801 Return a list of :any:`jedi.api.Completion`s object from a ``text`` and
1798 1802 cursor position.
1799 1803
1800 1804 Parameters
1801 1805 ----------
1802 1806 cursor_column : int
1803 1807 column position of the cursor in ``text``, 0-indexed.
1804 1808 cursor_line : int
1805 1809 line position of the cursor in ``text``, 0-indexed
1806 1810 text : str
1807 1811 text to complete
1808 1812
1809 1813 Notes
1810 1814 -----
1811 1815 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1812 1816 object containing a string with the Jedi debug information attached.
1813 1817
1814 1818 DEPRECATED: Deprecated since 8.6. Use ``_jedi_matcher`` instead.
1815 1819 """
1816 1820 namespaces = [self.namespace]
1817 1821 if self.global_namespace is not None:
1818 1822 namespaces.append(self.global_namespace)
1819 1823
1820 1824 completion_filter = lambda x:x
1821 1825 offset = cursor_to_position(text, cursor_line, cursor_column)
1822 1826 # filter output if we are completing for object members
1823 1827 if offset:
1824 1828 pre = text[offset-1]
1825 1829 if pre == '.':
1826 1830 if self.omit__names == 2:
1827 1831 completion_filter = lambda c:not c.name.startswith('_')
1828 1832 elif self.omit__names == 1:
1829 1833 completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__'))
1830 1834 elif self.omit__names == 0:
1831 1835 completion_filter = lambda x:x
1832 1836 else:
1833 1837 raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names))
1834 1838
1835 1839 interpreter = jedi.Interpreter(text[:offset], namespaces)
1836 1840 try_jedi = True
1837 1841
1838 1842 try:
1839 1843 # find the first token in the current tree -- if it is a ' or " then we are in a string
1840 1844 completing_string = False
1841 1845 try:
1842 1846 first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value'))
1843 1847 except StopIteration:
1844 1848 pass
1845 1849 else:
1846 1850 # note the value may be ', ", or it may also be ''' or """, or
1847 1851 # in some cases, """what/you/typed..., but all of these are
1848 1852 # strings.
1849 1853 completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'}
1850 1854
1851 1855 # if we are in a string jedi is likely not the right candidate for
1852 1856 # now. Skip it.
1853 1857 try_jedi = not completing_string
1854 1858 except Exception as e:
1855 1859 # many of things can go wrong, we are using private API just don't crash.
1856 1860 if self.debug:
1857 1861 print("Error detecting if completing a non-finished string :", e, '|')
1858 1862
1859 1863 if not try_jedi:
1860 1864 return []
1861 1865 try:
1862 1866 return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1))
1863 1867 except Exception as e:
1864 1868 if self.debug:
1865 1869 return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))]
1866 1870 else:
1867 1871 return []
1868 1872
1869 1873 def python_matches(self, text:str)->List[str]:
1870 1874 """Match attributes or global python names"""
1871 1875 if "." in text:
1872 1876 try:
1873 1877 matches = self.attr_matches(text)
1874 1878 if text.endswith('.') and self.omit__names:
1875 1879 if self.omit__names == 1:
1876 1880 # true if txt is _not_ a __ name, false otherwise:
1877 1881 no__name = (lambda txt:
1878 1882 re.match(r'.*\.__.*?__',txt) is None)
1879 1883 else:
1880 1884 # true if txt is _not_ a _ name, false otherwise:
1881 1885 no__name = (lambda txt:
1882 1886 re.match(r'\._.*?',txt[txt.rindex('.'):]) is None)
1883 1887 matches = filter(no__name, matches)
1884 1888 except NameError:
1885 1889 # catches <undefined attributes>.<tab>
1886 1890 matches = []
1887 1891 else:
1888 1892 matches = self.global_matches(text)
1889 1893 return matches
1890 1894
1891 1895 def _default_arguments_from_docstring(self, doc):
1892 1896 """Parse the first line of docstring for call signature.
1893 1897
1894 1898 Docstring should be of the form 'min(iterable[, key=func])\n'.
1895 1899 It can also parse cython docstring of the form
1896 1900 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'.
1897 1901 """
1898 1902 if doc is None:
1899 1903 return []
1900 1904
1901 1905 #care only the firstline
1902 1906 line = doc.lstrip().splitlines()[0]
1903 1907
1904 1908 #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*')
1905 1909 #'min(iterable[, key=func])\n' -> 'iterable[, key=func]'
1906 1910 sig = self.docstring_sig_re.search(line)
1907 1911 if sig is None:
1908 1912 return []
1909 1913 # iterable[, key=func]' -> ['iterable[' ,' key=func]']
1910 1914 sig = sig.groups()[0].split(',')
1911 1915 ret = []
1912 1916 for s in sig:
1913 1917 #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)')
1914 1918 ret += self.docstring_kwd_re.findall(s)
1915 1919 return ret
1916 1920
1917 1921 def _default_arguments(self, obj):
1918 1922 """Return the list of default arguments of obj if it is callable,
1919 1923 or empty list otherwise."""
1920 1924 call_obj = obj
1921 1925 ret = []
1922 1926 if inspect.isbuiltin(obj):
1923 1927 pass
1924 1928 elif not (inspect.isfunction(obj) or inspect.ismethod(obj)):
1925 1929 if inspect.isclass(obj):
1926 1930 #for cython embedsignature=True the constructor docstring
1927 1931 #belongs to the object itself not __init__
1928 1932 ret += self._default_arguments_from_docstring(
1929 1933 getattr(obj, '__doc__', ''))
1930 1934 # for classes, check for __init__,__new__
1931 1935 call_obj = (getattr(obj, '__init__', None) or
1932 1936 getattr(obj, '__new__', None))
1933 1937 # for all others, check if they are __call__able
1934 1938 elif hasattr(obj, '__call__'):
1935 1939 call_obj = obj.__call__
1936 1940 ret += self._default_arguments_from_docstring(
1937 1941 getattr(call_obj, '__doc__', ''))
1938 1942
1939 1943 _keeps = (inspect.Parameter.KEYWORD_ONLY,
1940 1944 inspect.Parameter.POSITIONAL_OR_KEYWORD)
1941 1945
1942 1946 try:
1943 1947 sig = inspect.signature(obj)
1944 1948 ret.extend(k for k, v in sig.parameters.items() if
1945 1949 v.kind in _keeps)
1946 1950 except ValueError:
1947 1951 pass
1948 1952
1949 1953 return list(set(ret))
1950 1954
1951 1955 @context_matcher()
1952 1956 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1953 1957 """Match named parameters (kwargs) of the last open function."""
1954 1958 matches = self.python_func_kw_matches(context.token)
1955 1959 return _convert_matcher_v1_result_to_v2(matches, type="param")
1956 1960
1957 1961 def python_func_kw_matches(self, text):
1958 1962 """Match named parameters (kwargs) of the last open function.
1959 1963
1960 1964 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1961 1965 """
1962 1966
1963 1967 if "." in text: # a parameter cannot be dotted
1964 1968 return []
1965 1969 try: regexp = self.__funcParamsRegex
1966 1970 except AttributeError:
1967 1971 regexp = self.__funcParamsRegex = re.compile(r'''
1968 1972 '.*?(?<!\\)' | # single quoted strings or
1969 1973 ".*?(?<!\\)" | # double quoted strings or
1970 1974 \w+ | # identifier
1971 1975 \S # other characters
1972 1976 ''', re.VERBOSE | re.DOTALL)
1973 1977 # 1. find the nearest identifier that comes before an unclosed
1974 1978 # parenthesis before the cursor
1975 1979 # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo"
1976 1980 tokens = regexp.findall(self.text_until_cursor)
1977 1981 iterTokens = reversed(tokens); openPar = 0
1978 1982
1979 1983 for token in iterTokens:
1980 1984 if token == ')':
1981 1985 openPar -= 1
1982 1986 elif token == '(':
1983 1987 openPar += 1
1984 1988 if openPar > 0:
1985 1989 # found the last unclosed parenthesis
1986 1990 break
1987 1991 else:
1988 1992 return []
1989 1993 # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" )
1990 1994 ids = []
1991 1995 isId = re.compile(r'\w+$').match
1992 1996
1993 1997 while True:
1994 1998 try:
1995 1999 ids.append(next(iterTokens))
1996 2000 if not isId(ids[-1]):
1997 2001 ids.pop(); break
1998 2002 if not next(iterTokens) == '.':
1999 2003 break
2000 2004 except StopIteration:
2001 2005 break
2002 2006
2003 2007 # Find all named arguments already assigned to, as to avoid suggesting
2004 2008 # them again
2005 2009 usedNamedArgs = set()
2006 2010 par_level = -1
2007 2011 for token, next_token in zip(tokens, tokens[1:]):
2008 2012 if token == '(':
2009 2013 par_level += 1
2010 2014 elif token == ')':
2011 2015 par_level -= 1
2012 2016
2013 2017 if par_level != 0:
2014 2018 continue
2015 2019
2016 2020 if next_token != '=':
2017 2021 continue
2018 2022
2019 2023 usedNamedArgs.add(token)
2020 2024
2021 2025 argMatches = []
2022 2026 try:
2023 2027 callableObj = '.'.join(ids[::-1])
2024 2028 namedArgs = self._default_arguments(eval(callableObj,
2025 2029 self.namespace))
2026 2030
2027 2031 # Remove used named arguments from the list, no need to show twice
2028 2032 for namedArg in set(namedArgs) - usedNamedArgs:
2029 2033 if namedArg.startswith(text):
2030 2034 argMatches.append("%s=" %namedArg)
2031 2035 except:
2032 2036 pass
2033 2037
2034 2038 return argMatches
2035 2039
2036 2040 @staticmethod
2037 2041 def _get_keys(obj: Any) -> List[Any]:
2038 2042 # Objects can define their own completions by defining an
2039 2043 # _ipy_key_completions_() method.
2040 2044 method = get_real_method(obj, '_ipython_key_completions_')
2041 2045 if method is not None:
2042 2046 return method()
2043 2047
2044 2048 # Special case some common in-memory dict-like types
2045 2049 if isinstance(obj, dict) or\
2046 2050 _safe_isinstance(obj, 'pandas', 'DataFrame'):
2047 2051 try:
2048 2052 return list(obj.keys())
2049 2053 except Exception:
2050 2054 return []
2051 2055 elif _safe_isinstance(obj, 'numpy', 'ndarray') or\
2052 2056 _safe_isinstance(obj, 'numpy', 'void'):
2053 2057 return obj.dtype.names or []
2054 2058 return []
2055 2059
2056 2060 @context_matcher()
2057 2061 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2058 2062 """Match string keys in a dictionary, after e.g. ``foo[``."""
2059 2063 matches = self.dict_key_matches(context.token)
2060 2064 return _convert_matcher_v1_result_to_v2(
2061 2065 matches, type="dict key", suppress_if_matches=True
2062 2066 )
2063 2067
2064 2068 def dict_key_matches(self, text: str) -> List[str]:
2065 2069 """Match string keys in a dictionary, after e.g. ``foo[``.
2066 2070
2067 2071 DEPRECATED: Deprecated since 8.6. Use `dict_key_matcher` instead.
2068 2072 """
2069 2073
2070 2074 if self.__dict_key_regexps is not None:
2071 2075 regexps = self.__dict_key_regexps
2072 2076 else:
2073 2077 dict_key_re_fmt = r'''(?x)
2074 2078 ( # match dict-referring expression wrt greedy setting
2075 2079 %s
2076 2080 )
2077 2081 \[ # open bracket
2078 2082 \s* # and optional whitespace
2079 2083 # Capture any number of str-like objects (e.g. "a", "b", 'c')
2080 2084 ((?:[uUbB]? # string prefix (r not handled)
2081 2085 (?:
2082 2086 '(?:[^']|(?<!\\)\\')*'
2083 2087 |
2084 2088 "(?:[^"]|(?<!\\)\\")*"
2085 2089 )
2086 2090 \s*,\s*
2087 2091 )*)
2088 2092 ([uUbB]? # string prefix (r not handled)
2089 2093 (?: # unclosed string
2090 2094 '(?:[^']|(?<!\\)\\')*
2091 2095 |
2092 2096 "(?:[^"]|(?<!\\)\\")*
2093 2097 )
2094 2098 )?
2095 2099 $
2096 2100 '''
2097 2101 regexps = self.__dict_key_regexps = {
2098 2102 False: re.compile(dict_key_re_fmt % r'''
2099 2103 # identifiers separated by .
2100 2104 (?!\d)\w+
2101 2105 (?:\.(?!\d)\w+)*
2102 2106 '''),
2103 2107 True: re.compile(dict_key_re_fmt % '''
2104 2108 .+
2105 2109 ''')
2106 2110 }
2107 2111
2108 2112 match = regexps[self.greedy].search(self.text_until_cursor)
2109 2113
2110 2114 if match is None:
2111 2115 return []
2112 2116
2113 2117 expr, prefix0, prefix = match.groups()
2114 2118 try:
2115 2119 obj = eval(expr, self.namespace)
2116 2120 except Exception:
2117 2121 try:
2118 2122 obj = eval(expr, self.global_namespace)
2119 2123 except Exception:
2120 2124 return []
2121 2125
2122 2126 keys = self._get_keys(obj)
2123 2127 if not keys:
2124 2128 return keys
2125 2129
2126 2130 extra_prefix = eval(prefix0) if prefix0 != '' else None
2127 2131
2128 2132 closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix)
2129 2133 if not matches:
2130 2134 return matches
2131 2135
2132 2136 # get the cursor position of
2133 2137 # - the text being completed
2134 2138 # - the start of the key text
2135 2139 # - the start of the completion
2136 2140 text_start = len(self.text_until_cursor) - len(text)
2137 2141 if prefix:
2138 2142 key_start = match.start(3)
2139 2143 completion_start = key_start + token_offset
2140 2144 else:
2141 2145 key_start = completion_start = match.end()
2142 2146
2143 2147 # grab the leading prefix, to make sure all completions start with `text`
2144 2148 if text_start > key_start:
2145 2149 leading = ''
2146 2150 else:
2147 2151 leading = text[text_start:completion_start]
2148 2152
2149 2153 # the index of the `[` character
2150 2154 bracket_idx = match.end(1)
2151 2155
2152 2156 # append closing quote and bracket as appropriate
2153 2157 # this is *not* appropriate if the opening quote or bracket is outside
2154 2158 # the text given to this method
2155 2159 suf = ''
2156 2160 continuation = self.line_buffer[len(self.text_until_cursor):]
2157 2161 if key_start > text_start and closing_quote:
2158 2162 # quotes were opened inside text, maybe close them
2159 2163 if continuation.startswith(closing_quote):
2160 2164 continuation = continuation[len(closing_quote):]
2161 2165 else:
2162 2166 suf += closing_quote
2163 2167 if bracket_idx > text_start:
2164 2168 # brackets were opened inside text, maybe close them
2165 2169 if not continuation.startswith(']'):
2166 2170 suf += ']'
2167 2171
2168 2172 return [leading + k + suf for k in matches]
2169 2173
2170 2174 @context_matcher()
2171 2175 def unicode_name_matcher(self, context):
2172 2176 fragment, matches = self.unicode_name_matches(context.token)
2173 2177 return _convert_matcher_v1_result_to_v2(
2174 2178 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2175 2179 )
2176 2180
2177 2181 @staticmethod
2178 2182 def unicode_name_matches(text: str) -> Tuple[str, List[str]]:
2179 2183 """Match Latex-like syntax for unicode characters base
2180 2184 on the name of the character.
2181 2185
2182 2186 This does ``\\GREEK SMALL LETTER ETA`` -> ``η``
2183 2187
2184 2188 Works only on valid python 3 identifier, or on combining characters that
2185 2189 will combine to form a valid identifier.
2186 2190 """
2187 2191 slashpos = text.rfind('\\')
2188 2192 if slashpos > -1:
2189 2193 s = text[slashpos+1:]
2190 2194 try :
2191 2195 unic = unicodedata.lookup(s)
2192 2196 # allow combining chars
2193 2197 if ('a'+unic).isidentifier():
2194 2198 return '\\'+s,[unic]
2195 2199 except KeyError:
2196 2200 pass
2197 2201 return '', []
2198 2202
2199 2203 @context_matcher()
2200 2204 def latex_name_matcher(self, context):
2201 2205 """Match Latex syntax for unicode characters.
2202 2206
2203 2207 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2204 2208 """
2205 2209 fragment, matches = self.latex_matches(context.token)
2206 2210 return _convert_matcher_v1_result_to_v2(
2207 2211 matches, type="latex", fragment=fragment, suppress_if_matches=True
2208 2212 )
2209 2213
2210 2214 def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]:
2211 2215 """Match Latex syntax for unicode characters.
2212 2216
2213 2217 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α``
2214 2218
2215 2219 DEPRECATED: Deprecated since 8.6. Use `latex_matcher` instead.
2216 2220 """
2217 2221 slashpos = text.rfind('\\')
2218 2222 if slashpos > -1:
2219 2223 s = text[slashpos:]
2220 2224 if s in latex_symbols:
2221 2225 # Try to complete a full latex symbol to unicode
2222 2226 # \\alpha -> α
2223 2227 return s, [latex_symbols[s]]
2224 2228 else:
2225 2229 # If a user has partially typed a latex symbol, give them
2226 2230 # a full list of options \al -> [\aleph, \alpha]
2227 2231 matches = [k for k in latex_symbols if k.startswith(s)]
2228 2232 if matches:
2229 2233 return s, matches
2230 2234 return '', ()
2231 2235
2232 2236 @context_matcher()
2233 2237 def custom_completer_matcher(self, context):
2234 2238 matches = self.dispatch_custom_completer(context.token) or []
2235 2239 result = _convert_matcher_v1_result_to_v2(
2236 2240 matches, type="<unknown>", suppress_if_matches=True
2237 2241 )
2238 2242 result["ordered"] = True
2239 2243 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2240 2244 return result
2241 2245
2242 2246 def dispatch_custom_completer(self, text):
2243 2247 """
2244 2248 DEPRECATED: Deprecated since 8.6. Use `custom_completer_matcher` instead.
2245 2249 """
2246 2250 if not self.custom_completers:
2247 2251 return
2248 2252
2249 2253 line = self.line_buffer
2250 2254 if not line.strip():
2251 2255 return None
2252 2256
2253 2257 # Create a little structure to pass all the relevant information about
2254 2258 # the current completion to any custom completer.
2255 2259 event = SimpleNamespace()
2256 2260 event.line = line
2257 2261 event.symbol = text
2258 2262 cmd = line.split(None,1)[0]
2259 2263 event.command = cmd
2260 2264 event.text_until_cursor = self.text_until_cursor
2261 2265
2262 2266 # for foo etc, try also to find completer for %foo
2263 2267 if not cmd.startswith(self.magic_escape):
2264 2268 try_magic = self.custom_completers.s_matches(
2265 2269 self.magic_escape + cmd)
2266 2270 else:
2267 2271 try_magic = []
2268 2272
2269 2273 for c in itertools.chain(self.custom_completers.s_matches(cmd),
2270 2274 try_magic,
2271 2275 self.custom_completers.flat_matches(self.text_until_cursor)):
2272 2276 try:
2273 2277 res = c(event)
2274 2278 if res:
2275 2279 # first, try case sensitive match
2276 2280 withcase = [r for r in res if r.startswith(text)]
2277 2281 if withcase:
2278 2282 return withcase
2279 2283 # if none, then case insensitive ones are ok too
2280 2284 text_low = text.lower()
2281 2285 return [r for r in res if r.lower().startswith(text_low)]
2282 2286 except TryNext:
2283 2287 pass
2284 2288 except KeyboardInterrupt:
2285 2289 """
2286 2290 If custom completer take too long,
2287 2291 let keyboard interrupt abort and return nothing.
2288 2292 """
2289 2293 break
2290 2294
2291 2295 return None
2292 2296
2293 2297 def completions(self, text: str, offset: int)->Iterator[Completion]:
2294 2298 """
2295 2299 Returns an iterator over the possible completions
2296 2300
2297 2301 .. warning::
2298 2302
2299 2303 Unstable
2300 2304
2301 2305 This function is unstable, API may change without warning.
2302 2306 It will also raise unless use in proper context manager.
2303 2307
2304 2308 Parameters
2305 2309 ----------
2306 2310 text : str
2307 2311 Full text of the current input, multi line string.
2308 2312 offset : int
2309 2313 Integer representing the position of the cursor in ``text``. Offset
2310 2314 is 0-based indexed.
2311 2315
2312 2316 Yields
2313 2317 ------
2314 2318 Completion
2315 2319
2316 2320 Notes
2317 2321 -----
2318 2322 The cursor on a text can either be seen as being "in between"
2319 2323 characters or "On" a character depending on the interface visible to
2320 2324 the user. For consistency the cursor being on "in between" characters X
2321 2325 and Y is equivalent to the cursor being "on" character Y, that is to say
2322 2326 the character the cursor is on is considered as being after the cursor.
2323 2327
2324 2328 Combining characters may span more that one position in the
2325 2329 text.
2326 2330
2327 2331 .. note::
2328 2332
2329 2333 If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--``
2330 2334 fake Completion token to distinguish completion returned by Jedi
2331 2335 and usual IPython completion.
2332 2336
2333 2337 .. note::
2334 2338
2335 2339 Completions are not completely deduplicated yet. If identical
2336 2340 completions are coming from different sources this function does not
2337 2341 ensure that each completion object will only be present once.
2338 2342 """
2339 2343 warnings.warn("_complete is a provisional API (as of IPython 6.0). "
2340 2344 "It may change without warnings. "
2341 2345 "Use in corresponding context manager.",
2342 2346 category=ProvisionalCompleterWarning, stacklevel=2)
2343 2347
2344 2348 seen = set()
2345 2349 profiler:Optional[cProfile.Profile]
2346 2350 try:
2347 2351 if self.profile_completions:
2348 2352 import cProfile
2349 2353 profiler = cProfile.Profile()
2350 2354 profiler.enable()
2351 2355 else:
2352 2356 profiler = None
2353 2357
2354 2358 for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000):
2355 2359 if c and (c in seen):
2356 2360 continue
2357 2361 yield c
2358 2362 seen.add(c)
2359 2363 except KeyboardInterrupt:
2360 2364 """if completions take too long and users send keyboard interrupt,
2361 2365 do not crash and return ASAP. """
2362 2366 pass
2363 2367 finally:
2364 2368 if profiler is not None:
2365 2369 profiler.disable()
2366 2370 ensure_dir_exists(self.profiler_output_dir)
2367 2371 output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4()))
2368 2372 print("Writing profiler output to", output_path)
2369 2373 profiler.dump_stats(output_path)
2370 2374
2371 2375 def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]:
2372 2376 """
2373 2377 Core completion module.Same signature as :any:`completions`, with the
2374 2378 extra `timeout` parameter (in seconds).
2375 2379
2376 2380 Computing jedi's completion ``.type`` can be quite expensive (it is a
2377 2381 lazy property) and can require some warm-up, more warm up than just
2378 2382 computing the ``name`` of a completion. The warm-up can be :
2379 2383
2380 2384 - Long warm-up the first time a module is encountered after
2381 2385 install/update: actually build parse/inference tree.
2382 2386
2383 2387 - first time the module is encountered in a session: load tree from
2384 2388 disk.
2385 2389
2386 2390 We don't want to block completions for tens of seconds so we give the
2387 2391 completer a "budget" of ``_timeout`` seconds per invocation to compute
2388 2392 completions types, the completions that have not yet been computed will
2389 2393 be marked as "unknown" an will have a chance to be computed next round
2390 2394 are things get cached.
2391 2395
2392 2396 Keep in mind that Jedi is not the only thing treating the completion so
2393 2397 keep the timeout short-ish as if we take more than 0.3 second we still
2394 2398 have lots of processing to do.
2395 2399
2396 2400 """
2397 2401 deadline = time.monotonic() + _timeout
2398 2402
2399 2403 before = full_text[:offset]
2400 2404 cursor_line, cursor_column = position_to_cursor(full_text, offset)
2401 2405
2402 2406 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2403 2407
2404 2408 results = self._complete(
2405 2409 full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column
2406 2410 )
2407 2411 non_jedi_results: Dict[str, SimpleMatcherResult] = {
2408 2412 identifier: result
2409 2413 for identifier, result in results.items()
2410 2414 if identifier != jedi_matcher_id
2411 2415 }
2412 2416
2413 2417 jedi_matches = (
2414 2418 cast(results[jedi_matcher_id], _JediMatcherResult)["completions"]
2415 2419 if jedi_matcher_id in results
2416 2420 else ()
2417 2421 )
2418 2422
2419 2423 iter_jm = iter(jedi_matches)
2420 2424 if _timeout:
2421 2425 for jm in iter_jm:
2422 2426 try:
2423 2427 type_ = jm.type
2424 2428 except Exception:
2425 2429 if self.debug:
2426 2430 print("Error in Jedi getting type of ", jm)
2427 2431 type_ = None
2428 2432 delta = len(jm.name_with_symbols) - len(jm.complete)
2429 2433 if type_ == 'function':
2430 2434 signature = _make_signature(jm)
2431 2435 else:
2432 2436 signature = ''
2433 2437 yield Completion(start=offset - delta,
2434 2438 end=offset,
2435 2439 text=jm.name_with_symbols,
2436 2440 type=type_,
2437 2441 signature=signature,
2438 2442 _origin='jedi')
2439 2443
2440 2444 if time.monotonic() > deadline:
2441 2445 break
2442 2446
2443 2447 for jm in iter_jm:
2444 2448 delta = len(jm.name_with_symbols) - len(jm.complete)
2445 2449 yield Completion(
2446 2450 start=offset - delta,
2447 2451 end=offset,
2448 2452 text=jm.name_with_symbols,
2449 2453 type=_UNKNOWN_TYPE, # don't compute type for speed
2450 2454 _origin="jedi",
2451 2455 signature="",
2452 2456 )
2453 2457
2454 2458 # TODO:
2455 2459 # Suppress this, right now just for debug.
2456 2460 if jedi_matches and non_jedi_results and self.debug:
2457 2461 some_start_offset = before.rfind(
2458 2462 next(iter(non_jedi_results.values()))["matched_fragment"]
2459 2463 )
2460 2464 yield Completion(
2461 2465 start=some_start_offset,
2462 2466 end=offset,
2463 2467 text="--jedi/ipython--",
2464 2468 _origin="debug",
2465 2469 type="none",
2466 2470 signature="",
2467 2471 )
2468 2472
2469 2473 ordered = []
2470 2474 sortable = []
2471 2475
2472 2476 for origin, result in non_jedi_results.items():
2473 2477 matched_text = result["matched_fragment"]
2474 2478 start_offset = before.rfind(matched_text)
2475 2479 is_ordered = result.get("ordered", False)
2476 2480 container = ordered if is_ordered else sortable
2477 2481
2478 2482 # I'm unsure if this is always true, so let's assert and see if it
2479 2483 # crash
2480 2484 assert before.endswith(matched_text)
2481 2485
2482 2486 for simple_completion in result["completions"]:
2483 2487 completion = Completion(
2484 2488 start=start_offset,
2485 2489 end=offset,
2486 2490 text=simple_completion.text,
2487 2491 _origin=origin,
2488 2492 signature="",
2489 2493 type=simple_completion.type or _UNKNOWN_TYPE,
2490 2494 )
2491 2495 container.append(completion)
2492 2496
2493 2497 yield from list(self._deduplicate(ordered + self._sort(sortable)))[
2494 2498 :MATCHES_LIMIT
2495 2499 ]
2496 2500
2497 2501 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2498 2502 """Find completions for the given text and line context.
2499 2503
2500 2504 Note that both the text and the line_buffer are optional, but at least
2501 2505 one of them must be given.
2502 2506
2503 2507 Parameters
2504 2508 ----------
2505 2509 text : string, optional
2506 2510 Text to perform the completion on. If not given, the line buffer
2507 2511 is split using the instance's CompletionSplitter object.
2508 2512 line_buffer : string, optional
2509 2513 If not given, the completer attempts to obtain the current line
2510 2514 buffer via readline. This keyword allows clients which are
2511 2515 requesting for text completions in non-readline contexts to inform
2512 2516 the completer of the entire text.
2513 2517 cursor_pos : int, optional
2514 2518 Index of the cursor in the full line buffer. Should be provided by
2515 2519 remote frontends where kernel has no access to frontend state.
2516 2520
2517 2521 Returns
2518 2522 -------
2519 2523 Tuple of two items:
2520 2524 text : str
2521 2525 Text that was actually used in the completion.
2522 2526 matches : list
2523 2527 A list of completion matches.
2524 2528
2525 2529 Notes
2526 2530 -----
2527 2531 This API is likely to be deprecated and replaced by
2528 2532 :any:`IPCompleter.completions` in the future.
2529 2533
2530 2534 """
2531 2535 warnings.warn('`Completer.complete` is pending deprecation since '
2532 2536 'IPython 6.0 and will be replaced by `Completer.completions`.',
2533 2537 PendingDeprecationWarning)
2534 2538 # potential todo, FOLD the 3rd throw away argument of _complete
2535 2539 # into the first 2 one.
2536 2540 # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?)
2537 2541 # TODO: should we deprecate now, or does it stay?
2538 2542
2539 2543 results = self._complete(
2540 2544 line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0
2541 2545 )
2542 2546
2543 2547 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2544 2548
2545 2549 return self._arrange_and_extract(
2546 2550 results,
2547 2551 # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version?
2548 2552 skip_matchers={jedi_matcher_id},
2549 2553 # this API does not support different start/end positions (fragments of token).
2550 2554 abort_if_offset_changes=True,
2551 2555 )
2552 2556
2553 2557 def _arrange_and_extract(
2554 2558 self,
2555 2559 results: Dict[str, MatcherResult],
2556 2560 skip_matchers: Set[str],
2557 2561 abort_if_offset_changes: bool,
2558 2562 ):
2559 2563
2560 2564 sortable = []
2561 2565 ordered = []
2562 2566 most_recent_fragment = None
2563 2567 for identifier, result in results.items():
2564 2568 if identifier in skip_matchers:
2565 2569 continue
2566 2570 if not result["completions"]:
2567 2571 continue
2568 2572 if not most_recent_fragment:
2569 2573 most_recent_fragment = result["matched_fragment"]
2570 2574 if (
2571 2575 abort_if_offset_changes
2572 2576 and result["matched_fragment"] != most_recent_fragment
2573 2577 ):
2574 2578 break
2575 2579 if result.get("ordered", False):
2576 2580 ordered.extend(result["completions"])
2577 2581 else:
2578 2582 sortable.extend(result["completions"])
2579 2583
2580 2584 if not most_recent_fragment:
2581 2585 most_recent_fragment = "" # to satisfy typechecker (and just in case)
2582 2586
2583 2587 return most_recent_fragment, [
2584 2588 m.text for m in self._deduplicate(ordered + self._sort(sortable))
2585 2589 ]
2586 2590
2587 2591 def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None,
2588 2592 full_text=None) -> _CompleteResult:
2589 2593 """
2590 2594 Like complete but can also returns raw jedi completions as well as the
2591 2595 origin of the completion text. This could (and should) be made much
2592 2596 cleaner but that will be simpler once we drop the old (and stateful)
2593 2597 :any:`complete` API.
2594 2598
2595 2599 With current provisional API, cursor_pos act both (depending on the
2596 2600 caller) as the offset in the ``text`` or ``line_buffer``, or as the
2597 2601 ``column`` when passing multiline strings this could/should be renamed
2598 2602 but would add extra noise.
2599 2603
2600 2604 Parameters
2601 2605 ----------
2602 2606 cursor_line
2603 2607 Index of the line the cursor is on. 0 indexed.
2604 2608 cursor_pos
2605 2609 Position of the cursor in the current line/line_buffer/text. 0
2606 2610 indexed.
2607 2611 line_buffer : optional, str
2608 2612 The current line the cursor is in, this is mostly due to legacy
2609 2613 reason that readline could only give a us the single current line.
2610 2614 Prefer `full_text`.
2611 2615 text : str
2612 2616 The current "token" the cursor is in, mostly also for historical
2613 2617 reasons. as the completer would trigger only after the current line
2614 2618 was parsed.
2615 2619 full_text : str
2616 2620 Full text of the current cell.
2617 2621
2618 2622 Returns
2619 2623 -------
2620 2624 An ordered dictionary where keys are identifiers of completion
2621 2625 matchers and values are ``MatcherResult``s.
2622 2626 """
2623 2627
2624 2628 # if the cursor position isn't given, the only sane assumption we can
2625 2629 # make is that it's at the end of the line (the common case)
2626 2630 if cursor_pos is None:
2627 2631 cursor_pos = len(line_buffer) if text is None else len(text)
2628 2632
2629 2633 if self.use_main_ns:
2630 2634 self.namespace = __main__.__dict__
2631 2635
2632 2636 # if text is either None or an empty string, rely on the line buffer
2633 2637 if (not line_buffer) and full_text:
2634 2638 line_buffer = full_text.split('\n')[cursor_line]
2635 2639 if not text: # issue #11508: check line_buffer before calling split_line
2636 2640 text = (
2637 2641 self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else ""
2638 2642 )
2639 2643
2640 2644 # If no line buffer is given, assume the input text is all there was
2641 2645 if line_buffer is None:
2642 2646 line_buffer = text
2643 2647
2644 2648 # deprecated - do not use `line_buffer` in new code.
2645 2649 self.line_buffer = line_buffer
2646 2650 self.text_until_cursor = self.line_buffer[:cursor_pos]
2647 2651
2648 2652 if not full_text:
2649 2653 full_text = line_buffer
2650 2654
2651 2655 context = CompletionContext(
2652 2656 full_text=full_text,
2653 2657 cursor_position=cursor_pos,
2654 2658 cursor_line=cursor_line,
2655 2659 token=text,
2656 2660 limit=MATCHES_LIMIT,
2657 2661 )
2658 2662
2659 2663 # Start with a clean slate of completions
2660 2664 results = {}
2661 2665
2662 2666 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2663 2667
2664 2668 suppressed_matchers = set()
2665 2669
2666 2670 matchers = {
2667 2671 _get_matcher_id(matcher): matcher
2668 2672 for matcher in sorted(
2669 2673 self.matchers, key=_get_matcher_priority, reverse=True
2670 2674 )
2671 2675 }
2672 2676
2673 2677 for matcher_id, matcher in matchers.items():
2674 2678 api_version = _get_matcher_api_version(matcher)
2675 2679 matcher_id = _get_matcher_id(matcher)
2676 2680
2677 2681 if matcher_id in self.disable_matchers:
2678 2682 continue
2679 2683
2680 2684 if matcher_id in results:
2681 2685 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
2682 2686
2683 2687 if matcher_id in suppressed_matchers:
2684 2688 continue
2685 2689
2686 2690 try:
2687 2691 if api_version == 1:
2688 2692 result = _convert_matcher_v1_result_to_v2(
2689 2693 matcher(text), type=_UNKNOWN_TYPE
2690 2694 )
2691 2695 elif api_version == 2:
2692 2696 result = cast(matcher, MatcherAPIv2)(context)
2693 2697 else:
2694 2698 raise ValueError(f"Unsupported API version {api_version}")
2695 2699 except:
2696 2700 # Show the ugly traceback if the matcher causes an
2697 2701 # exception, but do NOT crash the kernel!
2698 2702 sys.excepthook(*sys.exc_info())
2699 2703 continue
2700 2704
2701 2705 # set default value for matched fragment if suffix was not selected.
2702 2706 result["matched_fragment"] = result.get("matched_fragment", context.token)
2703 2707
2704 2708 if not suppressed_matchers:
2705 2709 suppression_recommended = result.get("suppress", False)
2706 2710
2707 2711 suppression_config = (
2708 2712 self.suppress_competing_matchers.get(matcher_id, None)
2709 2713 if isinstance(self.suppress_competing_matchers, dict)
2710 2714 else self.suppress_competing_matchers
2711 2715 )
2712 2716 should_suppress = (
2713 2717 (suppression_config is True)
2714 2718 or (suppression_recommended and (suppression_config is not False))
2715 2719 ) and len(result["completions"])
2716 2720
2717 2721 if should_suppress:
2718 2722 suppression_exceptions = result.get("do_not_suppress", set())
2719 2723 try:
2720 2724 to_suppress = set(suppression_recommended)
2721 2725 except TypeError:
2722 2726 to_suppress = set(matchers)
2723 2727 suppressed_matchers = to_suppress - suppression_exceptions
2724 2728
2725 2729 new_results = {}
2726 2730 for previous_matcher_id, previous_result in results.items():
2727 2731 if previous_matcher_id not in suppressed_matchers:
2728 2732 new_results[previous_matcher_id] = previous_result
2729 2733 results = new_results
2730 2734
2731 2735 results[matcher_id] = result
2732 2736
2733 2737 _, matches = self._arrange_and_extract(
2734 2738 results,
2735 2739 # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission?
2736 2740 # if it was omission, we can remove the filtering step, otherwise remove this comment.
2737 2741 skip_matchers={jedi_matcher_id},
2738 2742 abort_if_offset_changes=False,
2739 2743 )
2740 2744
2741 2745 # populate legacy stateful API
2742 2746 self.matches = matches
2743 2747
2744 2748 return results
2745 2749
2746 2750 @staticmethod
2747 2751 def _deduplicate(
2748 2752 matches: Sequence[SimpleCompletion],
2749 2753 ) -> Iterable[SimpleCompletion]:
2750 2754 filtered_matches = {}
2751 2755 for match in matches:
2752 2756 text = match.text
2753 2757 if (
2754 2758 text not in filtered_matches
2755 2759 or filtered_matches[text].type == _UNKNOWN_TYPE
2756 2760 ):
2757 2761 filtered_matches[text] = match
2758 2762
2759 2763 return filtered_matches.values()
2760 2764
2761 2765 @staticmethod
2762 2766 def _sort(matches: Sequence[SimpleCompletion]):
2763 2767 return sorted(matches, key=lambda x: completions_sorting_key(x.text))
2764 2768
2765 2769 @context_matcher()
2766 2770 def fwd_unicode_matcher(self, context):
2767 2771 """Same as ``fwd_unicode_match``, but adopted to new Matcher API."""
2768 2772 fragment, matches = self.latex_matches(context.token)
2769 2773 return _convert_matcher_v1_result_to_v2(
2770 2774 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2771 2775 )
2772 2776
2773 2777 def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]:
2774 2778 """
2775 2779 Forward match a string starting with a backslash with a list of
2776 2780 potential Unicode completions.
2777 2781
2778 2782 Will compute list list of Unicode character names on first call and cache it.
2779 2783
2780 2784 Returns
2781 2785 -------
2782 2786 At tuple with:
2783 2787 - matched text (empty if no matches)
2784 2788 - list of potential completions, empty tuple otherwise)
2785 2789
2786 2790 DEPRECATED: Deprecated since 8.6. Use `fwd_unicode_matcher` instead.
2787 2791 """
2788 2792 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2789 2793 # We could do a faster match using a Trie.
2790 2794
2791 2795 # Using pygtrie the following seem to work:
2792 2796
2793 2797 # s = PrefixSet()
2794 2798
2795 2799 # for c in range(0,0x10FFFF + 1):
2796 2800 # try:
2797 2801 # s.add(unicodedata.name(chr(c)))
2798 2802 # except ValueError:
2799 2803 # pass
2800 2804 # [''.join(k) for k in s.iter(prefix)]
2801 2805
2802 2806 # But need to be timed and adds an extra dependency.
2803 2807
2804 2808 slashpos = text.rfind('\\')
2805 2809 # if text starts with slash
2806 2810 if slashpos > -1:
2807 2811 # PERF: It's important that we don't access self._unicode_names
2808 2812 # until we're inside this if-block. _unicode_names is lazily
2809 2813 # initialized, and it takes a user-noticeable amount of time to
2810 2814 # initialize it, so we don't want to initialize it unless we're
2811 2815 # actually going to use it.
2812 2816 s = text[slashpos + 1 :]
2813 2817 sup = s.upper()
2814 2818 candidates = [x for x in self.unicode_names if x.startswith(sup)]
2815 2819 if candidates:
2816 2820 return s, candidates
2817 2821 candidates = [x for x in self.unicode_names if sup in x]
2818 2822 if candidates:
2819 2823 return s, candidates
2820 2824 splitsup = sup.split(" ")
2821 2825 candidates = [
2822 2826 x for x in self.unicode_names if all(u in x for u in splitsup)
2823 2827 ]
2824 2828 if candidates:
2825 2829 return s, candidates
2826 2830
2827 2831 return "", ()
2828 2832
2829 2833 # if text does not start with slash
2830 2834 else:
2831 2835 return '', ()
2832 2836
2833 2837 @property
2834 2838 def unicode_names(self) -> List[str]:
2835 2839 """List of names of unicode code points that can be completed.
2836 2840
2837 2841 The list is lazily initialized on first access.
2838 2842 """
2839 2843 if self._unicode_names is None:
2840 2844 names = []
2841 2845 for c in range(0,0x10FFFF + 1):
2842 2846 try:
2843 2847 names.append(unicodedata.name(chr(c)))
2844 2848 except ValueError:
2845 2849 pass
2846 2850 self._unicode_names = _unicode_name_compute(_UNICODE_RANGES)
2847 2851
2848 2852 return self._unicode_names
2849 2853
2850 2854 def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]:
2851 2855 names = []
2852 2856 for start,stop in ranges:
2853 2857 for c in range(start, stop) :
2854 2858 try:
2855 2859 names.append(unicodedata.name(chr(c)))
2856 2860 except ValueError:
2857 2861 pass
2858 2862 return names
@@ -1,938 +1,965 b''
1 1 """ History related magics and functionality """
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6
7 7 import atexit
8 8 import datetime
9 9 from pathlib import Path
10 10 import re
11 11 import sqlite3
12 12 import threading
13 13
14 14 from traitlets.config.configurable import LoggingConfigurable
15 15 from decorator import decorator
16 16 from IPython.utils.decorators import undoc
17 17 from IPython.paths import locate_profile
18 18 from traitlets import (
19 19 Any,
20 20 Bool,
21 21 Dict,
22 22 Instance,
23 23 Integer,
24 24 List,
25 25 Unicode,
26 26 Union,
27 27 TraitError,
28 28 default,
29 29 observe,
30 30 )
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Classes and functions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 @undoc
37 37 class DummyDB(object):
38 38 """Dummy DB that will act as a black hole for history.
39 39
40 40 Only used in the absence of sqlite"""
41 41 def execute(*args, **kwargs):
42 42 return []
43 43
44 44 def commit(self, *args, **kwargs):
45 45 pass
46 46
47 47 def __enter__(self, *args, **kwargs):
48 48 pass
49 49
50 50 def __exit__(self, *args, **kwargs):
51 51 pass
52 52
53 53
54 54 @decorator
55 55 def only_when_enabled(f, self, *a, **kw):
56 56 """Decorator: return an empty list in the absence of sqlite."""
57 57 if not self.enabled:
58 58 return []
59 59 else:
60 60 return f(self, *a, **kw)
61 61
62 62
63 63 # use 16kB as threshold for whether a corrupt history db should be saved
64 64 # that should be at least 100 entries or so
65 65 _SAVE_DB_SIZE = 16384
66 66
67 67 @decorator
68 68 def catch_corrupt_db(f, self, *a, **kw):
69 69 """A decorator which wraps HistoryAccessor method calls to catch errors from
70 70 a corrupt SQLite database, move the old database out of the way, and create
71 71 a new one.
72 72
73 73 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
74 74 not just a corrupt file.
75 75 """
76 76 try:
77 77 return f(self, *a, **kw)
78 78 except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
79 79 self._corrupt_db_counter += 1
80 80 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
81 81 if self.hist_file != ':memory:':
82 82 if self._corrupt_db_counter > self._corrupt_db_limit:
83 83 self.hist_file = ':memory:'
84 84 self.log.error("Failed to load history too many times, history will not be saved.")
85 85 elif self.hist_file.is_file():
86 86 # move the file out of the way
87 87 base = str(self.hist_file.parent / self.hist_file.stem)
88 88 ext = self.hist_file.suffix
89 89 size = self.hist_file.stat().st_size
90 90 if size >= _SAVE_DB_SIZE:
91 91 # if there's significant content, avoid clobbering
92 92 now = datetime.datetime.now().isoformat().replace(':', '.')
93 93 newpath = base + '-corrupt-' + now + ext
94 94 # don't clobber previous corrupt backups
95 95 for i in range(100):
96 96 if not Path(newpath).exists():
97 97 break
98 98 else:
99 99 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
100 100 else:
101 101 # not much content, possibly empty; don't worry about clobbering
102 102 # maybe we should just delete it?
103 103 newpath = base + '-corrupt' + ext
104 104 self.hist_file.rename(newpath)
105 105 self.log.error("History file was moved to %s and a new file created.", newpath)
106 106 self.init_db()
107 107 return []
108 108 else:
109 109 # Failed with :memory:, something serious is wrong
110 110 raise
111 111
112 112
113 113 class HistoryAccessorBase(LoggingConfigurable):
114 114 """An abstract class for History Accessors """
115 115
116 116 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
117 117 raise NotImplementedError
118 118
119 119 def search(self, pattern="*", raw=True, search_raw=True,
120 120 output=False, n=None, unique=False):
121 121 raise NotImplementedError
122 122
123 123 def get_range(self, session, start=1, stop=None, raw=True,output=False):
124 124 raise NotImplementedError
125 125
126 126 def get_range_by_str(self, rangestr, raw=True, output=False):
127 127 raise NotImplementedError
128 128
129 129
130 130 class HistoryAccessor(HistoryAccessorBase):
131 131 """Access the history database without adding to it.
132 132
133 133 This is intended for use by standalone history tools. IPython shells use
134 134 HistoryManager, below, which is a subclass of this."""
135 135
136 136 # counter for init_db retries, so we don't keep trying over and over
137 137 _corrupt_db_counter = 0
138 138 # after two failures, fallback on :memory:
139 139 _corrupt_db_limit = 2
140 140
141 141 # String holding the path to the history file
142 142 hist_file = Union(
143 143 [Instance(Path), Unicode()],
144 144 help="""Path to file to use for SQLite history database.
145 145
146 146 By default, IPython will put the history database in the IPython
147 147 profile directory. If you would rather share one history among
148 148 profiles, you can set this value in each, so that they are consistent.
149 149
150 150 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
151 151 mounts. If you see IPython hanging, try setting this to something on a
152 152 local disk, e.g::
153 153
154 154 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
155 155
156 156 you can also use the specific value `:memory:` (including the colon
157 157 at both end but not the back ticks), to avoid creating an history file.
158 158
159 159 """,
160 160 ).tag(config=True)
161 161
162 162 enabled = Bool(True,
163 163 help="""enable the SQLite history
164 164
165 165 set enabled=False to disable the SQLite history,
166 166 in which case there will be no stored history, no SQLite connection,
167 167 and no background saving thread. This may be necessary in some
168 168 threaded environments where IPython is embedded.
169 169 """,
170 170 ).tag(config=True)
171 171
172 172 connection_options = Dict(
173 173 help="""Options for configuring the SQLite connection
174 174
175 175 These options are passed as keyword args to sqlite3.connect
176 176 when establishing database connections.
177 177 """
178 178 ).tag(config=True)
179 179
180 180 # The SQLite database
181 181 db = Any()
182 182 @observe('db')
183 183 def _db_changed(self, change):
184 184 """validate the db, since it can be an Instance of two different types"""
185 185 new = change['new']
186 186 connection_types = (DummyDB, sqlite3.Connection)
187 187 if not isinstance(new, connection_types):
188 188 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
189 189 (self.__class__.__name__, new)
190 190 raise TraitError(msg)
191 191
192 192 def __init__(self, profile="default", hist_file="", **traits):
193 193 """Create a new history accessor.
194 194
195 195 Parameters
196 196 ----------
197 197 profile : str
198 198 The name of the profile from which to open history.
199 199 hist_file : str
200 200 Path to an SQLite history database stored by IPython. If specified,
201 201 hist_file overrides profile.
202 202 config : :class:`~traitlets.config.loader.Config`
203 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 205 super(HistoryAccessor, self).__init__(**traits)
207 206 # defer setting hist_file from kwarg until after init,
208 207 # otherwise the default kwarg value would clobber any value
209 208 # set by config
210 209 if hist_file:
211 210 self.hist_file = hist_file
212 211
213 212 try:
214 213 self.hist_file
215 214 except TraitError:
216 215 # No one has set the hist_file, yet.
217 216 self.hist_file = self._get_hist_file_name(profile)
218 217
219 218 self.init_db()
220 219
221 220 def _get_hist_file_name(self, profile='default'):
222 221 """Find the history file for the given profile name.
223 222
224 223 This is overridden by the HistoryManager subclass, to use the shell's
225 224 active profile.
226 225
227 226 Parameters
228 227 ----------
229 228 profile : str
230 229 The name of a profile which has a history file.
231 230 """
232 231 return Path(locate_profile(profile)) / "history.sqlite"
233 232
234 233 @catch_corrupt_db
235 234 def init_db(self):
236 235 """Connect to the database, and create tables if necessary."""
237 236 if not self.enabled:
238 237 self.db = DummyDB()
239 238 return
240 239
241 240 # use detect_types so that timestamps return datetime objects
242 241 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
243 242 kwargs.update(self.connection_options)
244 243 self.db = sqlite3.connect(str(self.hist_file), **kwargs)
245 244 with self.db:
246 245 self.db.execute(
247 246 """CREATE TABLE IF NOT EXISTS sessions (session integer
248 247 primary key autoincrement, start timestamp,
249 248 end timestamp, num_cmds integer, remark text)"""
250 249 )
251 250 self.db.execute(
252 251 """CREATE TABLE IF NOT EXISTS history
253 252 (session integer, line integer, source text, source_raw text,
254 253 PRIMARY KEY (session, line))"""
255 254 )
256 255 # Output history is optional, but ensure the table's there so it can be
257 256 # enabled later.
258 257 self.db.execute(
259 258 """CREATE TABLE IF NOT EXISTS output_history
260 259 (session integer, line integer, output text,
261 260 PRIMARY KEY (session, line))"""
262 261 )
263 262 # success! reset corrupt db count
264 263 self._corrupt_db_counter = 0
265 264
266 265 def writeout_cache(self):
267 266 """Overridden by HistoryManager to dump the cache before certain
268 267 database lookups."""
269 268 pass
270 269
271 270 ## -------------------------------
272 271 ## Methods for retrieving history:
273 272 ## -------------------------------
274 273 def _run_sql(self, sql, params, raw=True, output=False, latest=False):
275 274 """Prepares and runs an SQL query for the history database.
276 275
277 276 Parameters
278 277 ----------
279 278 sql : str
280 279 Any filtering expressions to go after SELECT ... FROM ...
281 280 params : tuple
282 281 Parameters passed to the SQL query (to replace "?")
283 282 raw, output : bool
284 283 See :meth:`get_range`
285 284 latest : bool
286 285 Select rows with max (session, line)
287 286
288 287 Returns
289 288 -------
290 289 Tuples as :meth:`get_range`
291 290 """
292 291 toget = 'source_raw' if raw else 'source'
293 292 sqlfrom = "history"
294 293 if output:
295 294 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
296 295 toget = "history.%s, output_history.output" % toget
297 296 if latest:
298 297 toget += ", MAX(session * 128 * 1024 + line)"
299 298 this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
300 299 cur = self.db.execute(this_querry, params)
301 300 if latest:
302 301 cur = (row[:-1] for row in cur)
303 302 if output: # Regroup into 3-tuples, and parse JSON
304 303 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
305 304 return cur
306 305
307 306 @only_when_enabled
308 307 @catch_corrupt_db
309 308 def get_session_info(self, session):
310 309 """Get info about a session.
311 310
312 311 Parameters
313 312 ----------
314 313 session : int
315 314 Session number to retrieve.
316 315
317 316 Returns
318 317 -------
319 318 session_id : int
320 319 Session ID number
321 320 start : datetime
322 321 Timestamp for the start of the session.
323 322 end : datetime
324 323 Timestamp for the end of the session, or None if IPython crashed.
325 324 num_cmds : int
326 325 Number of commands run, or None if IPython crashed.
327 326 remark : unicode
328 327 A manually set description.
329 328 """
330 329 query = "SELECT * from sessions where session == ?"
331 330 return self.db.execute(query, (session,)).fetchone()
332 331
333 332 @catch_corrupt_db
334 333 def get_last_session_id(self):
335 334 """Get the last session ID currently in the database.
336 335
337 336 Within IPython, this should be the same as the value stored in
338 337 :attr:`HistoryManager.session_number`.
339 338 """
340 339 for record in self.get_tail(n=1, include_latest=True):
341 340 return record[0]
342 341
343 342 @catch_corrupt_db
344 343 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
345 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 346 Parameters
353 347 ----------
354 348 n : int
355 349 The number of lines to get
356 350 raw, output : bool
357 351 See :meth:`get_range`
358 352 include_latest : bool
359 353 If False (default), n+1 lines are fetched, and the latest one
360 354 is discarded. This is intended to be used where the function
361 355 is called by a user command, which it should not return.
362 356
363 357 Returns
364 358 -------
365 359 Tuples as :meth:`get_range`
366 360 """
367 361 self.writeout_cache()
368 362 if not include_latest:
369 363 n += 1
370 # cursor/line/entry
371 this_cur = list(
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 )
364 cur = self._run_sql(
365 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
386 366 )
387
388 everything = this_cur + other_cur
389
390 everything = everything[:n]
391
392 367 if not include_latest:
393 return list(everything)[:0:-1]
394 return list(everything)[::-1]
368 return reversed(list(cur)[1:])
369 return reversed(list(cur))
395 370
396 371 @catch_corrupt_db
397 372 def search(self, pattern="*", raw=True, search_raw=True,
398 373 output=False, n=None, unique=False):
399 374 """Search the database using unix glob-style matching (wildcards
400 375 * and ?).
401 376
402 377 Parameters
403 378 ----------
404 379 pattern : str
405 380 The wildcarded pattern to match when searching
406 381 search_raw : bool
407 382 If True, search the raw input, otherwise, the parsed input
408 383 raw, output : bool
409 384 See :meth:`get_range`
410 385 n : None or int
411 386 If an integer is given, it defines the limit of
412 387 returned entries.
413 388 unique : bool
414 389 When it is true, return only unique entries.
415 390
416 391 Returns
417 392 -------
418 393 Tuples as :meth:`get_range`
419 394 """
420 395 tosearch = "source_raw" if search_raw else "source"
421 396 if output:
422 397 tosearch = "history." + tosearch
423 398 self.writeout_cache()
424 399 sqlform = "WHERE %s GLOB ?" % tosearch
425 400 params = (pattern,)
426 401 if unique:
427 402 sqlform += ' GROUP BY {0}'.format(tosearch)
428 403 if n is not None:
429 404 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
430 405 params += (n,)
431 406 elif unique:
432 407 sqlform += " ORDER BY session, line"
433 408 cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
434 409 if n is not None:
435 410 return reversed(list(cur))
436 411 return cur
437 412
438 413 @catch_corrupt_db
439 414 def get_range(self, session, start=1, stop=None, raw=True,output=False):
440 415 """Retrieve input by session.
441 416
442 417 Parameters
443 418 ----------
444 419 session : int
445 420 Session number to retrieve.
446 421 start : int
447 422 First line to retrieve.
448 423 stop : int
449 424 End of line range (excluded from output itself). If None, retrieve
450 425 to the end of the session.
451 426 raw : bool
452 427 If True, return untranslated input
453 428 output : bool
454 429 If True, attempt to include output. This will be 'real' Python
455 430 objects for the current session, or text reprs from previous
456 431 sessions if db_log_output was enabled at the time. Where no output
457 432 is found, None is used.
458 433
459 434 Returns
460 435 -------
461 436 entries
462 437 An iterator over the desired lines. Each line is a 3-tuple, either
463 438 (session, line, input) if output is False, or
464 439 (session, line, (input, output)) if output is True.
465 440 """
466 441 if stop:
467 442 lineclause = "line >= ? AND line < ?"
468 443 params = (session, start, stop)
469 444 else:
470 445 lineclause = "line>=?"
471 446 params = (session, start)
472 447
473 448 return self._run_sql("WHERE session==? AND %s" % lineclause,
474 449 params, raw=raw, output=output)
475 450
476 451 def get_range_by_str(self, rangestr, raw=True, output=False):
477 452 """Get lines of history from a string of ranges, as used by magic
478 453 commands %hist, %save, %macro, etc.
479 454
480 455 Parameters
481 456 ----------
482 457 rangestr : str
483 458 A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
484 459 this will return everything from current session's history.
485 460
486 461 See the documentation of :func:`%history` for the full details.
487 462
488 463 raw, output : bool
489 464 As :meth:`get_range`
490 465
491 466 Returns
492 467 -------
493 468 Tuples as :meth:`get_range`
494 469 """
495 470 for sess, s, e in extract_hist_ranges(rangestr):
496 471 for line in self.get_range(sess, s, e, raw=raw, output=output):
497 472 yield line
498 473
499 474
500 475 class HistoryManager(HistoryAccessor):
501 476 """A class to organize all history-related functionality in one place.
502 477 """
503 478 # Public interface
504 479
505 480 # An instance of the IPython shell we are attached to
506 481 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
507 482 allow_none=True)
508 483 # Lists to hold processed and raw history. These start with a blank entry
509 484 # so that we can index them starting from 1
510 485 input_hist_parsed = List([""])
511 486 input_hist_raw = List([""])
512 487 # A list of directories visited during session
513 488 dir_hist = List()
514 489 @default('dir_hist')
515 490 def _dir_hist_default(self):
516 491 try:
517 492 return [Path.cwd()]
518 493 except OSError:
519 494 return []
520 495
521 496 # A dict of output history, keyed with ints from the shell's
522 497 # execution count.
523 498 output_hist = Dict()
524 499 # The text/plain repr of outputs.
525 500 output_hist_reprs = Dict()
526 501
527 502 # The number of the current session in the history database
528 503 session_number = Integer()
529 504
530 505 db_log_output = Bool(False,
531 506 help="Should the history database include output? (default: no)"
532 507 ).tag(config=True)
533 508 db_cache_size = Integer(0,
534 509 help="Write to database every x commands (higher values save disk access & power).\n"
535 510 "Values of 1 or less effectively disable caching."
536 511 ).tag(config=True)
537 512 # The input and output caches
538 513 db_input_cache = List()
539 514 db_output_cache = List()
540 515
541 516 # History saving in separate thread
542 517 save_thread = Instance('IPython.core.history.HistorySavingThread',
543 518 allow_none=True)
544 519 save_flag = Instance(threading.Event, allow_none=True)
545 520
546 521 # Private interface
547 522 # Variables used to store the three last inputs from the user. On each new
548 523 # history update, we populate the user's namespace with these, shifted as
549 524 # necessary.
550 525 _i00 = Unicode(u'')
551 526 _i = Unicode(u'')
552 527 _ii = Unicode(u'')
553 528 _iii = Unicode(u'')
554 529
555 530 # A regex matching all forms of the exit command, so that we don't store
556 531 # them in the history (it's annoying to rewind the first entry and land on
557 532 # an exit call).
558 533 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
559 534
560 535 def __init__(self, shell=None, config=None, **traits):
561 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 538 super(HistoryManager, self).__init__(shell=shell, config=config,
565 539 **traits)
566 540 self.save_flag = threading.Event()
567 541 self.db_input_cache_lock = threading.Lock()
568 542 self.db_output_cache_lock = threading.Lock()
569 543
570 544 try:
571 545 self.new_session()
572 546 except sqlite3.OperationalError:
573 547 self.log.error("Failed to create history session in %s. History will not be saved.",
574 548 self.hist_file, exc_info=True)
575 549 self.hist_file = ':memory:'
576 550
577 551 if self.enabled and self.hist_file != ':memory:':
578 552 self.save_thread = HistorySavingThread(self)
579 553 self.save_thread.start()
580 554
581 555 def _get_hist_file_name(self, profile=None):
582 556 """Get default history file name based on the Shell's profile.
583 557
584 558 The profile parameter is ignored, but must exist for compatibility with
585 559 the parent class."""
586 560 profile_dir = self.shell.profile_dir.location
587 561 return Path(profile_dir) / "history.sqlite"
588 562
589 563 @only_when_enabled
590 564 def new_session(self, conn=None):
591 565 """Get a new session number."""
592 566 if conn is None:
593 567 conn = self.db
594 568
595 569 with conn:
596 570 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
597 571 NULL, "") """, (datetime.datetime.now(),))
598 572 self.session_number = cur.lastrowid
599 573
600 574 def end_session(self):
601 575 """Close the database session, filling in the end time and line count."""
602 576 self.writeout_cache()
603 577 with self.db:
604 578 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
605 579 session==?""", (datetime.datetime.now(),
606 580 len(self.input_hist_parsed)-1, self.session_number))
607 581 self.session_number = 0
608 582
609 583 def name_session(self, name):
610 584 """Give the current session a name in the history database."""
611 585 with self.db:
612 586 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
613 587 (name, self.session_number))
614 588
615 589 def reset(self, new_session=True):
616 590 """Clear the session history, releasing all object references, and
617 591 optionally open a new session."""
618 592 self.output_hist.clear()
619 593 # The directory history can't be completely empty
620 594 self.dir_hist[:] = [Path.cwd()]
621 595
622 596 if new_session:
623 597 if self.session_number:
624 598 self.end_session()
625 599 self.input_hist_parsed[:] = [""]
626 600 self.input_hist_raw[:] = [""]
627 601 self.new_session()
628 602
629 603 # ------------------------------
630 604 # Methods for retrieving history
631 605 # ------------------------------
632 606 def get_session_info(self, session=0):
633 607 """Get info about a session.
634 608
635 609 Parameters
636 610 ----------
637 611 session : int
638 612 Session number to retrieve. The current session is 0, and negative
639 613 numbers count back from current session, so -1 is the previous session.
640 614
641 615 Returns
642 616 -------
643 617 session_id : int
644 618 Session ID number
645 619 start : datetime
646 620 Timestamp for the start of the session.
647 621 end : datetime
648 622 Timestamp for the end of the session, or None if IPython crashed.
649 623 num_cmds : int
650 624 Number of commands run, or None if IPython crashed.
651 625 remark : unicode
652 626 A manually set description.
653 627 """
654 628 if session <= 0:
655 629 session += self.session_number
656 630
657 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 686 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
660 687 """Get input and output history from the current session. Called by
661 688 get_range, and takes similar parameters."""
662 689 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
663 690
664 691 n = len(input_hist)
665 692 if start < 0:
666 693 start += n
667 694 if not stop or (stop > n):
668 695 stop = n
669 696 elif stop < 0:
670 697 stop += n
671 698
672 699 for i in range(start, stop):
673 700 if output:
674 701 line = (input_hist[i], self.output_hist_reprs.get(i))
675 702 else:
676 703 line = input_hist[i]
677 704 yield (0, i, line)
678 705
679 706 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
680 707 """Retrieve input by session.
681 708
682 709 Parameters
683 710 ----------
684 711 session : int
685 712 Session number to retrieve. The current session is 0, and negative
686 713 numbers count back from current session, so -1 is previous session.
687 714 start : int
688 715 First line to retrieve.
689 716 stop : int
690 717 End of line range (excluded from output itself). If None, retrieve
691 718 to the end of the session.
692 719 raw : bool
693 720 If True, return untranslated input
694 721 output : bool
695 722 If True, attempt to include output. This will be 'real' Python
696 723 objects for the current session, or text reprs from previous
697 724 sessions if db_log_output was enabled at the time. Where no output
698 725 is found, None is used.
699 726
700 727 Returns
701 728 -------
702 729 entries
703 730 An iterator over the desired lines. Each line is a 3-tuple, either
704 731 (session, line, input) if output is False, or
705 732 (session, line, (input, output)) if output is True.
706 733 """
707 734 if session <= 0:
708 735 session += self.session_number
709 736 if session==self.session_number: # Current session
710 737 return self._get_range_session(start, stop, raw, output)
711 738 return super(HistoryManager, self).get_range(session, start, stop, raw,
712 739 output)
713 740
714 741 ## ----------------------------
715 742 ## Methods for storing history:
716 743 ## ----------------------------
717 744 def store_inputs(self, line_num, source, source_raw=None):
718 745 """Store source and raw input in history and create input cache
719 746 variables ``_i*``.
720 747
721 748 Parameters
722 749 ----------
723 750 line_num : int
724 751 The prompt number of this input.
725 752 source : str
726 753 Python input.
727 754 source_raw : str, optional
728 755 If given, this is the raw input without any IPython transformations
729 756 applied to it. If not given, ``source`` is used.
730 757 """
731 758 if source_raw is None:
732 759 source_raw = source
733 760 source = source.rstrip('\n')
734 761 source_raw = source_raw.rstrip('\n')
735 762
736 763 # do not store exit/quit commands
737 764 if self._exit_re.match(source_raw.strip()):
738 765 return
739 766
740 767 self.input_hist_parsed.append(source)
741 768 self.input_hist_raw.append(source_raw)
742 769
743 770 with self.db_input_cache_lock:
744 771 self.db_input_cache.append((line_num, source, source_raw))
745 772 # Trigger to flush cache and write to DB.
746 773 if len(self.db_input_cache) >= self.db_cache_size:
747 774 self.save_flag.set()
748 775
749 776 # update the auto _i variables
750 777 self._iii = self._ii
751 778 self._ii = self._i
752 779 self._i = self._i00
753 780 self._i00 = source_raw
754 781
755 782 # hackish access to user namespace to create _i1,_i2... dynamically
756 783 new_i = '_i%s' % line_num
757 784 to_main = {'_i': self._i,
758 785 '_ii': self._ii,
759 786 '_iii': self._iii,
760 787 new_i : self._i00 }
761 788
762 789 if self.shell is not None:
763 790 self.shell.push(to_main, interactive=False)
764 791
765 792 def store_output(self, line_num):
766 793 """If database output logging is enabled, this saves all the
767 794 outputs from the indicated prompt number to the database. It's
768 795 called by run_cell after code has been executed.
769 796
770 797 Parameters
771 798 ----------
772 799 line_num : int
773 800 The line number from which to save outputs
774 801 """
775 802 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
776 803 return
777 804 output = self.output_hist_reprs[line_num]
778 805
779 806 with self.db_output_cache_lock:
780 807 self.db_output_cache.append((line_num, output))
781 808 if self.db_cache_size <= 1:
782 809 self.save_flag.set()
783 810
784 811 def _writeout_input_cache(self, conn):
785 812 with conn:
786 813 for line in self.db_input_cache:
787 814 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
788 815 (self.session_number,)+line)
789 816
790 817 def _writeout_output_cache(self, conn):
791 818 with conn:
792 819 for line in self.db_output_cache:
793 820 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
794 821 (self.session_number,)+line)
795 822
796 823 @only_when_enabled
797 824 def writeout_cache(self, conn=None):
798 825 """Write any entries in the cache to the database."""
799 826 if conn is None:
800 827 conn = self.db
801 828
802 829 with self.db_input_cache_lock:
803 830 try:
804 831 self._writeout_input_cache(conn)
805 832 except sqlite3.IntegrityError:
806 833 self.new_session(conn)
807 834 print("ERROR! Session/line number was not unique in",
808 835 "database. History logging moved to new session",
809 836 self.session_number)
810 837 try:
811 838 # Try writing to the new session. If this fails, don't
812 839 # recurse
813 840 self._writeout_input_cache(conn)
814 841 except sqlite3.IntegrityError:
815 842 pass
816 843 finally:
817 844 self.db_input_cache = []
818 845
819 846 with self.db_output_cache_lock:
820 847 try:
821 848 self._writeout_output_cache(conn)
822 849 except sqlite3.IntegrityError:
823 850 print("!! Session/line number for output was not unique",
824 851 "in database. Output will not be stored.")
825 852 finally:
826 853 self.db_output_cache = []
827 854
828 855
829 856 class HistorySavingThread(threading.Thread):
830 857 """This thread takes care of writing history to the database, so that
831 858 the UI isn't held up while that happens.
832 859
833 860 It waits for the HistoryManager's save_flag to be set, then writes out
834 861 the history cache. The main thread is responsible for setting the flag when
835 862 the cache size reaches a defined threshold."""
836 863 daemon = True
837 864 stop_now = False
838 865 enabled = True
839 866 def __init__(self, history_manager):
840 867 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
841 868 self.history_manager = history_manager
842 869 self.enabled = history_manager.enabled
843 870 atexit.register(self.stop)
844 871
845 872 @only_when_enabled
846 873 def run(self):
847 874 # We need a separate db connection per thread:
848 875 try:
849 876 self.db = sqlite3.connect(
850 877 str(self.history_manager.hist_file),
851 878 **self.history_manager.connection_options,
852 879 )
853 880 while True:
854 881 self.history_manager.save_flag.wait()
855 882 if self.stop_now:
856 883 self.db.close()
857 884 return
858 885 self.history_manager.save_flag.clear()
859 886 self.history_manager.writeout_cache(self.db)
860 887 except Exception as e:
861 888 print(("The history saving thread hit an unexpected error (%s)."
862 889 "History will not be written to the database.") % repr(e))
863 890
864 891 def stop(self):
865 892 """This can be called from the main thread to safely stop this thread.
866 893
867 894 Note that it does not attempt to write out remaining history before
868 895 exiting. That should be done by calling the HistoryManager's
869 896 end_session method."""
870 897 self.stop_now = True
871 898 self.history_manager.save_flag.set()
872 899 self.join()
873 900
874 901
875 902 # To match, e.g. ~5/8-~2/3
876 903 range_re = re.compile(r"""
877 904 ((?P<startsess>~?\d+)/)?
878 905 (?P<start>\d+)?
879 906 ((?P<sep>[\-:])
880 907 ((?P<endsess>~?\d+)/)?
881 908 (?P<end>\d+))?
882 909 $""", re.VERBOSE)
883 910
884 911
885 912 def extract_hist_ranges(ranges_str):
886 913 """Turn a string of history ranges into 3-tuples of (session, start, stop).
887 914
888 915 Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
889 916 session".
890 917
891 918 Examples
892 919 --------
893 920 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
894 921 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
895 922 """
896 923 if ranges_str == "":
897 924 yield (0, 1, None) # Everything from current session
898 925 return
899 926
900 927 for range_str in ranges_str.split():
901 928 rmatch = range_re.match(range_str)
902 929 if not rmatch:
903 930 continue
904 931 start = rmatch.group("start")
905 932 if start:
906 933 start = int(start)
907 934 end = rmatch.group("end")
908 935 # If no end specified, get (a, a + 1)
909 936 end = int(end) if end else start + 1
910 937 else: # start not specified
911 938 if not rmatch.group('startsess'): # no startsess
912 939 continue
913 940 start = 1
914 941 end = None # provide the entire session hist
915 942
916 943 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
917 944 end += 1
918 945 startsess = rmatch.group("startsess") or "0"
919 946 endsess = rmatch.group("endsess") or startsess
920 947 startsess = int(startsess.replace("~","-"))
921 948 endsess = int(endsess.replace("~","-"))
922 949 assert endsess >= startsess, "start session must be earlier than end session"
923 950
924 951 if endsess == startsess:
925 952 yield (startsess, start, end)
926 953 continue
927 954 # Multiple sessions in one range:
928 955 yield (startsess, start, None)
929 956 for sess in range(startsess+1, endsess):
930 957 yield (sess, 1, None)
931 958 yield (endsess, 1, end)
932 959
933 960
934 961 def _format_lineno(session, line):
935 962 """Helper function to format line numbers properly."""
936 963 if session == 0:
937 964 return str(line)
938 965 return "%s#%s" % (session, line)
@@ -1,171 +1,173 b''
1 1 """Hooks for IPython.
2 2
3 3 In Python, it is possible to overwrite any method of any object if you really
4 4 want to. But IPython exposes a few 'hooks', methods which are *designed* to
5 5 be overwritten by users for customization purposes. This module defines the
6 6 default versions of all such hooks, which get used by IPython if not
7 7 overridden by the user.
8 8
9 9 Hooks are simple functions, but they should be declared with ``self`` as their
10 10 first argument, because when activated they are registered into IPython as
11 11 instance methods. The self argument will be the IPython running instance
12 12 itself, so hooks have full access to the entire IPython object.
13 13
14 14 If you wish to define a new hook and activate it, you can make an :doc:`extension
15 15 </config/extensions/index>` or a :ref:`startup script <startup_files>`. For
16 16 example, you could use a startup file like this::
17 17
18 18 import os
19 19
20 20 def calljed(self,filename, linenum):
21 21 "My editor hook calls the jed editor directly."
22 22 print "Calling my own editor, jed ..."
23 23 if os.system('jed +%d %s' % (linenum,filename)) != 0:
24 24 raise TryNext()
25 25
26 26 def load_ipython_extension(ip):
27 27 ip.set_hook('editor', calljed)
28 28
29 29 """
30 30
31 31 #*****************************************************************************
32 32 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
33 33 #
34 34 # Distributed under the terms of the BSD License. The full license is in
35 35 # the file COPYING, distributed as part of this software.
36 36 #*****************************************************************************
37 37
38 38 import os
39 39 import subprocess
40 40 import sys
41 41
42 42 from .error import TryNext
43 43
44 44 # List here all the default hooks. For now it's just the editor functions
45 45 # but over time we'll move here all the public API for user-accessible things.
46 46
47 47 __all__ = [
48 48 "editor",
49 49 "synchronize_with_editor",
50 50 "show_in_pager",
51 51 "pre_prompt_hook",
52 52 "clipboard_get",
53 53 ]
54 54
55 55 deprecated = {'pre_run_code_hook': "a callback for the 'pre_execute' or 'pre_run_cell' event",
56 56 'late_startup_hook': "a callback for the 'shell_initialized' event",
57 57 'shutdown_hook': "the atexit module",
58 58 }
59 59
60 60 def editor(self, filename, linenum=None, wait=True):
61 61 """Open the default editor at the given filename and linenumber.
62 62
63 63 This is IPython's default editor hook, you can use it as an example to
64 64 write your own modified one. To set your own editor function as the
65 65 new editor hook, call ip.set_hook('editor',yourfunc)."""
66 66
67 67 # IPython configures a default editor at startup by reading $EDITOR from
68 68 # the environment, and falling back on vi (unix) or notepad (win32).
69 69 editor = self.editor
70 70
71 71 # marker for at which line to open the file (for existing objects)
72 72 if linenum is None or editor=='notepad':
73 73 linemark = ''
74 74 else:
75 75 linemark = '+%d' % int(linenum)
76 76
77 77 # Enclose in quotes if necessary and legal
78 78 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
79 79 editor = '"%s"' % editor
80 80
81 81 # Call the actual editor
82 82 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
83 83 shell=True)
84 84 if wait and proc.wait() != 0:
85 85 raise TryNext()
86 86
87 87
88 88 def synchronize_with_editor(self, filename, linenum, column):
89 89 pass
90 90
91 91
92 92 class CommandChainDispatcher:
93 93 """ Dispatch calls to a chain of commands until some func can handle it
94 94
95 95 Usage: instantiate, execute "add" to add commands (with optional
96 96 priority), execute normally via f() calling mechanism.
97 97
98 98 """
99 99 def __init__(self,commands=None):
100 100 if commands is None:
101 101 self.chain = []
102 102 else:
103 103 self.chain = commands
104 104
105 105
106 106 def __call__(self,*args, **kw):
107 107 """ Command chain is called just like normal func.
108 108
109 109 This will call all funcs in chain with the same args as were given to
110 110 this function, and return the result of first func that didn't raise
111 111 TryNext"""
112 112 last_exc = TryNext()
113 113 for prio,cmd in self.chain:
114 114 #print "prio",prio,"cmd",cmd #dbg
115 115 try:
116 116 return cmd(*args, **kw)
117 117 except TryNext as exc:
118 118 last_exc = exc
119 119 # if no function will accept it, raise TryNext up to the caller
120 120 raise last_exc
121 121
122 122 def __str__(self):
123 123 return str(self.chain)
124 124
125 125 def add(self, func, priority=0):
126 126 """ Add a func to the cmd chain with given priority """
127 127 self.chain.append((priority, func))
128 128 self.chain.sort(key=lambda x: x[0])
129 129
130 130 def __iter__(self):
131 131 """ Return all objects in chain.
132 132
133 133 Handy if the objects are not callable.
134 134 """
135 135 return iter(self.chain)
136 136
137 137
138 138 def show_in_pager(self, data, start, screen_lines):
139 139 """ Run a string through pager """
140 140 # raising TryNext here will use the default paging functionality
141 141 raise TryNext
142 142
143 143
144 144 def pre_prompt_hook(self):
145 145 """ Run before displaying the next prompt
146 146
147 147 Use this e.g. to display output from asynchronous operations (in order
148 148 to not mess up text entry)
149 149 """
150 150
151 151 return None
152 152
153 153
154 154 def clipboard_get(self):
155 155 """ Get text from the clipboard.
156 156 """
157 157 from ..lib.clipboard import (
158 osx_clipboard_get, tkinter_clipboard_get,
159 win32_clipboard_get
158 osx_clipboard_get,
159 tkinter_clipboard_get,
160 win32_clipboard_get,
161 wayland_clipboard_get,
160 162 )
161 163 if sys.platform == 'win32':
162 164 chain = [win32_clipboard_get, tkinter_clipboard_get]
163 165 elif sys.platform == 'darwin':
164 166 chain = [osx_clipboard_get, tkinter_clipboard_get]
165 167 else:
166 chain = [tkinter_clipboard_get]
168 chain = [wayland_clipboard_get, tkinter_clipboard_get]
167 169 dispatcher = CommandChainDispatcher()
168 170 for func in chain:
169 171 dispatcher.add(func)
170 172 text = dispatcher()
171 173 return text
@@ -1,854 +1,855 b''
1 1 """Implementation of magic functions for interaction with the OS.
2 2
3 3 Note: this module is named 'osm' instead of 'os' to avoid a collision with the
4 4 builtin.
5 5 """
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import io
10 10 import os
11 import pathlib
11 12 import re
12 13 import sys
13 14 from pprint import pformat
14 15
15 16 from IPython.core import magic_arguments
16 17 from IPython.core import oinspect
17 18 from IPython.core import page
18 19 from IPython.core.alias import AliasError, Alias
19 20 from IPython.core.error import UsageError
20 21 from IPython.core.magic import (
21 22 Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
22 23 )
23 24 from IPython.testing.skipdoctest import skip_doctest
24 25 from IPython.utils.openpy import source_to_unicode
25 26 from IPython.utils.process import abbrev_cwd
26 27 from IPython.utils.terminal import set_term_title
27 28 from traitlets import Bool
28 29 from warnings import warn
29 30
30 31
31 32 @magics_class
32 33 class OSMagics(Magics):
33 34 """Magics to interact with the underlying OS (shell-type functionality).
34 35 """
35 36
36 37 cd_force_quiet = Bool(False,
37 38 help="Force %cd magic to be quiet even if -q is not passed."
38 39 ).tag(config=True)
39 40
40 41 def __init__(self, shell=None, **kwargs):
41 42
42 43 # Now define isexec in a cross platform manner.
43 44 self.is_posix = False
44 45 self.execre = None
45 46 if os.name == 'posix':
46 47 self.is_posix = True
47 48 else:
48 49 try:
49 50 winext = os.environ['pathext'].replace(';','|').replace('.','')
50 51 except KeyError:
51 52 winext = 'exe|com|bat|py'
52 53 try:
53 54 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
54 55 except re.error:
55 56 warn("Seems like your pathext environmental "
56 57 "variable is malformed. Please check it to "
57 58 "enable a proper handle of file extensions "
58 59 "managed for your system")
59 60 winext = 'exe|com|bat|py'
60 61 self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
61 62
62 63 # call up the chain
63 64 super().__init__(shell=shell, **kwargs)
64 65
65 66
66 67 def _isexec_POSIX(self, file):
67 68 """
68 69 Test for executable on a POSIX system
69 70 """
70 71 if os.access(file.path, os.X_OK):
71 72 # will fail on maxOS if access is not X_OK
72 73 return file.is_file()
73 74 return False
74 75
75 76
76 77
77 78 def _isexec_WIN(self, file):
78 79 """
79 80 Test for executable file on non POSIX system
80 81 """
81 82 return file.is_file() and self.execre.match(file.name) is not None
82 83
83 84 def isexec(self, file):
84 85 """
85 86 Test for executable file on non POSIX system
86 87 """
87 88 if self.is_posix:
88 89 return self._isexec_POSIX(file)
89 90 else:
90 91 return self._isexec_WIN(file)
91 92
92 93
93 94 @skip_doctest
94 95 @line_magic
95 96 def alias(self, parameter_s=''):
96 97 """Define an alias for a system command.
97 98
98 99 '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
99 100
100 101 Then, typing 'alias_name params' will execute the system command 'cmd
101 102 params' (from your underlying operating system).
102 103
103 104 Aliases have lower precedence than magic functions and Python normal
104 105 variables, so if 'foo' is both a Python variable and an alias, the
105 106 alias can not be executed until 'del foo' removes the Python variable.
106 107
107 108 You can use the %l specifier in an alias definition to represent the
108 109 whole line when the alias is called. For example::
109 110
110 111 In [2]: alias bracket echo "Input in brackets: <%l>"
111 112 In [3]: bracket hello world
112 113 Input in brackets: <hello world>
113 114
114 115 You can also define aliases with parameters using %s specifiers (one
115 116 per parameter)::
116 117
117 118 In [1]: alias parts echo first %s second %s
118 119 In [2]: %parts A B
119 120 first A second B
120 121 In [3]: %parts A
121 122 Incorrect number of arguments: 2 expected.
122 123 parts is an alias to: 'echo first %s second %s'
123 124
124 125 Note that %l and %s are mutually exclusive. You can only use one or
125 126 the other in your aliases.
126 127
127 128 Aliases expand Python variables just like system calls using ! or !!
128 129 do: all expressions prefixed with '$' get expanded. For details of
129 130 the semantic rules, see PEP-215:
130 131 https://peps.python.org/pep-0215/. This is the library used by
131 132 IPython for variable expansion. If you want to access a true shell
132 133 variable, an extra $ is necessary to prevent its expansion by
133 134 IPython::
134 135
135 136 In [6]: alias show echo
136 137 In [7]: PATH='A Python string'
137 138 In [8]: show $PATH
138 139 A Python string
139 140 In [9]: show $$PATH
140 141 /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
141 142
142 143 You can use the alias facility to access all of $PATH. See the %rehashx
143 144 function, which automatically creates aliases for the contents of your
144 145 $PATH.
145 146
146 147 If called with no parameters, %alias prints the current alias table
147 148 for your system. For posix systems, the default aliases are 'cat',
148 149 'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
149 150 aliases are added. For windows-based systems, the default aliases are
150 151 'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
151 152
152 153 You can see the definition of alias by adding a question mark in the
153 154 end::
154 155
155 156 In [1]: cat?
156 157 Repr: <alias cat for 'cat'>"""
157 158
158 159 par = parameter_s.strip()
159 160 if not par:
160 161 aliases = sorted(self.shell.alias_manager.aliases)
161 162 # stored = self.shell.db.get('stored_aliases', {} )
162 163 # for k, v in stored:
163 164 # atab.append(k, v[0])
164 165
165 166 print("Total number of aliases:", len(aliases))
166 167 sys.stdout.flush()
167 168 return aliases
168 169
169 170 # Now try to define a new one
170 171 try:
171 172 alias,cmd = par.split(None, 1)
172 173 except TypeError:
173 174 print(oinspect.getdoc(self.alias))
174 175 return
175 176
176 177 try:
177 178 self.shell.alias_manager.define_alias(alias, cmd)
178 179 except AliasError as e:
179 180 print(e)
180 181 # end magic_alias
181 182
182 183 @line_magic
183 184 def unalias(self, parameter_s=''):
184 185 """Remove an alias"""
185 186
186 187 aname = parameter_s.strip()
187 188 try:
188 189 self.shell.alias_manager.undefine_alias(aname)
189 190 except ValueError as e:
190 191 print(e)
191 192 return
192 193
193 194 stored = self.shell.db.get('stored_aliases', {} )
194 195 if aname in stored:
195 196 print("Removing %stored alias",aname)
196 197 del stored[aname]
197 198 self.shell.db['stored_aliases'] = stored
198 199
199 200 @line_magic
200 201 def rehashx(self, parameter_s=''):
201 202 """Update the alias table with all executable files in $PATH.
202 203
203 204 rehashx explicitly checks that every entry in $PATH is a file
204 205 with execute access (os.X_OK).
205 206
206 207 Under Windows, it checks executability as a match against a
207 208 '|'-separated string of extensions, stored in the IPython config
208 209 variable win_exec_ext. This defaults to 'exe|com|bat'.
209 210
210 211 This function also resets the root module cache of module completer,
211 212 used on slow filesystems.
212 213 """
213 214 from IPython.core.alias import InvalidAliasError
214 215
215 216 # for the benefit of module completer in ipy_completers.py
216 217 del self.shell.db['rootmodules_cache']
217 218
218 219 path = [os.path.abspath(os.path.expanduser(p)) for p in
219 220 os.environ.get('PATH','').split(os.pathsep)]
220 221
221 222 syscmdlist = []
222 223 savedir = os.getcwd()
223 224
224 225 # Now walk the paths looking for executables to alias.
225 226 try:
226 227 # write the whole loop for posix/Windows so we don't have an if in
227 228 # the innermost part
228 229 if self.is_posix:
229 230 for pdir in path:
230 231 try:
231 232 os.chdir(pdir)
232 233 except OSError:
233 234 continue
234 235
235 236 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
236 237 dirlist = os.scandir(path=pdir)
237 238 for ff in dirlist:
238 239 if self.isexec(ff):
239 240 fname = ff.name
240 241 try:
241 242 # Removes dots from the name since ipython
242 243 # will assume names with dots to be python.
243 244 if not self.shell.alias_manager.is_alias(fname):
244 245 self.shell.alias_manager.define_alias(
245 246 fname.replace('.',''), fname)
246 247 except InvalidAliasError:
247 248 pass
248 249 else:
249 250 syscmdlist.append(fname)
250 251 else:
251 252 no_alias = Alias.blacklist
252 253 for pdir in path:
253 254 try:
254 255 os.chdir(pdir)
255 256 except OSError:
256 257 continue
257 258
258 259 # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
259 260 dirlist = os.scandir(pdir)
260 261 for ff in dirlist:
261 262 fname = ff.name
262 263 base, ext = os.path.splitext(fname)
263 264 if self.isexec(ff) and base.lower() not in no_alias:
264 265 if ext.lower() == '.exe':
265 266 fname = base
266 267 try:
267 268 # Removes dots from the name since ipython
268 269 # will assume names with dots to be python.
269 270 self.shell.alias_manager.define_alias(
270 271 base.lower().replace('.',''), fname)
271 272 except InvalidAliasError:
272 273 pass
273 274 syscmdlist.append(fname)
274 275
275 276 self.shell.db['syscmdlist'] = syscmdlist
276 277 finally:
277 278 os.chdir(savedir)
278 279
279 280 @skip_doctest
280 281 @line_magic
281 282 def pwd(self, parameter_s=''):
282 283 """Return the current working directory path.
283 284
284 285 Examples
285 286 --------
286 287 ::
287 288
288 289 In [9]: pwd
289 290 Out[9]: '/home/tsuser/sprint/ipython'
290 291 """
291 292 try:
292 293 return os.getcwd()
293 294 except FileNotFoundError as e:
294 295 raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
295 296
296 297 @skip_doctest
297 298 @line_magic
298 299 def cd(self, parameter_s=''):
299 300 """Change the current working directory.
300 301
301 302 This command automatically maintains an internal list of directories
302 303 you visit during your IPython session, in the variable ``_dh``. The
303 304 command :magic:`%dhist` shows this history nicely formatted. You can
304 305 also do ``cd -<tab>`` to see directory history conveniently.
305 306 Usage:
306 307
307 308 - ``cd 'dir'``: changes to directory 'dir'.
308 309 - ``cd -``: changes to the last visited directory.
309 310 - ``cd -<n>``: changes to the n-th directory in the directory history.
310 311 - ``cd --foo``: change to directory that matches 'foo' in history
311 312 - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
312 313 - Hitting a tab key after ``cd -b`` allows you to tab-complete
313 314 bookmark names.
314 315
315 316 .. note::
316 317 ``cd <bookmark_name>`` is enough if there is no directory
317 318 ``<bookmark_name>``, but a bookmark with the name exists.
318 319
319 320 Options:
320 321
321 322 -q Be quiet. Do not print the working directory after the
322 323 cd command is executed. By default IPython's cd
323 324 command does print this directory, since the default
324 325 prompts do not display path information.
325 326
326 327 .. note::
327 328 Note that ``!cd`` doesn't work for this purpose because the shell
328 329 where ``!command`` runs is immediately discarded after executing
329 330 'command'.
330 331
331 332 Examples
332 333 --------
333 334 ::
334 335
335 336 In [10]: cd parent/child
336 337 /home/tsuser/parent/child
337 338 """
338 339
339 340 try:
340 341 oldcwd = os.getcwd()
341 342 except FileNotFoundError:
342 343 # Happens if the CWD has been deleted.
343 344 oldcwd = None
344 345
345 346 numcd = re.match(r'(-)(\d+)$',parameter_s)
346 347 # jump in directory history by number
347 348 if numcd:
348 349 nn = int(numcd.group(2))
349 350 try:
350 351 ps = self.shell.user_ns['_dh'][nn]
351 352 except IndexError:
352 353 print('The requested directory does not exist in history.')
353 354 return
354 355 else:
355 356 opts = {}
356 357 elif parameter_s.startswith('--'):
357 358 ps = None
358 359 fallback = None
359 360 pat = parameter_s[2:]
360 361 dh = self.shell.user_ns['_dh']
361 362 # first search only by basename (last component)
362 363 for ent in reversed(dh):
363 364 if pat in os.path.basename(ent) and os.path.isdir(ent):
364 365 ps = ent
365 366 break
366 367
367 368 if fallback is None and pat in ent and os.path.isdir(ent):
368 369 fallback = ent
369 370
370 371 # if we have no last part match, pick the first full path match
371 372 if ps is None:
372 373 ps = fallback
373 374
374 375 if ps is None:
375 376 print("No matching entry in directory history")
376 377 return
377 378 else:
378 379 opts = {}
379 380
380 381
381 382 else:
382 383 opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
383 384 # jump to previous
384 385 if ps == '-':
385 386 try:
386 387 ps = self.shell.user_ns['_dh'][-2]
387 388 except IndexError as e:
388 389 raise UsageError('%cd -: No previous directory to change to.') from e
389 390 # jump to bookmark if needed
390 391 else:
391 392 if not os.path.isdir(ps) or 'b' in opts:
392 393 bkms = self.shell.db.get('bookmarks', {})
393 394
394 395 if ps in bkms:
395 396 target = bkms[ps]
396 397 print('(bookmark:%s) -> %s' % (ps, target))
397 398 ps = target
398 399 else:
399 400 if 'b' in opts:
400 401 raise UsageError("Bookmark '%s' not found. "
401 402 "Use '%%bookmark -l' to see your bookmarks." % ps)
402 403
403 404 # at this point ps should point to the target dir
404 405 if ps:
405 406 try:
406 407 os.chdir(os.path.expanduser(ps))
407 408 if hasattr(self.shell, 'term_title') and self.shell.term_title:
408 409 set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
409 410 except OSError:
410 411 print(sys.exc_info()[1])
411 412 else:
412 cwd = os.getcwd()
413 cwd = pathlib.Path.cwd()
413 414 dhist = self.shell.user_ns['_dh']
414 415 if oldcwd != cwd:
415 416 dhist.append(cwd)
416 417 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
417 418
418 419 else:
419 420 os.chdir(self.shell.home_dir)
420 421 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 422 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 cwd = os.getcwd()
423 cwd = pathlib.Path.cwd()
423 424 dhist = self.shell.user_ns['_dh']
424 425
425 426 if oldcwd != cwd:
426 427 dhist.append(cwd)
427 428 self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
428 429 if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']:
429 430 print(self.shell.user_ns['_dh'][-1])
430 431
431 432 @line_magic
432 433 def env(self, parameter_s=''):
433 434 """Get, set, or list environment variables.
434 435
435 436 Usage:\\
436 437
437 438 :``%env``: lists all environment variables/values
438 439 :``%env var``: get value for var
439 440 :``%env var val``: set value for var
440 441 :``%env var=val``: set value for var
441 442 :``%env var=$val``: set value for var, using python expansion if possible
442 443 """
443 444 if parameter_s.strip():
444 445 split = '=' if '=' in parameter_s else ' '
445 446 bits = parameter_s.split(split)
446 447 if len(bits) == 1:
447 448 key = parameter_s.strip()
448 449 if key in os.environ:
449 450 return os.environ[key]
450 451 else:
451 452 err = "Environment does not have key: {0}".format(key)
452 453 raise UsageError(err)
453 454 if len(bits) > 1:
454 455 return self.set_env(parameter_s)
455 456 env = dict(os.environ)
456 457 # hide likely secrets when printing the whole environment
457 458 for key in list(env):
458 459 if any(s in key.lower() for s in ('key', 'token', 'secret')):
459 460 env[key] = '<hidden>'
460 461
461 462 return env
462 463
463 464 @line_magic
464 465 def set_env(self, parameter_s):
465 466 """Set environment variables. Assumptions are that either "val" is a
466 467 name in the user namespace, or val is something that evaluates to a
467 468 string.
468 469
469 470 Usage:\\
470 471 %set_env var val: set value for var
471 472 %set_env var=val: set value for var
472 473 %set_env var=$val: set value for var, using python expansion if possible
473 474 """
474 475 split = '=' if '=' in parameter_s else ' '
475 476 bits = parameter_s.split(split, 1)
476 477 if not parameter_s.strip() or len(bits)<2:
477 478 raise UsageError("usage is 'set_env var=val'")
478 479 var = bits[0].strip()
479 480 val = bits[1].strip()
480 481 if re.match(r'.*\s.*', var):
481 482 # an environment variable with whitespace is almost certainly
482 483 # not what the user intended. what's more likely is the wrong
483 484 # split was chosen, ie for "set_env cmd_args A=B", we chose
484 485 # '=' for the split and should have chosen ' '. to get around
485 486 # this, users should just assign directly to os.environ or use
486 487 # standard magic {var} expansion.
487 488 err = "refusing to set env var with whitespace: '{0}'"
488 489 err = err.format(val)
489 490 raise UsageError(err)
490 491 os.environ[var] = val
491 492 print('env: {0}={1}'.format(var,val))
492 493
493 494 @line_magic
494 495 def pushd(self, parameter_s=''):
495 496 """Place the current dir on stack and change directory.
496 497
497 498 Usage:\\
498 499 %pushd ['dirname']
499 500 """
500 501
501 502 dir_s = self.shell.dir_stack
502 503 tgt = os.path.expanduser(parameter_s)
503 504 cwd = os.getcwd().replace(self.shell.home_dir,'~')
504 505 if tgt:
505 506 self.cd(parameter_s)
506 507 dir_s.insert(0,cwd)
507 508 return self.shell.run_line_magic('dirs', '')
508 509
509 510 @line_magic
510 511 def popd(self, parameter_s=''):
511 512 """Change to directory popped off the top of the stack.
512 513 """
513 514 if not self.shell.dir_stack:
514 515 raise UsageError("%popd on empty stack")
515 516 top = self.shell.dir_stack.pop(0)
516 517 self.cd(top)
517 518 print("popd ->",top)
518 519
519 520 @line_magic
520 521 def dirs(self, parameter_s=''):
521 522 """Return the current directory stack."""
522 523
523 524 return self.shell.dir_stack
524 525
525 526 @line_magic
526 527 def dhist(self, parameter_s=''):
527 528 """Print your history of visited directories.
528 529
529 530 %dhist -> print full history\\
530 531 %dhist n -> print last n entries only\\
531 532 %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
532 533
533 534 This history is automatically maintained by the %cd command, and
534 535 always available as the global list variable _dh. You can use %cd -<n>
535 536 to go to directory number <n>.
536 537
537 538 Note that most of time, you should view directory history by entering
538 539 cd -<TAB>.
539 540
540 541 """
541 542
542 543 dh = self.shell.user_ns['_dh']
543 544 if parameter_s:
544 545 try:
545 546 args = map(int,parameter_s.split())
546 547 except:
547 548 self.arg_err(self.dhist)
548 549 return
549 550 if len(args) == 1:
550 551 ini,fin = max(len(dh)-(args[0]),0),len(dh)
551 552 elif len(args) == 2:
552 553 ini,fin = args
553 554 fin = min(fin, len(dh))
554 555 else:
555 556 self.arg_err(self.dhist)
556 557 return
557 558 else:
558 559 ini,fin = 0,len(dh)
559 560 print('Directory history (kept in _dh)')
560 561 for i in range(ini, fin):
561 562 print("%d: %s" % (i, dh[i]))
562 563
563 564 @skip_doctest
564 565 @line_magic
565 566 def sc(self, parameter_s=''):
566 567 """Shell capture - run shell command and capture output (DEPRECATED use !).
567 568
568 569 DEPRECATED. Suboptimal, retained for backwards compatibility.
569 570
570 571 You should use the form 'var = !command' instead. Example:
571 572
572 573 "%sc -l myfiles = ls ~" should now be written as
573 574
574 575 "myfiles = !ls ~"
575 576
576 577 myfiles.s, myfiles.l and myfiles.n still apply as documented
577 578 below.
578 579
579 580 --
580 581 %sc [options] varname=command
581 582
582 583 IPython will run the given command using commands.getoutput(), and
583 584 will then update the user's interactive namespace with a variable
584 585 called varname, containing the value of the call. Your command can
585 586 contain shell wildcards, pipes, etc.
586 587
587 588 The '=' sign in the syntax is mandatory, and the variable name you
588 589 supply must follow Python's standard conventions for valid names.
589 590
590 591 (A special format without variable name exists for internal use)
591 592
592 593 Options:
593 594
594 595 -l: list output. Split the output on newlines into a list before
595 596 assigning it to the given variable. By default the output is stored
596 597 as a single string.
597 598
598 599 -v: verbose. Print the contents of the variable.
599 600
600 601 In most cases you should not need to split as a list, because the
601 602 returned value is a special type of string which can automatically
602 603 provide its contents either as a list (split on newlines) or as a
603 604 space-separated string. These are convenient, respectively, either
604 605 for sequential processing or to be passed to a shell command.
605 606
606 607 For example::
607 608
608 609 # Capture into variable a
609 610 In [1]: sc a=ls *py
610 611
611 612 # a is a string with embedded newlines
612 613 In [2]: a
613 614 Out[2]: 'setup.py\\nwin32_manual_post_install.py'
614 615
615 616 # which can be seen as a list:
616 617 In [3]: a.l
617 618 Out[3]: ['setup.py', 'win32_manual_post_install.py']
618 619
619 620 # or as a whitespace-separated string:
620 621 In [4]: a.s
621 622 Out[4]: 'setup.py win32_manual_post_install.py'
622 623
623 624 # a.s is useful to pass as a single command line:
624 625 In [5]: !wc -l $a.s
625 626 146 setup.py
626 627 130 win32_manual_post_install.py
627 628 276 total
628 629
629 630 # while the list form is useful to loop over:
630 631 In [6]: for f in a.l:
631 632 ...: !wc -l $f
632 633 ...:
633 634 146 setup.py
634 635 130 win32_manual_post_install.py
635 636
636 637 Similarly, the lists returned by the -l option are also special, in
637 638 the sense that you can equally invoke the .s attribute on them to
638 639 automatically get a whitespace-separated string from their contents::
639 640
640 641 In [7]: sc -l b=ls *py
641 642
642 643 In [8]: b
643 644 Out[8]: ['setup.py', 'win32_manual_post_install.py']
644 645
645 646 In [9]: b.s
646 647 Out[9]: 'setup.py win32_manual_post_install.py'
647 648
648 649 In summary, both the lists and strings used for output capture have
649 650 the following special attributes::
650 651
651 652 .l (or .list) : value as list.
652 653 .n (or .nlstr): value as newline-separated string.
653 654 .s (or .spstr): value as space-separated string.
654 655 """
655 656
656 657 opts,args = self.parse_options(parameter_s, 'lv')
657 658 # Try to get a variable name and command to run
658 659 try:
659 660 # the variable name must be obtained from the parse_options
660 661 # output, which uses shlex.split to strip options out.
661 662 var,_ = args.split('=', 1)
662 663 var = var.strip()
663 664 # But the command has to be extracted from the original input
664 665 # parameter_s, not on what parse_options returns, to avoid the
665 666 # quote stripping which shlex.split performs on it.
666 667 _,cmd = parameter_s.split('=', 1)
667 668 except ValueError:
668 669 var,cmd = '',''
669 670 # If all looks ok, proceed
670 671 split = 'l' in opts
671 672 out = self.shell.getoutput(cmd, split=split)
672 673 if 'v' in opts:
673 674 print('%s ==\n%s' % (var, pformat(out)))
674 675 if var:
675 676 self.shell.user_ns.update({var:out})
676 677 else:
677 678 return out
678 679
679 680 @line_cell_magic
680 681 def sx(self, line='', cell=None):
681 682 """Shell execute - run shell command and capture output (!! is short-hand).
682 683
683 684 %sx command
684 685
685 686 IPython will run the given command using commands.getoutput(), and
686 687 return the result formatted as a list (split on '\\n'). Since the
687 688 output is _returned_, it will be stored in ipython's regular output
688 689 cache Out[N] and in the '_N' automatic variables.
689 690
690 691 Notes:
691 692
692 693 1) If an input line begins with '!!', then %sx is automatically
693 694 invoked. That is, while::
694 695
695 696 !ls
696 697
697 698 causes ipython to simply issue system('ls'), typing::
698 699
699 700 !!ls
700 701
701 702 is a shorthand equivalent to::
702 703
703 704 %sx ls
704 705
705 706 2) %sx differs from %sc in that %sx automatically splits into a list,
706 707 like '%sc -l'. The reason for this is to make it as easy as possible
707 708 to process line-oriented shell output via further python commands.
708 709 %sc is meant to provide much finer control, but requires more
709 710 typing.
710 711
711 712 3) Just like %sc -l, this is a list with special attributes:
712 713 ::
713 714
714 715 .l (or .list) : value as list.
715 716 .n (or .nlstr): value as newline-separated string.
716 717 .s (or .spstr): value as whitespace-separated string.
717 718
718 719 This is very useful when trying to use such lists as arguments to
719 720 system commands."""
720 721
721 722 if cell is None:
722 723 # line magic
723 724 return self.shell.getoutput(line)
724 725 else:
725 726 opts,args = self.parse_options(line, '', 'out=')
726 727 output = self.shell.getoutput(cell)
727 728 out_name = opts.get('out', opts.get('o'))
728 729 if out_name:
729 730 self.shell.user_ns[out_name] = output
730 731 else:
731 732 return output
732 733
733 734 system = line_cell_magic('system')(sx)
734 735 bang = cell_magic('!')(sx)
735 736
736 737 @line_magic
737 738 def bookmark(self, parameter_s=''):
738 739 """Manage IPython's bookmark system.
739 740
740 741 %bookmark <name> - set bookmark to current dir
741 742 %bookmark <name> <dir> - set bookmark to <dir>
742 743 %bookmark -l - list all bookmarks
743 744 %bookmark -d <name> - remove bookmark
744 745 %bookmark -r - remove all bookmarks
745 746
746 747 You can later on access a bookmarked folder with::
747 748
748 749 %cd -b <name>
749 750
750 751 or simply '%cd <name>' if there is no directory called <name> AND
751 752 there is such a bookmark defined.
752 753
753 754 Your bookmarks persist through IPython sessions, but they are
754 755 associated with each profile."""
755 756
756 757 opts,args = self.parse_options(parameter_s,'drl',mode='list')
757 758 if len(args) > 2:
758 759 raise UsageError("%bookmark: too many arguments")
759 760
760 761 bkms = self.shell.db.get('bookmarks',{})
761 762
762 763 if 'd' in opts:
763 764 try:
764 765 todel = args[0]
765 766 except IndexError as e:
766 767 raise UsageError(
767 768 "%bookmark -d: must provide a bookmark to delete") from e
768 769 else:
769 770 try:
770 771 del bkms[todel]
771 772 except KeyError as e:
772 773 raise UsageError(
773 774 "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
774 775
775 776 elif 'r' in opts:
776 777 bkms = {}
777 778 elif 'l' in opts:
778 779 bks = sorted(bkms)
779 780 if bks:
780 781 size = max(map(len, bks))
781 782 else:
782 783 size = 0
783 784 fmt = '%-'+str(size)+'s -> %s'
784 785 print('Current bookmarks:')
785 786 for bk in bks:
786 787 print(fmt % (bk, bkms[bk]))
787 788 else:
788 789 if not args:
789 790 raise UsageError("%bookmark: You must specify the bookmark name")
790 791 elif len(args)==1:
791 792 bkms[args[0]] = os.getcwd()
792 793 elif len(args)==2:
793 794 bkms[args[0]] = args[1]
794 795 self.shell.db['bookmarks'] = bkms
795 796
796 797 @line_magic
797 798 def pycat(self, parameter_s=''):
798 799 """Show a syntax-highlighted file through a pager.
799 800
800 801 This magic is similar to the cat utility, but it will assume the file
801 802 to be Python source and will show it with syntax highlighting.
802 803
803 804 This magic command can either take a local filename, an url,
804 805 an history range (see %history) or a macro as argument.
805 806
806 807 If no parameter is given, prints out history of current session up to
807 808 this point. ::
808 809
809 810 %pycat myscript.py
810 811 %pycat 7-27
811 812 %pycat myMacro
812 813 %pycat http://www.example.com/myscript.py
813 814 """
814 815 try:
815 816 cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
816 817 except (ValueError, IOError):
817 818 print("Error: no such file, variable, URL, history range or macro")
818 819 return
819 820
820 821 page.page(self.shell.pycolorize(source_to_unicode(cont)))
821 822
822 823 @magic_arguments.magic_arguments()
823 824 @magic_arguments.argument(
824 825 '-a', '--append', action='store_true', default=False,
825 826 help='Append contents of the cell to an existing file. '
826 827 'The file will be created if it does not exist.'
827 828 )
828 829 @magic_arguments.argument(
829 830 'filename', type=str,
830 831 help='file to write'
831 832 )
832 833 @cell_magic
833 834 def writefile(self, line, cell):
834 835 """Write the contents of the cell to a file.
835 836
836 837 The file will be overwritten unless the -a (--append) flag is specified.
837 838 """
838 839 args = magic_arguments.parse_argstring(self.writefile, line)
839 840 if re.match(r'^(\'.*\')|(".*")$', args.filename):
840 841 filename = os.path.expanduser(args.filename[1:-1])
841 842 else:
842 843 filename = os.path.expanduser(args.filename)
843 844
844 845 if os.path.exists(filename):
845 846 if args.append:
846 847 print("Appending to %s" % filename)
847 848 else:
848 849 print("Overwriting %s" % filename)
849 850 else:
850 851 print("Writing %s" % filename)
851 852
852 853 mode = 'a' if args.append else 'w'
853 854 with io.open(filename, mode, encoding='utf-8') as f:
854 855 f.write(cell)
@@ -1,54 +1,54 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Release data for the IPython project."""
3 3
4 4 #-----------------------------------------------------------------------------
5 5 # Copyright (c) 2008, IPython Development Team.
6 6 # Copyright (c) 2001, Fernando Perez <fernando.perez@colorado.edu>
7 7 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
8 8 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
9 9 #
10 10 # Distributed under the terms of the Modified BSD License.
11 11 #
12 12 # The full license is in the file COPYING.txt, distributed with this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 # IPython version information. An empty _version_extra corresponds to a full
16 16 # release. 'dev' as a _version_extra string means this is a development
17 17 # version
18 18 _version_major = 8
19 _version_minor = 5
19 _version_minor = 6
20 20 _version_patch = 0
21 21 _version_extra = ".dev"
22 22 # _version_extra = "rc1"
23 23 # _version_extra = "" # Uncomment this for full releases
24 24
25 25 # Construct full version string from these.
26 26 _ver = [_version_major, _version_minor, _version_patch]
27 27
28 28 __version__ = '.'.join(map(str, _ver))
29 29 if _version_extra:
30 30 __version__ = __version__ + _version_extra
31 31
32 32 version = __version__ # backwards compatibility name
33 33 version_info = (_version_major, _version_minor, _version_patch, _version_extra)
34 34
35 35 # Change this when incrementing the kernel protocol version
36 36 kernel_protocol_version_info = (5, 0)
37 37 kernel_protocol_version = "%i.%i" % kernel_protocol_version_info
38 38
39 39 license = 'BSD'
40 40
41 41 authors = {'Fernando' : ('Fernando Perez','fperez.net@gmail.com'),
42 42 'Janko' : ('Janko Hauser','jhauser@zscout.de'),
43 43 'Nathan' : ('Nathaniel Gray','n8gray@caltech.edu'),
44 44 'Ville' : ('Ville Vainio','vivainio@gmail.com'),
45 45 'Brian' : ('Brian E Granger', 'ellisonbg@gmail.com'),
46 46 'Min' : ('Min Ragan-Kelley', 'benjaminrk@gmail.com'),
47 47 'Thomas' : ('Thomas A. Kluyver', 'takowl@gmail.com'),
48 48 'Jorgen' : ('Jorgen Stenarson', 'jorgen.stenarson@bostream.nu'),
49 49 'Matthias' : ('Matthias Bussonnier', 'bussonniermatthias@gmail.com'),
50 50 }
51 51
52 52 author = 'The IPython Development Team'
53 53
54 54 author_email = 'ipython-dev@python.org'
@@ -1,2 +1,3 b''
1 1 import sys
2
2 3 print(sys.argv[1:])
@@ -1,66 +1,67 b''
1 1 """These kinds of tests are less than ideal, but at least they run.
2 2
3 3 This was an old test that was being run interactively in the top-level tests/
4 4 directory, which we are removing. For now putting this here ensures at least
5 5 we do run the test, though ultimately this functionality should all be tested
6 6 with better-isolated tests that don't rely on the global instance in iptest.
7 7 """
8 8 from IPython.core.splitinput import LineInfo
9 9 from IPython.core.prefilter import AutocallChecker
10 10
11
11 12 def doctest_autocall():
12 13 """
13 14 In [1]: def f1(a,b,c):
14 15 ...: return a+b+c
15 16 ...:
16 17
17 18 In [2]: def f2(a):
18 19 ...: return a + a
19 20 ...:
20 21
21 22 In [3]: def r(x):
22 23 ...: return True
23 24 ...:
24 25
25 26 In [4]: ;f2 a b c
26 27 Out[4]: 'a b ca b c'
27 28
28 29 In [5]: assert _ == "a b ca b c"
29 30
30 31 In [6]: ,f1 a b c
31 32 Out[6]: 'abc'
32 33
33 34 In [7]: assert _ == 'abc'
34 35
35 36 In [8]: print(_)
36 37 abc
37 38
38 39 In [9]: /f1 1,2,3
39 40 Out[9]: 6
40 41
41 42 In [10]: assert _ == 6
42 43
43 44 In [11]: /f2 4
44 45 Out[11]: 8
45 46
46 47 In [12]: assert _ == 8
47 48
48 49 In [12]: del f1, f2
49 50
50 51 In [13]: ,r a
51 52 Out[13]: True
52 53
53 54 In [14]: assert _ == True
54 55
55 56 In [15]: r'a'
56 57 Out[15]: 'a'
57 58
58 59 In [16]: assert _ == 'a'
59 60 """
60 61
61 62
62 63 def test_autocall_should_ignore_raw_strings():
63 64 line_info = LineInfo("r'a'")
64 65 pm = ip.prefilter_manager
65 66 ac = AutocallChecker(shell=pm.shell, prefilter_manager=pm, config=pm.config)
66 67 assert ac.check(line_info) is None
@@ -1,112 +1,112 b''
1 1 import sys
2 2 from IPython.testing.tools import AssertPrints, AssertNotPrints
3 3 from IPython.core.displayhook import CapturingDisplayHook
4 4 from IPython.utils.capture import CapturedIO
5 5
6 6 def test_output_displayed():
7 7 """Checking to make sure that output is displayed"""
8 8
9 9 with AssertPrints('2'):
10 10 ip.run_cell('1+1', store_history=True)
11 11
12 12 with AssertPrints('2'):
13 13 ip.run_cell('1+1 # comment with a semicolon;', store_history=True)
14 14
15 15 with AssertPrints('2'):
16 16 ip.run_cell('1+1\n#commented_out_function();', store_history=True)
17 17
18 18
19 19 def test_output_quiet():
20 20 """Checking to make sure that output is quiet"""
21 21
22 22 with AssertNotPrints('2'):
23 23 ip.run_cell('1+1;', store_history=True)
24 24
25 25 with AssertNotPrints('2'):
26 26 ip.run_cell('1+1; # comment with a semicolon', store_history=True)
27 27
28 28 with AssertNotPrints('2'):
29 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 32 ip.run_cell('_ = 42', store_history=True)
33 33 ip.run_cell('1+1', store_history=True)
34 34
35 35 with AssertPrints('42'):
36 36 ip.run_cell('print(_)', store_history=True)
37 37
38 38 ip.run_cell('del _', store_history=True)
39 39 ip.run_cell('6+6', store_history=True)
40 40 with AssertPrints('12'):
41 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 45 ip.run_cell("import gettext ; gettext.install('foo')", store_history=True)
46 46 ip.run_cell('3+3', store_history=True)
47 47
48 48 with AssertPrints('gettext'):
49 49 ip.run_cell('print(_)', store_history=True)
50 50
51 51 ip.run_cell('_ = "userset"', store_history=True)
52 52
53 53 with AssertPrints('userset'):
54 54 ip.run_cell('print(_)', store_history=True)
55 55 ip.run_cell('import builtins; del builtins._')
56 56
57 57
58 58 def test_interactivehooks_ast_modes():
59 59 """
60 60 Test that ast nodes can be triggered with different modes
61 61 """
62 62 saved_mode = ip.ast_node_interactivity
63 63 ip.ast_node_interactivity = 'last_expr_or_assign'
64 64
65 65 try:
66 66 with AssertPrints('2'):
67 67 ip.run_cell('a = 1+1', store_history=True)
68 68
69 69 with AssertPrints('9'):
70 70 ip.run_cell('b = 1+8 # comment with a semicolon;', store_history=False)
71 71
72 72 with AssertPrints('7'):
73 73 ip.run_cell('c = 1+6\n#commented_out_function();', store_history=True)
74 74
75 75 ip.run_cell('d = 11', store_history=True)
76 76 with AssertPrints('12'):
77 77 ip.run_cell('d += 1', store_history=True)
78 78
79 79 with AssertNotPrints('42'):
80 80 ip.run_cell('(u,v) = (41+1, 43-1)')
81 81
82 82 finally:
83 83 ip.ast_node_interactivity = saved_mode
84 84
85 85 def test_interactivehooks_ast_modes_semi_suppress():
86 86 """
87 87 Test that ast nodes can be triggered with different modes and suppressed
88 88 by semicolon
89 89 """
90 90 saved_mode = ip.ast_node_interactivity
91 91 ip.ast_node_interactivity = 'last_expr_or_assign'
92 92
93 93 try:
94 94 with AssertNotPrints('2'):
95 95 ip.run_cell('x = 1+1;', store_history=True)
96 96
97 97 with AssertNotPrints('7'):
98 98 ip.run_cell('y = 1+6; # comment with a semicolon', store_history=True)
99 99
100 100 with AssertNotPrints('9'):
101 101 ip.run_cell('z = 1+8;\n#commented_out_function()', store_history=True)
102 102
103 103 finally:
104 104 ip.ast_node_interactivity = saved_mode
105 105
106 106 def test_capture_display_hook_format():
107 107 """Tests that the capture display hook conforms to the CapturedIO output format"""
108 108 hook = CapturingDisplayHook(ip)
109 109 hook({"foo": "bar"})
110 110 captured = CapturedIO(sys.stdout, sys.stderr, hook.outputs)
111 111 # Should not raise with RichOutput transformation error
112 112 captured.outputs
@@ -1,229 +1,307 b''
1 1 # coding: utf-8
2 2 """Tests for the IPython tab-completion machinery.
3 3 """
4 4 #-----------------------------------------------------------------------------
5 5 # Module imports
6 6 #-----------------------------------------------------------------------------
7 7
8 8 # stdlib
9 9 import io
10 10 import sqlite3
11 11 import sys
12 12 import tempfile
13 13 from datetime import datetime
14 14 from pathlib import Path
15 15
16 16 from tempfile import TemporaryDirectory
17 17 # our own packages
18 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 23 def test_proper_default_encoding():
24 24 assert sys.getdefaultencoding() == "utf-8"
25 25
26 26 def test_history():
27 27 ip = get_ipython()
28 28 with TemporaryDirectory() as tmpdir:
29 29 tmp_path = Path(tmpdir)
30 30 hist_manager_ori = ip.history_manager
31 31 hist_file = tmp_path / "history.sqlite"
32 32 try:
33 33 ip.history_manager = HistoryManager(shell=ip, hist_file=hist_file)
34 34 hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"]
35 35 for i, h in enumerate(hist, start=1):
36 36 ip.history_manager.store_inputs(i, h)
37 37
38 38 ip.history_manager.db_log_output = True
39 39 # Doesn't match the input, but we'll just check it's stored.
40 40 ip.history_manager.output_hist_reprs[3] = "spam"
41 41 ip.history_manager.store_output(3)
42 42
43 43 assert ip.history_manager.input_hist_raw == [""] + hist
44 44
45 45 # Detailed tests for _get_range_session
46 46 grs = ip.history_manager._get_range_session
47 47 assert list(grs(start=2, stop=-1)) == list(zip([0], [2], hist[1:-1]))
48 48 assert list(grs(start=-2)) == list(zip([0, 0], [2, 3], hist[-2:]))
49 49 assert list(grs(output=True)) == list(
50 50 zip([0, 0, 0], [1, 2, 3], zip(hist, [None, None, "spam"]))
51 51 )
52 52
53 53 # Check whether specifying a range beyond the end of the current
54 54 # session results in an error (gh-804)
55 55 ip.run_line_magic("hist", "2-500")
56 56
57 57 # Check that we can write non-ascii characters to a file
58 58 ip.run_line_magic("hist", "-f %s" % (tmp_path / "test1"))
59 59 ip.run_line_magic("hist", "-pf %s" % (tmp_path / "test2"))
60 60 ip.run_line_magic("hist", "-nf %s" % (tmp_path / "test3"))
61 61 ip.run_line_magic("save", "%s 1-10" % (tmp_path / "test4"))
62 62
63 63 # New session
64 64 ip.history_manager.reset()
65 65 newcmds = ["z=5", "class X(object):\n pass", "k='p'", "z=5"]
66 66 for i, cmd in enumerate(newcmds, start=1):
67 67 ip.history_manager.store_inputs(i, cmd)
68 68 gothist = ip.history_manager.get_range(start=1, stop=4)
69 69 assert list(gothist) == list(zip([0, 0, 0], [1, 2, 3], newcmds))
70 70 # Previous session:
71 71 gothist = ip.history_manager.get_range(-1, 1, 4)
72 72 assert list(gothist) == list(zip([1, 1, 1], [1, 2, 3], hist))
73 73
74 74 newhist = [(2, i, c) for (i, c) in enumerate(newcmds, 1)]
75 75
76 76 # Check get_hist_tail
77 77 gothist = ip.history_manager.get_tail(5, output=True,
78 78 include_latest=True)
79 79 expected = [(1, 3, (hist[-1], "spam"))] \
80 80 + [(s, n, (c, None)) for (s, n, c) in newhist]
81 81 assert list(gothist) == expected
82 82
83 83 gothist = ip.history_manager.get_tail(2)
84 84 expected = newhist[-3:-1]
85 85 assert list(gothist) == expected
86 86
87 87 # Check get_hist_search
88 88
89 89 gothist = ip.history_manager.search("*test*")
90 90 assert list(gothist) == [(1, 2, hist[1])]
91 91
92 92 gothist = ip.history_manager.search("*=*")
93 93 assert list(gothist) == [
94 94 (1, 1, hist[0]),
95 95 (1, 2, hist[1]),
96 96 (1, 3, hist[2]),
97 97 newhist[0],
98 98 newhist[2],
99 99 newhist[3],
100 100 ]
101 101
102 102 gothist = ip.history_manager.search("*=*", n=4)
103 103 assert list(gothist) == [
104 104 (1, 3, hist[2]),
105 105 newhist[0],
106 106 newhist[2],
107 107 newhist[3],
108 108 ]
109 109
110 110 gothist = ip.history_manager.search("*=*", unique=True)
111 111 assert list(gothist) == [
112 112 (1, 1, hist[0]),
113 113 (1, 2, hist[1]),
114 114 (1, 3, hist[2]),
115 115 newhist[2],
116 116 newhist[3],
117 117 ]
118 118
119 119 gothist = ip.history_manager.search("*=*", unique=True, n=3)
120 120 assert list(gothist) == [(1, 3, hist[2]), newhist[2], newhist[3]]
121 121
122 122 gothist = ip.history_manager.search("b*", output=True)
123 123 assert list(gothist) == [(1, 3, (hist[2], "spam"))]
124 124
125 125 # Cross testing: check that magic %save can get previous session.
126 126 testfilename = (tmp_path / "test.py").resolve()
127 127 ip.run_line_magic("save", str(testfilename) + " ~1/1-3")
128 128 with io.open(testfilename, encoding="utf-8") as testfile:
129 129 assert testfile.read() == "# coding: utf-8\n" + "\n".join(hist) + "\n"
130 130
131 131 # Duplicate line numbers - check that it doesn't crash, and
132 132 # gets a new session
133 133 ip.history_manager.store_inputs(1, "rogue")
134 134 ip.history_manager.writeout_cache()
135 135 assert ip.history_manager.session_number == 3
136 136
137 137 # Check that session and line values are not just max values
138 138 sessid, lineno, entry = newhist[-1]
139 139 assert lineno > 1
140 140 ip.history_manager.reset()
141 141 lineno = 1
142 142 ip.history_manager.store_inputs(lineno, entry)
143 143 gothist = ip.history_manager.search("*=*", unique=True)
144 144 hist = list(gothist)[-1]
145 145 assert sessid < hist[0]
146 146 assert hist[1:] == (lineno, entry)
147 147 finally:
148 148 # Ensure saving thread is shut down before we try to clean up the files
149 149 ip.history_manager.save_thread.stop()
150 150 # Forcibly close database rather than relying on garbage collection
151 151 ip.history_manager.db.close()
152 152 # Restore history manager
153 153 ip.history_manager = hist_manager_ori
154 154
155 155
156 156 def test_extract_hist_ranges():
157 157 instr = "1 2/3 ~4/5-6 ~4/7-~4/9 ~9/2-~7/5 ~10/"
158 158 expected = [(0, 1, 2), # 0 == current session
159 159 (2, 3, 4),
160 160 (-4, 5, 7),
161 161 (-4, 7, 10),
162 162 (-9, 2, None), # None == to end
163 163 (-8, 1, None),
164 164 (-7, 1, 6),
165 165 (-10, 1, None)]
166 166 actual = list(extract_hist_ranges(instr))
167 167 assert actual == expected
168 168
169 169
170 170 def test_extract_hist_ranges_empty_str():
171 171 instr = ""
172 172 expected = [(0, 1, None)] # 0 == current session, None == to end
173 173 actual = list(extract_hist_ranges(instr))
174 174 assert actual == expected
175 175
176 176
177 177 def test_magic_rerun():
178 178 """Simple test for %rerun (no args -> rerun last line)"""
179 179 ip = get_ipython()
180 180 ip.run_cell("a = 10", store_history=True)
181 181 ip.run_cell("a += 1", store_history=True)
182 182 assert ip.user_ns["a"] == 11
183 183 ip.run_cell("%rerun", store_history=True)
184 184 assert ip.user_ns["a"] == 12
185 185
186 186 def test_timestamp_type():
187 187 ip = get_ipython()
188 188 info = ip.history_manager.get_session_info()
189 189 assert isinstance(info[1], datetime)
190 190
191 191 def test_hist_file_config():
192 192 cfg = Config()
193 193 tfile = tempfile.NamedTemporaryFile(delete=False)
194 194 cfg.HistoryManager.hist_file = Path(tfile.name)
195 195 try:
196 196 hm = HistoryManager(shell=get_ipython(), config=cfg)
197 197 assert hm.hist_file == cfg.HistoryManager.hist_file
198 198 finally:
199 199 try:
200 200 Path(tfile.name).unlink()
201 201 except OSError:
202 202 # same catch as in testing.tools.TempFileMixin
203 203 # On Windows, even though we close the file, we still can't
204 204 # delete it. I have no clue why
205 205 pass
206 206
207 207 def test_histmanager_disabled():
208 208 """Ensure that disabling the history manager doesn't create a database."""
209 209 cfg = Config()
210 210 cfg.HistoryAccessor.enabled = False
211 211
212 212 ip = get_ipython()
213 213 with TemporaryDirectory() as tmpdir:
214 214 hist_manager_ori = ip.history_manager
215 215 hist_file = Path(tmpdir) / "history.sqlite"
216 216 cfg.HistoryManager.hist_file = hist_file
217 217 try:
218 218 ip.history_manager = HistoryManager(shell=ip, config=cfg)
219 219 hist = ["a=1", "def f():\n test = 1\n return test", "b='€Æ¾÷ß'"]
220 220 for i, h in enumerate(hist, start=1):
221 221 ip.history_manager.store_inputs(i, h)
222 222 assert ip.history_manager.input_hist_raw == [""] + hist
223 223 ip.history_manager.reset()
224 224 ip.history_manager.end_session()
225 225 finally:
226 226 ip.history_manager = hist_manager_ori
227 227
228 228 # hist_file should not be created
229 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 1 # coding: utf-8
2 2
3 3 from IPython.core.splitinput import split_user_input, LineInfo
4 4 from IPython.testing import tools as tt
5 5
6 6 tests = [
7 7 ("x=1", ("", "", "x", "=1")),
8 8 ("?", ("", "?", "", "")),
9 9 ("??", ("", "??", "", "")),
10 10 (" ?", (" ", "?", "", "")),
11 11 (" ??", (" ", "??", "", "")),
12 12 ("??x", ("", "??", "x", "")),
13 13 ("?x=1", ("", "?", "x", "=1")),
14 14 ("!ls", ("", "!", "ls", "")),
15 15 (" !ls", (" ", "!", "ls", "")),
16 16 ("!!ls", ("", "!!", "ls", "")),
17 17 (" !!ls", (" ", "!!", "ls", "")),
18 18 (",ls", ("", ",", "ls", "")),
19 19 (";ls", ("", ";", "ls", "")),
20 20 (" ;ls", (" ", ";", "ls", "")),
21 21 ("f.g(x)", ("", "", "f.g", "(x)")),
22 22 ("f.g (x)", ("", "", "f.g", "(x)")),
23 23 ("?%hist1", ("", "?", "%hist1", "")),
24 24 ("?%%hist2", ("", "?", "%%hist2", "")),
25 25 ("??%hist3", ("", "??", "%hist3", "")),
26 26 ("??%%hist4", ("", "??", "%%hist4", "")),
27 27 ("?x*", ("", "?", "x*", "")),
28 28 ]
29 29 tests.append(("Pérez Fernando", ("", "", "Pérez", "Fernando")))
30 30
31 31
32 32 def test_split_user_input():
33 33 return tt.check_pairs(split_user_input, tests)
34 34
35
35 36 def test_LineInfo():
36 37 """Simple test for LineInfo construction and str()"""
37 38 linfo = LineInfo(" %cd /home")
38 39 assert str(linfo) == "LineInfo [ |%|cd|/home]"
@@ -1,627 +1,627 b''
1 1 """IPython extension to reload modules before executing user code.
2 2
3 3 ``autoreload`` reloads modules automatically before entering the execution of
4 4 code typed at the IPython prompt.
5 5
6 6 This makes for example the following workflow possible:
7 7
8 8 .. sourcecode:: ipython
9 9
10 10 In [1]: %load_ext autoreload
11 11
12 12 In [2]: %autoreload 2
13 13
14 14 In [3]: from foo import some_function
15 15
16 16 In [4]: some_function()
17 17 Out[4]: 42
18 18
19 19 In [5]: # open foo.py in an editor and change some_function to return 43
20 20
21 21 In [6]: some_function()
22 22 Out[6]: 43
23 23
24 24 The module was reloaded without reloading it explicitly, and the object
25 25 imported with ``from foo import ...`` was also updated.
26 26
27 27 Usage
28 28 =====
29 29
30 30 The following magic commands are provided:
31 31
32 32 ``%autoreload``
33 33
34 34 Reload all modules (except those excluded by ``%aimport``)
35 35 automatically now.
36 36
37 37 ``%autoreload 0``
38 38
39 39 Disable automatic reloading.
40 40
41 41 ``%autoreload 1``
42 42
43 43 Reload all modules imported with ``%aimport`` every time before
44 44 executing the Python code typed.
45 45
46 46 ``%autoreload 2``
47 47
48 48 Reload all modules (except those excluded by ``%aimport``) every
49 49 time before executing the Python code typed.
50 50
51 51 ``%autoreload 3``
52 52
53 53 Reload all modules AND autoload newly added objects
54 54 every time before executing the Python code typed.
55 55
56 56 ``%aimport``
57 57
58 58 List modules which are to be automatically imported or not to be imported.
59 59
60 60 ``%aimport foo``
61 61
62 62 Import module 'foo' and mark it to be autoreloaded for ``%autoreload 1``
63 63
64 64 ``%aimport foo, bar``
65 65
66 66 Import modules 'foo', 'bar' and mark them to be autoreloaded for ``%autoreload 1``
67 67
68 68 ``%aimport -foo``
69 69
70 70 Mark module 'foo' to not be autoreloaded.
71 71
72 72 Caveats
73 73 =======
74 74
75 75 Reloading Python modules in a reliable way is in general difficult,
76 76 and unexpected things may occur. ``%autoreload`` tries to work around
77 77 common pitfalls by replacing function code objects and parts of
78 78 classes previously in the module with new versions. This makes the
79 79 following things to work:
80 80
81 81 - Functions and classes imported via 'from xxx import foo' are upgraded
82 82 to new versions when 'xxx' is reloaded.
83 83
84 84 - Methods and properties of classes are upgraded on reload, so that
85 85 calling 'c.foo()' on an object 'c' created before the reload causes
86 86 the new code for 'foo' to be executed.
87 87
88 88 Some of the known remaining caveats are:
89 89
90 90 - Replacing code objects does not always succeed: changing a @property
91 91 in a class to an ordinary method or a method to a member variable
92 92 can cause problems (but in old objects only).
93 93
94 94 - Functions that are removed (eg. via monkey-patching) from a module
95 95 before it is reloaded are not upgraded.
96 96
97 97 - C extension modules cannot be reloaded, and so cannot be autoreloaded.
98 98 """
99 99
100 100 __skip_doctest__ = True
101 101
102 102 # -----------------------------------------------------------------------------
103 103 # Copyright (C) 2000 Thomas Heller
104 104 # Copyright (C) 2008 Pauli Virtanen <pav@iki.fi>
105 105 # Copyright (C) 2012 The IPython Development Team
106 106 #
107 107 # Distributed under the terms of the BSD License. The full license is in
108 108 # the file COPYING, distributed as part of this software.
109 109 # -----------------------------------------------------------------------------
110 110 #
111 111 # This IPython module is written by Pauli Virtanen, based on the autoreload
112 112 # code by Thomas Heller.
113 113
114 114 # -----------------------------------------------------------------------------
115 115 # Imports
116 116 # -----------------------------------------------------------------------------
117 117
118 118 import os
119 119 import sys
120 120 import traceback
121 121 import types
122 122 import weakref
123 123 import gc
124 124 from importlib import import_module, reload
125 125 from importlib.util import source_from_cache
126 126
127 127 # ------------------------------------------------------------------------------
128 128 # Autoreload functionality
129 129 # ------------------------------------------------------------------------------
130 130
131 131
132 132 class ModuleReloader:
133 133 enabled = False
134 134 """Whether this reloader is enabled"""
135 135
136 136 check_all = True
137 137 """Autoreload all modules, not just those listed in 'modules'"""
138 138
139 139 autoload_obj = False
140 140 """Autoreload all modules AND autoload all new objects"""
141 141
142 142 def __init__(self, shell=None):
143 143 # Modules that failed to reload: {module: mtime-on-failed-reload, ...}
144 144 self.failed = {}
145 145 # Modules specially marked as autoreloadable.
146 146 self.modules = {}
147 147 # Modules specially marked as not autoreloadable.
148 148 self.skip_modules = {}
149 149 # (module-name, name) -> weakref, for replacing old code objects
150 150 self.old_objects = {}
151 151 # Module modification timestamps
152 152 self.modules_mtimes = {}
153 153 self.shell = shell
154 154
155 155 # Cache module modification times
156 156 self.check(check_all=True, do_reload=False)
157 157
158 158 def mark_module_skipped(self, module_name):
159 159 """Skip reloading the named module in the future"""
160 160 try:
161 161 del self.modules[module_name]
162 162 except KeyError:
163 163 pass
164 164 self.skip_modules[module_name] = True
165 165
166 166 def mark_module_reloadable(self, module_name):
167 167 """Reload the named module in the future (if it is imported)"""
168 168 try:
169 169 del self.skip_modules[module_name]
170 170 except KeyError:
171 171 pass
172 172 self.modules[module_name] = True
173 173
174 174 def aimport_module(self, module_name):
175 175 """Import a module, and mark it reloadable
176 176
177 177 Returns
178 178 -------
179 179 top_module : module
180 180 The imported module if it is top-level, or the top-level
181 181 top_name : module
182 182 Name of top_module
183 183
184 184 """
185 185 self.mark_module_reloadable(module_name)
186 186
187 187 import_module(module_name)
188 188 top_name = module_name.split(".")[0]
189 189 top_module = sys.modules[top_name]
190 190 return top_module, top_name
191 191
192 192 def filename_and_mtime(self, module):
193 193 if not hasattr(module, "__file__") or module.__file__ is None:
194 194 return None, None
195 195
196 196 if getattr(module, "__name__", None) in [None, "__mp_main__", "__main__"]:
197 197 # we cannot reload(__main__) or reload(__mp_main__)
198 198 return None, None
199 199
200 200 filename = module.__file__
201 201 path, ext = os.path.splitext(filename)
202 202
203 203 if ext.lower() == ".py":
204 204 py_filename = filename
205 205 else:
206 206 try:
207 207 py_filename = source_from_cache(filename)
208 208 except ValueError:
209 209 return None, None
210 210
211 211 try:
212 212 pymtime = os.stat(py_filename).st_mtime
213 213 except OSError:
214 214 return None, None
215 215
216 216 return py_filename, pymtime
217 217
218 218 def check(self, check_all=False, do_reload=True):
219 219 """Check whether some modules need to be reloaded."""
220 220
221 221 if not self.enabled and not check_all:
222 222 return
223 223
224 224 if check_all or self.check_all:
225 225 modules = list(sys.modules.keys())
226 226 else:
227 227 modules = list(self.modules.keys())
228 228
229 229 for modname in modules:
230 230 m = sys.modules.get(modname, None)
231 231
232 232 if modname in self.skip_modules:
233 233 continue
234 234
235 235 py_filename, pymtime = self.filename_and_mtime(m)
236 236 if py_filename is None:
237 237 continue
238 238
239 239 try:
240 240 if pymtime <= self.modules_mtimes[modname]:
241 241 continue
242 242 except KeyError:
243 243 self.modules_mtimes[modname] = pymtime
244 244 continue
245 245 else:
246 246 if self.failed.get(py_filename, None) == pymtime:
247 247 continue
248 248
249 249 self.modules_mtimes[modname] = pymtime
250 250
251 251 # If we've reached this point, we should try to reload the module
252 252 if do_reload:
253 253 try:
254 254 if self.autoload_obj:
255 255 superreload(m, reload, self.old_objects, self.shell)
256 256 else:
257 257 superreload(m, reload, self.old_objects)
258 258 if py_filename in self.failed:
259 259 del self.failed[py_filename]
260 260 except:
261 261 print(
262 262 "[autoreload of {} failed: {}]".format(
263 263 modname, traceback.format_exc(10)
264 264 ),
265 265 file=sys.stderr,
266 266 )
267 267 self.failed[py_filename] = pymtime
268 268
269 269
270 270 # ------------------------------------------------------------------------------
271 271 # superreload
272 272 # ------------------------------------------------------------------------------
273 273
274 274
275 275 func_attrs = [
276 276 "__code__",
277 277 "__defaults__",
278 278 "__doc__",
279 279 "__closure__",
280 280 "__globals__",
281 281 "__dict__",
282 282 ]
283 283
284 284
285 285 def update_function(old, new):
286 286 """Upgrade the code object of a function"""
287 287 for name in func_attrs:
288 288 try:
289 289 setattr(old, name, getattr(new, name))
290 290 except (AttributeError, TypeError):
291 291 pass
292 292
293 293
294 294 def update_instances(old, new):
295 295 """Use garbage collector to find all instances that refer to the old
296 296 class definition and update their __class__ to point to the new class
297 297 definition"""
298 298
299 299 refs = gc.get_referrers(old)
300 300
301 301 for ref in refs:
302 302 if type(ref) is old:
303 ref.__class__ = new
303 object.__setattr__(ref, "__class__", new)
304 304
305 305
306 306 def update_class(old, new):
307 307 """Replace stuff in the __dict__ of a class, and upgrade
308 308 method code objects, and add new methods, if any"""
309 309 for key in list(old.__dict__.keys()):
310 310 old_obj = getattr(old, key)
311 311 try:
312 312 new_obj = getattr(new, key)
313 313 # explicitly checking that comparison returns True to handle
314 314 # cases where `==` doesn't return a boolean.
315 315 if (old_obj == new_obj) is True:
316 316 continue
317 317 except AttributeError:
318 318 # obsolete attribute: remove it
319 319 try:
320 320 delattr(old, key)
321 321 except (AttributeError, TypeError):
322 322 pass
323 323 continue
324 324 except ValueError:
325 325 # can't compare nested structures containing
326 326 # numpy arrays using `==`
327 327 pass
328 328
329 329 if update_generic(old_obj, new_obj):
330 330 continue
331 331
332 332 try:
333 333 setattr(old, key, getattr(new, key))
334 334 except (AttributeError, TypeError):
335 335 pass # skip non-writable attributes
336 336
337 337 for key in list(new.__dict__.keys()):
338 338 if key not in list(old.__dict__.keys()):
339 339 try:
340 340 setattr(old, key, getattr(new, key))
341 341 except (AttributeError, TypeError):
342 342 pass # skip non-writable attributes
343 343
344 344 # update all instances of class
345 345 update_instances(old, new)
346 346
347 347
348 348 def update_property(old, new):
349 349 """Replace get/set/del functions of a property"""
350 350 update_generic(old.fdel, new.fdel)
351 351 update_generic(old.fget, new.fget)
352 352 update_generic(old.fset, new.fset)
353 353
354 354
355 355 def isinstance2(a, b, typ):
356 356 return isinstance(a, typ) and isinstance(b, typ)
357 357
358 358
359 359 UPDATE_RULES = [
360 360 (lambda a, b: isinstance2(a, b, type), update_class),
361 361 (lambda a, b: isinstance2(a, b, types.FunctionType), update_function),
362 362 (lambda a, b: isinstance2(a, b, property), update_property),
363 363 ]
364 364 UPDATE_RULES.extend(
365 365 [
366 366 (
367 367 lambda a, b: isinstance2(a, b, types.MethodType),
368 368 lambda a, b: update_function(a.__func__, b.__func__),
369 369 ),
370 370 ]
371 371 )
372 372
373 373
374 374 def update_generic(a, b):
375 375 for type_check, update in UPDATE_RULES:
376 376 if type_check(a, b):
377 377 update(a, b)
378 378 return True
379 379 return False
380 380
381 381
382 382 class StrongRef:
383 383 def __init__(self, obj):
384 384 self.obj = obj
385 385
386 386 def __call__(self):
387 387 return self.obj
388 388
389 389
390 390 mod_attrs = [
391 391 "__name__",
392 392 "__doc__",
393 393 "__package__",
394 394 "__loader__",
395 395 "__spec__",
396 396 "__file__",
397 397 "__cached__",
398 398 "__builtins__",
399 399 ]
400 400
401 401
402 402 def append_obj(module, d, name, obj, autoload=False):
403 403 in_module = hasattr(obj, "__module__") and obj.__module__ == module.__name__
404 404 if autoload:
405 405 # check needed for module global built-ins
406 406 if not in_module and name in mod_attrs:
407 407 return False
408 408 else:
409 409 if not in_module:
410 410 return False
411 411
412 412 key = (module.__name__, name)
413 413 try:
414 414 d.setdefault(key, []).append(weakref.ref(obj))
415 415 except TypeError:
416 416 pass
417 417 return True
418 418
419 419
420 420 def superreload(module, reload=reload, old_objects=None, shell=None):
421 421 """Enhanced version of the builtin reload function.
422 422
423 423 superreload remembers objects previously in the module, and
424 424
425 425 - upgrades the class dictionary of every old class in the module
426 426 - upgrades the code object of every old function and method
427 427 - clears the module's namespace before reloading
428 428
429 429 """
430 430 if old_objects is None:
431 431 old_objects = {}
432 432
433 433 # collect old objects in the module
434 434 for name, obj in list(module.__dict__.items()):
435 435 if not append_obj(module, old_objects, name, obj):
436 436 continue
437 437 key = (module.__name__, name)
438 438 try:
439 439 old_objects.setdefault(key, []).append(weakref.ref(obj))
440 440 except TypeError:
441 441 pass
442 442
443 443 # reload module
444 444 try:
445 445 # clear namespace first from old cruft
446 446 old_dict = module.__dict__.copy()
447 447 old_name = module.__name__
448 448 module.__dict__.clear()
449 449 module.__dict__["__name__"] = old_name
450 450 module.__dict__["__loader__"] = old_dict["__loader__"]
451 451 except (TypeError, AttributeError, KeyError):
452 452 pass
453 453
454 454 try:
455 455 module = reload(module)
456 456 except:
457 457 # restore module dictionary on failed reload
458 458 module.__dict__.update(old_dict)
459 459 raise
460 460
461 461 # iterate over all objects and update functions & classes
462 462 for name, new_obj in list(module.__dict__.items()):
463 463 key = (module.__name__, name)
464 464 if key not in old_objects:
465 465 # here 'shell' acts both as a flag and as an output var
466 466 if (
467 467 shell is None
468 468 or name == "Enum"
469 469 or not append_obj(module, old_objects, name, new_obj, True)
470 470 ):
471 471 continue
472 472 shell.user_ns[name] = new_obj
473 473
474 474 new_refs = []
475 475 for old_ref in old_objects[key]:
476 476 old_obj = old_ref()
477 477 if old_obj is None:
478 478 continue
479 479 new_refs.append(old_ref)
480 480 update_generic(old_obj, new_obj)
481 481
482 482 if new_refs:
483 483 old_objects[key] = new_refs
484 484 else:
485 485 del old_objects[key]
486 486
487 487 return module
488 488
489 489
490 490 # ------------------------------------------------------------------------------
491 491 # IPython connectivity
492 492 # ------------------------------------------------------------------------------
493 493
494 494 from IPython.core.magic import Magics, magics_class, line_magic
495 495
496 496
497 497 @magics_class
498 498 class AutoreloadMagics(Magics):
499 499 def __init__(self, *a, **kw):
500 500 super().__init__(*a, **kw)
501 501 self._reloader = ModuleReloader(self.shell)
502 502 self._reloader.check_all = False
503 503 self._reloader.autoload_obj = False
504 504 self.loaded_modules = set(sys.modules)
505 505
506 506 @line_magic
507 507 def autoreload(self, parameter_s=""):
508 508 r"""%autoreload => Reload modules automatically
509 509
510 510 %autoreload
511 511 Reload all modules (except those excluded by %aimport) automatically
512 512 now.
513 513
514 514 %autoreload 0
515 515 Disable automatic reloading.
516 516
517 517 %autoreload 1
518 518 Reload all modules imported with %aimport every time before executing
519 519 the Python code typed.
520 520
521 521 %autoreload 2
522 522 Reload all modules (except those excluded by %aimport) every time
523 523 before executing the Python code typed.
524 524
525 525 Reloading Python modules in a reliable way is in general
526 526 difficult, and unexpected things may occur. %autoreload tries to
527 527 work around common pitfalls by replacing function code objects and
528 528 parts of classes previously in the module with new versions. This
529 529 makes the following things to work:
530 530
531 531 - Functions and classes imported via 'from xxx import foo' are upgraded
532 532 to new versions when 'xxx' is reloaded.
533 533
534 534 - Methods and properties of classes are upgraded on reload, so that
535 535 calling 'c.foo()' on an object 'c' created before the reload causes
536 536 the new code for 'foo' to be executed.
537 537
538 538 Some of the known remaining caveats are:
539 539
540 540 - Replacing code objects does not always succeed: changing a @property
541 541 in a class to an ordinary method or a method to a member variable
542 542 can cause problems (but in old objects only).
543 543
544 544 - Functions that are removed (eg. via monkey-patching) from a module
545 545 before it is reloaded are not upgraded.
546 546
547 547 - C extension modules cannot be reloaded, and so cannot be
548 548 autoreloaded.
549 549
550 550 """
551 551 if parameter_s == "":
552 552 self._reloader.check(True)
553 553 elif parameter_s == "0":
554 554 self._reloader.enabled = False
555 555 elif parameter_s == "1":
556 556 self._reloader.check_all = False
557 557 self._reloader.enabled = True
558 558 elif parameter_s == "2":
559 559 self._reloader.check_all = True
560 560 self._reloader.enabled = True
561 561 self._reloader.enabled = True
562 562 elif parameter_s == "3":
563 563 self._reloader.check_all = True
564 564 self._reloader.enabled = True
565 565 self._reloader.autoload_obj = True
566 566
567 567 @line_magic
568 568 def aimport(self, parameter_s="", stream=None):
569 569 """%aimport => Import modules for automatic reloading.
570 570
571 571 %aimport
572 572 List modules to automatically import and not to import.
573 573
574 574 %aimport foo
575 575 Import module 'foo' and mark it to be autoreloaded for %autoreload 1
576 576
577 577 %aimport foo, bar
578 578 Import modules 'foo', 'bar' and mark them to be autoreloaded for %autoreload 1
579 579
580 580 %aimport -foo
581 581 Mark module 'foo' to not be autoreloaded for %autoreload 1
582 582 """
583 583 modname = parameter_s
584 584 if not modname:
585 585 to_reload = sorted(self._reloader.modules.keys())
586 586 to_skip = sorted(self._reloader.skip_modules.keys())
587 587 if stream is None:
588 588 stream = sys.stdout
589 589 if self._reloader.check_all:
590 590 stream.write("Modules to reload:\nall-except-skipped\n")
591 591 else:
592 592 stream.write("Modules to reload:\n%s\n" % " ".join(to_reload))
593 593 stream.write("\nModules to skip:\n%s\n" % " ".join(to_skip))
594 594 elif modname.startswith("-"):
595 595 modname = modname[1:]
596 596 self._reloader.mark_module_skipped(modname)
597 597 else:
598 598 for _module in [_.strip() for _ in modname.split(",")]:
599 599 top_module, top_name = self._reloader.aimport_module(_module)
600 600
601 601 # Inject module to user namespace
602 602 self.shell.push({top_name: top_module})
603 603
604 604 def pre_run_cell(self):
605 605 if self._reloader.enabled:
606 606 try:
607 607 self._reloader.check()
608 608 except:
609 609 pass
610 610
611 611 def post_execute_hook(self):
612 612 """Cache the modification times of any modules imported in this execution"""
613 613 newly_loaded_modules = set(sys.modules) - self.loaded_modules
614 614 for modname in newly_loaded_modules:
615 615 _, pymtime = self._reloader.filename_and_mtime(sys.modules[modname])
616 616 if pymtime is not None:
617 617 self._reloader.modules_mtimes[modname] = pymtime
618 618
619 619 self.loaded_modules.update(newly_loaded_modules)
620 620
621 621
622 622 def load_ipython_extension(ip):
623 623 """Load the extension in IPython."""
624 624 auto_reload = AutoreloadMagics(ip)
625 625 ip.register_magics(auto_reload)
626 626 ip.events.register("pre_run_cell", auto_reload.pre_run_cell)
627 627 ip.events.register("post_execute", auto_reload.post_execute_hook)
@@ -1,69 +1,101 b''
1 1 """ Utilities for accessing the platform's clipboard.
2 2 """
3
3 import os
4 4 import subprocess
5 5
6 6 from IPython.core.error import TryNext
7 7 import IPython.utils.py3compat as py3compat
8 8
9
9 10 class ClipboardEmpty(ValueError):
10 11 pass
11 12
13
12 14 def win32_clipboard_get():
13 15 """ Get the current clipboard's text on Windows.
14 16
15 17 Requires Mark Hammond's pywin32 extensions.
16 18 """
17 19 try:
18 20 import win32clipboard
19 21 except ImportError as e:
20 22 raise TryNext("Getting text from the clipboard requires the pywin32 "
21 23 "extensions: http://sourceforge.net/projects/pywin32/") from e
22 24 win32clipboard.OpenClipboard()
23 25 try:
24 26 text = win32clipboard.GetClipboardData(win32clipboard.CF_UNICODETEXT)
25 27 except (TypeError, win32clipboard.error):
26 28 try:
27 29 text = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)
28 30 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
29 31 except (TypeError, win32clipboard.error) as e:
30 32 raise ClipboardEmpty from e
31 33 finally:
32 34 win32clipboard.CloseClipboard()
33 35 return text
34 36
37
35 38 def osx_clipboard_get() -> str:
36 39 """ Get the clipboard's text on OS X.
37 40 """
38 41 p = subprocess.Popen(['pbpaste', '-Prefer', 'ascii'],
39 42 stdout=subprocess.PIPE)
40 43 bytes_, stderr = p.communicate()
41 44 # Text comes in with old Mac \r line endings. Change them to \n.
42 45 bytes_ = bytes_.replace(b'\r', b'\n')
43 46 text = py3compat.decode(bytes_)
44 47 return text
45 48
49
46 50 def tkinter_clipboard_get():
47 51 """ Get the clipboard's text using Tkinter.
48 52
49 53 This is the default on systems that are not Windows or OS X. It may
50 54 interfere with other UI toolkits and should be replaced with an
51 55 implementation that uses that toolkit.
52 56 """
53 57 try:
54 58 from tkinter import Tk, TclError
55 59 except ImportError as e:
56 60 raise TryNext("Getting text from the clipboard on this platform requires tkinter.") from e
57 61
58 62 root = Tk()
59 63 root.withdraw()
60 64 try:
61 65 text = root.clipboard_get()
62 66 except TclError as e:
63 67 raise ClipboardEmpty from e
64 68 finally:
65 69 root.destroy()
66 70 text = py3compat.cast_unicode(text, py3compat.DEFAULT_ENCODING)
67 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 1 # -*- coding: utf-8 -*-
2 2 """Tools for handling LaTeX."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from io import BytesIO, open
8 8 import os
9 9 import tempfile
10 10 import shutil
11 11 import subprocess
12 12 from base64 import encodebytes
13 13 import textwrap
14 14
15 15 from pathlib import Path, PurePath
16 16
17 17 from IPython.utils.process import find_cmd, FindCmdError
18 18 from traitlets.config import get_config
19 19 from traitlets.config.configurable import SingletonConfigurable
20 20 from traitlets import List, Bool, Unicode
21 21 from IPython.utils.py3compat import cast_unicode
22 22
23 23
24 24 class LaTeXTool(SingletonConfigurable):
25 25 """An object to store configuration of the LaTeX tool."""
26 26 def _config_default(self):
27 27 return get_config()
28 28
29 29 backends = List(
30 30 Unicode(), ["matplotlib", "dvipng"],
31 31 help="Preferred backend to draw LaTeX math equations. "
32 32 "Backends in the list are checked one by one and the first "
33 33 "usable one is used. Note that `matplotlib` backend "
34 34 "is usable only for inline style equations. To draw "
35 35 "display style equations, `dvipng` backend must be specified. ",
36 36 # It is a List instead of Enum, to make configuration more
37 37 # flexible. For example, to use matplotlib mainly but dvipng
38 38 # for display style, the default ["matplotlib", "dvipng"] can
39 39 # be used. To NOT use dvipng so that other repr such as
40 40 # unicode pretty printing is used, you can use ["matplotlib"].
41 41 ).tag(config=True)
42 42
43 43 use_breqn = Bool(
44 44 True,
45 45 help="Use breqn.sty to automatically break long equations. "
46 46 "This configuration takes effect only for dvipng backend.",
47 47 ).tag(config=True)
48 48
49 49 packages = List(
50 50 ['amsmath', 'amsthm', 'amssymb', 'bm'],
51 51 help="A list of packages to use for dvipng backend. "
52 52 "'breqn' will be automatically appended when use_breqn=True.",
53 53 ).tag(config=True)
54 54
55 55 preamble = Unicode(
56 56 help="Additional preamble to use when generating LaTeX source "
57 57 "for dvipng backend.",
58 58 ).tag(config=True)
59 59
60 60
61 61 def latex_to_png(s, encode=False, backend=None, wrap=False, color='Black',
62 62 scale=1.0):
63 63 """Render a LaTeX string to PNG.
64 64
65 65 Parameters
66 66 ----------
67 67 s : str
68 68 The raw string containing valid inline LaTeX.
69 69 encode : bool, optional
70 70 Should the PNG data base64 encoded to make it JSON'able.
71 71 backend : {matplotlib, dvipng}
72 72 Backend for producing PNG data.
73 73 wrap : bool
74 74 If true, Automatically wrap `s` as a LaTeX equation.
75 75 color : string
76 76 Foreground color name among dvipsnames, e.g. 'Maroon' or on hex RGB
77 77 format, e.g. '#AA20FA'.
78 78 scale : float
79 79 Scale factor for the resulting PNG.
80 80 None is returned when the backend cannot be used.
81 81
82 82 """
83 83 s = cast_unicode(s)
84 84 allowed_backends = LaTeXTool.instance().backends
85 85 if backend is None:
86 86 backend = allowed_backends[0]
87 87 if backend not in allowed_backends:
88 88 return None
89 89 if backend == 'matplotlib':
90 90 f = latex_to_png_mpl
91 91 elif backend == 'dvipng':
92 92 f = latex_to_png_dvipng
93 93 if color.startswith('#'):
94 94 # Convert hex RGB color to LaTeX RGB color.
95 95 if len(color) == 7:
96 96 try:
97 97 color = "RGB {}".format(" ".join([str(int(x, 16)) for x in
98 98 textwrap.wrap(color[1:], 2)]))
99 99 except ValueError as e:
100 100 raise ValueError('Invalid color specification {}.'.format(color)) from e
101 101 else:
102 102 raise ValueError('Invalid color specification {}.'.format(color))
103 103 else:
104 104 raise ValueError('No such backend {0}'.format(backend))
105 105 bin_data = f(s, wrap, color, scale)
106 106 if encode and bin_data:
107 107 bin_data = encodebytes(bin_data)
108 108 return bin_data
109 109
110 110
111 111 def latex_to_png_mpl(s, wrap, color='Black', scale=1.0):
112 112 try:
113 113 from matplotlib import figure, font_manager, mathtext
114 114 from matplotlib.backends import backend_agg
115 115 from pyparsing import ParseFatalException
116 116 except ImportError:
117 117 return None
118 118
119 119 # mpl mathtext doesn't support display math, force inline
120 120 s = s.replace('$$', '$')
121 121 if wrap:
122 122 s = u'${0}$'.format(s)
123 123
124 124 try:
125 125 prop = font_manager.FontProperties(size=12)
126 126 dpi = 120 * scale
127 127 buffer = BytesIO()
128 128
129 129 # Adapted from mathtext.math_to_image
130 130 parser = mathtext.MathTextParser("path")
131 131 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
132 132 fig = figure.Figure(figsize=(width / 72, height / 72))
133 133 fig.text(0, depth / height, s, fontproperties=prop, color=color)
134 134 backend_agg.FigureCanvasAgg(fig)
135 135 fig.savefig(buffer, dpi=dpi, format="png", transparent=True)
136 136 return buffer.getvalue()
137 137 except (ValueError, RuntimeError, ParseFatalException):
138 138 return None
139 139
140 140
141 141 def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):
142 142 try:
143 143 find_cmd('latex')
144 144 find_cmd('dvipng')
145 145 except FindCmdError:
146 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 154 try:
148 155 workdir = Path(tempfile.mkdtemp())
149 tmpfile = workdir.joinpath("tmp.tex")
150 dvifile = workdir.joinpath("tmp.dvi")
151 outfile = workdir.joinpath("tmp.png")
156 tmpfile = "tmp.tex"
157 dvifile = "tmp.dvi"
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 161 f.writelines(genelatex(s, wrap))
155 162
156 163 with open(os.devnull, 'wb') as devnull:
157 164 subprocess.check_call(
158 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 172 resolution = round(150*scale)
162 173 subprocess.check_call(
163 174 [
164 175 "dvipng",
165 176 "-T",
166 177 "tight",
167 178 "-D",
168 179 str(resolution),
169 180 "-z",
170 181 "9",
171 182 "-bg",
172 183 "Transparent",
173 184 "-o",
174 185 outfile,
175 186 dvifile,
176 187 "-fg",
177 188 color,
178 189 ],
179 190 cwd=workdir,
180 191 stdout=devnull,
181 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 197 return f.read()
186 198 except subprocess.CalledProcessError:
187 199 return None
188 200 finally:
189 201 shutil.rmtree(workdir)
190 202
191 203
192 204 def kpsewhich(filename):
193 205 """Invoke kpsewhich command with an argument `filename`."""
194 206 try:
195 207 find_cmd("kpsewhich")
196 208 proc = subprocess.Popen(
197 209 ["kpsewhich", filename],
198 210 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
199 211 (stdout, stderr) = proc.communicate()
200 212 return stdout.strip().decode('utf8', 'replace')
201 213 except FindCmdError:
202 214 pass
203 215
204 216
205 217 def genelatex(body, wrap):
206 218 """Generate LaTeX document for dvipng backend."""
207 219 lt = LaTeXTool.instance()
208 220 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
209 221 yield r'\documentclass{article}'
210 222 packages = lt.packages
211 223 if breqn:
212 224 packages = packages + ['breqn']
213 225 for pack in packages:
214 226 yield r'\usepackage{{{0}}}'.format(pack)
215 227 yield r'\pagestyle{empty}'
216 228 if lt.preamble:
217 229 yield lt.preamble
218 230 yield r'\begin{document}'
219 231 if breqn:
220 232 yield r'\begin{dmath*}'
221 233 yield body
222 234 yield r'\end{dmath*}'
223 235 elif wrap:
224 236 yield u'$${0}$$'.format(body)
225 237 else:
226 238 yield body
227 239 yield u'\\end{document}'
228 240
229 241
230 242 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
231 243
232 244 def latex_to_html(s, alt='image'):
233 245 """Render LaTeX to HTML with embedded PNG data using data URIs.
234 246
235 247 Parameters
236 248 ----------
237 249 s : str
238 250 The raw string containing valid inline LateX.
239 251 alt : str
240 252 The alt text to use for the HTML.
241 253 """
242 254 base64_data = latex_to_png(s, encode=True).decode('ascii')
243 255 if base64_data:
244 256 return _data_uri_template_png % (base64_data, alt)
245 257
246 258
@@ -1,951 +1,953 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Python advanced pretty printer. This pretty printer is intended to
4 4 replace the old `pprint` python module which does not allow developers
5 5 to provide their own pretty print callbacks.
6 6
7 7 This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`.
8 8
9 9
10 10 Example Usage
11 11 -------------
12 12
13 13 To directly print the representation of an object use `pprint`::
14 14
15 15 from pretty import pprint
16 16 pprint(complex_object)
17 17
18 18 To get a string of the output use `pretty`::
19 19
20 20 from pretty import pretty
21 21 string = pretty(complex_object)
22 22
23 23
24 24 Extending
25 25 ---------
26 26
27 27 The pretty library allows developers to add pretty printing rules for their
28 28 own objects. This process is straightforward. All you have to do is to
29 29 add a `_repr_pretty_` method to your object and call the methods on the
30 30 pretty printer passed::
31 31
32 32 class MyObject(object):
33 33
34 34 def _repr_pretty_(self, p, cycle):
35 35 ...
36 36
37 37 Here's an example for a class with a simple constructor::
38 38
39 39 class MySimpleObject:
40 40
41 41 def __init__(self, a, b, *, c=None):
42 42 self.a = a
43 43 self.b = b
44 44 self.c = c
45 45
46 46 def _repr_pretty_(self, p, cycle):
47 47 ctor = CallExpression.factory(self.__class__.__name__)
48 48 if self.c is None:
49 49 p.pretty(ctor(a, b))
50 50 else:
51 51 p.pretty(ctor(a, b, c=c))
52 52
53 53 Here is an example implementation of a `_repr_pretty_` method for a list
54 54 subclass::
55 55
56 56 class MyList(list):
57 57
58 58 def _repr_pretty_(self, p, cycle):
59 59 if cycle:
60 60 p.text('MyList(...)')
61 61 else:
62 62 with p.group(8, 'MyList([', '])'):
63 63 for idx, item in enumerate(self):
64 64 if idx:
65 65 p.text(',')
66 66 p.breakable()
67 67 p.pretty(item)
68 68
69 69 The `cycle` parameter is `True` if pretty detected a cycle. You *have* to
70 70 react to that or the result is an infinite loop. `p.text()` just adds
71 71 non breaking text to the output, `p.breakable()` either adds a whitespace
72 72 or breaks here. If you pass it an argument it's used instead of the
73 73 default space. `p.pretty` prettyprints another object using the pretty print
74 74 method.
75 75
76 76 The first parameter to the `group` function specifies the extra indentation
77 77 of the next line. In this example the next item will either be on the same
78 78 line (if the items are short enough) or aligned with the right edge of the
79 79 opening bracket of `MyList`.
80 80
81 81 If you just want to indent something you can use the group function
82 82 without open / close parameters. You can also use this code::
83 83
84 84 with p.indent(2):
85 85 ...
86 86
87 87 Inheritance diagram:
88 88
89 89 .. inheritance-diagram:: IPython.lib.pretty
90 90 :parts: 3
91 91
92 92 :copyright: 2007 by Armin Ronacher.
93 93 Portions (c) 2009 by Robert Kern.
94 94 :license: BSD License.
95 95 """
96 96
97 97 from contextlib import contextmanager
98 98 import datetime
99 99 import os
100 100 import re
101 101 import sys
102 102 import types
103 103 from collections import deque
104 104 from inspect import signature
105 105 from io import StringIO
106 106 from warnings import warn
107 107
108 108 from IPython.utils.decorators import undoc
109 109 from IPython.utils.py3compat import PYPY
110 110
111 111 __all__ = ['pretty', 'pprint', 'PrettyPrinter', 'RepresentationPrinter',
112 112 'for_type', 'for_type_by_name', 'RawText', 'RawStringLiteral', 'CallExpression']
113 113
114 114
115 115 MAX_SEQ_LENGTH = 1000
116 116 _re_pattern_type = type(re.compile(''))
117 117
118 118 def _safe_getattr(obj, attr, default=None):
119 119 """Safe version of getattr.
120 120
121 121 Same as getattr, but will return ``default`` on any Exception,
122 122 rather than raising.
123 123 """
124 124 try:
125 125 return getattr(obj, attr, default)
126 126 except Exception:
127 127 return default
128 128
129 129 @undoc
130 130 class CUnicodeIO(StringIO):
131 131 def __init__(self, *args, **kwargs):
132 132 super().__init__(*args, **kwargs)
133 133 warn(("CUnicodeIO is deprecated since IPython 6.0. "
134 134 "Please use io.StringIO instead."),
135 135 DeprecationWarning, stacklevel=2)
136 136
137 137 def _sorted_for_pprint(items):
138 138 """
139 139 Sort the given items for pretty printing. Since some predictable
140 140 sorting is better than no sorting at all, we sort on the string
141 141 representation if normal sorting fails.
142 142 """
143 143 items = list(items)
144 144 try:
145 145 return sorted(items)
146 146 except Exception:
147 147 try:
148 148 return sorted(items, key=str)
149 149 except Exception:
150 150 return items
151 151
152 152 def pretty(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
153 153 """
154 154 Pretty print the object's representation.
155 155 """
156 156 stream = StringIO()
157 157 printer = RepresentationPrinter(stream, verbose, max_width, newline, max_seq_length=max_seq_length)
158 158 printer.pretty(obj)
159 159 printer.flush()
160 160 return stream.getvalue()
161 161
162 162
163 163 def pprint(obj, verbose=False, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
164 164 """
165 165 Like `pretty` but print to stdout.
166 166 """
167 167 printer = RepresentationPrinter(sys.stdout, verbose, max_width, newline, max_seq_length=max_seq_length)
168 168 printer.pretty(obj)
169 169 printer.flush()
170 170 sys.stdout.write(newline)
171 171 sys.stdout.flush()
172 172
173 173 class _PrettyPrinterBase(object):
174 174
175 175 @contextmanager
176 176 def indent(self, indent):
177 177 """with statement support for indenting/dedenting."""
178 178 self.indentation += indent
179 179 try:
180 180 yield
181 181 finally:
182 182 self.indentation -= indent
183 183
184 184 @contextmanager
185 185 def group(self, indent=0, open='', close=''):
186 186 """like begin_group / end_group but for the with statement."""
187 187 self.begin_group(indent, open)
188 188 try:
189 189 yield
190 190 finally:
191 191 self.end_group(indent, close)
192 192
193 193 class PrettyPrinter(_PrettyPrinterBase):
194 194 """
195 195 Baseclass for the `RepresentationPrinter` prettyprinter that is used to
196 196 generate pretty reprs of objects. Contrary to the `RepresentationPrinter`
197 197 this printer knows nothing about the default pprinters or the `_repr_pretty_`
198 198 callback method.
199 199 """
200 200
201 201 def __init__(self, output, max_width=79, newline='\n', max_seq_length=MAX_SEQ_LENGTH):
202 202 self.output = output
203 203 self.max_width = max_width
204 204 self.newline = newline
205 205 self.max_seq_length = max_seq_length
206 206 self.output_width = 0
207 207 self.buffer_width = 0
208 208 self.buffer = deque()
209 209
210 210 root_group = Group(0)
211 211 self.group_stack = [root_group]
212 212 self.group_queue = GroupQueue(root_group)
213 213 self.indentation = 0
214 214
215 215 def _break_one_group(self, group):
216 216 while group.breakables:
217 217 x = self.buffer.popleft()
218 218 self.output_width = x.output(self.output, self.output_width)
219 219 self.buffer_width -= x.width
220 220 while self.buffer and isinstance(self.buffer[0], Text):
221 221 x = self.buffer.popleft()
222 222 self.output_width = x.output(self.output, self.output_width)
223 223 self.buffer_width -= x.width
224 224
225 225 def _break_outer_groups(self):
226 226 while self.max_width < self.output_width + self.buffer_width:
227 227 group = self.group_queue.deq()
228 228 if not group:
229 229 return
230 230 self._break_one_group(group)
231 231
232 232 def text(self, obj):
233 233 """Add literal text to the output."""
234 234 width = len(obj)
235 235 if self.buffer:
236 236 text = self.buffer[-1]
237 237 if not isinstance(text, Text):
238 238 text = Text()
239 239 self.buffer.append(text)
240 240 text.add(obj, width)
241 241 self.buffer_width += width
242 242 self._break_outer_groups()
243 243 else:
244 244 self.output.write(obj)
245 245 self.output_width += width
246 246
247 247 def breakable(self, sep=' '):
248 248 """
249 249 Add a breakable separator to the output. This does not mean that it
250 250 will automatically break here. If no breaking on this position takes
251 251 place the `sep` is inserted which default to one space.
252 252 """
253 253 width = len(sep)
254 254 group = self.group_stack[-1]
255 255 if group.want_break:
256 256 self.flush()
257 257 self.output.write(self.newline)
258 258 self.output.write(' ' * self.indentation)
259 259 self.output_width = self.indentation
260 260 self.buffer_width = 0
261 261 else:
262 262 self.buffer.append(Breakable(sep, width, self))
263 263 self.buffer_width += width
264 264 self._break_outer_groups()
265 265
266 266 def break_(self):
267 267 """
268 268 Explicitly insert a newline into the output, maintaining correct indentation.
269 269 """
270 270 group = self.group_queue.deq()
271 271 if group:
272 272 self._break_one_group(group)
273 273 self.flush()
274 274 self.output.write(self.newline)
275 275 self.output.write(' ' * self.indentation)
276 276 self.output_width = self.indentation
277 277 self.buffer_width = 0
278 278
279 279
280 280 def begin_group(self, indent=0, open=''):
281 281 """
282 282 Begin a group.
283 283 The first parameter specifies the indentation for the next line (usually
284 284 the width of the opening text), the second the opening text. All
285 285 parameters are optional.
286 286 """
287 287 if open:
288 288 self.text(open)
289 289 group = Group(self.group_stack[-1].depth + 1)
290 290 self.group_stack.append(group)
291 291 self.group_queue.enq(group)
292 292 self.indentation += indent
293 293
294 294 def _enumerate(self, seq):
295 295 """like enumerate, but with an upper limit on the number of items"""
296 296 for idx, x in enumerate(seq):
297 297 if self.max_seq_length and idx >= self.max_seq_length:
298 298 self.text(',')
299 299 self.breakable()
300 300 self.text('...')
301 301 return
302 302 yield idx, x
303 303
304 304 def end_group(self, dedent=0, close=''):
305 305 """End a group. See `begin_group` for more details."""
306 306 self.indentation -= dedent
307 307 group = self.group_stack.pop()
308 308 if not group.breakables:
309 309 self.group_queue.remove(group)
310 310 if close:
311 311 self.text(close)
312 312
313 313 def flush(self):
314 314 """Flush data that is left in the buffer."""
315 315 for data in self.buffer:
316 316 self.output_width += data.output(self.output, self.output_width)
317 317 self.buffer.clear()
318 318 self.buffer_width = 0
319 319
320 320
321 321 def _get_mro(obj_class):
322 322 """ Get a reasonable method resolution order of a class and its superclasses
323 323 for both old-style and new-style classes.
324 324 """
325 325 if not hasattr(obj_class, '__mro__'):
326 326 # Old-style class. Mix in object to make a fake new-style class.
327 327 try:
328 328 obj_class = type(obj_class.__name__, (obj_class, object), {})
329 329 except TypeError:
330 330 # Old-style extension type that does not descend from object.
331 331 # FIXME: try to construct a more thorough MRO.
332 332 mro = [obj_class]
333 333 else:
334 334 mro = obj_class.__mro__[1:-1]
335 335 else:
336 336 mro = obj_class.__mro__
337 337 return mro
338 338
339 339
340 340 class RepresentationPrinter(PrettyPrinter):
341 341 """
342 342 Special pretty printer that has a `pretty` method that calls the pretty
343 343 printer for a python object.
344 344
345 345 This class stores processing data on `self` so you must *never* use
346 346 this class in a threaded environment. Always lock it or reinstanciate
347 347 it.
348 348
349 349 Instances also have a verbose flag callbacks can access to control their
350 350 output. For example the default instance repr prints all attributes and
351 351 methods that are not prefixed by an underscore if the printer is in
352 352 verbose mode.
353 353 """
354 354
355 355 def __init__(self, output, verbose=False, max_width=79, newline='\n',
356 356 singleton_pprinters=None, type_pprinters=None, deferred_pprinters=None,
357 357 max_seq_length=MAX_SEQ_LENGTH):
358 358
359 359 PrettyPrinter.__init__(self, output, max_width, newline, max_seq_length=max_seq_length)
360 360 self.verbose = verbose
361 361 self.stack = []
362 362 if singleton_pprinters is None:
363 363 singleton_pprinters = _singleton_pprinters.copy()
364 364 self.singleton_pprinters = singleton_pprinters
365 365 if type_pprinters is None:
366 366 type_pprinters = _type_pprinters.copy()
367 367 self.type_pprinters = type_pprinters
368 368 if deferred_pprinters is None:
369 369 deferred_pprinters = _deferred_type_pprinters.copy()
370 370 self.deferred_pprinters = deferred_pprinters
371 371
372 372 def pretty(self, obj):
373 373 """Pretty print the given object."""
374 374 obj_id = id(obj)
375 375 cycle = obj_id in self.stack
376 376 self.stack.append(obj_id)
377 377 self.begin_group()
378 378 try:
379 379 obj_class = _safe_getattr(obj, '__class__', None) or type(obj)
380 380 # First try to find registered singleton printers for the type.
381 381 try:
382 382 printer = self.singleton_pprinters[obj_id]
383 383 except (TypeError, KeyError):
384 384 pass
385 385 else:
386 386 return printer(obj, self, cycle)
387 387 # Next walk the mro and check for either:
388 388 # 1) a registered printer
389 389 # 2) a _repr_pretty_ method
390 390 for cls in _get_mro(obj_class):
391 391 if cls in self.type_pprinters:
392 392 # printer registered in self.type_pprinters
393 393 return self.type_pprinters[cls](obj, self, cycle)
394 394 else:
395 395 # deferred printer
396 396 printer = self._in_deferred_types(cls)
397 397 if printer is not None:
398 398 return printer(obj, self, cycle)
399 399 else:
400 400 # Finally look for special method names.
401 401 # Some objects automatically create any requested
402 402 # attribute. Try to ignore most of them by checking for
403 403 # callability.
404 404 if '_repr_pretty_' in cls.__dict__:
405 405 meth = cls._repr_pretty_
406 406 if callable(meth):
407 407 return meth(obj, self, cycle)
408 408 if cls is not object \
409 409 and callable(cls.__dict__.get('__repr__')):
410 410 return _repr_pprint(obj, self, cycle)
411 411
412 412 return _default_pprint(obj, self, cycle)
413 413 finally:
414 414 self.end_group()
415 415 self.stack.pop()
416 416
417 417 def _in_deferred_types(self, cls):
418 418 """
419 419 Check if the given class is specified in the deferred type registry.
420 420
421 421 Returns the printer from the registry if it exists, and None if the
422 422 class is not in the registry. Successful matches will be moved to the
423 423 regular type registry for future use.
424 424 """
425 425 mod = _safe_getattr(cls, '__module__', None)
426 426 name = _safe_getattr(cls, '__name__', None)
427 427 key = (mod, name)
428 428 printer = None
429 429 if key in self.deferred_pprinters:
430 430 # Move the printer over to the regular registry.
431 431 printer = self.deferred_pprinters.pop(key)
432 432 self.type_pprinters[cls] = printer
433 433 return printer
434 434
435 435
436 436 class Printable(object):
437 437
438 438 def output(self, stream, output_width):
439 439 return output_width
440 440
441 441
442 442 class Text(Printable):
443 443
444 444 def __init__(self):
445 445 self.objs = []
446 446 self.width = 0
447 447
448 448 def output(self, stream, output_width):
449 449 for obj in self.objs:
450 450 stream.write(obj)
451 451 return output_width + self.width
452 452
453 453 def add(self, obj, width):
454 454 self.objs.append(obj)
455 455 self.width += width
456 456
457 457
458 458 class Breakable(Printable):
459 459
460 460 def __init__(self, seq, width, pretty):
461 461 self.obj = seq
462 462 self.width = width
463 463 self.pretty = pretty
464 464 self.indentation = pretty.indentation
465 465 self.group = pretty.group_stack[-1]
466 466 self.group.breakables.append(self)
467 467
468 468 def output(self, stream, output_width):
469 469 self.group.breakables.popleft()
470 470 if self.group.want_break:
471 471 stream.write(self.pretty.newline)
472 472 stream.write(' ' * self.indentation)
473 473 return self.indentation
474 474 if not self.group.breakables:
475 475 self.pretty.group_queue.remove(self.group)
476 476 stream.write(self.obj)
477 477 return output_width + self.width
478 478
479 479
480 480 class Group(Printable):
481 481
482 482 def __init__(self, depth):
483 483 self.depth = depth
484 484 self.breakables = deque()
485 485 self.want_break = False
486 486
487 487
488 488 class GroupQueue(object):
489 489
490 490 def __init__(self, *groups):
491 491 self.queue = []
492 492 for group in groups:
493 493 self.enq(group)
494 494
495 495 def enq(self, group):
496 496 depth = group.depth
497 497 while depth > len(self.queue) - 1:
498 498 self.queue.append([])
499 499 self.queue[depth].append(group)
500 500
501 501 def deq(self):
502 502 for stack in self.queue:
503 503 for idx, group in enumerate(reversed(stack)):
504 504 if group.breakables:
505 505 del stack[idx]
506 506 group.want_break = True
507 507 return group
508 508 for group in stack:
509 509 group.want_break = True
510 510 del stack[:]
511 511
512 512 def remove(self, group):
513 513 try:
514 514 self.queue[group.depth].remove(group)
515 515 except ValueError:
516 516 pass
517 517
518 518
519 519 class RawText:
520 520 """ Object such that ``p.pretty(RawText(value))`` is the same as ``p.text(value)``.
521 521
522 522 An example usage of this would be to show a list as binary numbers, using
523 523 ``p.pretty([RawText(bin(i)) for i in integers])``.
524 524 """
525 525 def __init__(self, value):
526 526 self.value = value
527 527
528 528 def _repr_pretty_(self, p, cycle):
529 529 p.text(self.value)
530 530
531 531
532 532 class CallExpression:
533 533 """ Object which emits a line-wrapped call expression in the form `__name(*args, **kwargs)` """
534 534 def __init__(__self, __name, *args, **kwargs):
535 535 # dunders are to avoid clashes with kwargs, as python's name manging
536 536 # will kick in.
537 537 self = __self
538 538 self.name = __name
539 539 self.args = args
540 540 self.kwargs = kwargs
541 541
542 542 @classmethod
543 543 def factory(cls, name):
544 544 def inner(*args, **kwargs):
545 545 return cls(name, *args, **kwargs)
546 546 return inner
547 547
548 548 def _repr_pretty_(self, p, cycle):
549 549 # dunders are to avoid clashes with kwargs, as python's name manging
550 550 # will kick in.
551 551
552 552 started = False
553 553 def new_item():
554 554 nonlocal started
555 555 if started:
556 556 p.text(",")
557 557 p.breakable()
558 558 started = True
559 559
560 560 prefix = self.name + "("
561 561 with p.group(len(prefix), prefix, ")"):
562 562 for arg in self.args:
563 563 new_item()
564 564 p.pretty(arg)
565 565 for arg_name, arg in self.kwargs.items():
566 566 new_item()
567 567 arg_prefix = arg_name + "="
568 568 with p.group(len(arg_prefix), arg_prefix):
569 569 p.pretty(arg)
570 570
571 571
572 572 class RawStringLiteral:
573 573 """ Wrapper that shows a string with a `r` prefix """
574 574 def __init__(self, value):
575 575 self.value = value
576 576
577 577 def _repr_pretty_(self, p, cycle):
578 578 base_repr = repr(self.value)
579 579 if base_repr[:1] in 'uU':
580 580 base_repr = base_repr[1:]
581 581 prefix = 'ur'
582 582 else:
583 583 prefix = 'r'
584 584 base_repr = prefix + base_repr.replace('\\\\', '\\')
585 585 p.text(base_repr)
586 586
587 587
588 588 def _default_pprint(obj, p, cycle):
589 589 """
590 590 The default print function. Used if an object does not provide one and
591 591 it's none of the builtin objects.
592 592 """
593 593 klass = _safe_getattr(obj, '__class__', None) or type(obj)
594 594 if _safe_getattr(klass, '__repr__', None) is not object.__repr__:
595 595 # A user-provided repr. Find newlines and replace them with p.break_()
596 596 _repr_pprint(obj, p, cycle)
597 597 return
598 598 p.begin_group(1, '<')
599 599 p.pretty(klass)
600 600 p.text(' at 0x%x' % id(obj))
601 601 if cycle:
602 602 p.text(' ...')
603 603 elif p.verbose:
604 604 first = True
605 605 for key in dir(obj):
606 606 if not key.startswith('_'):
607 607 try:
608 608 value = getattr(obj, key)
609 609 except AttributeError:
610 610 continue
611 611 if isinstance(value, types.MethodType):
612 612 continue
613 613 if not first:
614 614 p.text(',')
615 615 p.breakable()
616 616 p.text(key)
617 617 p.text('=')
618 618 step = len(key) + 1
619 619 p.indentation += step
620 620 p.pretty(value)
621 621 p.indentation -= step
622 622 first = False
623 623 p.end_group(1, '>')
624 624
625 625
626 626 def _seq_pprinter_factory(start, end):
627 627 """
628 628 Factory that returns a pprint function useful for sequences. Used by
629 629 the default pprint for tuples and lists.
630 630 """
631 631 def inner(obj, p, cycle):
632 632 if cycle:
633 633 return p.text(start + '...' + end)
634 634 step = len(start)
635 635 p.begin_group(step, start)
636 636 for idx, x in p._enumerate(obj):
637 637 if idx:
638 638 p.text(',')
639 639 p.breakable()
640 640 p.pretty(x)
641 641 if len(obj) == 1 and isinstance(obj, tuple):
642 642 # Special case for 1-item tuples.
643 643 p.text(',')
644 644 p.end_group(step, end)
645 645 return inner
646 646
647 647
648 648 def _set_pprinter_factory(start, end):
649 649 """
650 650 Factory that returns a pprint function useful for sets and frozensets.
651 651 """
652 652 def inner(obj, p, cycle):
653 653 if cycle:
654 654 return p.text(start + '...' + end)
655 655 if len(obj) == 0:
656 656 # Special case.
657 657 p.text(type(obj).__name__ + '()')
658 658 else:
659 659 step = len(start)
660 660 p.begin_group(step, start)
661 661 # Like dictionary keys, we will try to sort the items if there aren't too many
662 662 if not (p.max_seq_length and len(obj) >= p.max_seq_length):
663 663 items = _sorted_for_pprint(obj)
664 664 else:
665 665 items = obj
666 666 for idx, x in p._enumerate(items):
667 667 if idx:
668 668 p.text(',')
669 669 p.breakable()
670 670 p.pretty(x)
671 671 p.end_group(step, end)
672 672 return inner
673 673
674 674
675 675 def _dict_pprinter_factory(start, end):
676 676 """
677 677 Factory that returns a pprint function used by the default pprint of
678 678 dicts and dict proxies.
679 679 """
680 680 def inner(obj, p, cycle):
681 681 if cycle:
682 682 return p.text('{...}')
683 683 step = len(start)
684 684 p.begin_group(step, start)
685 685 keys = obj.keys()
686 686 for idx, key in p._enumerate(keys):
687 687 if idx:
688 688 p.text(',')
689 689 p.breakable()
690 690 p.pretty(key)
691 691 p.text(': ')
692 692 p.pretty(obj[key])
693 693 p.end_group(step, end)
694 694 return inner
695 695
696 696
697 697 def _super_pprint(obj, p, cycle):
698 698 """The pprint for the super type."""
699 699 p.begin_group(8, '<super: ')
700 700 p.pretty(obj.__thisclass__)
701 701 p.text(',')
702 702 p.breakable()
703 703 if PYPY: # In PyPy, super() objects don't have __self__ attributes
704 704 dself = obj.__repr__.__self__
705 705 p.pretty(None if dself is obj else dself)
706 706 else:
707 707 p.pretty(obj.__self__)
708 708 p.end_group(8, '>')
709 709
710 710
711 711
712 712 class _ReFlags:
713 713 def __init__(self, value):
714 714 self.value = value
715 715
716 716 def _repr_pretty_(self, p, cycle):
717 717 done_one = False
718 718 for flag in ('TEMPLATE', 'IGNORECASE', 'LOCALE', 'MULTILINE', 'DOTALL',
719 719 'UNICODE', 'VERBOSE', 'DEBUG'):
720 720 if self.value & getattr(re, flag):
721 721 if done_one:
722 722 p.text('|')
723 723 p.text('re.' + flag)
724 724 done_one = True
725 725
726 726
727 727 def _re_pattern_pprint(obj, p, cycle):
728 728 """The pprint function for regular expression patterns."""
729 729 re_compile = CallExpression.factory('re.compile')
730 730 if obj.flags:
731 731 p.pretty(re_compile(RawStringLiteral(obj.pattern), _ReFlags(obj.flags)))
732 732 else:
733 733 p.pretty(re_compile(RawStringLiteral(obj.pattern)))
734 734
735 735
736 736 def _types_simplenamespace_pprint(obj, p, cycle):
737 737 """The pprint function for types.SimpleNamespace."""
738 738 namespace = CallExpression.factory('namespace')
739 739 if cycle:
740 740 p.pretty(namespace(RawText("...")))
741 741 else:
742 742 p.pretty(namespace(**obj.__dict__))
743 743
744 744
745 745 def _type_pprint(obj, p, cycle):
746 746 """The pprint for classes and types."""
747 747 # Heap allocated types might not have the module attribute,
748 748 # and others may set it to None.
749 749
750 750 # Checks for a __repr__ override in the metaclass. Can't compare the
751 751 # type(obj).__repr__ directly because in PyPy the representation function
752 752 # inherited from type isn't the same type.__repr__
753 753 if [m for m in _get_mro(type(obj)) if "__repr__" in vars(m)][:1] != [type]:
754 754 _repr_pprint(obj, p, cycle)
755 755 return
756 756
757 757 mod = _safe_getattr(obj, '__module__', None)
758 758 try:
759 759 name = obj.__qualname__
760 760 if not isinstance(name, str):
761 761 # This can happen if the type implements __qualname__ as a property
762 762 # or other descriptor in Python 2.
763 763 raise Exception("Try __name__")
764 764 except Exception:
765 765 name = obj.__name__
766 766 if not isinstance(name, str):
767 767 name = '<unknown type>'
768 768
769 769 if mod in (None, '__builtin__', 'builtins', 'exceptions'):
770 770 p.text(name)
771 771 else:
772 772 p.text(mod + '.' + name)
773 773
774 774
775 775 def _repr_pprint(obj, p, cycle):
776 776 """A pprint that just redirects to the normal repr function."""
777 777 # Find newlines and replace them with p.break_()
778 778 output = repr(obj)
779 779 lines = output.splitlines()
780 780 with p.group():
781 781 for idx, output_line in enumerate(lines):
782 782 if idx:
783 783 p.break_()
784 784 p.text(output_line)
785 785
786 786
787 787 def _function_pprint(obj, p, cycle):
788 788 """Base pprint for all functions and builtin functions."""
789 789 name = _safe_getattr(obj, '__qualname__', obj.__name__)
790 790 mod = obj.__module__
791 791 if mod and mod not in ('__builtin__', 'builtins', 'exceptions'):
792 792 name = mod + '.' + name
793 793 try:
794 794 func_def = name + str(signature(obj))
795 795 except ValueError:
796 796 func_def = name
797 797 p.text('<function %s>' % func_def)
798 798
799 799
800 800 def _exception_pprint(obj, p, cycle):
801 801 """Base pprint for all exceptions."""
802 802 name = getattr(obj.__class__, '__qualname__', obj.__class__.__name__)
803 803 if obj.__class__.__module__ not in ('exceptions', 'builtins'):
804 804 name = '%s.%s' % (obj.__class__.__module__, name)
805 805
806 806 p.pretty(CallExpression(name, *getattr(obj, 'args', ())))
807 807
808 808
809 809 #: the exception base
810 810 try:
811 811 _exception_base = BaseException
812 812 except NameError:
813 813 _exception_base = Exception
814 814
815 815
816 816 #: printers for builtin types
817 817 _type_pprinters = {
818 818 int: _repr_pprint,
819 819 float: _repr_pprint,
820 820 str: _repr_pprint,
821 821 tuple: _seq_pprinter_factory('(', ')'),
822 822 list: _seq_pprinter_factory('[', ']'),
823 823 dict: _dict_pprinter_factory('{', '}'),
824 824 set: _set_pprinter_factory('{', '}'),
825 825 frozenset: _set_pprinter_factory('frozenset({', '})'),
826 826 super: _super_pprint,
827 827 _re_pattern_type: _re_pattern_pprint,
828 828 type: _type_pprint,
829 829 types.FunctionType: _function_pprint,
830 830 types.BuiltinFunctionType: _function_pprint,
831 831 types.MethodType: _repr_pprint,
832 832 types.SimpleNamespace: _types_simplenamespace_pprint,
833 833 datetime.datetime: _repr_pprint,
834 834 datetime.timedelta: _repr_pprint,
835 835 _exception_base: _exception_pprint
836 836 }
837 837
838 838 # render os.environ like a dict
839 839 _env_type = type(os.environ)
840 840 # future-proof in case os.environ becomes a plain dict?
841 841 if _env_type is not dict:
842 842 _type_pprinters[_env_type] = _dict_pprinter_factory('environ{', '}')
843 843
844 844 try:
845 845 # In PyPy, types.DictProxyType is dict, setting the dictproxy printer
846 846 # using dict.setdefault avoids overwriting the dict printer
847 847 _type_pprinters.setdefault(types.DictProxyType,
848 848 _dict_pprinter_factory('dict_proxy({', '})'))
849 849 _type_pprinters[types.ClassType] = _type_pprint
850 850 _type_pprinters[types.SliceType] = _repr_pprint
851 851 except AttributeError: # Python 3
852 852 _type_pprinters[types.MappingProxyType] = \
853 853 _dict_pprinter_factory('mappingproxy({', '})')
854 854 _type_pprinters[slice] = _repr_pprint
855 855
856 856 _type_pprinters[range] = _repr_pprint
857 857 _type_pprinters[bytes] = _repr_pprint
858 858
859 859 #: printers for types specified by name
860 860 _deferred_type_pprinters = {
861 861 }
862 862
863 863 def for_type(typ, func):
864 864 """
865 865 Add a pretty printer for a given type.
866 866 """
867 867 oldfunc = _type_pprinters.get(typ, None)
868 868 if func is not None:
869 869 # To support easy restoration of old pprinters, we need to ignore Nones.
870 870 _type_pprinters[typ] = func
871 871 return oldfunc
872 872
873 873 def for_type_by_name(type_module, type_name, func):
874 874 """
875 875 Add a pretty printer for a type specified by the module and name of a type
876 876 rather than the type object itself.
877 877 """
878 878 key = (type_module, type_name)
879 879 oldfunc = _deferred_type_pprinters.get(key, None)
880 880 if func is not None:
881 881 # To support easy restoration of old pprinters, we need to ignore Nones.
882 882 _deferred_type_pprinters[key] = func
883 883 return oldfunc
884 884
885 885
886 886 #: printers for the default singletons
887 887 _singleton_pprinters = dict.fromkeys(map(id, [None, True, False, Ellipsis,
888 888 NotImplemented]), _repr_pprint)
889 889
890 890
891 891 def _defaultdict_pprint(obj, p, cycle):
892 892 cls_ctor = CallExpression.factory(obj.__class__.__name__)
893 893 if cycle:
894 894 p.pretty(cls_ctor(RawText("...")))
895 895 else:
896 896 p.pretty(cls_ctor(obj.default_factory, dict(obj)))
897 897
898 898 def _ordereddict_pprint(obj, p, cycle):
899 899 cls_ctor = CallExpression.factory(obj.__class__.__name__)
900 900 if cycle:
901 901 p.pretty(cls_ctor(RawText("...")))
902 902 elif len(obj):
903 903 p.pretty(cls_ctor(list(obj.items())))
904 904 else:
905 905 p.pretty(cls_ctor())
906 906
907 907 def _deque_pprint(obj, p, cycle):
908 908 cls_ctor = CallExpression.factory(obj.__class__.__name__)
909 909 if cycle:
910 910 p.pretty(cls_ctor(RawText("...")))
911 elif obj.maxlen is not None:
912 p.pretty(cls_ctor(list(obj), maxlen=obj.maxlen))
911 913 else:
912 914 p.pretty(cls_ctor(list(obj)))
913 915
914 916 def _counter_pprint(obj, p, cycle):
915 917 cls_ctor = CallExpression.factory(obj.__class__.__name__)
916 918 if cycle:
917 919 p.pretty(cls_ctor(RawText("...")))
918 920 elif len(obj):
919 921 p.pretty(cls_ctor(dict(obj)))
920 922 else:
921 923 p.pretty(cls_ctor())
922 924
923 925
924 926 def _userlist_pprint(obj, p, cycle):
925 927 cls_ctor = CallExpression.factory(obj.__class__.__name__)
926 928 if cycle:
927 929 p.pretty(cls_ctor(RawText("...")))
928 930 else:
929 931 p.pretty(cls_ctor(obj.data))
930 932
931 933
932 934 for_type_by_name('collections', 'defaultdict', _defaultdict_pprint)
933 935 for_type_by_name('collections', 'OrderedDict', _ordereddict_pprint)
934 936 for_type_by_name('collections', 'deque', _deque_pprint)
935 937 for_type_by_name('collections', 'Counter', _counter_pprint)
936 938 for_type_by_name("collections", "UserList", _userlist_pprint)
937 939
938 940 if __name__ == '__main__':
939 941 from random import randrange
940 942 class Foo(object):
941 943 def __init__(self):
942 944 self.foo = 1
943 945 self.bar = re.compile(r'\s+')
944 946 self.blub = dict.fromkeys(range(30), randrange(1, 40))
945 947 self.hehe = 23424.234234
946 948 self.list = ["blub", "blah", self]
947 949
948 950 def get_foo(self):
949 951 print("foo")
950 952
951 953 pprint(Foo(), verbose=True)
@@ -1,19 +1,20 b''
1 1 from IPython.core.error import TryNext
2 2 from IPython.lib.clipboard import ClipboardEmpty
3 3 from IPython.testing.decorators import skip_if_no_x11
4 4
5
5 6 @skip_if_no_x11
6 7 def test_clipboard_get():
7 8 # Smoketest for clipboard access - we can't easily guarantee that the
8 9 # clipboard is accessible and has something on it, but this tries to
9 10 # exercise the relevant code anyway.
10 11 try:
11 12 a = get_ipython().hooks.clipboard_get()
12 13 except ClipboardEmpty:
13 14 # Nothing in clipboard to get
14 15 pass
15 16 except TryNext:
16 17 # No clipboard access API available
17 18 pass
18 19 else:
19 20 assert isinstance(a, str)
@@ -1,769 +1,774 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import asyncio
4 4 import os
5 5 import sys
6 6 from warnings import warn
7 7
8 8 from IPython.core.async_helpers import get_asyncio_loop
9 9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 10 from IPython.utils.py3compat import input
11 11 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
12 12 from IPython.utils.process import abbrev_cwd
13 13 from traitlets import (
14 14 Bool,
15 15 Unicode,
16 16 Dict,
17 17 Integer,
18 18 observe,
19 19 Instance,
20 20 Type,
21 21 default,
22 22 Enum,
23 23 Union,
24 24 Any,
25 25 validate,
26 26 Float,
27 27 )
28 28
29 29 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
30 30 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
31 31 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
32 32 from prompt_toolkit.formatted_text import PygmentsTokens
33 33 from prompt_toolkit.history import History
34 34 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
35 35 from prompt_toolkit.output import ColorDepth
36 36 from prompt_toolkit.patch_stdout import patch_stdout
37 37 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
38 38 from prompt_toolkit.styles import DynamicStyle, merge_styles
39 39 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
40 40 from prompt_toolkit import __version__ as ptk_version
41 41
42 42 from pygments.styles import get_style_by_name
43 43 from pygments.style import Style
44 44 from pygments.token import Token
45 45
46 46 from .debugger import TerminalPdb, Pdb
47 47 from .magics import TerminalMagics
48 48 from .pt_inputhooks import get_inputhook_name_and_func
49 49 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
50 50 from .ptutils import IPythonPTCompleter, IPythonPTLexer
51 51 from .shortcuts import create_ipython_shortcuts
52 52
53 53 PTK3 = ptk_version.startswith('3.')
54 54
55 55
56 56 class _NoStyle(Style): pass
57 57
58 58
59 59
60 60 _style_overrides_light_bg = {
61 61 Token.Prompt: '#ansibrightblue',
62 62 Token.PromptNum: '#ansiblue bold',
63 63 Token.OutPrompt: '#ansibrightred',
64 64 Token.OutPromptNum: '#ansired bold',
65 65 }
66 66
67 67 _style_overrides_linux = {
68 68 Token.Prompt: '#ansibrightgreen',
69 69 Token.PromptNum: '#ansigreen bold',
70 70 Token.OutPrompt: '#ansibrightred',
71 71 Token.OutPromptNum: '#ansired bold',
72 72 }
73 73
74 74 def get_default_editor():
75 75 try:
76 76 return os.environ['EDITOR']
77 77 except KeyError:
78 78 pass
79 79 except UnicodeError:
80 80 warn("$EDITOR environment variable is not pure ASCII. Using platform "
81 81 "default editor.")
82 82
83 83 if os.name == 'posix':
84 84 return 'vi' # the only one guaranteed to be there!
85 85 else:
86 86 return 'notepad' # same in Windows!
87 87
88 88 # conservatively check for tty
89 89 # overridden streams can result in things like:
90 90 # - sys.stdin = None
91 91 # - no isatty method
92 92 for _name in ('stdin', 'stdout', 'stderr'):
93 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 100 _is_tty = False
96 101 break
97 102 else:
98 103 _is_tty = True
99 104
100 105
101 106 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
102 107
103 108 def black_reformat_handler(text_before_cursor):
104 109 """
105 110 We do not need to protect against error,
106 111 this is taken care at a higher level where any reformat error is ignored.
107 112 Indeed we may call reformatting on incomplete code.
108 113 """
109 114 import black
110 115
111 116 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
112 117 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
113 118 formatted_text = formatted_text[:-1]
114 119 return formatted_text
115 120
116 121
117 122 def yapf_reformat_handler(text_before_cursor):
118 123 from yapf.yapflib import file_resources
119 124 from yapf.yapflib import yapf_api
120 125
121 126 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
122 127 formatted_text, was_formatted = yapf_api.FormatCode(
123 128 text_before_cursor, style_config=style_config
124 129 )
125 130 if was_formatted:
126 131 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
127 132 formatted_text = formatted_text[:-1]
128 133 return formatted_text
129 134 else:
130 135 return text_before_cursor
131 136
132 137
133 138 class PtkHistoryAdapter(History):
134 139 """
135 140 Prompt toolkit has it's own way of handling history, Where it assumes it can
136 141 Push/pull from history.
137 142
138 143 """
139 144
140 145 def __init__(self, shell):
141 146 super().__init__()
142 147 self.shell = shell
143 148 self._refresh()
144 149
145 150 def append_string(self, string):
146 151 # we rely on sql for that.
147 152 self._loaded = False
148 153 self._refresh()
149 154
150 155 def _refresh(self):
151 156 if not self._loaded:
152 157 self._loaded_strings = list(self.load_history_strings())
153 158
154 159 def load_history_strings(self):
155 160 last_cell = ""
156 161 res = []
157 162 for __, ___, cell in self.shell.history_manager.get_tail(
158 163 self.shell.history_load_length, include_latest=True
159 164 ):
160 165 # Ignore blank lines and consecutive duplicates
161 166 cell = cell.rstrip()
162 167 if cell and (cell != last_cell):
163 168 res.append(cell)
164 169 last_cell = cell
165 170 yield from res[::-1]
166 171
167 172 def store_string(self, string: str) -> None:
168 173 pass
169 174
170 175 class TerminalInteractiveShell(InteractiveShell):
171 176 mime_renderers = Dict().tag(config=True)
172 177
173 178 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
174 179 'to reserve for the tab completion menu, '
175 180 'search history, ...etc, the height of '
176 181 'these menus will at most this value. '
177 182 'Increase it is you prefer long and skinny '
178 183 'menus, decrease for short and wide.'
179 184 ).tag(config=True)
180 185
181 186 pt_app = None
182 187 debugger_history = None
183 188
184 189 debugger_history_file = Unicode(
185 190 "~/.pdbhistory", help="File in which to store and read history"
186 191 ).tag(config=True)
187 192
188 193 simple_prompt = Bool(_use_simple_prompt,
189 194 help="""Use `raw_input` for the REPL, without completion and prompt colors.
190 195
191 196 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
192 197 IPython own testing machinery, and emacs inferior-shell integration through elpy.
193 198
194 199 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
195 200 environment variable is set, or the current terminal is not a tty."""
196 201 ).tag(config=True)
197 202
198 203 @property
199 204 def debugger_cls(self):
200 205 return Pdb if self.simple_prompt else TerminalPdb
201 206
202 207 confirm_exit = Bool(True,
203 208 help="""
204 209 Set to confirm when you try to exit IPython with an EOF (Control-D
205 210 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
206 211 you can force a direct exit without any confirmation.""",
207 212 ).tag(config=True)
208 213
209 214 editing_mode = Unicode('emacs',
210 215 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
211 216 ).tag(config=True)
212 217
213 218 emacs_bindings_in_vi_insert_mode = Bool(
214 219 True,
215 220 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
216 221 ).tag(config=True)
217 222
218 223 modal_cursor = Bool(
219 224 True,
220 225 help="""
221 226 Cursor shape changes depending on vi mode: beam in vi insert mode,
222 227 block in nav mode, underscore in replace mode.""",
223 228 ).tag(config=True)
224 229
225 230 ttimeoutlen = Float(
226 231 0.01,
227 232 help="""The time in milliseconds that is waited for a key code
228 233 to complete.""",
229 234 ).tag(config=True)
230 235
231 236 timeoutlen = Float(
232 237 0.5,
233 238 help="""The time in milliseconds that is waited for a mapped key
234 239 sequence to complete.""",
235 240 ).tag(config=True)
236 241
237 242 autoformatter = Unicode(
238 243 None,
239 244 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
240 245 allow_none=True
241 246 ).tag(config=True)
242 247
243 248 auto_match = Bool(
244 249 False,
245 250 help="""
246 251 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
247 252 Brackets: (), [], {}
248 253 Quotes: '', \"\"
249 254 """,
250 255 ).tag(config=True)
251 256
252 257 mouse_support = Bool(False,
253 258 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
254 259 ).tag(config=True)
255 260
256 261 # We don't load the list of styles for the help string, because loading
257 262 # Pygments plugins takes time and can cause unexpected errors.
258 263 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
259 264 help="""The name or class of a Pygments style to use for syntax
260 265 highlighting. To see available styles, run `pygmentize -L styles`."""
261 266 ).tag(config=True)
262 267
263 268 @validate('editing_mode')
264 269 def _validate_editing_mode(self, proposal):
265 270 if proposal['value'].lower() == 'vim':
266 271 proposal['value']= 'vi'
267 272 elif proposal['value'].lower() == 'default':
268 273 proposal['value']= 'emacs'
269 274
270 275 if hasattr(EditingMode, proposal['value'].upper()):
271 276 return proposal['value'].lower()
272 277
273 278 return self.editing_mode
274 279
275 280
276 281 @observe('editing_mode')
277 282 def _editing_mode(self, change):
278 283 if self.pt_app:
279 284 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
280 285
281 286 def _set_formatter(self, formatter):
282 287 if formatter is None:
283 288 self.reformat_handler = lambda x:x
284 289 elif formatter == 'black':
285 290 self.reformat_handler = black_reformat_handler
286 291 elif formatter == "yapf":
287 292 self.reformat_handler = yapf_reformat_handler
288 293 else:
289 294 raise ValueError
290 295
291 296 @observe("autoformatter")
292 297 def _autoformatter_changed(self, change):
293 298 formatter = change.new
294 299 self._set_formatter(formatter)
295 300
296 301 @observe('highlighting_style')
297 302 @observe('colors')
298 303 def _highlighting_style_changed(self, change):
299 304 self.refresh_style()
300 305
301 306 def refresh_style(self):
302 307 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
303 308
304 309
305 310 highlighting_style_overrides = Dict(
306 311 help="Override highlighting format for specific tokens"
307 312 ).tag(config=True)
308 313
309 314 true_color = Bool(False,
310 315 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
311 316 If your terminal supports true color, the following command should
312 317 print ``TRUECOLOR`` in orange::
313 318
314 319 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
315 320 """,
316 321 ).tag(config=True)
317 322
318 323 editor = Unicode(get_default_editor(),
319 324 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
320 325 ).tag(config=True)
321 326
322 327 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
323 328
324 329 prompts = Instance(Prompts)
325 330
326 331 @default('prompts')
327 332 def _prompts_default(self):
328 333 return self.prompts_class(self)
329 334
330 335 # @observe('prompts')
331 336 # def _(self, change):
332 337 # self._update_layout()
333 338
334 339 @default('displayhook_class')
335 340 def _displayhook_class_default(self):
336 341 return RichPromptDisplayHook
337 342
338 343 term_title = Bool(True,
339 344 help="Automatically set the terminal title"
340 345 ).tag(config=True)
341 346
342 347 term_title_format = Unicode("IPython: {cwd}",
343 348 help="Customize the terminal title format. This is a python format string. " +
344 349 "Available substitutions are: {cwd}."
345 350 ).tag(config=True)
346 351
347 352 display_completions = Enum(('column', 'multicolumn','readlinelike'),
348 353 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
349 354 "'readlinelike'. These options are for `prompt_toolkit`, see "
350 355 "`prompt_toolkit` documentation for more information."
351 356 ),
352 357 default_value='multicolumn').tag(config=True)
353 358
354 359 highlight_matching_brackets = Bool(True,
355 360 help="Highlight matching brackets.",
356 361 ).tag(config=True)
357 362
358 363 extra_open_editor_shortcuts = Bool(False,
359 364 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
360 365 "This is in addition to the F2 binding, which is always enabled."
361 366 ).tag(config=True)
362 367
363 368 handle_return = Any(None,
364 369 help="Provide an alternative handler to be called when the user presses "
365 370 "Return. This is an advanced option intended for debugging, which "
366 371 "may be changed or removed in later releases."
367 372 ).tag(config=True)
368 373
369 374 enable_history_search = Bool(True,
370 375 help="Allows to enable/disable the prompt toolkit history search"
371 376 ).tag(config=True)
372 377
373 378 autosuggestions_provider = Unicode(
374 379 "AutoSuggestFromHistory",
375 380 help="Specifies from which source automatic suggestions are provided. "
376 381 "Can be set to `'AutoSuggestFromHistory`' or `None` to disable"
377 382 "automatic suggestions. Default is `'AutoSuggestFromHistory`'.",
378 383 allow_none=True,
379 384 ).tag(config=True)
380 385
381 386 def _set_autosuggestions(self, provider):
382 387 if provider is None:
383 388 self.auto_suggest = None
384 389 elif provider == "AutoSuggestFromHistory":
385 390 self.auto_suggest = AutoSuggestFromHistory()
386 391 else:
387 392 raise ValueError("No valid provider.")
388 393 if self.pt_app:
389 394 self.pt_app.auto_suggest = self.auto_suggest
390 395
391 396 @observe("autosuggestions_provider")
392 397 def _autosuggestions_provider_changed(self, change):
393 398 provider = change.new
394 399 self._set_autosuggestions(provider)
395 400
396 401 prompt_includes_vi_mode = Bool(True,
397 402 help="Display the current vi mode (when using vi editing mode)."
398 403 ).tag(config=True)
399 404
400 405 @observe('term_title')
401 406 def init_term_title(self, change=None):
402 407 # Enable or disable the terminal title.
403 408 if self.term_title:
404 409 toggle_set_term_title(True)
405 410 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
406 411 else:
407 412 toggle_set_term_title(False)
408 413
409 414 def restore_term_title(self):
410 415 if self.term_title:
411 416 restore_term_title()
412 417
413 418 def init_display_formatter(self):
414 419 super(TerminalInteractiveShell, self).init_display_formatter()
415 420 # terminal only supports plain text
416 421 self.display_formatter.active_types = ["text/plain"]
417 422
418 423 def init_prompt_toolkit_cli(self):
419 424 if self.simple_prompt:
420 425 # Fall back to plain non-interactive output for tests.
421 426 # This is very limited.
422 427 def prompt():
423 428 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
424 429 lines = [input(prompt_text)]
425 430 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
426 431 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
427 432 lines.append( input(prompt_continuation) )
428 433 return '\n'.join(lines)
429 434 self.prompt_for_code = prompt
430 435 return
431 436
432 437 # Set up keyboard shortcuts
433 438 key_bindings = create_ipython_shortcuts(self)
434 439
435 440
436 441 # Pre-populate history from IPython's history database
437 442 history = PtkHistoryAdapter(self)
438 443
439 444 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
440 445 self.style = DynamicStyle(lambda: self._style)
441 446
442 447 editing_mode = getattr(EditingMode, self.editing_mode.upper())
443 448
444 449 self.pt_loop = asyncio.new_event_loop()
445 450 self.pt_app = PromptSession(
446 451 auto_suggest=self.auto_suggest,
447 452 editing_mode=editing_mode,
448 453 key_bindings=key_bindings,
449 454 history=history,
450 455 completer=IPythonPTCompleter(shell=self),
451 456 enable_history_search=self.enable_history_search,
452 457 style=self.style,
453 458 include_default_pygments_style=False,
454 459 mouse_support=self.mouse_support,
455 460 enable_open_in_editor=self.extra_open_editor_shortcuts,
456 461 color_depth=self.color_depth,
457 462 tempfile_suffix=".py",
458 463 **self._extra_prompt_options()
459 464 )
460 465
461 466 def _make_style_from_name_or_cls(self, name_or_cls):
462 467 """
463 468 Small wrapper that make an IPython compatible style from a style name
464 469
465 470 We need that to add style for prompt ... etc.
466 471 """
467 472 style_overrides = {}
468 473 if name_or_cls == 'legacy':
469 474 legacy = self.colors.lower()
470 475 if legacy == 'linux':
471 476 style_cls = get_style_by_name('monokai')
472 477 style_overrides = _style_overrides_linux
473 478 elif legacy == 'lightbg':
474 479 style_overrides = _style_overrides_light_bg
475 480 style_cls = get_style_by_name('pastie')
476 481 elif legacy == 'neutral':
477 482 # The default theme needs to be visible on both a dark background
478 483 # and a light background, because we can't tell what the terminal
479 484 # looks like. These tweaks to the default theme help with that.
480 485 style_cls = get_style_by_name('default')
481 486 style_overrides.update({
482 487 Token.Number: '#ansigreen',
483 488 Token.Operator: 'noinherit',
484 489 Token.String: '#ansiyellow',
485 490 Token.Name.Function: '#ansiblue',
486 491 Token.Name.Class: 'bold #ansiblue',
487 492 Token.Name.Namespace: 'bold #ansiblue',
488 493 Token.Name.Variable.Magic: '#ansiblue',
489 494 Token.Prompt: '#ansigreen',
490 495 Token.PromptNum: '#ansibrightgreen bold',
491 496 Token.OutPrompt: '#ansired',
492 497 Token.OutPromptNum: '#ansibrightred bold',
493 498 })
494 499
495 500 # Hack: Due to limited color support on the Windows console
496 501 # the prompt colors will be wrong without this
497 502 if os.name == 'nt':
498 503 style_overrides.update({
499 504 Token.Prompt: '#ansidarkgreen',
500 505 Token.PromptNum: '#ansigreen bold',
501 506 Token.OutPrompt: '#ansidarkred',
502 507 Token.OutPromptNum: '#ansired bold',
503 508 })
504 509 elif legacy =='nocolor':
505 510 style_cls=_NoStyle
506 511 style_overrides = {}
507 512 else :
508 513 raise ValueError('Got unknown colors: ', legacy)
509 514 else :
510 515 if isinstance(name_or_cls, str):
511 516 style_cls = get_style_by_name(name_or_cls)
512 517 else:
513 518 style_cls = name_or_cls
514 519 style_overrides = {
515 520 Token.Prompt: '#ansigreen',
516 521 Token.PromptNum: '#ansibrightgreen bold',
517 522 Token.OutPrompt: '#ansired',
518 523 Token.OutPromptNum: '#ansibrightred bold',
519 524 }
520 525 style_overrides.update(self.highlighting_style_overrides)
521 526 style = merge_styles([
522 527 style_from_pygments_cls(style_cls),
523 528 style_from_pygments_dict(style_overrides),
524 529 ])
525 530
526 531 return style
527 532
528 533 @property
529 534 def pt_complete_style(self):
530 535 return {
531 536 'multicolumn': CompleteStyle.MULTI_COLUMN,
532 537 'column': CompleteStyle.COLUMN,
533 538 'readlinelike': CompleteStyle.READLINE_LIKE,
534 539 }[self.display_completions]
535 540
536 541 @property
537 542 def color_depth(self):
538 543 return (ColorDepth.TRUE_COLOR if self.true_color else None)
539 544
540 545 def _extra_prompt_options(self):
541 546 """
542 547 Return the current layout option for the current Terminal InteractiveShell
543 548 """
544 549 def get_message():
545 550 return PygmentsTokens(self.prompts.in_prompt_tokens())
546 551
547 552 if self.editing_mode == 'emacs':
548 553 # with emacs mode the prompt is (usually) static, so we call only
549 554 # the function once. With VI mode it can toggle between [ins] and
550 555 # [nor] so we can't precompute.
551 556 # here I'm going to favor the default keybinding which almost
552 557 # everybody uses to decrease CPU usage.
553 558 # if we have issues with users with custom Prompts we can see how to
554 559 # work around this.
555 560 get_message = get_message()
556 561
557 562 options = {
558 563 'complete_in_thread': False,
559 564 'lexer':IPythonPTLexer(),
560 565 'reserve_space_for_menu':self.space_for_menu,
561 566 'message': get_message,
562 567 'prompt_continuation': (
563 568 lambda width, lineno, is_soft_wrap:
564 569 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
565 570 'multiline': True,
566 571 'complete_style': self.pt_complete_style,
567 572
568 573 # Highlight matching brackets, but only when this setting is
569 574 # enabled, and only when the DEFAULT_BUFFER has the focus.
570 575 'input_processors': [ConditionalProcessor(
571 576 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
572 577 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
573 578 Condition(lambda: self.highlight_matching_brackets))],
574 579 }
575 580 if not PTK3:
576 581 options['inputhook'] = self.inputhook
577 582
578 583 return options
579 584
580 585 def prompt_for_code(self):
581 586 if self.rl_next_input:
582 587 default = self.rl_next_input
583 588 self.rl_next_input = None
584 589 else:
585 590 default = ''
586 591
587 592 # In order to make sure that asyncio code written in the
588 593 # interactive shell doesn't interfere with the prompt, we run the
589 594 # prompt in a different event loop.
590 595 # If we don't do this, people could spawn coroutine with a
591 596 # while/true inside which will freeze the prompt.
592 597
593 598 policy = asyncio.get_event_loop_policy()
594 599 old_loop = get_asyncio_loop()
595 600
596 601 # FIXME: prompt_toolkit is using the deprecated `asyncio.get_event_loop`
597 602 # to get the current event loop.
598 603 # This will probably be replaced by an attribute or input argument,
599 604 # at which point we can stop calling the soon-to-be-deprecated `set_event_loop` here.
600 605 if old_loop is not self.pt_loop:
601 606 policy.set_event_loop(self.pt_loop)
602 607 try:
603 608 with patch_stdout(raw=True):
604 609 text = self.pt_app.prompt(
605 610 default=default,
606 611 **self._extra_prompt_options())
607 612 finally:
608 613 # Restore the original event loop.
609 614 if old_loop is not None and old_loop is not self.pt_loop:
610 615 policy.set_event_loop(old_loop)
611 616
612 617 return text
613 618
614 619 def enable_win_unicode_console(self):
615 620 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
616 621 # console by default, so WUC shouldn't be needed.
617 622 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
618 623 DeprecationWarning,
619 624 stacklevel=2)
620 625
621 626 def init_io(self):
622 627 if sys.platform not in {'win32', 'cli'}:
623 628 return
624 629
625 630 import colorama
626 631 colorama.init()
627 632
628 633 def init_magics(self):
629 634 super(TerminalInteractiveShell, self).init_magics()
630 635 self.register_magics(TerminalMagics)
631 636
632 637 def init_alias(self):
633 638 # The parent class defines aliases that can be safely used with any
634 639 # frontend.
635 640 super(TerminalInteractiveShell, self).init_alias()
636 641
637 642 # Now define aliases that only make sense on the terminal, because they
638 643 # need direct access to the console in a way that we can't emulate in
639 644 # GUI or web frontend
640 645 if os.name == 'posix':
641 646 for cmd in ('clear', 'more', 'less', 'man'):
642 647 self.alias_manager.soft_define_alias(cmd, cmd)
643 648
644 649
645 650 def __init__(self, *args, **kwargs):
646 651 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
647 652 self._set_autosuggestions(self.autosuggestions_provider)
648 653 self.init_prompt_toolkit_cli()
649 654 self.init_term_title()
650 655 self.keep_running = True
651 656 self._set_formatter(self.autoformatter)
652 657
653 658
654 659 def ask_exit(self):
655 660 self.keep_running = False
656 661
657 662 rl_next_input = None
658 663
659 664 def interact(self):
660 665 self.keep_running = True
661 666 while self.keep_running:
662 667 print(self.separate_in, end='')
663 668
664 669 try:
665 670 code = self.prompt_for_code()
666 671 except EOFError:
667 672 if (not self.confirm_exit) \
668 673 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
669 674 self.ask_exit()
670 675
671 676 else:
672 677 if code:
673 678 self.run_cell(code, store_history=True)
674 679
675 680 def mainloop(self):
676 681 # An extra layer of protection in case someone mashing Ctrl-C breaks
677 682 # out of our internal code.
678 683 while True:
679 684 try:
680 685 self.interact()
681 686 break
682 687 except KeyboardInterrupt as e:
683 688 print("\n%s escaped interact()\n" % type(e).__name__)
684 689 finally:
685 690 # An interrupt during the eventloop will mess up the
686 691 # internal state of the prompt_toolkit library.
687 692 # Stopping the eventloop fixes this, see
688 693 # https://github.com/ipython/ipython/pull/9867
689 694 if hasattr(self, '_eventloop'):
690 695 self._eventloop.stop()
691 696
692 697 self.restore_term_title()
693 698
694 699 # try to call some at-exit operation optimistically as some things can't
695 700 # be done during interpreter shutdown. this is technically inaccurate as
696 701 # this make mainlool not re-callable, but that should be a rare if not
697 702 # in existent use case.
698 703
699 704 self._atexit_once()
700 705
701 706
702 707 _inputhook = None
703 708 def inputhook(self, context):
704 709 if self._inputhook is not None:
705 710 self._inputhook(context)
706 711
707 712 active_eventloop = None
708 713 def enable_gui(self, gui=None):
709 714 if gui and (gui != 'inline') :
710 715 self.active_eventloop, self._inputhook =\
711 716 get_inputhook_name_and_func(gui)
712 717 else:
713 718 self.active_eventloop = self._inputhook = None
714 719
715 720 # For prompt_toolkit 3.0. We have to create an asyncio event loop with
716 721 # this inputhook.
717 722 if PTK3:
718 723 import asyncio
719 724 from prompt_toolkit.eventloop import new_eventloop_with_inputhook
720 725
721 726 if gui == 'asyncio':
722 727 # When we integrate the asyncio event loop, run the UI in the
723 728 # same event loop as the rest of the code. don't use an actual
724 729 # input hook. (Asyncio is not made for nesting event loops.)
725 730 self.pt_loop = get_asyncio_loop()
726 731
727 732 elif self._inputhook:
728 733 # If an inputhook was set, create a new asyncio event loop with
729 734 # this inputhook for the prompt.
730 735 self.pt_loop = new_eventloop_with_inputhook(self._inputhook)
731 736 else:
732 737 # When there's no inputhook, run the prompt in a separate
733 738 # asyncio event loop.
734 739 self.pt_loop = asyncio.new_event_loop()
735 740
736 741 # Run !system commands directly, not through pipes, so terminal programs
737 742 # work correctly.
738 743 system = InteractiveShell.system_raw
739 744
740 745 def auto_rewrite_input(self, cmd):
741 746 """Overridden from the parent class to use fancy rewriting prompt"""
742 747 if not self.show_rewritten_input:
743 748 return
744 749
745 750 tokens = self.prompts.rewrite_prompt_tokens()
746 751 if self.pt_app:
747 752 print_formatted_text(PygmentsTokens(tokens), end='',
748 753 style=self.pt_app.app.style)
749 754 print(cmd)
750 755 else:
751 756 prompt = ''.join(s for t, s in tokens)
752 757 print(prompt, cmd, sep='')
753 758
754 759 _prompts_before = None
755 760 def switch_doctest_mode(self, mode):
756 761 """Switch prompts to classic for %doctest_mode"""
757 762 if mode:
758 763 self._prompts_before = self.prompts
759 764 self.prompts = ClassicPrompts(self)
760 765 elif self._prompts_before:
761 766 self.prompts = self._prompts_before
762 767 self._prompts_before = None
763 768 # self._update_layout()
764 769
765 770
766 771 InteractiveShellABC.register(TerminalInteractiveShell)
767 772
768 773 if __name__ == '__main__':
769 774 TerminalInteractiveShell.instance().interact()
@@ -1,197 +1,204 b''
1 1 """prompt-toolkit utilities
2 2
3 3 Everything in this module is a private API,
4 4 not to be used outside IPython.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 import unicodedata
11 11 from wcwidth import wcwidth
12 12
13 13 from IPython.core.completer import (
14 14 provisionalcompleter, cursor_to_position,
15 15 _deduplicate_completions)
16 16 from prompt_toolkit.completion import Completer, Completion
17 17 from prompt_toolkit.lexers import Lexer
18 18 from prompt_toolkit.lexers import PygmentsLexer
19 19 from prompt_toolkit.patch_stdout import patch_stdout
20 20
21 21 import pygments.lexers as pygments_lexers
22 22 import os
23 23 import sys
24 24 import traceback
25 25
26 26 _completion_sentinel = object()
27 27
28 28 def _elide_point(string:str, *, min_elide=30)->str:
29 29 """
30 30 If a string is long enough, and has at least 3 dots,
31 31 replace the middle part with ellipses.
32 32
33 33 If a string naming a file is long enough, and has at least 3 slashes,
34 34 replace the middle part with ellipses.
35 35
36 36 If three consecutive dots, or two consecutive dots are encountered these are
37 37 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
38 38 equivalents
39 39 """
40 40 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
41 41 string = string.replace('..','\N{TWO DOT LEADER}')
42 42 if len(string) < min_elide:
43 43 return string
44 44
45 45 object_parts = string.split('.')
46 46 file_parts = string.split(os.sep)
47 47 if file_parts[-1] == '':
48 48 file_parts.pop()
49 49
50 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 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 63 return string
57 64
58 65 def _elide_typed(string:str, typed:str, *, min_elide:int=30)->str:
59 66 """
60 67 Elide the middle of a long string if the beginning has already been typed.
61 68 """
62 69
63 70 if len(string) < min_elide:
64 71 return string
65 72 cut_how_much = len(typed)-3
66 73 if cut_how_much < 7:
67 74 return string
68 75 if string.startswith(typed) and len(string)> len(typed):
69 76 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
70 77 return string
71 78
72 79 def _elide(string:str, typed:str, min_elide=30)->str:
73 80 return _elide_typed(
74 81 _elide_point(string, min_elide=min_elide),
75 82 typed, min_elide=min_elide)
76 83
77 84
78 85
79 86 def _adjust_completion_text_based_on_context(text, body, offset):
80 87 if text.endswith('=') and len(body) > offset and body[offset] == '=':
81 88 return text[:-1]
82 89 else:
83 90 return text
84 91
85 92
86 93 class IPythonPTCompleter(Completer):
87 94 """Adaptor to provide IPython completions to prompt_toolkit"""
88 95 def __init__(self, ipy_completer=None, shell=None):
89 96 if shell is None and ipy_completer is None:
90 97 raise TypeError("Please pass shell=an InteractiveShell instance.")
91 98 self._ipy_completer = ipy_completer
92 99 self.shell = shell
93 100
94 101 @property
95 102 def ipy_completer(self):
96 103 if self._ipy_completer:
97 104 return self._ipy_completer
98 105 else:
99 106 return self.shell.Completer
100 107
101 108 def get_completions(self, document, complete_event):
102 109 if not document.current_line.strip():
103 110 return
104 111 # Some bits of our completion system may print stuff (e.g. if a module
105 112 # is imported). This context manager ensures that doesn't interfere with
106 113 # the prompt.
107 114
108 115 with patch_stdout(), provisionalcompleter():
109 116 body = document.text
110 117 cursor_row = document.cursor_position_row
111 118 cursor_col = document.cursor_position_col
112 119 cursor_position = document.cursor_position
113 120 offset = cursor_to_position(body, cursor_row, cursor_col)
114 121 try:
115 122 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
116 123 except Exception as e:
117 124 try:
118 125 exc_type, exc_value, exc_tb = sys.exc_info()
119 126 traceback.print_exception(exc_type, exc_value, exc_tb)
120 127 except AttributeError:
121 128 print('Unrecoverable Error in completions')
122 129
123 130 @staticmethod
124 131 def _get_completions(body, offset, cursor_position, ipyc):
125 132 """
126 133 Private equivalent of get_completions() use only for unit_testing.
127 134 """
128 135 debug = getattr(ipyc, 'debug', False)
129 136 completions = _deduplicate_completions(
130 137 body, ipyc.completions(body, offset))
131 138 for c in completions:
132 139 if not c.text:
133 140 # Guard against completion machinery giving us an empty string.
134 141 continue
135 142 text = unicodedata.normalize('NFC', c.text)
136 143 # When the first character of the completion has a zero length,
137 144 # then it's probably a decomposed unicode character. E.g. caused by
138 145 # the "\dot" completion. Try to compose again with the previous
139 146 # character.
140 147 if wcwidth(text[0]) == 0:
141 148 if cursor_position + c.start > 0:
142 149 char_before = body[c.start - 1]
143 150 fixed_text = unicodedata.normalize(
144 151 'NFC', char_before + text)
145 152
146 153 # Yield the modified completion instead, if this worked.
147 154 if wcwidth(text[0:1]) == 1:
148 155 yield Completion(fixed_text, start_position=c.start - offset - 1)
149 156 continue
150 157
151 158 # TODO: Use Jedi to determine meta_text
152 159 # (Jedi currently has a bug that results in incorrect information.)
153 160 # meta_text = ''
154 161 # yield Completion(m, start_position=start_pos,
155 162 # display_meta=meta_text)
156 163 display_text = c.text
157 164
158 165 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
159 166 if c.type == 'function':
160 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 168 else:
162 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 171 class IPythonPTLexer(Lexer):
165 172 """
166 173 Wrapper around PythonLexer and BashLexer.
167 174 """
168 175 def __init__(self):
169 176 l = pygments_lexers
170 177 self.python_lexer = PygmentsLexer(l.Python3Lexer)
171 178 self.shell_lexer = PygmentsLexer(l.BashLexer)
172 179
173 180 self.magic_lexers = {
174 181 'HTML': PygmentsLexer(l.HtmlLexer),
175 182 'html': PygmentsLexer(l.HtmlLexer),
176 183 'javascript': PygmentsLexer(l.JavascriptLexer),
177 184 'js': PygmentsLexer(l.JavascriptLexer),
178 185 'perl': PygmentsLexer(l.PerlLexer),
179 186 'ruby': PygmentsLexer(l.RubyLexer),
180 187 'latex': PygmentsLexer(l.TexLexer),
181 188 }
182 189
183 190 def lex_document(self, document):
184 191 text = document.text.lstrip()
185 192
186 193 lexer = self.python_lexer
187 194
188 195 if text.startswith('!') or text.startswith('%%bash'):
189 196 lexer = self.shell_lexer
190 197
191 198 elif text.startswith('%%'):
192 199 for magic, l in self.magic_lexers.items():
193 200 if text.startswith('%%' + magic):
194 201 lexer = l
195 202 break
196 203
197 204 return lexer.lex_document(document)
@@ -1,551 +1,585 b''
1 1 """
2 2 Module to define and register Terminal IPython shortcuts with
3 3 :mod:`prompt_toolkit`
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import warnings
10 10 import signal
11 11 import sys
12 12 import re
13 13 import os
14 14 from typing import Callable
15 15
16 16
17 17 from prompt_toolkit.application.current import get_app
18 18 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
19 19 from prompt_toolkit.filters import (has_focus, has_selection, Condition,
20 20 vi_insert_mode, emacs_insert_mode, has_completions, vi_mode)
21 21 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
22 22 from prompt_toolkit.key_binding import KeyBindings
23 23 from prompt_toolkit.key_binding.bindings import named_commands as nc
24 24 from prompt_toolkit.key_binding.vi_state import InputMode, ViState
25 25
26 26 from IPython.utils.decorators import undoc
27 27
28 28 @undoc
29 29 @Condition
30 30 def cursor_in_leading_ws():
31 31 before = get_app().current_buffer.document.current_line_before_cursor
32 32 return (not before) or before.isspace()
33 33
34 34
35 35 # Needed for to accept autosuggestions in vi insert mode
36 36 def _apply_autosuggest(event):
37 37 """
38 38 Apply autosuggestion if at end of line.
39 39 """
40 40 b = event.current_buffer
41 41 d = b.document
42 42 after_cursor = d.text[d.cursor_position :]
43 43 lines = after_cursor.split("\n")
44 44 end_of_current_line = lines[0].strip()
45 45 suggestion = b.suggestion
46 46 if (suggestion is not None) and (suggestion.text) and (end_of_current_line == ""):
47 47 b.insert_text(suggestion.text)
48 48 else:
49 49 nc.end_of_line(event)
50 50
51 51 def create_ipython_shortcuts(shell):
52 52 """Set up the prompt_toolkit keyboard shortcuts for IPython"""
53 53
54 54 kb = KeyBindings()
55 55 insert_mode = vi_insert_mode | emacs_insert_mode
56 56
57 57 if getattr(shell, 'handle_return', None):
58 58 return_handler = shell.handle_return(shell)
59 59 else:
60 60 return_handler = newline_or_execute_outer(shell)
61 61
62 62 kb.add('enter', filter=(has_focus(DEFAULT_BUFFER)
63 63 & ~has_selection
64 64 & insert_mode
65 65 ))(return_handler)
66 66
67 67 def reformat_and_execute(event):
68 68 reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell)
69 69 event.current_buffer.validate_and_handle()
70 70
71 71 kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER)
72 72 & ~has_selection
73 73 & insert_mode
74 74 ))(reformat_and_execute)
75 75
76 76 kb.add("c-\\")(quit)
77 77
78 78 kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
79 79 )(previous_history_or_previous_completion)
80 80
81 81 kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER))
82 82 )(next_history_or_next_completion)
83 83
84 84 kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions)
85 85 )(dismiss_completion)
86 86
87 87 kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer)
88 88
89 89 kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer)
90 90
91 91 supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP'))
92 92 kb.add('c-z', filter=supports_suspend)(suspend_to_bg)
93 93
94 94 # Ctrl+I == Tab
95 95 kb.add('tab', filter=(has_focus(DEFAULT_BUFFER)
96 96 & ~has_selection
97 97 & insert_mode
98 98 & cursor_in_leading_ws
99 99 ))(indent_buffer)
100 100 kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)
101 101 )(newline_autoindent_outer(shell.input_transformer_manager))
102 102
103 103 kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor)
104 104
105 105 @Condition
106 106 def auto_match():
107 107 return shell.auto_match
108 108
109 109 focused_insert = (vi_insert_mode | emacs_insert_mode) & has_focus(DEFAULT_BUFFER)
110 110 _preceding_text_cache = {}
111 111 _following_text_cache = {}
112 112
113 113 def preceding_text(pattern):
114 114 try:
115 115 return _preceding_text_cache[pattern]
116 116 except KeyError:
117 117 pass
118 118 m = re.compile(pattern)
119 119
120 120 def _preceding_text():
121 121 app = get_app()
122 122 return bool(m.match(app.current_buffer.document.current_line_before_cursor))
123 123
124 124 condition = Condition(_preceding_text)
125 125 _preceding_text_cache[pattern] = condition
126 126 return condition
127 127
128 128 def following_text(pattern):
129 129 try:
130 130 return _following_text_cache[pattern]
131 131 except KeyError:
132 132 pass
133 133 m = re.compile(pattern)
134 134
135 135 def _following_text():
136 136 app = get_app()
137 137 return bool(m.match(app.current_buffer.document.current_line_after_cursor))
138 138
139 139 condition = Condition(_following_text)
140 140 _following_text_cache[pattern] = condition
141 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 155 # auto match
144 156 @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
145 157 def _(event):
146 158 event.current_buffer.insert_text("()")
147 159 event.current_buffer.cursor_left()
148 160
149 161 @kb.add("[", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
150 162 def _(event):
151 163 event.current_buffer.insert_text("[]")
152 164 event.current_buffer.cursor_left()
153 165
154 166 @kb.add("{", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
155 167 def _(event):
156 168 event.current_buffer.insert_text("{}")
157 169 event.current_buffer.cursor_left()
158 170
159 171 @kb.add(
160 172 '"',
161 173 filter=focused_insert
162 174 & auto_match
163 & preceding_text(r'^([^"]+|"[^"]*")*$')
175 & not_inside_unclosed_string
164 176 & following_text(r"[,)}\]]|$"),
165 177 )
166 178 def _(event):
167 179 event.current_buffer.insert_text('""')
168 180 event.current_buffer.cursor_left()
169 181
170 182 @kb.add(
171 183 "'",
172 184 filter=focused_insert
173 185 & auto_match
174 & preceding_text(r"^([^']+|'[^']*')*$")
186 & not_inside_unclosed_string
175 187 & following_text(r"[,)}\]]|$"),
176 188 )
177 189 def _(event):
178 190 event.current_buffer.insert_text("''")
179 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 215 # raw string
182 216 @kb.add(
183 217 "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
184 218 )
185 219 def _(event):
186 220 matches = re.match(
187 221 r".*(r|R)[\"'](-*)",
188 222 event.current_buffer.document.current_line_before_cursor,
189 223 )
190 224 dashes = matches.group(2) or ""
191 225 event.current_buffer.insert_text("()" + dashes)
192 226 event.current_buffer.cursor_left(len(dashes) + 1)
193 227
194 228 @kb.add(
195 229 "[", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
196 230 )
197 231 def _(event):
198 232 matches = re.match(
199 233 r".*(r|R)[\"'](-*)",
200 234 event.current_buffer.document.current_line_before_cursor,
201 235 )
202 236 dashes = matches.group(2) or ""
203 237 event.current_buffer.insert_text("[]" + dashes)
204 238 event.current_buffer.cursor_left(len(dashes) + 1)
205 239
206 240 @kb.add(
207 241 "{", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
208 242 )
209 243 def _(event):
210 244 matches = re.match(
211 245 r".*(r|R)[\"'](-*)",
212 246 event.current_buffer.document.current_line_before_cursor,
213 247 )
214 248 dashes = matches.group(2) or ""
215 249 event.current_buffer.insert_text("{}" + dashes)
216 250 event.current_buffer.cursor_left(len(dashes) + 1)
217 251
218 252 # just move cursor
219 253 @kb.add(")", filter=focused_insert & auto_match & following_text(r"^\)"))
220 254 @kb.add("]", filter=focused_insert & auto_match & following_text(r"^\]"))
221 255 @kb.add("}", filter=focused_insert & auto_match & following_text(r"^\}"))
222 256 @kb.add('"', filter=focused_insert & auto_match & following_text('^"'))
223 257 @kb.add("'", filter=focused_insert & auto_match & following_text("^'"))
224 258 def _(event):
225 259 event.current_buffer.cursor_right()
226 260
227 261 @kb.add(
228 262 "backspace",
229 263 filter=focused_insert
230 264 & preceding_text(r".*\($")
231 265 & auto_match
232 266 & following_text(r"^\)"),
233 267 )
234 268 @kb.add(
235 269 "backspace",
236 270 filter=focused_insert
237 271 & preceding_text(r".*\[$")
238 272 & auto_match
239 273 & following_text(r"^\]"),
240 274 )
241 275 @kb.add(
242 276 "backspace",
243 277 filter=focused_insert
244 278 & preceding_text(r".*\{$")
245 279 & auto_match
246 280 & following_text(r"^\}"),
247 281 )
248 282 @kb.add(
249 283 "backspace",
250 284 filter=focused_insert
251 285 & preceding_text('.*"$')
252 286 & auto_match
253 287 & following_text('^"'),
254 288 )
255 289 @kb.add(
256 290 "backspace",
257 291 filter=focused_insert
258 292 & preceding_text(r".*'$")
259 293 & auto_match
260 294 & following_text(r"^'"),
261 295 )
262 296 def _(event):
263 297 event.current_buffer.delete()
264 298 event.current_buffer.delete_before_cursor()
265 299
266 300 if shell.display_completions == "readlinelike":
267 301 kb.add(
268 302 "c-i",
269 303 filter=(
270 304 has_focus(DEFAULT_BUFFER)
271 305 & ~has_selection
272 306 & insert_mode
273 307 & ~cursor_in_leading_ws
274 308 ),
275 309 )(display_completions_like_readline)
276 310
277 311 if sys.platform == "win32":
278 312 kb.add("c-v", filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste)
279 313
280 314 @Condition
281 315 def ebivim():
282 316 return shell.emacs_bindings_in_vi_insert_mode
283 317
284 318 focused_insert_vi = has_focus(DEFAULT_BUFFER) & vi_insert_mode
285 319
286 320 @kb.add("end", filter=has_focus(DEFAULT_BUFFER) & (ebivim | ~vi_insert_mode))
287 321 def _(event):
288 322 _apply_autosuggest(event)
289 323
290 324 @kb.add("c-e", filter=focused_insert_vi & ebivim)
291 325 def _(event):
292 326 _apply_autosuggest(event)
293 327
294 328 @kb.add("c-f", filter=focused_insert_vi)
295 329 def _(event):
296 330 b = event.current_buffer
297 331 suggestion = b.suggestion
298 332 if suggestion:
299 333 b.insert_text(suggestion.text)
300 334 else:
301 335 nc.forward_char(event)
302 336
303 337 @kb.add("escape", "f", filter=focused_insert_vi & ebivim)
304 338 def _(event):
305 339 b = event.current_buffer
306 340 suggestion = b.suggestion
307 341 if suggestion:
308 342 t = re.split(r"(\S+\s+)", suggestion.text)
309 343 b.insert_text(next((x for x in t if x), ""))
310 344 else:
311 345 nc.forward_word(event)
312 346
313 347 # Simple Control keybindings
314 348 key_cmd_dict = {
315 349 "c-a": nc.beginning_of_line,
316 350 "c-b": nc.backward_char,
317 351 "c-k": nc.kill_line,
318 352 "c-w": nc.backward_kill_word,
319 353 "c-y": nc.yank,
320 354 "c-_": nc.undo,
321 355 }
322 356
323 357 for key, cmd in key_cmd_dict.items():
324 358 kb.add(key, filter=focused_insert_vi & ebivim)(cmd)
325 359
326 360 # Alt and Combo Control keybindings
327 361 keys_cmd_dict = {
328 362 # Control Combos
329 363 ("c-x", "c-e"): nc.edit_and_execute,
330 364 ("c-x", "e"): nc.edit_and_execute,
331 365 # Alt
332 366 ("escape", "b"): nc.backward_word,
333 367 ("escape", "c"): nc.capitalize_word,
334 368 ("escape", "d"): nc.kill_word,
335 369 ("escape", "h"): nc.backward_kill_word,
336 370 ("escape", "l"): nc.downcase_word,
337 371 ("escape", "u"): nc.uppercase_word,
338 372 ("escape", "y"): nc.yank_pop,
339 373 ("escape", "."): nc.yank_last_arg,
340 374 }
341 375
342 376 for keys, cmd in keys_cmd_dict.items():
343 377 kb.add(*keys, filter=focused_insert_vi & ebivim)(cmd)
344 378
345 379 def get_input_mode(self):
346 380 app = get_app()
347 381 app.ttimeoutlen = shell.ttimeoutlen
348 382 app.timeoutlen = shell.timeoutlen
349 383
350 384 return self._input_mode
351 385
352 386 def set_input_mode(self, mode):
353 387 shape = {InputMode.NAVIGATION: 2, InputMode.REPLACE: 4}.get(mode, 6)
354 388 cursor = "\x1b[{} q".format(shape)
355 389
356 390 sys.stdout.write(cursor)
357 391 sys.stdout.flush()
358 392
359 393 self._input_mode = mode
360 394
361 395 if shell.editing_mode == "vi" and shell.modal_cursor:
362 396 ViState._input_mode = InputMode.INSERT
363 397 ViState.input_mode = property(get_input_mode, set_input_mode)
364 398
365 399 return kb
366 400
367 401
368 402 def reformat_text_before_cursor(buffer, document, shell):
369 403 text = buffer.delete_before_cursor(len(document.text[:document.cursor_position]))
370 404 try:
371 405 formatted_text = shell.reformat_handler(text)
372 406 buffer.insert_text(formatted_text)
373 407 except Exception as e:
374 408 buffer.insert_text(text)
375 409
376 410
377 411 def newline_or_execute_outer(shell):
378 412
379 413 def newline_or_execute(event):
380 414 """When the user presses return, insert a newline or execute the code."""
381 415 b = event.current_buffer
382 416 d = b.document
383 417
384 418 if b.complete_state:
385 419 cc = b.complete_state.current_completion
386 420 if cc:
387 421 b.apply_completion(cc)
388 422 else:
389 423 b.cancel_completion()
390 424 return
391 425
392 426 # If there's only one line, treat it as if the cursor is at the end.
393 427 # See https://github.com/ipython/ipython/issues/10425
394 428 if d.line_count == 1:
395 429 check_text = d.text
396 430 else:
397 431 check_text = d.text[:d.cursor_position]
398 432 status, indent = shell.check_complete(check_text)
399 433
400 434 # if all we have after the cursor is whitespace: reformat current text
401 435 # before cursor
402 436 after_cursor = d.text[d.cursor_position:]
403 437 reformatted = False
404 438 if not after_cursor.strip():
405 439 reformat_text_before_cursor(b, d, shell)
406 440 reformatted = True
407 441 if not (d.on_last_line or
408 442 d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()
409 443 ):
410 444 if shell.autoindent:
411 445 b.insert_text('\n' + indent)
412 446 else:
413 447 b.insert_text('\n')
414 448 return
415 449
416 450 if (status != 'incomplete') and b.accept_handler:
417 451 if not reformatted:
418 452 reformat_text_before_cursor(b, d, shell)
419 453 b.validate_and_handle()
420 454 else:
421 455 if shell.autoindent:
422 456 b.insert_text('\n' + indent)
423 457 else:
424 458 b.insert_text('\n')
425 459 return newline_or_execute
426 460
427 461
428 462 def previous_history_or_previous_completion(event):
429 463 """
430 464 Control-P in vi edit mode on readline is history next, unlike default prompt toolkit.
431 465
432 466 If completer is open this still select previous completion.
433 467 """
434 468 event.current_buffer.auto_up()
435 469
436 470
437 471 def next_history_or_next_completion(event):
438 472 """
439 473 Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit.
440 474
441 475 If completer is open this still select next completion.
442 476 """
443 477 event.current_buffer.auto_down()
444 478
445 479
446 480 def dismiss_completion(event):
447 481 b = event.current_buffer
448 482 if b.complete_state:
449 483 b.cancel_completion()
450 484
451 485
452 486 def reset_buffer(event):
453 487 b = event.current_buffer
454 488 if b.complete_state:
455 489 b.cancel_completion()
456 490 else:
457 491 b.reset()
458 492
459 493
460 494 def reset_search_buffer(event):
461 495 if event.current_buffer.document.text:
462 496 event.current_buffer.reset()
463 497 else:
464 498 event.app.layout.focus(DEFAULT_BUFFER)
465 499
466 500 def suspend_to_bg(event):
467 501 event.app.suspend_to_background()
468 502
469 503 def quit(event):
470 504 """
471 505 On platforms that support SIGQUIT, send SIGQUIT to the current process.
472 506 On other platforms, just exit the process with a message.
473 507 """
474 508 sigquit = getattr(signal, "SIGQUIT", None)
475 509 if sigquit is not None:
476 510 os.kill(0, signal.SIGQUIT)
477 511 else:
478 512 sys.exit("Quit")
479 513
480 514 def indent_buffer(event):
481 515 event.current_buffer.insert_text(' ' * 4)
482 516
483 517 @undoc
484 518 def newline_with_copy_margin(event):
485 519 """
486 520 DEPRECATED since IPython 6.0
487 521
488 522 See :any:`newline_autoindent_outer` for a replacement.
489 523
490 524 Preserve margin and cursor position when using
491 525 Control-O to insert a newline in EMACS mode
492 526 """
493 527 warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. "
494 528 "see `newline_autoindent_outer(shell)(event)` for a replacement.",
495 529 DeprecationWarning, stacklevel=2)
496 530
497 531 b = event.current_buffer
498 532 cursor_start_pos = b.document.cursor_position_col
499 533 b.newline(copy_margin=True)
500 534 b.cursor_up(count=1)
501 535 cursor_end_pos = b.document.cursor_position_col
502 536 if cursor_start_pos != cursor_end_pos:
503 537 pos_diff = cursor_start_pos - cursor_end_pos
504 538 b.cursor_right(count=pos_diff)
505 539
506 540 def newline_autoindent_outer(inputsplitter) -> Callable[..., None]:
507 541 """
508 542 Return a function suitable for inserting a indented newline after the cursor.
509 543
510 544 Fancier version of deprecated ``newline_with_copy_margin`` which should
511 545 compute the correct indentation of the inserted line. That is to say, indent
512 546 by 4 extra space after a function definition, class definition, context
513 547 manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``.
514 548 """
515 549
516 550 def newline_autoindent(event):
517 551 """insert a newline after the cursor indented appropriately."""
518 552 b = event.current_buffer
519 553 d = b.document
520 554
521 555 if b.complete_state:
522 556 b.cancel_completion()
523 557 text = d.text[:d.cursor_position] + '\n'
524 558 _, indent = inputsplitter.check_complete(text)
525 559 b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False)
526 560
527 561 return newline_autoindent
528 562
529 563
530 564 def open_input_in_editor(event):
531 565 event.app.current_buffer.open_in_editor()
532 566
533 567
534 568 if sys.platform == 'win32':
535 569 from IPython.core.error import TryNext
536 570 from IPython.lib.clipboard import (ClipboardEmpty,
537 571 win32_clipboard_get,
538 572 tkinter_clipboard_get)
539 573
540 574 @undoc
541 575 def win_paste(event):
542 576 try:
543 577 text = win32_clipboard_get()
544 578 except TryNext:
545 579 try:
546 580 text = tkinter_clipboard_get()
547 581 except (TryNext, ClipboardEmpty):
548 582 return
549 583 except ClipboardEmpty:
550 584 return
551 585 event.current_buffer.insert_text(text.replace("\t", " " * 4))
@@ -1,60 +1,61 b''
1 1 # encoding: utf-8
2 2 """Miscellaneous context managers.
3 3 """
4 4
5 5 import warnings
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10
10 11 class preserve_keys(object):
11 12 """Preserve a set of keys in a dictionary.
12 13
13 14 Upon entering the context manager the current values of the keys
14 15 will be saved. Upon exiting, the dictionary will be updated to
15 16 restore the original value of the preserved keys. Preserved keys
16 17 which did not exist when entering the context manager will be
17 18 deleted.
18 19
19 20 Examples
20 21 --------
21 22
22 23 >>> d = {'a': 1, 'b': 2, 'c': 3}
23 24 >>> with preserve_keys(d, 'b', 'c', 'd'):
24 25 ... del d['a']
25 26 ... del d['b'] # will be reset to 2
26 27 ... d['c'] = None # will be reset to 3
27 28 ... d['d'] = 4 # will be deleted
28 29 ... d['e'] = 5
29 30 ... print(sorted(d.items()))
30 31 ...
31 32 [('c', None), ('d', 4), ('e', 5)]
32 33 >>> print(sorted(d.items()))
33 34 [('b', 2), ('c', 3), ('e', 5)]
34 35 """
35 36
36 37 def __init__(self, dictionary, *keys):
37 38 self.dictionary = dictionary
38 39 self.keys = keys
39 40
40 41 def __enter__(self):
41 42 # Actions to perform upon exiting.
42 43 to_delete = []
43 44 to_update = {}
44 45
45 46 d = self.dictionary
46 47 for k in self.keys:
47 48 if k in d:
48 49 to_update[k] = d[k]
49 50 else:
50 51 to_delete.append(k)
51 52
52 53 self.to_delete = to_delete
53 54 self.to_update = to_update
54 55
55 56 def __exit__(self, *exc_info):
56 57 d = self.dictionary
57 58
58 59 for k in self.to_delete:
59 60 d.pop(k, None)
60 61 d.update(self.to_update)
@@ -1,6 +1,5 b''
1
2 1 from warnings import warn
3 2
4 3 warn("IPython.utils.eventful has moved to traitlets.eventful", stacklevel=2)
5 4
6 5 from traitlets.eventful import *
@@ -1,6 +1,5 b''
1
2 1 from warnings import warn
3 2
4 3 warn("IPython.utils.log has moved to traitlets.log", stacklevel=2)
5 4
6 5 from traitlets.log import *
@@ -1,392 +1,393 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for path handling.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import os
10 10 import sys
11 11 import errno
12 12 import shutil
13 13 import random
14 14 import glob
15 15 from warnings import warn
16 16
17 17 from IPython.utils.process import system
18 18 from IPython.utils.decorators import undoc
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Code
22 22 #-----------------------------------------------------------------------------
23 23 fs_encoding = sys.getfilesystemencoding()
24 24
25 25 def _writable_dir(path):
26 26 """Whether `path` is a directory, to which the user has write access."""
27 27 return os.path.isdir(path) and os.access(path, os.W_OK)
28 28
29 29 if sys.platform == 'win32':
30 30 def _get_long_path_name(path):
31 31 """Get a long path name (expand ~) on Windows using ctypes.
32 32
33 33 Examples
34 34 --------
35 35
36 36 >>> get_long_path_name('c:\\\\docume~1')
37 37 'c:\\\\Documents and Settings'
38 38
39 39 """
40 40 try:
41 41 import ctypes
42 42 except ImportError as e:
43 43 raise ImportError('you need to have ctypes installed for this to work') from e
44 44 _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW
45 45 _GetLongPathName.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p,
46 46 ctypes.c_uint ]
47 47
48 48 buf = ctypes.create_unicode_buffer(260)
49 49 rv = _GetLongPathName(path, buf, 260)
50 50 if rv == 0 or rv > 260:
51 51 return path
52 52 else:
53 53 return buf.value
54 54 else:
55 55 def _get_long_path_name(path):
56 56 """Dummy no-op."""
57 57 return path
58 58
59 59
60 60
61 61 def get_long_path_name(path):
62 62 """Expand a path into its long form.
63 63
64 64 On Windows this expands any ~ in the paths. On other platforms, it is
65 65 a null operation.
66 66 """
67 67 return _get_long_path_name(path)
68 68
69 69
70 70 def compress_user(path):
71 71 """Reverse of :func:`os.path.expanduser`
72 72 """
73 73 home = os.path.expanduser('~')
74 74 if path.startswith(home):
75 75 path = "~" + path[len(home):]
76 76 return path
77 77
78 78 def get_py_filename(name):
79 79 """Return a valid python filename in the current directory.
80 80
81 81 If the given name is not a file, it adds '.py' and searches again.
82 82 Raises IOError with an informative message if the file isn't found.
83 83 """
84 84
85 85 name = os.path.expanduser(name)
86 if not os.path.isfile(name) and not name.endswith('.py'):
87 name += '.py'
88 86 if os.path.isfile(name):
89 87 return name
90 else:
91 raise IOError('File `%r` not found.' % name)
88 if not name.endswith(".py"):
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 95 def filefind(filename: str, path_dirs=None) -> str:
95 96 """Find a file by looking through a sequence of paths.
96 97
97 98 This iterates through a sequence of paths looking for a file and returns
98 99 the full, absolute path of the first occurrence of the file. If no set of
99 100 path dirs is given, the filename is tested as is, after running through
100 101 :func:`expandvars` and :func:`expanduser`. Thus a simple call::
101 102
102 103 filefind('myfile.txt')
103 104
104 105 will find the file in the current working dir, but::
105 106
106 107 filefind('~/myfile.txt')
107 108
108 109 Will find the file in the users home directory. This function does not
109 110 automatically try any paths, such as the cwd or the user's home directory.
110 111
111 112 Parameters
112 113 ----------
113 114 filename : str
114 115 The filename to look for.
115 116 path_dirs : str, None or sequence of str
116 117 The sequence of paths to look for the file in. If None, the filename
117 118 need to be absolute or be in the cwd. If a string, the string is
118 119 put into a sequence and the searched. If a sequence, walk through
119 120 each element and join with ``filename``, calling :func:`expandvars`
120 121 and :func:`expanduser` before testing for existence.
121 122
122 123 Returns
123 124 -------
124 125 path : str
125 126 returns absolute path to file.
126 127
127 128 Raises
128 129 ------
129 130 IOError
130 131 """
131 132
132 133 # If paths are quoted, abspath gets confused, strip them...
133 134 filename = filename.strip('"').strip("'")
134 135 # If the input is an absolute path, just check it exists
135 136 if os.path.isabs(filename) and os.path.isfile(filename):
136 137 return filename
137 138
138 139 if path_dirs is None:
139 140 path_dirs = ("",)
140 141 elif isinstance(path_dirs, str):
141 142 path_dirs = (path_dirs,)
142 143
143 144 for path in path_dirs:
144 145 if path == '.': path = os.getcwd()
145 146 testname = expand_path(os.path.join(path, filename))
146 147 if os.path.isfile(testname):
147 148 return os.path.abspath(testname)
148 149
149 150 raise IOError("File %r does not exist in any of the search paths: %r" %
150 151 (filename, path_dirs) )
151 152
152 153
153 154 class HomeDirError(Exception):
154 155 pass
155 156
156 157
157 158 def get_home_dir(require_writable=False) -> str:
158 159 """Return the 'home' directory, as a unicode string.
159 160
160 161 Uses os.path.expanduser('~'), and checks for writability.
161 162
162 163 See stdlib docs for how this is determined.
163 164 For Python <3.8, $HOME is first priority on *ALL* platforms.
164 165 For Python >=3.8 on Windows, %HOME% is no longer considered.
165 166
166 167 Parameters
167 168 ----------
168 169 require_writable : bool [default: False]
169 170 if True:
170 171 guarantees the return value is a writable directory, otherwise
171 172 raises HomeDirError
172 173 if False:
173 174 The path is resolved, but it is not guaranteed to exist or be writable.
174 175 """
175 176
176 177 homedir = os.path.expanduser('~')
177 178 # Next line will make things work even when /home/ is a symlink to
178 179 # /usr/home as it is on FreeBSD, for example
179 180 homedir = os.path.realpath(homedir)
180 181
181 182 if not _writable_dir(homedir) and os.name == 'nt':
182 183 # expanduser failed, use the registry to get the 'My Documents' folder.
183 184 try:
184 185 import winreg as wreg
185 186 with wreg.OpenKey(
186 187 wreg.HKEY_CURRENT_USER,
187 188 r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
188 189 ) as key:
189 190 homedir = wreg.QueryValueEx(key,'Personal')[0]
190 191 except:
191 192 pass
192 193
193 194 if (not require_writable) or _writable_dir(homedir):
194 195 assert isinstance(homedir, str), "Homedir should be unicode not bytes"
195 196 return homedir
196 197 else:
197 198 raise HomeDirError('%s is not a writable dir, '
198 199 'set $HOME environment variable to override' % homedir)
199 200
200 201 def get_xdg_dir():
201 202 """Return the XDG_CONFIG_HOME, if it is defined and exists, else None.
202 203
203 204 This is only for non-OS X posix (Linux,Unix,etc.) systems.
204 205 """
205 206
206 207 env = os.environ
207 208
208 209 if os.name == "posix":
209 210 # Linux, Unix, AIX, etc.
210 211 # use ~/.config if empty OR not set
211 212 xdg = env.get("XDG_CONFIG_HOME", None) or os.path.join(get_home_dir(), '.config')
212 213 if xdg and _writable_dir(xdg):
213 214 assert isinstance(xdg, str)
214 215 return xdg
215 216
216 217 return None
217 218
218 219
219 220 def get_xdg_cache_dir():
220 221 """Return the XDG_CACHE_HOME, if it is defined and exists, else None.
221 222
222 223 This is only for non-OS X posix (Linux,Unix,etc.) systems.
223 224 """
224 225
225 226 env = os.environ
226 227
227 228 if os.name == "posix":
228 229 # Linux, Unix, AIX, etc.
229 230 # use ~/.cache if empty OR not set
230 231 xdg = env.get("XDG_CACHE_HOME", None) or os.path.join(get_home_dir(), '.cache')
231 232 if xdg and _writable_dir(xdg):
232 233 assert isinstance(xdg, str)
233 234 return xdg
234 235
235 236 return None
236 237
237 238
238 239 def expand_path(s):
239 240 """Expand $VARS and ~names in a string, like a shell
240 241
241 242 :Examples:
242 243
243 244 In [2]: os.environ['FOO']='test'
244 245
245 246 In [3]: expand_path('variable FOO is $FOO')
246 247 Out[3]: 'variable FOO is test'
247 248 """
248 249 # This is a pretty subtle hack. When expand user is given a UNC path
249 250 # on Windows (\\server\share$\%username%), os.path.expandvars, removes
250 251 # the $ to get (\\server\share\%username%). I think it considered $
251 252 # alone an empty var. But, we need the $ to remains there (it indicates
252 253 # a hidden share).
253 254 if os.name=='nt':
254 255 s = s.replace('$\\', 'IPYTHON_TEMP')
255 256 s = os.path.expandvars(os.path.expanduser(s))
256 257 if os.name=='nt':
257 258 s = s.replace('IPYTHON_TEMP', '$\\')
258 259 return s
259 260
260 261
261 262 def unescape_glob(string):
262 263 """Unescape glob pattern in `string`."""
263 264 def unescape(s):
264 265 for pattern in '*[]!?':
265 266 s = s.replace(r'\{0}'.format(pattern), pattern)
266 267 return s
267 268 return '\\'.join(map(unescape, string.split('\\\\')))
268 269
269 270
270 271 def shellglob(args):
271 272 """
272 273 Do glob expansion for each element in `args` and return a flattened list.
273 274
274 275 Unmatched glob pattern will remain as-is in the returned list.
275 276
276 277 """
277 278 expanded = []
278 279 # Do not unescape backslash in Windows as it is interpreted as
279 280 # path separator:
280 281 unescape = unescape_glob if sys.platform != 'win32' else lambda x: x
281 282 for a in args:
282 283 expanded.extend(glob.glob(a) or [unescape(a)])
283 284 return expanded
284 285
285 286
286 287 def target_outdated(target,deps):
287 288 """Determine whether a target is out of date.
288 289
289 290 target_outdated(target,deps) -> 1/0
290 291
291 292 deps: list of filenames which MUST exist.
292 293 target: single filename which may or may not exist.
293 294
294 295 If target doesn't exist or is older than any file listed in deps, return
295 296 true, otherwise return false.
296 297 """
297 298 try:
298 299 target_time = os.path.getmtime(target)
299 300 except os.error:
300 301 return 1
301 302 for dep in deps:
302 303 dep_time = os.path.getmtime(dep)
303 304 if dep_time > target_time:
304 305 #print "For target",target,"Dep failed:",dep # dbg
305 306 #print "times (dep,tar):",dep_time,target_time # dbg
306 307 return 1
307 308 return 0
308 309
309 310
310 311 def target_update(target,deps,cmd):
311 312 """Update a target with a given command given a list of dependencies.
312 313
313 314 target_update(target,deps,cmd) -> runs cmd if target is outdated.
314 315
315 316 This is just a wrapper around target_outdated() which calls the given
316 317 command if target is outdated."""
317 318
318 319 if target_outdated(target,deps):
319 320 system(cmd)
320 321
321 322
322 323 ENOLINK = 1998
323 324
324 325 def link(src, dst):
325 326 """Hard links ``src`` to ``dst``, returning 0 or errno.
326 327
327 328 Note that the special errno ``ENOLINK`` will be returned if ``os.link`` isn't
328 329 supported by the operating system.
329 330 """
330 331
331 332 if not hasattr(os, "link"):
332 333 return ENOLINK
333 334 link_errno = 0
334 335 try:
335 336 os.link(src, dst)
336 337 except OSError as e:
337 338 link_errno = e.errno
338 339 return link_errno
339 340
340 341
341 342 def link_or_copy(src, dst):
342 343 """Attempts to hardlink ``src`` to ``dst``, copying if the link fails.
343 344
344 345 Attempts to maintain the semantics of ``shutil.copy``.
345 346
346 347 Because ``os.link`` does not overwrite files, a unique temporary file
347 348 will be used if the target already exists, then that file will be moved
348 349 into place.
349 350 """
350 351
351 352 if os.path.isdir(dst):
352 353 dst = os.path.join(dst, os.path.basename(src))
353 354
354 355 link_errno = link(src, dst)
355 356 if link_errno == errno.EEXIST:
356 357 if os.stat(src).st_ino == os.stat(dst).st_ino:
357 358 # dst is already a hard link to the correct file, so we don't need
358 359 # to do anything else. If we try to link and rename the file
359 360 # anyway, we get duplicate files - see http://bugs.python.org/issue21876
360 361 return
361 362
362 363 new_dst = dst + "-temp-%04X" %(random.randint(1, 16**4), )
363 364 try:
364 365 link_or_copy(src, new_dst)
365 366 except:
366 367 try:
367 368 os.remove(new_dst)
368 369 except OSError:
369 370 pass
370 371 raise
371 372 os.rename(new_dst, dst)
372 373 elif link_errno != 0:
373 374 # Either link isn't supported, or the filesystem doesn't support
374 375 # linking, or 'src' and 'dst' are on different filesystems.
375 376 shutil.copy(src, dst)
376 377
377 378 def ensure_dir_exists(path, mode=0o755):
378 379 """ensure that a directory exists
379 380
380 381 If it doesn't exist, try to create it and protect against a race condition
381 382 if another process is doing the same.
382 383
383 384 The default permissions are 755, which differ from os.makedirs default of 777.
384 385 """
385 386 if not os.path.exists(path):
386 387 try:
387 388 os.makedirs(path, mode=mode)
388 389 except OSError as e:
389 390 if e.errno != errno.EEXIST:
390 391 raise
391 392 elif not os.path.isdir(path):
392 393 raise IOError("%r exists but is not a directory" % path)
@@ -1,58 +1,59 b''
1 1 """ This module contains classes - NamedFileInTemporaryDirectory, TemporaryWorkingDirectory.
2 2
3 3 These classes add extra features such as creating a named file in temporary directory and
4 4 creating a context manager for the working directory which is also temporary.
5 5 """
6 6
7 7 import os as _os
8 8 from pathlib import Path
9 9 from tempfile import TemporaryDirectory
10 10
11 11
12 12 class NamedFileInTemporaryDirectory(object):
13 13 def __init__(self, filename, mode="w+b", bufsize=-1, add_to_syspath=False, **kwds):
14 14 """
15 15 Open a file named `filename` in a temporary directory.
16 16
17 17 This context manager is preferred over `NamedTemporaryFile` in
18 18 stdlib `tempfile` when one needs to reopen the file.
19 19
20 20 Arguments `mode` and `bufsize` are passed to `open`.
21 21 Rest of the arguments are passed to `TemporaryDirectory`.
22 22
23 23 """
24 24 self._tmpdir = TemporaryDirectory(**kwds)
25 25 path = Path(self._tmpdir.name) / filename
26 26 encoding = None if "b" in mode else "utf-8"
27 27 self.file = open(path, mode, bufsize, encoding=encoding)
28 28
29 29 def cleanup(self):
30 30 self.file.close()
31 31 self._tmpdir.cleanup()
32 32
33 33 __del__ = cleanup
34 34
35 35 def __enter__(self):
36 36 return self.file
37 37
38 38 def __exit__(self, type, value, traceback):
39 39 self.cleanup()
40 40
41 41
42 42 class TemporaryWorkingDirectory(TemporaryDirectory):
43 43 """
44 44 Creates a temporary directory and sets the cwd to that directory.
45 45 Automatically reverts to previous cwd upon cleanup.
46 46 Usage example:
47 47
48 48 with TemporaryWorkingDirectory() as tmpdir:
49 49 ...
50 50 """
51
51 52 def __enter__(self):
52 53 self.old_wd = Path.cwd()
53 54 _os.chdir(self.name)
54 55 return super(TemporaryWorkingDirectory, self).__enter__()
55 56
56 57 def __exit__(self, exc, value, tb):
57 58 _os.chdir(self.old_wd)
58 59 return super(TemporaryWorkingDirectory, self).__exit__(exc, value, tb)
@@ -1,33 +1,34 b''
1 1 =====================
2 2 Development version
3 3 =====================
4 4
5 5 This document describes in-flight development work.
6 6
7 7 .. warning::
8 8
9 9 Please do not edit this file by hand (doing so will likely cause merge
10 10 conflicts for other Pull Requests). Instead, create a new file in the
11 11 `docs/source/whatsnew/pr` folder
12 12
13 13
14 14 Released .... ...., 2019
15 15
16 16
17 17 Need to be updated:
18 18
19 19 .. toctree::
20 20 :maxdepth: 2
21 21 :glob:
22 22
23 23 pr/*
24 24
25 25
26 26
27 27
28
28 29 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
29 30
30 31 Backwards incompatible changes
31 32 ------------------------------
32 33
33 34 .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT.
@@ -1,1080 +1,1196 b''
1 1 ============
2 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 121 .. _version 8.4.0:
6 122
7 123 IPython 8.4.0
8 124 -------------
9 125
10 126 As for 7.34, this version contains a single fix: fix uncaught BdbQuit exceptions on ipdb
11 127 exit :ghpull:`13668`, and a single typo fix in documentation: :ghpull:`13682`
12 128
13 129 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
14 130 work on IPython and related libraries.
15 131
16 132
17 133 .. _version 8.3.0:
18 134
19 135 IPython 8.3.0
20 136 -------------
21 137
22 138 - :ghpull:`13625`, using ``?``, ``??``, ``*?`` will not call
23 139 ``set_next_input`` as most frontend allow proper multiline editing and it was
24 140 causing issues for many users of multi-cell frontends. This has been backported to 7.33
25 141
26 142
27 143 - :ghpull:`13600`, ``pre_run_*``-hooks will now have a ``cell_id`` attribute on
28 144 the info object when frontend provide it. This has been backported to 7.33
29 145
30 146 - :ghpull:`13624`, fixed :kbd:`End` key being broken after accepting an
31 147 auto-suggestion.
32 148
33 149 - :ghpull:`13657` fix issue where history from different sessions would be mixed.
34 150
35 151 .. _version 8.2.0:
36 152
37 153 IPython 8.2.0
38 154 -------------
39 155
40 156 IPython 8.2 mostly bring bugfixes to IPython.
41 157
42 158 - Auto-suggestion can now be elected with the ``end`` key. :ghpull:`13566`
43 159 - Some traceback issues with ``assert etb is not None`` have been fixed. :ghpull:`13588`
44 160 - History is now pulled from the sqitel database and not from in-memory.
45 161 In particular when using the ``%paste`` magic, the content of the pasted text will
46 162 be part of the history and not the verbatim text ``%paste`` anymore. :ghpull:`13592`
47 163 - Fix ``Ctrl-\\`` exit cleanup :ghpull:`13603`
48 164 - Fixes to ``ultratb`` ipdb support when used outside of IPython. :ghpull:`13498`
49 165
50 166
51 167 I am still trying to fix and investigate :ghissue:`13598`, which seem to be
52 168 random, and would appreciate help if you find reproducible minimal case. I've
53 169 tried to make various changes to the codebase to mitigate it, but a proper fix
54 170 will be difficult without understanding the cause.
55 171
56 172
57 173 All the issues on pull-requests for this release can be found in the `8.2
58 174 milestone. <https://github.com/ipython/ipython/milestone/100>`__ . And some
59 175 documentation only PR can be found as part of the `7.33 milestone
60 176 <https://github.com/ipython/ipython/milestone/101>`__ (currently not released).
61 177
62 178 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
63 179 work on IPython and related libraries.
64 180
65 181 .. _version 8.1.1:
66 182
67 183 IPython 8.1.1
68 184 -------------
69 185
70 186 Fix an issue with virtualenv and Python 3.8 introduced in 8.1
71 187
72 188 Revert :ghpull:`13537` (fix an issue with symlinks in virtualenv) that raises an
73 189 error in Python 3.8, and fixed in a different way in :ghpull:`13559`.
74 190
75 191 .. _version 8.1:
76 192
77 193 IPython 8.1.0
78 194 -------------
79 195
80 196 IPython 8.1 is the first minor release after 8.0 and fixes a number of bugs and
81 197 Update a few behavior that were problematic with the 8.0 as with many new major
82 198 release.
83 199
84 200 Note that beyond the changes listed here, IPython 8.1.0 also contains all the
85 201 features listed in :ref:`version 7.32`.
86 202
87 203 - Misc and multiple fixes around quotation auto-closing. It is now disabled by
88 204 default. Run with ``TerminalInteractiveShell.auto_match=True`` to re-enabled
89 205 - Require pygments>=2.4.0 :ghpull:`13459`, this was implicit in the code, but
90 206 is now explicit in ``setup.cfg``/``setup.py``
91 207 - Docs improvement of ``core.magic_arguments`` examples. :ghpull:`13433`
92 208 - Multi-line edit executes too early with await. :ghpull:`13424`
93 209
94 210 - ``black`` is back as an optional dependency, and autoformatting disabled by
95 211 default until some fixes are implemented (black improperly reformat magics).
96 212 :ghpull:`13471` Additionally the ability to use ``yapf`` as a code
97 213 reformatter has been added :ghpull:`13528` . You can use
98 214 ``TerminalInteractiveShell.autoformatter="black"``,
99 215 ``TerminalInteractiveShell.autoformatter="yapf"`` to re-enable auto formating
100 216 with black, or switch to yapf.
101 217
102 218 - Fix and issue where ``display`` was not defined.
103 219
104 220 - Auto suggestions are now configurable. Currently only
105 221 ``AutoSuggestFromHistory`` (default) and ``None``. new provider contribution
106 222 welcomed. :ghpull:`13475`
107 223
108 224 - multiple packaging/testing improvement to simplify downstream packaging
109 225 (xfail with reasons, try to not access network...).
110 226
111 227 - Update deprecation. ``InteractiveShell.magic`` internal method has been
112 228 deprecated for many years but did not emit a warning until now.
113 229
114 230 - internal ``appended_to_syspath`` context manager has been deprecated.
115 231
116 232 - fix an issue with symlinks in virtualenv :ghpull:`13537` (Reverted in 8.1.1)
117 233
118 234 - Fix an issue with vim mode, where cursor would not be reset on exit :ghpull:`13472`
119 235
120 236 - ipython directive now remove only known pseudo-decorators :ghpull:`13532`
121 237
122 238 - ``IPython/lib/security`` which used to be used for jupyter notebook has been
123 239 removed.
124 240
125 241 - Fix an issue where ``async with`` would execute on new lines. :ghpull:`13436`
126 242
127 243
128 244 We want to remind users that IPython is part of the Jupyter organisations, and
129 245 thus governed by a Code of Conduct. Some of the behavior we have seen on GitHub is not acceptable.
130 246 Abuse and non-respectful comments on discussion will not be tolerated.
131 247
132 248 Many thanks to all the contributors to this release, many of the above fixed issue and
133 249 new features where done by first time contributors, showing there is still
134 250 plenty of easy contribution possible in IPython
135 251 . You can find all individual contributions
136 252 to this milestone `on github <https://github.com/ipython/ipython/milestone/91>`__.
137 253
138 254 Thanks as well to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
139 255 work on IPython and related libraries. In particular the Lazy autoloading of
140 256 magics that you will find described in the 7.32 release notes.
141 257
142 258
143 259 .. _version 8.0.1:
144 260
145 261 IPython 8.0.1 (CVE-2022-21699)
146 262 ------------------------------
147 263
148 264 IPython 8.0.1, 7.31.1 and 5.11 are security releases that change some default
149 265 values in order to prevent potential Execution with Unnecessary Privileges.
150 266
151 267 Almost all version of IPython looks for configuration and profiles in current
152 268 working directory. Since IPython was developed before pip and environments
153 269 existed it was used a convenient way to load code/packages in a project
154 270 dependant way.
155 271
156 272 In 2022, it is not necessary anymore, and can lead to confusing behavior where
157 273 for example cloning a repository and starting IPython or loading a notebook from
158 274 any Jupyter-Compatible interface that has ipython set as a kernel can lead to
159 275 code execution.
160 276
161 277
162 278 I did not find any standard way for packaged to advertise CVEs they fix, I'm
163 279 thus trying to add a ``__patched_cves__`` attribute to the IPython module that
164 280 list the CVEs that should have been fixed. This attribute is informational only
165 281 as if a executable has a flaw, this value can always be changed by an attacker.
166 282
167 283 .. code::
168 284
169 285 In [1]: import IPython
170 286
171 287 In [2]: IPython.__patched_cves__
172 288 Out[2]: {'CVE-2022-21699'}
173 289
174 290 In [3]: 'CVE-2022-21699' in IPython.__patched_cves__
175 291 Out[3]: True
176 292
177 293 Thus starting with this version:
178 294
179 295 - The current working directory is not searched anymore for profiles or
180 296 configurations files.
181 297 - Added a ``__patched_cves__`` attribute (set of strings) to IPython module that contain
182 298 the list of fixed CVE. This is informational only.
183 299
184 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 303 .. _version 8.0:
188 304
189 305 IPython 8.0
190 306 -----------
191 307
192 308 IPython 8.0 is bringing a large number of new features and improvements to both the
193 309 user of the terminal and of the kernel via Jupyter. The removal of compatibility
194 310 with older version of Python is also the opportunity to do a couple of
195 311 performance improvements in particular with respect to startup time.
196 312 The 8.x branch started diverging from its predecessor around IPython 7.12
197 313 (January 2020).
198 314
199 315 This release contains 250+ pull requests, in addition to many of the features
200 316 and backports that have made it to the 7.x branch. Please see the
201 317 `8.0 milestone <https://github.com/ipython/ipython/milestone/73?closed=1>`__ for the full list of pull requests.
202 318
203 319 Please feel free to send pull requests to updates those notes after release,
204 320 I have likely forgotten a few things reviewing 250+ PRs.
205 321
206 322 Dependencies changes/downstream packaging
207 323 -----------------------------------------
208 324
209 325 Most of our building steps have been changed to be (mostly) declarative
210 326 and follow PEP 517. We are trying to completely remove ``setup.py`` (:ghpull:`13238`) and are
211 327 looking for help to do so.
212 328
213 329 - minimum supported ``traitlets`` version is now 5+
214 330 - we now require ``stack_data``
215 331 - minimal Python is now 3.8
216 332 - ``nose`` is not a testing requirement anymore
217 333 - ``pytest`` replaces nose.
218 334 - ``iptest``/``iptest3`` cli entrypoints do not exists anymore.
219 335 - minimum officially support ``numpy`` version has been bumped, but this should
220 336 not have much effect on packaging.
221 337
222 338
223 339 Deprecation and removal
224 340 -----------------------
225 341
226 342 We removed almost all features, arguments, functions, and modules that were
227 343 marked as deprecated between IPython 1.0 and 5.0. As a reminder, 5.0 was released
228 344 in 2016, and 1.0 in 2013. Last release of the 5 branch was 5.10.0, in May 2020.
229 345 The few remaining deprecated features we left have better deprecation warnings
230 346 or have been turned into explicit errors for better error messages.
231 347
232 348 I will use this occasion to add the following requests to anyone emitting a
233 349 deprecation warning:
234 350
235 351 - Please add at least ``stacklevel=2`` so that the warning is emitted into the
236 352 caller context, and not the callee one.
237 353 - Please add **since which version** something is deprecated.
238 354
239 355 As a side note, it is much easier to conditionally compare version
240 356 numbers rather than using ``try/except`` when functionality changes with a version.
241 357
242 358 I won't list all the removed features here, but modules like ``IPython.kernel``,
243 359 which was just a shim module around ``ipykernel`` for the past 8 years, have been
244 360 removed, and so many other similar things that pre-date the name **Jupyter**
245 361 itself.
246 362
247 363 We no longer need to add ``IPython.extensions`` to the PYTHONPATH because that is being
248 364 handled by ``load_extension``.
249 365
250 366 We are also removing ``Cythonmagic``, ``sympyprinting`` and ``rmagic`` as they are now in
251 367 other packages and no longer need to be inside IPython.
252 368
253 369
254 370 Documentation
255 371 -------------
256 372
257 373 The majority of our docstrings have now been reformatted and automatically fixed by
258 374 the experimental `Vélin <https://pypi.org/project/velin/>`_ project to conform
259 375 to numpydoc.
260 376
261 377 Type annotations
262 378 ----------------
263 379
264 380 While IPython itself is highly dynamic and can't be completely typed, many of
265 381 the functions now have type annotations, and part of the codebase is now checked
266 382 by mypy.
267 383
268 384
269 385 Featured changes
270 386 ----------------
271 387
272 388 Here is a features list of changes in IPython 8.0. This is of course non-exhaustive.
273 389 Please note as well that many features have been added in the 7.x branch as well
274 390 (and hence why you want to read the 7.x what's new notes), in particular
275 391 features contributed by QuantStack (with respect to debugger protocol and Xeus
276 392 Python), as well as many debugger features that I was pleased to implement as
277 393 part of my work at QuanSight and sponsored by DE Shaw.
278 394
279 395 Traceback improvements
280 396 ~~~~~~~~~~~~~~~~~~~~~~
281 397
282 398 Previously, error tracebacks for errors happening in code cells were showing a
283 399 hash, the one used for compiling the Python AST::
284 400
285 401 In [1]: def foo():
286 402 ...: return 3 / 0
287 403 ...:
288 404
289 405 In [2]: foo()
290 406 ---------------------------------------------------------------------------
291 407 ZeroDivisionError Traceback (most recent call last)
292 408 <ipython-input-2-c19b6d9633cf> in <module>
293 409 ----> 1 foo()
294 410
295 411 <ipython-input-1-1595a74c32d5> in foo()
296 412 1 def foo():
297 413 ----> 2 return 3 / 0
298 414 3
299 415
300 416 ZeroDivisionError: division by zero
301 417
302 418 The error traceback is now correctly formatted, showing the cell number in which the error happened::
303 419
304 420 In [1]: def foo():
305 421 ...: return 3 / 0
306 422 ...:
307 423
308 424 Input In [2]: foo()
309 425 ---------------------------------------------------------------------------
310 426 ZeroDivisionError Traceback (most recent call last)
311 427 input In [2], in <module>
312 428 ----> 1 foo()
313 429
314 430 Input In [1], in foo()
315 431 1 def foo():
316 432 ----> 2 return 3 / 0
317 433
318 434 ZeroDivisionError: division by zero
319 435
320 436 The ``stack_data`` package has been integrated, which provides smarter information in the traceback;
321 437 in particular it will highlight the AST node where an error occurs which can help to quickly narrow down errors.
322 438
323 439 For example in the following snippet::
324 440
325 441 def foo(i):
326 442 x = [[[0]]]
327 443 return x[0][i][0]
328 444
329 445
330 446 def bar():
331 447 return foo(0) + foo(
332 448 1
333 449 ) + foo(2)
334 450
335 451
336 452 calling ``bar()`` would raise an ``IndexError`` on the return line of ``foo``,
337 453 and IPython 8.0 is capable of telling you where the index error occurs::
338 454
339 455
340 456 IndexError
341 457 Input In [2], in <module>
342 458 ----> 1 bar()
343 459 ^^^^^
344 460
345 461 Input In [1], in bar()
346 462 6 def bar():
347 463 ----> 7 return foo(0) + foo(
348 464 ^^^^
349 465 8 1
350 466 ^^^^^^^^
351 467 9 ) + foo(2)
352 468 ^^^^
353 469
354 470 Input In [1], in foo(i)
355 471 1 def foo(i):
356 472 2 x = [[[0]]]
357 473 ----> 3 return x[0][i][0]
358 474 ^^^^^^^
359 475
360 476 The corresponding locations marked here with ``^`` will show up highlighted in
361 477 the terminal and notebooks.
362 478
363 479 Finally, a colon ``::`` and line number is appended after a filename in
364 480 traceback::
365 481
366 482
367 483 ZeroDivisionError Traceback (most recent call last)
368 484 File ~/error.py:4, in <module>
369 485 1 def f():
370 486 2 1/0
371 487 ----> 4 f()
372 488
373 489 File ~/error.py:2, in f()
374 490 1 def f():
375 491 ----> 2 1/0
376 492
377 493 Many terminals and editors have integrations enabling you to directly jump to the
378 494 relevant file/line when this syntax is used, so this small addition may have a high
379 495 impact on productivity.
380 496
381 497
382 498 Autosuggestions
383 499 ~~~~~~~~~~~~~~~
384 500
385 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 503 `Ptpython <https://github.com/prompt-toolkit/ptpython#ptpython>`__ allows users to enable this feature in
388 504 `ptpython/config.py <https://github.com/prompt-toolkit/ptpython/blob/master/examples/ptpython_config/config.py#L90>`__.
389 505
390 506 This feature allows users to accept autosuggestions with ctrl e, ctrl f,
391 507 or right arrow as described below.
392 508
393 509 1. Start ipython
394 510
395 511 .. image:: ../_images/8.0/auto_suggest_1_prompt_no_text.png
396 512
397 513 2. Run ``print("hello")``
398 514
399 515 .. image:: ../_images/8.0/auto_suggest_2_print_hello_suggest.png
400 516
401 517 3. start typing ``print`` again to see the autosuggestion
402 518
403 519 .. image:: ../_images/8.0/auto_suggest_3_print_hello_suggest.png
404 520
405 521 4. Press ``ctrl-f``, or ``ctrl-e``, or ``right-arrow`` to accept the suggestion
406 522
407 523 .. image:: ../_images/8.0/auto_suggest_4_print_hello.png
408 524
409 525 You can also complete word by word:
410 526
411 527 1. Run ``def say_hello(): print("hello")``
412 528
413 529 .. image:: ../_images/8.0/auto_suggest_second_prompt.png
414 530
415 531 2. Start typing the first letter if ``def`` to see the autosuggestion
416 532
417 533 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
418 534
419 535 3. Press ``alt-f`` (or ``escape`` followed by ``f``), to accept the first word of the suggestion
420 536
421 537 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
422 538
423 539 Importantly, this feature does not interfere with tab completion:
424 540
425 541 1. After running ``def say_hello(): print("hello")``, press d
426 542
427 543 .. image:: ../_images/8.0/auto_suggest_d_phantom.png
428 544
429 545 2. Press Tab to start tab completion
430 546
431 547 .. image:: ../_images/8.0/auto_suggest_d_completions.png
432 548
433 549 3A. Press Tab again to select the first option
434 550
435 551 .. image:: ../_images/8.0/auto_suggest_def_completions.png
436 552
437 553 3B. Press ``alt f`` (``escape``, ``f``) to accept to accept the first word of the suggestion
438 554
439 555 .. image:: ../_images/8.0/auto_suggest_def_phantom.png
440 556
441 557 3C. Press ``ctrl-f`` or ``ctrl-e`` to accept the entire suggestion
442 558
443 559 .. image:: ../_images/8.0/auto_suggest_match_parens.png
444 560
445 561
446 562 Currently, autosuggestions are only shown in the emacs or vi insert editing modes:
447 563
448 564 - The ctrl e, ctrl f, and alt f shortcuts work by default in emacs mode.
449 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 568 Show pinfo information in ipdb using "?" and "??"
453 569 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
454 570
455 571 In IPDB, it is now possible to show the information about an object using "?"
456 572 and "??", in much the same way that it can be done when using the IPython prompt::
457 573
458 574 ipdb> partial?
459 575 Init signature: partial(self, /, *args, **kwargs)
460 576 Docstring:
461 577 partial(func, *args, **keywords) - new function with partial application
462 578 of the given arguments and keywords.
463 579 File: ~/.pyenv/versions/3.8.6/lib/python3.8/functools.py
464 580 Type: type
465 581 Subclasses:
466 582
467 583 Previously, ``pinfo`` or ``pinfo2`` command had to be used for this purpose.
468 584
469 585
470 586 Autoreload 3 feature
471 587 ~~~~~~~~~~~~~~~~~~~~
472 588
473 589 Example: When an IPython session is run with the 'autoreload' extension loaded,
474 590 you will now have the option '3' to select, which means the following:
475 591
476 592 1. replicate all functionality from option 2
477 593 2. autoload all new funcs/classes/enums/globals from the module when they are added
478 594 3. autoload all newly imported funcs/classes/enums/globals from external modules
479 595
480 596 Try ``%autoreload 3`` in an IPython session after running ``%load_ext autoreload``.
481 597
482 598 For more information please see the following unit test : ``extensions/tests/test_autoreload.py:test_autoload_newly_added_objects``
483 599
484 600 Auto formatting with black in the CLI
485 601 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
486 602
487 603 This feature was present in 7.x, but disabled by default.
488 604
489 605 In 8.0, input was automatically reformatted with Black when black was installed.
490 606 This feature has been reverted for the time being.
491 607 You can re-enable it by setting ``TerminalInteractiveShell.autoformatter`` to ``"black"``
492 608
493 609 History Range Glob feature
494 610 ~~~~~~~~~~~~~~~~~~~~~~~~~~
495 611
496 612 Previously, when using ``%history``, users could specify either
497 613 a range of sessions and lines, for example:
498 614
499 615 .. code-block:: python
500 616
501 617 ~8/1-~6/5 # see history from the first line of 8 sessions ago,
502 618 # to the fifth line of 6 sessions ago.``
503 619
504 620 Or users could specify a glob pattern:
505 621
506 622 .. code-block:: python
507 623
508 624 -g <pattern> # glob ALL history for the specified pattern.
509 625
510 626 However users could *not* specify both.
511 627
512 628 If a user *did* specify both a range and a glob pattern,
513 629 then the glob pattern would be used (globbing *all* history) *and the range would be ignored*.
514 630
515 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 633 Don't start a multi-line cell with sunken parenthesis
518 634 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
519 635
520 636 From now on, IPython will not ask for the next line of input when given a single
521 637 line with more closing than opening brackets. For example, this means that if
522 638 you (mis)type ``]]`` instead of ``[]``, a ``SyntaxError`` will show up, instead of
523 639 the ``...:`` prompt continuation.
524 640
525 641 IPython shell for ipdb interact
526 642 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
527 643
528 644 The ipdb ``interact`` starts an IPython shell instead of Python's built-in ``code.interact()``.
529 645
530 646 Automatic Vi prompt stripping
531 647 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
532 648
533 649 When pasting code into IPython, it will strip the leading prompt characters if
534 650 there are any. For example, you can paste the following code into the console -
535 651 it will still work, even though each line is prefixed with prompts (``In``,
536 652 ``Out``)::
537 653
538 654 In [1]: 2 * 2 == 4
539 655 Out[1]: True
540 656
541 657 In [2]: print("This still works as pasted")
542 658
543 659
544 660 Previously, this was not the case for the Vi-mode prompts::
545 661
546 662 In [1]: [ins] In [13]: 2 * 2 == 4
547 663 ...: Out[13]: True
548 664 ...:
549 665 File "<ipython-input-1-727bb88eaf33>", line 1
550 666 [ins] In [13]: 2 * 2 == 4
551 667 ^
552 668 SyntaxError: invalid syntax
553 669
554 670 This is now fixed, and Vi prompt prefixes - ``[ins]`` and ``[nav]`` - are
555 671 skipped just as the normal ``In`` would be.
556 672
557 673 IPython shell can be started in the Vi mode using ``ipython --TerminalInteractiveShell.editing_mode=vi``,
558 674 You should be able to change mode dynamically with ``%config TerminalInteractiveShell.editing_mode='vi'``
559 675
560 676 Empty History Ranges
561 677 ~~~~~~~~~~~~~~~~~~~~
562 678
563 679 A number of magics that take history ranges can now be used with an empty
564 680 range. These magics are:
565 681
566 682 * ``%save``
567 683 * ``%load``
568 684 * ``%pastebin``
569 685 * ``%pycat``
570 686
571 687 Using them this way will make them take the history of the current session up
572 688 to the point of the magic call (such that the magic itself will not be
573 689 included).
574 690
575 691 Therefore it is now possible to save the whole history to a file using
576 692 ``%save <filename>``, load and edit it using ``%load`` (makes for a nice usage
577 693 when followed with :kbd:`F2`), send it to `dpaste.org <http://dpast.org>`_ using
578 694 ``%pastebin``, or view the whole thing syntax-highlighted with a single
579 695 ``%pycat``.
580 696
581 697
582 698 Windows timing implementation: Switch to process_time
583 699 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
584 700 Timing on Windows, for example with ``%%time``, was changed from being based on ``time.perf_counter``
585 701 (which counted time even when the process was sleeping) to being based on ``time.process_time`` instead
586 702 (which only counts CPU time). This brings it closer to the behavior on Linux. See :ghpull:`12984`.
587 703
588 704 Miscellaneous
589 705 ~~~~~~~~~~~~~
590 706 - Non-text formatters are not disabled in the terminal, which should simplify
591 707 writing extensions displaying images or other mimetypes in supporting terminals.
592 708 :ghpull:`12315`
593 709 - It is now possible to automatically insert matching brackets in Terminal IPython using the
594 710 ``TerminalInteractiveShell.auto_match=True`` option. :ghpull:`12586`
595 711 - We are thinking of deprecating the current ``%%javascript`` magic in favor of a better replacement. See :ghpull:`13376`.
596 712 - ``~`` is now expanded when part of a path in most magics :ghpull:`13385`
597 713 - ``%/%%timeit`` magic now adds a comma every thousands to make reading a long number easier :ghpull:`13379`
598 714 - ``"info"`` messages can now be customised to hide some fields :ghpull:`13343`
599 715 - ``collections.UserList`` now pretty-prints :ghpull:`13320`
600 716 - The debugger now has a persistent history, which should make it less
601 717 annoying to retype commands :ghpull:`13246`
602 718 - ``!pip`` ``!conda`` ``!cd`` or ``!ls`` are likely doing the wrong thing. We
603 719 now warn users if they use one of those commands. :ghpull:`12954`
604 720 - Make ``%precision`` work for ``numpy.float64`` type :ghpull:`12902`
605 721
606 722 Re-added support for XDG config directories
607 723 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
608 724
609 725 XDG support through the years comes and goes. There is a tension between having
610 726 an identical location for configuration in all platforms versus having simple instructions.
611 727 After initial failures a couple of years ago, IPython was modified to automatically migrate XDG
612 728 config files back into ``~/.ipython``. That migration code has now been removed.
613 729 IPython now checks the XDG locations, so if you _manually_ move your config
614 730 files to your preferred location, IPython will not move them back.
615 731
616 732
617 733 Preparing for Python 3.10
618 734 -------------------------
619 735
620 736 To prepare for Python 3.10, we have started working on removing reliance and
621 737 any dependency that is not compatible with Python 3.10. This includes migrating our
622 738 test suite to pytest and starting to remove nose. This also means that the
623 739 ``iptest`` command is now gone and all testing is via pytest.
624 740
625 741 This was in large part thanks to the NumFOCUS Small Developer grant, which enabled us to
626 742 allocate \$4000 to hire `Nikita Kniazev (@Kojoley) <https://github.com/Kojoley>`_,
627 743 who did a fantastic job at updating our code base, migrating to pytest, pushing
628 744 our coverage, and fixing a large number of bugs. I highly recommend contacting
629 745 them if you need help with C++ and Python projects.
630 746
631 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 749 Removing support for older Python versions
634 750 ------------------------------------------
635 751
636 752
637 753 We are removing support for Python up through 3.7, allowing internal code to use the more
638 754 efficient ``pathlib`` and to make better use of type annotations.
639 755
640 756 .. image:: ../_images/8.0/pathlib_pathlib_everywhere.jpg
641 757 :alt: "Meme image of Toy Story with Woody and Buzz, with the text 'pathlib, pathlib everywhere'"
642 758
643 759
644 760 We had about 34 PRs only to update some logic to update some functions from managing strings to
645 761 using Pathlib.
646 762
647 763 The completer has also seen significant updates and now makes use of newer Jedi APIs,
648 764 offering faster and more reliable tab completion.
649 765
650 766 Misc Statistics
651 767 ---------------
652 768
653 769 Here are some numbers::
654 770
655 771 7.x: 296 files, 12561 blank lines, 20282 comments, 35142 line of code.
656 772 8.0: 252 files, 12053 blank lines, 19232 comments, 34505 line of code.
657 773
658 774 $ git diff --stat 7.x...master | tail -1
659 775 340 files changed, 13399 insertions(+), 12421 deletions(-)
660 776
661 777 We have commits from 162 authors, who contributed 1916 commits in 23 month, excluding merges (to not bias toward
662 778 maintainers pushing buttons).::
663 779
664 780 $ git shortlog -s --no-merges 7.x...master | sort -nr
665 781 535 Matthias Bussonnier
666 782 86 Nikita Kniazev
667 783 69 Blazej Michalik
668 784 49 Samuel Gaist
669 785 27 Itamar Turner-Trauring
670 786 18 Spas Kalaydzhisyki
671 787 17 Thomas Kluyver
672 788 17 Quentin Peter
673 789 17 James Morris
674 790 17 Artur Svistunov
675 791 15 Bart Skowron
676 792 14 Alex Hall
677 793 13 rushabh-v
678 794 13 Terry Davis
679 795 13 Benjamin Ragan-Kelley
680 796 8 martinRenou
681 797 8 farisachugthai
682 798 7 dswij
683 799 7 Gal B
684 800 7 Corentin Cadiou
685 801 6 yuji96
686 802 6 Martin Skarzynski
687 803 6 Justin Palmer
688 804 6 Daniel Goldfarb
689 805 6 Ben Greiner
690 806 5 Sammy Al Hashemi
691 807 5 Paul Ivanov
692 808 5 Inception95
693 809 5 Eyenpi
694 810 5 Douglas Blank
695 811 5 Coco Mishra
696 812 5 Bibo Hao
697 813 5 André A. Gomes
698 814 5 Ahmed Fasih
699 815 4 takuya fujiwara
700 816 4 palewire
701 817 4 Thomas A Caswell
702 818 4 Talley Lambert
703 819 4 Scott Sanderson
704 820 4 Ram Rachum
705 821 4 Nick Muoh
706 822 4 Nathan Goldbaum
707 823 4 Mithil Poojary
708 824 4 Michael T
709 825 4 Jakub Klus
710 826 4 Ian Castleden
711 827 4 Eli Rykoff
712 828 4 Ashwin Vishnu
713 829 3 谭九鼎
714 830 3 sleeping
715 831 3 Sylvain Corlay
716 832 3 Peter Corke
717 833 3 Paul Bissex
718 834 3 Matthew Feickert
719 835 3 Fernando Perez
720 836 3 Eric Wieser
721 837 3 Daniel Mietchen
722 838 3 Aditya Sathe
723 839 3 007vedant
724 840 2 rchiodo
725 841 2 nicolaslazo
726 842 2 luttik
727 843 2 gorogoroumaru
728 844 2 foobarbyte
729 845 2 bar-hen
730 846 2 Theo Ouzhinski
731 847 2 Strawkage
732 848 2 Samreen Zarroug
733 849 2 Pete Blois
734 850 2 Meysam Azad
735 851 2 Matthieu Ancellin
736 852 2 Mark Schmitz
737 853 2 Maor Kleinberger
738 854 2 MRCWirtz
739 855 2 Lumir Balhar
740 856 2 Julien Rabinow
741 857 2 Juan Luis Cano Rodríguez
742 858 2 Joyce Er
743 859 2 Jakub
744 860 2 Faris A Chugthai
745 861 2 Ethan Madden
746 862 2 Dimitri Papadopoulos
747 863 2 Diego Fernandez
748 864 2 Daniel Shimon
749 865 2 Coco Bennett
750 866 2 Carlos Cordoba
751 867 2 Boyuan Liu
752 868 2 BaoGiang HoangVu
753 869 2 Augusto
754 870 2 Arthur Svistunov
755 871 2 Arthur Moreira
756 872 2 Ali Nabipour
757 873 2 Adam Hackbarth
758 874 1 richard
759 875 1 linar-jether
760 876 1 lbennett
761 877 1 juacrumar
762 878 1 gpotter2
763 879 1 digitalvirtuoso
764 880 1 dalthviz
765 881 1 Yonatan Goldschmidt
766 882 1 Tomasz Kłoczko
767 883 1 Tobias Bengfort
768 884 1 Timur Kushukov
769 885 1 Thomas
770 886 1 Snir Broshi
771 887 1 Shao Yang Hong
772 888 1 Sanjana-03
773 889 1 Romulo Filho
774 890 1 Rodolfo Carvalho
775 891 1 Richard Shadrach
776 892 1 Reilly Tucker Siemens
777 893 1 Rakessh Roshan
778 894 1 Piers Titus van der Torren
779 895 1 PhanatosZou
780 896 1 Pavel Safronov
781 897 1 Paulo S. Costa
782 898 1 Paul McCarthy
783 899 1 NotWearingPants
784 900 1 Naelson Douglas
785 901 1 Michael Tiemann
786 902 1 Matt Wozniski
787 903 1 Markus Wageringel
788 904 1 Marcus Wirtz
789 905 1 Marcio Mazza
790 906 1 Lumír 'Frenzy' Balhar
791 907 1 Lightyagami1
792 908 1 Leon Anavi
793 909 1 LeafyLi
794 910 1 L0uisJ0shua
795 911 1 Kyle Cutler
796 912 1 Krzysztof Cybulski
797 913 1 Kevin Kirsche
798 914 1 KIU Shueng Chuan
799 915 1 Jonathan Slenders
800 916 1 Jay Qi
801 917 1 Jake VanderPlas
802 918 1 Iwan Briquemont
803 919 1 Hussaina Begum Nandyala
804 920 1 Gordon Ball
805 921 1 Gabriel Simonetto
806 922 1 Frank Tobia
807 923 1 Erik
808 924 1 Elliott Sales de Andrade
809 925 1 Daniel Hahler
810 926 1 Dan Green-Leipciger
811 927 1 Dan Green
812 928 1 Damian Yurzola
813 929 1 Coon, Ethan T
814 930 1 Carol Willing
815 931 1 Brian Lee
816 932 1 Brendan Gerrity
817 933 1 Blake Griffin
818 934 1 Bastian Ebeling
819 935 1 Bartosz Telenczuk
820 936 1 Ankitsingh6299
821 937 1 Andrew Port
822 938 1 Andrew J. Hesford
823 939 1 Albert Zhang
824 940 1 Adam Johnson
825 941
826 942 This does not, of course, represent non-code contributions, for which we are also grateful.
827 943
828 944
829 945 API Changes using Frappuccino
830 946 -----------------------------
831 947
832 948 This is an experimental exhaustive API difference using `Frappuccino <https://pypi.org/project/frappuccino/>`_
833 949
834 950
835 951 The following items are new in IPython 8.0 ::
836 952
837 953 + IPython.core.async_helpers.get_asyncio_loop()
838 954 + IPython.core.completer.Dict
839 955 + IPython.core.completer.Pattern
840 956 + IPython.core.completer.Sequence
841 957 + IPython.core.completer.__skip_doctest__
842 958 + IPython.core.debugger.Pdb.precmd(self, line)
843 959 + IPython.core.debugger.__skip_doctest__
844 960 + IPython.core.display.__getattr__(name)
845 961 + IPython.core.display.warn
846 962 + IPython.core.display_functions
847 963 + IPython.core.display_functions.DisplayHandle
848 964 + IPython.core.display_functions.DisplayHandle.display(self, obj, **kwargs)
849 965 + IPython.core.display_functions.DisplayHandle.update(self, obj, **kwargs)
850 966 + IPython.core.display_functions.__all__
851 967 + IPython.core.display_functions.__builtins__
852 968 + IPython.core.display_functions.__cached__
853 969 + IPython.core.display_functions.__doc__
854 970 + IPython.core.display_functions.__file__
855 971 + IPython.core.display_functions.__loader__
856 972 + IPython.core.display_functions.__name__
857 973 + IPython.core.display_functions.__package__
858 974 + IPython.core.display_functions.__spec__
859 975 + IPython.core.display_functions.b2a_hex
860 976 + IPython.core.display_functions.clear_output(wait=False)
861 977 + IPython.core.display_functions.display(*objs, include='None', exclude='None', metadata='None', transient='None', display_id='None', raw=False, clear=False, **kwargs)
862 978 + IPython.core.display_functions.publish_display_data(data, metadata='None', source='<deprecated>', *, transient='None', **kwargs)
863 979 + IPython.core.display_functions.update_display(obj, *, display_id, **kwargs)
864 980 + IPython.core.extensions.BUILTINS_EXTS
865 981 + IPython.core.inputtransformer2.has_sunken_brackets(tokens)
866 982 + IPython.core.interactiveshell.Callable
867 983 + IPython.core.interactiveshell.__annotations__
868 984 + IPython.core.ultratb.List
869 985 + IPython.core.ultratb.Tuple
870 986 + IPython.lib.pretty.CallExpression
871 987 + IPython.lib.pretty.CallExpression.factory(name)
872 988 + IPython.lib.pretty.RawStringLiteral
873 989 + IPython.lib.pretty.RawText
874 990 + IPython.terminal.debugger.TerminalPdb.do_interact(self, arg)
875 991 + IPython.terminal.embed.Set
876 992
877 993 The following items have been removed (or moved to superclass)::
878 994
879 995 - IPython.core.application.BaseIPythonApplication.initialize_subcommand
880 996 - IPython.core.completer.Sentinel
881 997 - IPython.core.completer.skip_doctest
882 998 - IPython.core.debugger.Tracer
883 999 - IPython.core.display.DisplayHandle
884 1000 - IPython.core.display.DisplayHandle.display
885 1001 - IPython.core.display.DisplayHandle.update
886 1002 - IPython.core.display.b2a_hex
887 1003 - IPython.core.display.clear_output
888 1004 - IPython.core.display.display
889 1005 - IPython.core.display.publish_display_data
890 1006 - IPython.core.display.update_display
891 1007 - IPython.core.excolors.Deprec
892 1008 - IPython.core.excolors.ExceptionColors
893 1009 - IPython.core.history.warn
894 1010 - IPython.core.hooks.late_startup_hook
895 1011 - IPython.core.hooks.pre_run_code_hook
896 1012 - IPython.core.hooks.shutdown_hook
897 1013 - IPython.core.interactiveshell.InteractiveShell.init_deprecation_warnings
898 1014 - IPython.core.interactiveshell.InteractiveShell.init_readline
899 1015 - IPython.core.interactiveshell.InteractiveShell.write
900 1016 - IPython.core.interactiveshell.InteractiveShell.write_err
901 1017 - IPython.core.interactiveshell.get_default_colors
902 1018 - IPython.core.interactiveshell.removed_co_newlocals
903 1019 - IPython.core.magics.execution.ExecutionMagics.profile_missing_notice
904 1020 - IPython.core.magics.script.PIPE
905 1021 - IPython.core.prefilter.PrefilterManager.init_transformers
906 1022 - IPython.core.release.classifiers
907 1023 - IPython.core.release.description
908 1024 - IPython.core.release.keywords
909 1025 - IPython.core.release.long_description
910 1026 - IPython.core.release.name
911 1027 - IPython.core.release.platforms
912 1028 - IPython.core.release.url
913 1029 - IPython.core.ultratb.VerboseTB.format_records
914 1030 - IPython.core.ultratb.find_recursion
915 1031 - IPython.core.ultratb.findsource
916 1032 - IPython.core.ultratb.fix_frame_records_filenames
917 1033 - IPython.core.ultratb.inspect_error
918 1034 - IPython.core.ultratb.is_recursion_error
919 1035 - IPython.core.ultratb.with_patch_inspect
920 1036 - IPython.external.__all__
921 1037 - IPython.external.__builtins__
922 1038 - IPython.external.__cached__
923 1039 - IPython.external.__doc__
924 1040 - IPython.external.__file__
925 1041 - IPython.external.__loader__
926 1042 - IPython.external.__name__
927 1043 - IPython.external.__package__
928 1044 - IPython.external.__path__
929 1045 - IPython.external.__spec__
930 1046 - IPython.kernel.KernelConnectionInfo
931 1047 - IPython.kernel.__builtins__
932 1048 - IPython.kernel.__cached__
933 1049 - IPython.kernel.__warningregistry__
934 1050 - IPython.kernel.pkg
935 1051 - IPython.kernel.protocol_version
936 1052 - IPython.kernel.protocol_version_info
937 1053 - IPython.kernel.src
938 1054 - IPython.kernel.version_info
939 1055 - IPython.kernel.warn
940 1056 - IPython.lib.backgroundjobs
941 1057 - IPython.lib.backgroundjobs.BackgroundJobBase
942 1058 - IPython.lib.backgroundjobs.BackgroundJobBase.run
943 1059 - IPython.lib.backgroundjobs.BackgroundJobBase.traceback
944 1060 - IPython.lib.backgroundjobs.BackgroundJobExpr
945 1061 - IPython.lib.backgroundjobs.BackgroundJobExpr.call
946 1062 - IPython.lib.backgroundjobs.BackgroundJobFunc
947 1063 - IPython.lib.backgroundjobs.BackgroundJobFunc.call
948 1064 - IPython.lib.backgroundjobs.BackgroundJobManager
949 1065 - IPython.lib.backgroundjobs.BackgroundJobManager.flush
950 1066 - IPython.lib.backgroundjobs.BackgroundJobManager.new
951 1067 - IPython.lib.backgroundjobs.BackgroundJobManager.remove
952 1068 - IPython.lib.backgroundjobs.BackgroundJobManager.result
953 1069 - IPython.lib.backgroundjobs.BackgroundJobManager.status
954 1070 - IPython.lib.backgroundjobs.BackgroundJobManager.traceback
955 1071 - IPython.lib.backgroundjobs.__builtins__
956 1072 - IPython.lib.backgroundjobs.__cached__
957 1073 - IPython.lib.backgroundjobs.__doc__
958 1074 - IPython.lib.backgroundjobs.__file__
959 1075 - IPython.lib.backgroundjobs.__loader__
960 1076 - IPython.lib.backgroundjobs.__name__
961 1077 - IPython.lib.backgroundjobs.__package__
962 1078 - IPython.lib.backgroundjobs.__spec__
963 1079 - IPython.lib.kernel.__builtins__
964 1080 - IPython.lib.kernel.__cached__
965 1081 - IPython.lib.kernel.__doc__
966 1082 - IPython.lib.kernel.__file__
967 1083 - IPython.lib.kernel.__loader__
968 1084 - IPython.lib.kernel.__name__
969 1085 - IPython.lib.kernel.__package__
970 1086 - IPython.lib.kernel.__spec__
971 1087 - IPython.lib.kernel.__warningregistry__
972 1088 - IPython.paths.fs_encoding
973 1089 - IPython.terminal.debugger.DEFAULT_BUFFER
974 1090 - IPython.terminal.debugger.cursor_in_leading_ws
975 1091 - IPython.terminal.debugger.emacs_insert_mode
976 1092 - IPython.terminal.debugger.has_selection
977 1093 - IPython.terminal.debugger.vi_insert_mode
978 1094 - IPython.terminal.interactiveshell.DISPLAY_BANNER_DEPRECATED
979 1095 - IPython.terminal.ipapp.TerminalIPythonApp.parse_command_line
980 1096 - IPython.testing.test
981 1097 - IPython.utils.contexts.NoOpContext
982 1098 - IPython.utils.io.IOStream
983 1099 - IPython.utils.io.IOStream.close
984 1100 - IPython.utils.io.IOStream.write
985 1101 - IPython.utils.io.IOStream.writelines
986 1102 - IPython.utils.io.__warningregistry__
987 1103 - IPython.utils.io.atomic_writing
988 1104 - IPython.utils.io.stderr
989 1105 - IPython.utils.io.stdin
990 1106 - IPython.utils.io.stdout
991 1107 - IPython.utils.io.unicode_std_stream
992 1108 - IPython.utils.path.get_ipython_cache_dir
993 1109 - IPython.utils.path.get_ipython_dir
994 1110 - IPython.utils.path.get_ipython_module_path
995 1111 - IPython.utils.path.get_ipython_package_dir
996 1112 - IPython.utils.path.locate_profile
997 1113 - IPython.utils.path.unquote_filename
998 1114 - IPython.utils.py3compat.PY2
999 1115 - IPython.utils.py3compat.PY3
1000 1116 - IPython.utils.py3compat.buffer_to_bytes
1001 1117 - IPython.utils.py3compat.builtin_mod_name
1002 1118 - IPython.utils.py3compat.cast_bytes
1003 1119 - IPython.utils.py3compat.getcwd
1004 1120 - IPython.utils.py3compat.isidentifier
1005 1121 - IPython.utils.py3compat.u_format
1006 1122
1007 1123 The following signatures differ between 7.x and 8.0::
1008 1124
1009 1125 - IPython.core.completer.IPCompleter.unicode_name_matches(self, text)
1010 1126 + IPython.core.completer.IPCompleter.unicode_name_matches(text)
1011 1127
1012 1128 - IPython.core.completer.match_dict_keys(keys, prefix, delims)
1013 1129 + IPython.core.completer.match_dict_keys(keys, prefix, delims, extra_prefix='None')
1014 1130
1015 1131 - IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0)
1016 1132 + IPython.core.interactiveshell.InteractiveShell.object_inspect_mime(self, oname, detail_level=0, omit_sections='()')
1017 1133
1018 1134 - IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None', _warn_deprecated=True)
1019 1135 + IPython.core.interactiveshell.InteractiveShell.set_hook(self, name, hook, priority=50, str_key='None', re_key='None')
1020 1136
1021 1137 - IPython.core.oinspect.Inspector.info(self, obj, oname='', formatter='None', info='None', detail_level=0)
1022 1138 + IPython.core.oinspect.Inspector.info(self, obj, oname='', info='None', detail_level=0)
1023 1139
1024 1140 - IPython.core.oinspect.Inspector.pinfo(self, obj, oname='', formatter='None', info='None', detail_level=0, enable_html_pager=True)
1025 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 1143 - IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path='None', overwrite=False)
1028 1144 + IPython.core.profiledir.ProfileDir.copy_config_file(self, config_file, path, overwrite=False)
1029 1145
1030 1146 - IPython.core.ultratb.VerboseTB.format_record(self, frame, file, lnum, func, lines, index)
1031 1147 + IPython.core.ultratb.VerboseTB.format_record(self, frame_info)
1032 1148
1033 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 1150 + IPython.terminal.embed.InteractiveShellEmbed.mainloop(self, local_ns='None', module='None', stack_depth=0, compile_flags='None')
1035 1151
1036 1152 - IPython.terminal.embed.embed(**kwargs)
1037 1153 + IPython.terminal.embed.embed(*, header='', compile_flags='None', **kwargs)
1038 1154
1039 1155 - IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self, display_banner='<object object at 0xffffff>')
1040 1156 + IPython.terminal.interactiveshell.TerminalInteractiveShell.interact(self)
1041 1157
1042 1158 - IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self, display_banner='<object object at 0xffffff>')
1043 1159 + IPython.terminal.interactiveshell.TerminalInteractiveShell.mainloop(self)
1044 1160
1045 1161 - IPython.utils.path.get_py_filename(name, force_win32='None')
1046 1162 + IPython.utils.path.get_py_filename(name)
1047 1163
1048 1164 The following are new attributes (that might be inherited)::
1049 1165
1050 1166 + IPython.core.completer.IPCompleter.unicode_names
1051 1167 + IPython.core.debugger.InterruptiblePdb.precmd
1052 1168 + IPython.core.debugger.Pdb.precmd
1053 1169 + IPython.core.ultratb.AutoFormattedTB.has_colors
1054 1170 + IPython.core.ultratb.ColorTB.has_colors
1055 1171 + IPython.core.ultratb.FormattedTB.has_colors
1056 1172 + IPython.core.ultratb.ListTB.has_colors
1057 1173 + IPython.core.ultratb.SyntaxTB.has_colors
1058 1174 + IPython.core.ultratb.TBTools.has_colors
1059 1175 + IPython.core.ultratb.VerboseTB.has_colors
1060 1176 + IPython.terminal.debugger.TerminalPdb.do_interact
1061 1177 + IPython.terminal.debugger.TerminalPdb.precmd
1062 1178
1063 1179 The following attribute/methods have been removed::
1064 1180
1065 1181 - IPython.core.application.BaseIPythonApplication.deprecated_subcommands
1066 1182 - IPython.core.ultratb.AutoFormattedTB.format_records
1067 1183 - IPython.core.ultratb.ColorTB.format_records
1068 1184 - IPython.core.ultratb.FormattedTB.format_records
1069 1185 - IPython.terminal.embed.InteractiveShellEmbed.init_deprecation_warnings
1070 1186 - IPython.terminal.embed.InteractiveShellEmbed.init_readline
1071 1187 - IPython.terminal.embed.InteractiveShellEmbed.write
1072 1188 - IPython.terminal.embed.InteractiveShellEmbed.write_err
1073 1189 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_deprecation_warnings
1074 1190 - IPython.terminal.interactiveshell.TerminalInteractiveShell.init_readline
1075 1191 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write
1076 1192 - IPython.terminal.interactiveshell.TerminalInteractiveShell.write_err
1077 1193 - IPython.terminal.ipapp.LocateIPythonApp.deprecated_subcommands
1078 1194 - IPython.terminal.ipapp.LocateIPythonApp.initialize_subcommand
1079 1195 - IPython.terminal.ipapp.TerminalIPythonApp.deprecated_subcommands
1080 1196 - IPython.terminal.ipapp.TerminalIPythonApp.initialize_subcommand
@@ -1,116 +1,115 b''
1 1 [metadata]
2 2 name = ipython
3 3 version = attr: IPython.core.release.__version__
4 4 url = https://ipython.org
5 5 description = IPython: Productive Interactive Computing
6 6 long_description_content_type = text/x-rst
7 7 long_description = file: long_description.rst
8 8 license_file = LICENSE
9 9 project_urls =
10 10 Documentation = https://ipython.readthedocs.io/
11 11 Funding = https://numfocus.org/
12 12 Source = https://github.com/ipython/ipython
13 13 Tracker = https://github.com/ipython/ipython/issues
14 14 keywords = Interactive, Interpreter, Shell, Embedding
15 15 platforms = Linux, Mac OSX, Windows
16 16 classifiers =
17 17 Framework :: IPython
18 18 Framework :: Jupyter
19 19 Intended Audience :: Developers
20 20 Intended Audience :: Science/Research
21 21 License :: OSI Approved :: BSD License
22 22 Programming Language :: Python
23 23 Programming Language :: Python :: 3
24 24 Programming Language :: Python :: 3 :: Only
25 25 Topic :: System :: Shells
26 26
27 27 [options]
28 28 packages = find:
29 29 python_requires = >=3.8
30 30 zip_safe = False
31 31 install_requires =
32 32 appnope; sys_platform == "darwin"
33 33 backcall
34 34 colorama; sys_platform == "win32"
35 35 decorator
36 36 jedi>=0.16
37 37 matplotlib-inline
38 38 pexpect>4.3; sys_platform != "win32"
39 39 pickleshare
40 40 prompt_toolkit>3.0.1,<3.1.0
41 41 pygments>=2.4.0
42 setuptools>=18.5
43 42 stack_data
44 43 traitlets>=5
45 44
46 45 [options.extras_require]
47 46 black =
48 47 black
49 48 doc =
50 49 Sphinx>=1.3
51 50 kernel =
52 51 ipykernel
53 52 nbconvert =
54 53 nbconvert
55 54 nbformat =
56 55 nbformat
57 56 notebook =
58 57 ipywidgets
59 58 notebook
60 59 parallel =
61 60 ipyparallel
62 61 qtconsole =
63 62 qtconsole
64 63 terminal =
65 64 test =
66 65 pytest<7.1
67 66 pytest-asyncio
68 67 testpath
69 68 test_extra =
70 69 %(test)s
71 70 curio
72 71 matplotlib!=3.2.0
73 72 nbformat
74 73 numpy>=1.19
75 74 pandas
76 75 trio
77 76 typing_extensions
78 77 all =
79 78 %(black)s
80 79 %(doc)s
81 80 %(kernel)s
82 81 %(nbconvert)s
83 82 %(nbformat)s
84 83 %(notebook)s
85 84 %(parallel)s
86 85 %(qtconsole)s
87 86 %(terminal)s
88 87 %(test_extra)s
89 88 %(test)s
90 89
91 90 [options.packages.find]
92 91 exclude =
93 92 setupext
94 93
95 94 [options.package_data]
96 95 IPython.core = profile/README*
97 96 IPython.core.tests = *.png, *.jpg, daft_extension/*.py
98 97 IPython.lib.tests = *.wav
99 98 IPython.testing.plugin = *.txt
100 99
101 100 [options.entry_points]
102 101 console_scripts =
103 102 ipython = IPython:start_ipython
104 103 ipython3 = IPython:start_ipython
105 104 pygments.lexers =
106 105 ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer
107 106 ipython = IPython.lib.lexers:IPythonLexer
108 107 ipython3 = IPython.lib.lexers:IPython3Lexer
109 108
110 109 [velin]
111 110 ignore_patterns =
112 111 IPython/core/tests
113 112 IPython/testing
114 113
115 114 [tool.black]
116 115 exclude = 'timing\.py'
@@ -1,230 +1,230 b''
1 1 #!/usr/bin/env python
2 2 """Simple tools to query github.com and gather stats about issues.
3 3
4 4 To generate a report for IPython 2.0, run:
5 5
6 6 python github_stats.py --milestone 2.0 --since-tag rel-1.0.0
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Imports
10 10 #-----------------------------------------------------------------------------
11 11
12 12
13 13 import sys
14 14
15 15 from argparse import ArgumentParser
16 16 from datetime import datetime, timedelta
17 17 from subprocess import check_output
18 18
19 19 from gh_api import (
20 20 get_paged_request, make_auth_header, get_pull_request, is_pull_request,
21 21 get_milestone_id, get_issues_list, get_authors,
22 22 )
23 23 #-----------------------------------------------------------------------------
24 24 # Globals
25 25 #-----------------------------------------------------------------------------
26 26
27 27 ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
28 28 PER_PAGE = 100
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Functions
32 32 #-----------------------------------------------------------------------------
33 33
34 34 def round_hour(dt):
35 35 return dt.replace(minute=0,second=0,microsecond=0)
36 36
37 37 def _parse_datetime(s):
38 38 """Parse dates in the format returned by the Github API."""
39 39 if s:
40 40 return datetime.strptime(s, ISO8601)
41 41 else:
42 42 return datetime.fromtimestamp(0)
43 43
44 44 def issues2dict(issues):
45 45 """Convert a list of issues to a dict, keyed by issue number."""
46 46 idict = {}
47 47 for i in issues:
48 48 idict[i['number']] = i
49 49 return idict
50 50
51 51 def split_pulls(all_issues, project="ipython/ipython"):
52 52 """split a list of closed issues into non-PR Issues and Pull Requests"""
53 53 pulls = []
54 54 issues = []
55 55 for i in all_issues:
56 56 if is_pull_request(i):
57 57 pull = get_pull_request(project, i['number'], auth=True)
58 58 pulls.append(pull)
59 59 else:
60 60 issues.append(i)
61 61 return issues, pulls
62 62
63 63
64 64 def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", pulls=False):
65 65 """Get all issues closed since a particular point in time. period
66 66 can either be a datetime object, or a timedelta object. In the
67 67 latter case, it is used as a time before the present.
68 68 """
69 69
70 70 which = 'pulls' if pulls else 'issues'
71 71
72 72 if isinstance(period, timedelta):
73 73 since = round_hour(datetime.utcnow() - period)
74 74 else:
75 75 since = period
76 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 77 allclosed = get_paged_request(url, headers=make_auth_header())
78 78
79 79 filtered = [ i for i in allclosed if _parse_datetime(i['closed_at']) > since ]
80 80 if pulls:
81 81 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
82 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 84 else:
85 85 filtered = [ i for i in filtered if not is_pull_request(i) ]
86 86
87 87 return filtered
88 88
89 89
90 90 def sorted_by_field(issues, field='closed_at', reverse=False):
91 91 """Return a list of issues sorted by closing date date."""
92 92 return sorted(issues, key = lambda i:i[field], reverse=reverse)
93 93
94 94
95 95 def report(issues, show_urls=False):
96 96 """Summary report about a list of issues, printing number and title."""
97 97 if show_urls:
98 98 for i in issues:
99 99 role = 'ghpull' if 'merged_at' in i else 'ghissue'
100 100 print(u'* :%s:`%d`: %s' % (role, i['number'],
101 101 i['title'].replace(u'`', u'``')))
102 102 else:
103 103 for i in issues:
104 104 print(u'* %d: %s' % (i['number'], i['title'].replace(u'`', u'``')))
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Main script
108 108 #-----------------------------------------------------------------------------
109 109
110 110 if __name__ == "__main__":
111 111
112 112 print("DEPRECATE: backport_pr.py is deprecated and it is now recommended"
113 113 "to install `ghpro` from PyPI.", file=sys.stderr)
114 114
115 115
116 116 # Whether to add reST urls for all issues in printout.
117 117 show_urls = True
118 118
119 119 parser = ArgumentParser()
120 120 parser.add_argument('--since-tag', type=str,
121 121 help="The git tag to use for the starting point (typically the last major release)."
122 122 )
123 123 parser.add_argument('--milestone', type=str,
124 124 help="The GitHub milestone to use for filtering issues [optional]."
125 125 )
126 126 parser.add_argument('--days', type=int,
127 127 help="The number of days of data to summarize (use this or --since-tag)."
128 128 )
129 129 parser.add_argument('--project', type=str, default="ipython/ipython",
130 130 help="The project to summarize."
131 131 )
132 132 parser.add_argument('--links', action='store_true', default=False,
133 133 help="Include links to all closed Issues and PRs in the output."
134 134 )
135 135
136 136 opts = parser.parse_args()
137 137 tag = opts.since_tag
138 138
139 139 # set `since` from days or git tag
140 140 if opts.days:
141 141 since = datetime.utcnow() - timedelta(days=opts.days)
142 142 else:
143 143 if not tag:
144 144 tag = check_output(['git', 'describe', '--abbrev=0']).strip().decode('utf8')
145 145 cmd = ['git', 'log', '-1', '--format=%ai', tag]
146 146 tagday, tz = check_output(cmd).strip().decode('utf8').rsplit(' ', 1)
147 147 since = datetime.strptime(tagday, "%Y-%m-%d %H:%M:%S")
148 148 h = int(tz[1:3])
149 149 m = int(tz[3:])
150 150 td = timedelta(hours=h, minutes=m)
151 151 if tz[0] == '-':
152 152 since += td
153 153 else:
154 154 since -= td
155 155
156 156 since = round_hour(since)
157 157
158 158 milestone = opts.milestone
159 159 project = opts.project
160 160
161 161 print("fetching GitHub stats since %s (tag: %s, milestone: %s)" % (since, tag, milestone), file=sys.stderr)
162 162 if milestone:
163 163 milestone_id = get_milestone_id(project=project, milestone=milestone,
164 164 auth=True)
165 165 issues_and_pulls = get_issues_list(project=project,
166 166 milestone=milestone_id,
167 167 state='closed',
168 168 auth=True,
169 169 )
170 170 issues, pulls = split_pulls(issues_and_pulls, project=project)
171 171 else:
172 172 issues = issues_closed_since(since, project=project, pulls=False)
173 173 pulls = issues_closed_since(since, project=project, pulls=True)
174 174
175 175 # For regular reports, it's nice to show them in reverse chronological order
176 176 issues = sorted_by_field(issues, reverse=True)
177 177 pulls = sorted_by_field(pulls, reverse=True)
178 178
179 179 n_issues, n_pulls = map(len, (issues, pulls))
180 180 n_total = n_issues + n_pulls
181 181
182 182 # Print summary report we can directly include into release notes.
183 183
184 184 print()
185 185 since_day = since.strftime("%Y/%m/%d")
186 186 today = datetime.today().strftime("%Y/%m/%d")
187 187 print("GitHub stats for %s - %s (tag: %s)" % (since_day, today, tag))
188 188 print()
189 189 print("These lists are automatically generated, and may be incomplete or contain duplicates.")
190 190 print()
191 191
192 192 ncommits = 0
193 193 all_authors = []
194 194 if tag:
195 195 # print git info, in addition to GitHub info:
196 196 since_tag = tag+'..'
197 197 cmd = ['git', 'log', '--oneline', since_tag]
198 198 ncommits += len(check_output(cmd).splitlines())
199 199
200 200 author_cmd = ['git', 'log', '--use-mailmap', "--format=* %aN", since_tag]
201 201 all_authors.extend(check_output(author_cmd).decode('utf-8', 'replace').splitlines())
202 202
203 203 pr_authors = []
204 204 for pr in pulls:
205 205 pr_authors.extend(get_authors(pr))
206 206 ncommits = len(pr_authors) + ncommits - len(pulls)
207 207 author_cmd = ['git', 'check-mailmap'] + pr_authors
208 208 with_email = check_output(author_cmd).decode('utf-8', 'replace').splitlines()
209 209 all_authors.extend([ u'* ' + a.split(' <')[0] for a in with_email ])
210 210 unique_authors = sorted(set(all_authors), key=lambda s: s.lower())
211 211
212 212 print("We closed %d issues and merged %d pull requests." % (n_issues, n_pulls))
213 213 if milestone:
214 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 217 print()
218 218 print("The following %i authors contributed %i commits." % (len(unique_authors), ncommits))
219 219 print()
220 220 print('\n'.join(unique_authors))
221 221
222 222 if opts.links:
223 223 print()
224 224 print("GitHub issues and pull requests:")
225 225 print()
226 226 print('Pull Requests (%d):\n' % n_pulls)
227 227 report(pulls, show_urls)
228 228 print()
229 229 print('Issues (%d):\n' % n_issues)
230 230 report(issues, show_urls)
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now