Show More
@@ -7,11 +7,11 b' jobs:' | |||
|
7 | 7 | runs-on: ubuntu-latest |
|
8 | 8 | |
|
9 | 9 | steps: |
|
10 |
- uses: actions/checkout@v |
|
|
11 |
- name: Set up Python |
|
|
12 |
uses: actions/setup-python@v |
|
|
10 | - uses: actions/checkout@v3 | |
|
11 | - name: Set up Python | |
|
12 | uses: actions/setup-python@v4 | |
|
13 | 13 | with: |
|
14 |
python-version: 3. |
|
|
14 | python-version: 3.x | |
|
15 | 15 | - name: Install Graphviz |
|
16 | 16 | run: | |
|
17 | 17 | sudo apt-get update |
@@ -21,9 +21,9 b' jobs:' | |||
|
21 | 21 | python-version: "3.9" |
|
22 | 22 | |
|
23 | 23 | steps: |
|
24 |
- uses: actions/checkout@v |
|
|
24 | - uses: actions/checkout@v3 | |
|
25 | 25 | - name: Set up Python ${{ matrix.python-version }} |
|
26 |
uses: actions/setup-python@v |
|
|
26 | uses: actions/setup-python@v4 | |
|
27 | 27 | with: |
|
28 | 28 | python-version: ${{ matrix.python-version }} |
|
29 | 29 | - name: Update Python installer |
@@ -15,9 +15,9 b' jobs:' | |||
|
15 | 15 | python-version: [3.8] |
|
16 | 16 | |
|
17 | 17 | steps: |
|
18 |
- uses: actions/checkout@v |
|
|
18 | - uses: actions/checkout@v3 | |
|
19 | 19 | - name: Set up Python ${{ matrix.python-version }} |
|
20 |
uses: actions/setup-python@v |
|
|
20 | uses: actions/setup-python@v4 | |
|
21 | 21 | with: |
|
22 | 22 | python-version: ${{ matrix.python-version }} |
|
23 | 23 | - name: Install dependencies |
@@ -14,18 +14,14 b' jobs:' | |||
|
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@v |
|
|
18 | - uses: actions/checkout@v3 | |
|
23 | 19 | with: |
|
24 | 20 | fetch-depth: 0 |
|
25 |
- name: Set up Python |
|
|
26 |
uses: actions/setup-python@v |
|
|
21 | - name: Set up Python | |
|
22 | uses: actions/setup-python@v4 | |
|
27 | 23 | with: |
|
28 |
python-version: |
|
|
24 | python-version: 3.x | |
|
29 | 25 | - name: Install dependencies |
|
30 | 26 | run: | |
|
31 | 27 | python -m pip install --upgrade pip |
@@ -49,9 +49,9 b' jobs:' | |||
|
49 | 49 | deps: test |
|
50 | 50 | |
|
51 | 51 | steps: |
|
52 |
- uses: actions/checkout@v |
|
|
52 | - uses: actions/checkout@v3 | |
|
53 | 53 | - name: Set up Python ${{ matrix.python-version }} |
|
54 |
uses: actions/setup-python@v |
|
|
54 | uses: actions/setup-python@v4 | |
|
55 | 55 | with: |
|
56 | 56 | python-version: ${{ matrix.python-version }} |
|
57 | 57 | cache: pip |
@@ -906,19 +906,23 b' class Completer(Configurable):' | |||
|
906 | 906 | matches = [] |
|
907 | 907 | match_append = matches.append |
|
908 | 908 | n = len(text) |
|
909 |
for lst in [ |
|
|
910 | builtin_mod.__dict__.keys(), | |
|
911 | self.namespace.keys(), | |
|
912 |
|
|
|
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 |
|
|
|
921 |
|
|
|
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]) |
@@ -202,7 +202,6 b' class HistoryAccessor(HistoryAccessorBase):' | |||
|
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 |
@@ -344,11 +343,6 b' class HistoryAccessor(HistoryAccessorBase):' | |||
|
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 |
@@ -367,31 +361,12 b' class HistoryAccessor(HistoryAccessorBase):' | |||
|
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( |
|
|
394 |
return list( |
|
|
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, |
@@ -560,7 +535,6 b' class HistoryManager(HistoryAccessor):' | |||
|
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() |
@@ -656,6 +630,59 b' class HistoryManager(HistoryAccessor):' | |||
|
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.""" |
@@ -155,15 +155,17 b' def clipboard_get(self):' | |||
|
155 | 155 | """ Get text from the clipboard. |
|
156 | 156 | """ |
|
157 | 157 | from ..lib.clipboard import ( |
|
158 |
osx_clipboard_get, |
|
|
159 |
|
|
|
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) |
@@ -8,6 +8,7 b' builtin.' | |||
|
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 |
@@ -409,7 +410,7 b' class OSMagics(Magics):' | |||
|
409 | 410 | except OSError: |
|
410 | 411 | print(sys.exc_info()[1]) |
|
411 | 412 | else: |
|
412 |
cwd = |
|
|
413 | cwd = pathlib.Path.cwd() | |
|
413 | 414 | dhist = self.shell.user_ns['_dh'] |
|
414 | 415 | if oldcwd != cwd: |
|
415 | 416 | dhist.append(cwd) |
@@ -419,7 +420,7 b' class OSMagics(Magics):' | |||
|
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 = |
|
|
423 | cwd = pathlib.Path.cwd() | |
|
423 | 424 | dhist = self.shell.user_ns['_dh'] |
|
424 | 425 | |
|
425 | 426 | if oldcwd != cwd: |
@@ -16,7 +16,7 b'' | |||
|
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 = |
|
|
19 | _version_minor = 6 | |
|
20 | 20 | _version_patch = 0 |
|
21 | 21 | _version_extra = ".dev" |
|
22 | 22 | # _version_extra = "rc1" |
@@ -8,6 +8,7 b" with better-isolated tests that don't rely on the global instance in iptest." | |||
|
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): |
@@ -28,7 +28,7 b' def test_output_quiet():' | |||
|
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 | |
@@ -41,7 +41,7 b' def test_underscore_no_overrite_user():' | |||
|
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 |
@@ -17,7 +17,7 b' 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(): |
@@ -227,3 +227,81 b' def test_histmanager_disabled():' | |||
|
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() |
@@ -32,6 +32,7 b' tests.append(("P\xc3\xa9rez Fernando", ("", "", "P\xc3\xa9rez", "Fernando")))' | |||
|
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") |
@@ -300,7 +300,7 b' def update_instances(old, new):' | |||
|
300 | 300 | |
|
301 | 301 | for ref in refs: |
|
302 | 302 | if type(ref) is old: |
|
303 |
ref |
|
|
303 | object.__setattr__(ref, "__class__", new) | |
|
304 | 304 | |
|
305 | 305 | |
|
306 | 306 | def update_class(old, new): |
@@ -1,14 +1,16 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 | |
@@ -32,6 +34,7 b' def win32_clipboard_get():' | |||
|
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 | """ |
@@ -43,6 +46,7 b' def osx_clipboard_get() -> str:' | |||
|
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 | |
@@ -67,3 +71,31 b' def tkinter_clipboard_get():' | |||
|
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 |
@@ -144,19 +144,30 b" def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):" | |||
|
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 = |
|
|
150 |
dvifile = |
|
|
151 |
outfile = |
|
|
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, |
|
|
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( |
@@ -179,9 +190,10 b" def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):" | |||
|
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 |
@@ -908,6 +908,8 b' 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 |
@@ -2,6 +2,7 b' 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 |
@@ -91,7 +91,12 b' def get_default_editor():' | |||
|
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: |
@@ -48,10 +48,17 b' def _elide_point(string:str, *, min_elide=30)->str:' | |||
|
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 ( |
|
|
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 |
@@ -140,6 +140,18 b' def create_ipython_shortcuts(shell):' | |||
|
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): |
@@ -160,7 +172,7 b' def create_ipython_shortcuts(shell):' | |||
|
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): |
@@ -171,13 +183,35 b' def create_ipython_shortcuts(shell):' | |||
|
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)[\"'](-*)$") |
@@ -7,6 +7,7 b' import warnings' | |||
|
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 |
@@ -1,4 +1,3 b'' | |||
|
1 | ||
|
2 | 1 |
|
|
3 | 2 | |
|
4 | 3 | warn("IPython.utils.eventful has moved to traitlets.eventful", stacklevel=2) |
@@ -1,4 +1,3 b'' | |||
|
1 | ||
|
2 | 1 |
|
|
3 | 2 | |
|
4 | 3 | warn("IPython.utils.log has moved to traitlets.log", stacklevel=2) |
@@ -83,12 +83,13 b' def get_py_filename(name):' | |||
|
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: |
@@ -48,6 +48,7 b' class TemporaryWorkingDirectory(TemporaryDirectory):' | |||
|
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) |
@@ -25,6 +25,7 b' Need to be updated:' | |||
|
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 |
@@ -2,6 +2,122 b'' | |||
|
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 |
@@ -39,7 +39,6 b' install_requires =' | |||
|
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 |
@@ -80,7 +80,7 b' def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", p' | |||
|
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 = [ |
|
|
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 |
|
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