##// END OF EJS Templates
Merge branch 'main' into completion-matcher
Michał Krassowski -
r27777:b0daec19 merge
parent child Browse files
Show More
@@ -7,11 +7,11 b' jobs:'
7 runs-on: ubuntu-latest
7 runs-on: ubuntu-latest
8
8
9 steps:
9 steps:
10 - uses: actions/checkout@v2
10 - uses: actions/checkout@v3
11 - name: Set up Python 3.8
11 - name: Set up Python
12 uses: actions/setup-python@v2
12 uses: actions/setup-python@v4
13 with:
13 with:
14 python-version: 3.8
14 python-version: 3.x
15 - name: Install Graphviz
15 - name: Install Graphviz
16 run: |
16 run: |
17 sudo apt-get update
17 sudo apt-get update
@@ -21,9 +21,9 b' jobs:'
21 python-version: "3.9"
21 python-version: "3.9"
22
22
23 steps:
23 steps:
24 - uses: actions/checkout@v2
24 - uses: actions/checkout@v3
25 - name: Set up Python ${{ matrix.python-version }}
25 - name: Set up Python ${{ matrix.python-version }}
26 uses: actions/setup-python@v2
26 uses: actions/setup-python@v4
27 with:
27 with:
28 python-version: ${{ matrix.python-version }}
28 python-version: ${{ matrix.python-version }}
29 - name: Update Python installer
29 - name: Update Python installer
@@ -15,9 +15,9 b' jobs:'
15 python-version: [3.8]
15 python-version: [3.8]
16
16
17 steps:
17 steps:
18 - uses: actions/checkout@v2
18 - uses: actions/checkout@v3
19 - name: Set up Python ${{ matrix.python-version }}
19 - name: Set up Python ${{ matrix.python-version }}
20 uses: actions/setup-python@v2
20 uses: actions/setup-python@v4
21 with:
21 with:
22 python-version: ${{ matrix.python-version }}
22 python-version: ${{ matrix.python-version }}
23 - name: Install dependencies
23 - name: Install dependencies
@@ -14,18 +14,14 b' jobs:'
14
14
15 runs-on: ubuntu-latest
15 runs-on: ubuntu-latest
16 timeout-minutes: 5
16 timeout-minutes: 5
17 strategy:
18 matrix:
19 python-version: [3.8]
20
21 steps:
17 steps:
22 - uses: actions/checkout@v2
18 - uses: actions/checkout@v3
23 with:
19 with:
24 fetch-depth: 0
20 fetch-depth: 0
25 - name: Set up Python ${{ matrix.python-version }}
21 - name: Set up Python
26 uses: actions/setup-python@v2
22 uses: actions/setup-python@v4
27 with:
23 with:
28 python-version: ${{ matrix.python-version }}
24 python-version: 3.x
29 - name: Install dependencies
25 - name: Install dependencies
30 run: |
26 run: |
31 python -m pip install --upgrade pip
27 python -m pip install --upgrade pip
@@ -49,9 +49,9 b' jobs:'
49 deps: test
49 deps: test
50
50
51 steps:
51 steps:
52 - uses: actions/checkout@v2
52 - uses: actions/checkout@v3
53 - name: Set up Python ${{ matrix.python-version }}
53 - name: Set up Python ${{ matrix.python-version }}
54 uses: actions/setup-python@v2
54 uses: actions/setup-python@v4
55 with:
55 with:
56 python-version: ${{ matrix.python-version }}
56 python-version: ${{ matrix.python-version }}
57 cache: pip
57 cache: pip
@@ -906,19 +906,23 b' class Completer(Configurable):'
906 matches = []
906 matches = []
907 match_append = matches.append
907 match_append = matches.append
908 n = len(text)
908 n = len(text)
909 for lst in [keyword.kwlist,
909 for lst in [
910 keyword.kwlist,
910 builtin_mod.__dict__.keys(),
911 builtin_mod.__dict__.keys(),
911 self.namespace.keys(),
912 list(self.namespace.keys()),
912 self.global_namespace.keys()]:
913 list(self.global_namespace.keys()),
914 ]:
913 for word in lst:
915 for word in lst:
914 if word[:n] == text and word != "__builtins__":
916 if word[:n] == text and word != "__builtins__":
915 match_append(word)
917 match_append(word)
916
918
917 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
919 snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z")
918 for lst in [self.namespace.keys(),
920 for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]:
919 self.global_namespace.keys()]:
921 shortened = {
920 shortened = {"_".join([sub[0] for sub in word.split('_')]) : word
922 "_".join([sub[0] for sub in word.split("_")]): word
921 for word in lst if snake_case_re.match(word)}
923 for word in lst
924 if snake_case_re.match(word)
925 }
922 for word in shortened.keys():
926 for word in shortened.keys():
923 if word[:n] == text and word != "__builtins__":
927 if word[:n] == text and word != "__builtins__":
924 match_append(shortened[word])
928 match_append(shortened[word])
@@ -202,7 +202,6 b' class HistoryAccessor(HistoryAccessorBase):'
202 config : :class:`~traitlets.config.loader.Config`
202 config : :class:`~traitlets.config.loader.Config`
203 Config object. hist_file can also be set through this.
203 Config object. hist_file can also be set through this.
204 """
204 """
205 # We need a pointer back to the shell for various tasks.
206 super(HistoryAccessor, self).__init__(**traits)
205 super(HistoryAccessor, self).__init__(**traits)
207 # defer setting hist_file from kwarg until after init,
206 # defer setting hist_file from kwarg until after init,
208 # otherwise the default kwarg value would clobber any value
207 # otherwise the default kwarg value would clobber any value
@@ -344,11 +343,6 b' class HistoryAccessor(HistoryAccessorBase):'
344 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
343 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
345 """Get the last n lines from the history database.
344 """Get the last n lines from the history database.
346
345
347 Most recent entry last.
348
349 Completion will be reordered so that that the last ones are when
350 possible from current session.
351
352 Parameters
346 Parameters
353 ----------
347 ----------
354 n : int
348 n : int
@@ -367,31 +361,12 b' class HistoryAccessor(HistoryAccessorBase):'
367 self.writeout_cache()
361 self.writeout_cache()
368 if not include_latest:
362 if not include_latest:
369 n += 1
363 n += 1
370 # cursor/line/entry
364 cur = self._run_sql(
371 this_cur = list(
365 "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
372 self._run_sql(
373 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
374 (self.session_number, n),
375 raw=raw,
376 output=output,
377 )
378 )
379 other_cur = list(
380 self._run_sql(
381 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
382 (self.session_number, n),
383 raw=raw,
384 output=output,
385 )
386 )
366 )
387
388 everything = this_cur + other_cur
389
390 everything = everything[:n]
391
392 if not include_latest:
367 if not include_latest:
393 return list(everything)[:0:-1]
368 return reversed(list(cur)[1:])
394 return list(everything)[::-1]
369 return reversed(list(cur))
395
370
396 @catch_corrupt_db
371 @catch_corrupt_db
397 def search(self, pattern="*", raw=True, search_raw=True,
372 def search(self, pattern="*", raw=True, search_raw=True,
@@ -560,7 +535,6 b' class HistoryManager(HistoryAccessor):'
560 def __init__(self, shell=None, config=None, **traits):
535 def __init__(self, shell=None, config=None, **traits):
561 """Create a new history manager associated with a shell instance.
536 """Create a new history manager associated with a shell instance.
562 """
537 """
563 # We need a pointer back to the shell for various tasks.
564 super(HistoryManager, self).__init__(shell=shell, config=config,
538 super(HistoryManager, self).__init__(shell=shell, config=config,
565 **traits)
539 **traits)
566 self.save_flag = threading.Event()
540 self.save_flag = threading.Event()
@@ -656,6 +630,59 b' class HistoryManager(HistoryAccessor):'
656
630
657 return super(HistoryManager, self).get_session_info(session=session)
631 return super(HistoryManager, self).get_session_info(session=session)
658
632
633 @catch_corrupt_db
634 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
635 """Get the last n lines from the history database.
636
637 Most recent entry last.
638
639 Completion will be reordered so that that the last ones are when
640 possible from current session.
641
642 Parameters
643 ----------
644 n : int
645 The number of lines to get
646 raw, output : bool
647 See :meth:`get_range`
648 include_latest : bool
649 If False (default), n+1 lines are fetched, and the latest one
650 is discarded. This is intended to be used where the function
651 is called by a user command, which it should not return.
652
653 Returns
654 -------
655 Tuples as :meth:`get_range`
656 """
657 self.writeout_cache()
658 if not include_latest:
659 n += 1
660 # cursor/line/entry
661 this_cur = list(
662 self._run_sql(
663 "WHERE session == ? ORDER BY line DESC LIMIT ? ",
664 (self.session_number, n),
665 raw=raw,
666 output=output,
667 )
668 )
669 other_cur = list(
670 self._run_sql(
671 "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
672 (self.session_number, n),
673 raw=raw,
674 output=output,
675 )
676 )
677
678 everything = this_cur + other_cur
679
680 everything = everything[:n]
681
682 if not include_latest:
683 return list(everything)[:0:-1]
684 return list(everything)[::-1]
685
659 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
686 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
660 """Get input and output history from the current session. Called by
687 """Get input and output history from the current session. Called by
661 get_range, and takes similar parameters."""
688 get_range, and takes similar parameters."""
@@ -155,15 +155,17 b' def clipboard_get(self):'
155 """ Get text from the clipboard.
155 """ Get text from the clipboard.
156 """
156 """
157 from ..lib.clipboard import (
157 from ..lib.clipboard import (
158 osx_clipboard_get, tkinter_clipboard_get,
158 osx_clipboard_get,
159 win32_clipboard_get
159 tkinter_clipboard_get,
160 win32_clipboard_get,
161 wayland_clipboard_get,
160 )
162 )
161 if sys.platform == 'win32':
163 if sys.platform == 'win32':
162 chain = [win32_clipboard_get, tkinter_clipboard_get]
164 chain = [win32_clipboard_get, tkinter_clipboard_get]
163 elif sys.platform == 'darwin':
165 elif sys.platform == 'darwin':
164 chain = [osx_clipboard_get, tkinter_clipboard_get]
166 chain = [osx_clipboard_get, tkinter_clipboard_get]
165 else:
167 else:
166 chain = [tkinter_clipboard_get]
168 chain = [wayland_clipboard_get, tkinter_clipboard_get]
167 dispatcher = CommandChainDispatcher()
169 dispatcher = CommandChainDispatcher()
168 for func in chain:
170 for func in chain:
169 dispatcher.add(func)
171 dispatcher.add(func)
@@ -8,6 +8,7 b' builtin.'
8
8
9 import io
9 import io
10 import os
10 import os
11 import pathlib
11 import re
12 import re
12 import sys
13 import sys
13 from pprint import pformat
14 from pprint import pformat
@@ -409,7 +410,7 b' class OSMagics(Magics):'
409 except OSError:
410 except OSError:
410 print(sys.exc_info()[1])
411 print(sys.exc_info()[1])
411 else:
412 else:
412 cwd = os.getcwd()
413 cwd = pathlib.Path.cwd()
413 dhist = self.shell.user_ns['_dh']
414 dhist = self.shell.user_ns['_dh']
414 if oldcwd != cwd:
415 if oldcwd != cwd:
415 dhist.append(cwd)
416 dhist.append(cwd)
@@ -419,7 +420,7 b' class OSMagics(Magics):'
419 os.chdir(self.shell.home_dir)
420 os.chdir(self.shell.home_dir)
420 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 if hasattr(self.shell, 'term_title') and self.shell.term_title:
421 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 set_term_title(self.shell.term_title_format.format(cwd="~"))
422 cwd = os.getcwd()
423 cwd = pathlib.Path.cwd()
423 dhist = self.shell.user_ns['_dh']
424 dhist = self.shell.user_ns['_dh']
424
425
425 if oldcwd != cwd:
426 if oldcwd != cwd:
@@ -16,7 +16,7 b''
16 # release. 'dev' as a _version_extra string means this is a development
16 # release. 'dev' as a _version_extra string means this is a development
17 # version
17 # version
18 _version_major = 8
18 _version_major = 8
19 _version_minor = 5
19 _version_minor = 6
20 _version_patch = 0
20 _version_patch = 0
21 _version_extra = ".dev"
21 _version_extra = ".dev"
22 # _version_extra = "rc1"
22 # _version_extra = "rc1"
@@ -1,2 +1,3 b''
1 import sys
1 import sys
2
2 print(sys.argv[1:])
3 print(sys.argv[1:])
@@ -8,6 +8,7 b" with better-isolated tests that don't rely on the global instance in iptest."
8 from IPython.core.splitinput import LineInfo
8 from IPython.core.splitinput import LineInfo
9 from IPython.core.prefilter import AutocallChecker
9 from IPython.core.prefilter import AutocallChecker
10
10
11
11 def doctest_autocall():
12 def doctest_autocall():
12 """
13 """
13 In [1]: def f1(a,b,c):
14 In [1]: def f1(a,b,c):
@@ -28,7 +28,7 b' def test_output_quiet():'
28 with AssertNotPrints('2'):
28 with AssertNotPrints('2'):
29 ip.run_cell('1+1;\n#commented_out_function()', store_history=True)
29 ip.run_cell('1+1;\n#commented_out_function()', store_history=True)
30
30
31 def test_underscore_no_overrite_user():
31 def test_underscore_no_overwrite_user():
32 ip.run_cell('_ = 42', store_history=True)
32 ip.run_cell('_ = 42', store_history=True)
33 ip.run_cell('1+1', store_history=True)
33 ip.run_cell('1+1', store_history=True)
34
34
@@ -41,7 +41,7 b' def test_underscore_no_overrite_user():'
41 ip.run_cell('_', store_history=True)
41 ip.run_cell('_', store_history=True)
42
42
43
43
44 def test_underscore_no_overrite_builtins():
44 def test_underscore_no_overwrite_builtins():
45 ip.run_cell("import gettext ; gettext.install('foo')", store_history=True)
45 ip.run_cell("import gettext ; gettext.install('foo')", store_history=True)
46 ip.run_cell('3+3', store_history=True)
46 ip.run_cell('3+3', store_history=True)
47
47
@@ -17,7 +17,7 b' from tempfile import TemporaryDirectory'
17 # our own packages
17 # our own packages
18 from traitlets.config.loader import Config
18 from traitlets.config.loader import Config
19
19
20 from IPython.core.history import HistoryManager, extract_hist_ranges
20 from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges
21
21
22
22
23 def test_proper_default_encoding():
23 def test_proper_default_encoding():
@@ -227,3 +227,81 b' def test_histmanager_disabled():'
227
227
228 # hist_file should not be created
228 # hist_file should not be created
229 assert hist_file.exists() is False
229 assert hist_file.exists() is False
230
231
232 def test_get_tail_session_awareness():
233 """Test .get_tail() is:
234 - session specific in HistoryManager
235 - session agnostic in HistoryAccessor
236 same for .get_last_session_id()
237 """
238 ip = get_ipython()
239 with TemporaryDirectory() as tmpdir:
240 tmp_path = Path(tmpdir)
241 hist_file = tmp_path / "history.sqlite"
242 get_source = lambda x: x[2]
243 hm1 = None
244 hm2 = None
245 ha = None
246 try:
247 # hm1 creates a new session and adds history entries,
248 # ha catches up
249 hm1 = HistoryManager(shell=ip, hist_file=hist_file)
250 hm1_last_sid = hm1.get_last_session_id
251 ha = HistoryAccessor(hist_file=hist_file)
252 ha_last_sid = ha.get_last_session_id
253
254 hist1 = ["a=1", "b=1", "c=1"]
255 for i, h in enumerate(hist1 + [""], start=1):
256 hm1.store_inputs(i, h)
257 assert list(map(get_source, hm1.get_tail())) == hist1
258 assert list(map(get_source, ha.get_tail())) == hist1
259 sid1 = hm1_last_sid()
260 assert sid1 is not None
261 assert ha_last_sid() == sid1
262
263 # hm2 creates a new session and adds entries,
264 # ha catches up
265 hm2 = HistoryManager(shell=ip, hist_file=hist_file)
266 hm2_last_sid = hm2.get_last_session_id
267
268 hist2 = ["a=2", "b=2", "c=2"]
269 for i, h in enumerate(hist2 + [""], start=1):
270 hm2.store_inputs(i, h)
271 tail = hm2.get_tail(n=3)
272 assert list(map(get_source, tail)) == hist2
273 tail = ha.get_tail(n=3)
274 assert list(map(get_source, tail)) == hist2
275 sid2 = hm2_last_sid()
276 assert sid2 is not None
277 assert ha_last_sid() == sid2
278 assert sid2 != sid1
279
280 # but hm1 still maintains its point of reference
281 # and adding more entries to it doesn't change others
282 # immediate perspective
283 assert hm1_last_sid() == sid1
284 tail = hm1.get_tail(n=3)
285 assert list(map(get_source, tail)) == hist1
286
287 hist3 = ["a=3", "b=3", "c=3"]
288 for i, h in enumerate(hist3 + [""], start=5):
289 hm1.store_inputs(i, h)
290 tail = hm1.get_tail(n=7)
291 assert list(map(get_source, tail)) == hist1 + [""] + hist3
292 tail = hm2.get_tail(n=3)
293 assert list(map(get_source, tail)) == hist2
294 tail = ha.get_tail(n=3)
295 assert list(map(get_source, tail)) == hist2
296 assert hm1_last_sid() == sid1
297 assert hm2_last_sid() == sid2
298 assert ha_last_sid() == sid2
299 finally:
300 if hm1:
301 hm1.save_thread.stop()
302 hm1.db.close()
303 if hm2:
304 hm2.save_thread.stop()
305 hm2.db.close()
306 if ha:
307 ha.db.close()
@@ -32,6 +32,7 b' tests.append(("P\xc3\xa9rez Fernando", ("", "", "P\xc3\xa9rez", "Fernando")))'
32 def test_split_user_input():
32 def test_split_user_input():
33 return tt.check_pairs(split_user_input, tests)
33 return tt.check_pairs(split_user_input, tests)
34
34
35
35 def test_LineInfo():
36 def test_LineInfo():
36 """Simple test for LineInfo construction and str()"""
37 """Simple test for LineInfo construction and str()"""
37 linfo = LineInfo(" %cd /home")
38 linfo = LineInfo(" %cd /home")
@@ -300,7 +300,7 b' def update_instances(old, new):'
300
300
301 for ref in refs:
301 for ref in refs:
302 if type(ref) is old:
302 if type(ref) is old:
303 ref.__class__ = new
303 object.__setattr__(ref, "__class__", new)
304
304
305
305
306 def update_class(old, new):
306 def update_class(old, new):
@@ -1,14 +1,16 b''
1 """ Utilities for accessing the platform's clipboard.
1 """ Utilities for accessing the platform's clipboard.
2 """
2 """
3
3 import os
4 import subprocess
4 import subprocess
5
5
6 from IPython.core.error import TryNext
6 from IPython.core.error import TryNext
7 import IPython.utils.py3compat as py3compat
7 import IPython.utils.py3compat as py3compat
8
8
9
9 class ClipboardEmpty(ValueError):
10 class ClipboardEmpty(ValueError):
10 pass
11 pass
11
12
13
12 def win32_clipboard_get():
14 def win32_clipboard_get():
13 """ Get the current clipboard's text on Windows.
15 """ Get the current clipboard's text on Windows.
14
16
@@ -32,6 +34,7 b' def win32_clipboard_get():'
32 win32clipboard.CloseClipboard()
34 win32clipboard.CloseClipboard()
33 return text
35 return text
34
36
37
35 def osx_clipboard_get() -> str:
38 def osx_clipboard_get() -> str:
36 """ Get the clipboard's text on OS X.
39 """ Get the clipboard's text on OS X.
37 """
40 """
@@ -43,6 +46,7 b' def osx_clipboard_get() -> str:'
43 text = py3compat.decode(bytes_)
46 text = py3compat.decode(bytes_)
44 return text
47 return text
45
48
49
46 def tkinter_clipboard_get():
50 def tkinter_clipboard_get():
47 """ Get the clipboard's text using Tkinter.
51 """ Get the clipboard's text using Tkinter.
48
52
@@ -67,3 +71,31 b' def tkinter_clipboard_get():'
67 return text
71 return text
68
72
69
73
74 def wayland_clipboard_get():
75 """Get the clipboard's text under Wayland using wl-paste command.
76
77 This requires Wayland and wl-clipboard installed and running.
78 """
79 if os.environ.get("XDG_SESSION_TYPE") != "wayland":
80 raise TryNext("wayland is not detected")
81
82 try:
83 with subprocess.Popen(["wl-paste"], stdout=subprocess.PIPE) as p:
84 raw, err = p.communicate()
85 if p.wait():
86 raise TryNext(err)
87 except FileNotFoundError as e:
88 raise TryNext(
89 "Getting text from the clipboard under Wayland requires the wl-clipboard "
90 "extension: https://github.com/bugaevc/wl-clipboard"
91 ) from e
92
93 if not raw:
94 raise ClipboardEmpty
95
96 try:
97 text = py3compat.decode(raw)
98 except UnicodeDecodeError as e:
99 raise ClipboardEmpty from e
100
101 return text
@@ -144,19 +144,30 b" def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):"
144 find_cmd('dvipng')
144 find_cmd('dvipng')
145 except FindCmdError:
145 except FindCmdError:
146 return None
146 return None
147
148 startupinfo = None
149 if os.name == "nt":
150 # prevent popup-windows
151 startupinfo = subprocess.STARTUPINFO()
152 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
153
147 try:
154 try:
148 workdir = Path(tempfile.mkdtemp())
155 workdir = Path(tempfile.mkdtemp())
149 tmpfile = workdir.joinpath("tmp.tex")
156 tmpfile = "tmp.tex"
150 dvifile = workdir.joinpath("tmp.dvi")
157 dvifile = "tmp.dvi"
151 outfile = workdir.joinpath("tmp.png")
158 outfile = "tmp.png"
152
159
153 with tmpfile.open("w", encoding="utf8") as f:
160 with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f:
154 f.writelines(genelatex(s, wrap))
161 f.writelines(genelatex(s, wrap))
155
162
156 with open(os.devnull, 'wb') as devnull:
163 with open(os.devnull, 'wb') as devnull:
157 subprocess.check_call(
164 subprocess.check_call(
158 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
165 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
159 cwd=workdir, stdout=devnull, stderr=devnull)
166 cwd=workdir,
167 stdout=devnull,
168 stderr=devnull,
169 startupinfo=startupinfo,
170 )
160
171
161 resolution = round(150*scale)
172 resolution = round(150*scale)
162 subprocess.check_call(
173 subprocess.check_call(
@@ -179,9 +190,10 b" def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0):"
179 cwd=workdir,
190 cwd=workdir,
180 stdout=devnull,
191 stdout=devnull,
181 stderr=devnull,
192 stderr=devnull,
193 startupinfo=startupinfo,
182 )
194 )
183
195
184 with outfile.open("rb") as f:
196 with workdir.joinpath(outfile).open("rb") as f:
185 return f.read()
197 return f.read()
186 except subprocess.CalledProcessError:
198 except subprocess.CalledProcessError:
187 return None
199 return None
@@ -908,6 +908,8 b' def _deque_pprint(obj, p, cycle):'
908 cls_ctor = CallExpression.factory(obj.__class__.__name__)
908 cls_ctor = CallExpression.factory(obj.__class__.__name__)
909 if cycle:
909 if cycle:
910 p.pretty(cls_ctor(RawText("...")))
910 p.pretty(cls_ctor(RawText("...")))
911 elif obj.maxlen is not None:
912 p.pretty(cls_ctor(list(obj), maxlen=obj.maxlen))
911 else:
913 else:
912 p.pretty(cls_ctor(list(obj)))
914 p.pretty(cls_ctor(list(obj)))
913
915
@@ -2,6 +2,7 b' from IPython.core.error import TryNext'
2 from IPython.lib.clipboard import ClipboardEmpty
2 from IPython.lib.clipboard import ClipboardEmpty
3 from IPython.testing.decorators import skip_if_no_x11
3 from IPython.testing.decorators import skip_if_no_x11
4
4
5
5 @skip_if_no_x11
6 @skip_if_no_x11
6 def test_clipboard_get():
7 def test_clipboard_get():
7 # Smoketest for clipboard access - we can't easily guarantee that the
8 # Smoketest for clipboard access - we can't easily guarantee that the
@@ -91,7 +91,12 b' def get_default_editor():'
91 # - no isatty method
91 # - no isatty method
92 for _name in ('stdin', 'stdout', 'stderr'):
92 for _name in ('stdin', 'stdout', 'stderr'):
93 _stream = getattr(sys, _name)
93 _stream = getattr(sys, _name)
94 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
94 try:
95 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
96 _is_tty = False
97 break
98 except ValueError:
99 # stream is closed
95 _is_tty = False
100 _is_tty = False
96 break
101 break
97 else:
102 else:
@@ -48,10 +48,17 b' def _elide_point(string:str, *, min_elide=30)->str:'
48 file_parts.pop()
48 file_parts.pop()
49
49
50 if len(object_parts) > 3:
50 if len(object_parts) > 3:
51 return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1])
51 return "{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}".format(
52 object_parts[0],
53 object_parts[1][:1],
54 object_parts[-2][-1:],
55 object_parts[-1],
56 )
52
57
53 elif len(file_parts) > 3:
58 elif len(file_parts) > 3:
54 return ('{}' + os.sep + '{}\N{HORIZONTAL ELLIPSIS}{}' + os.sep + '{}').format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1])
59 return ("{}" + os.sep + "{}\N{HORIZONTAL ELLIPSIS}{}" + os.sep + "{}").format(
60 file_parts[0], file_parts[1][:1], file_parts[-2][-1:], file_parts[-1]
61 )
55
62
56 return string
63 return string
57
64
@@ -140,6 +140,18 b' def create_ipython_shortcuts(shell):'
140 _following_text_cache[pattern] = condition
140 _following_text_cache[pattern] = condition
141 return condition
141 return condition
142
142
143 @Condition
144 def not_inside_unclosed_string():
145 app = get_app()
146 s = app.current_buffer.document.text_before_cursor
147 # remove escaped quotes
148 s = s.replace('\\"', "").replace("\\'", "")
149 # remove triple-quoted string literals
150 s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s)
151 # remove single-quoted string literals
152 s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s)
153 return not ('"' in s or "'" in s)
154
143 # auto match
155 # auto match
144 @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
156 @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$"))
145 def _(event):
157 def _(event):
@@ -160,7 +172,7 b' def create_ipython_shortcuts(shell):'
160 '"',
172 '"',
161 filter=focused_insert
173 filter=focused_insert
162 & auto_match
174 & auto_match
163 & preceding_text(r'^([^"]+|"[^"]*")*$')
175 & not_inside_unclosed_string
164 & following_text(r"[,)}\]]|$"),
176 & following_text(r"[,)}\]]|$"),
165 )
177 )
166 def _(event):
178 def _(event):
@@ -171,13 +183,35 b' def create_ipython_shortcuts(shell):'
171 "'",
183 "'",
172 filter=focused_insert
184 filter=focused_insert
173 & auto_match
185 & auto_match
174 & preceding_text(r"^([^']+|'[^']*')*$")
186 & not_inside_unclosed_string
175 & following_text(r"[,)}\]]|$"),
187 & following_text(r"[,)}\]]|$"),
176 )
188 )
177 def _(event):
189 def _(event):
178 event.current_buffer.insert_text("''")
190 event.current_buffer.insert_text("''")
179 event.current_buffer.cursor_left()
191 event.current_buffer.cursor_left()
180
192
193 @kb.add(
194 '"',
195 filter=focused_insert
196 & auto_match
197 & not_inside_unclosed_string
198 & preceding_text(r'^.*""$'),
199 )
200 def _(event):
201 event.current_buffer.insert_text('""""')
202 event.current_buffer.cursor_left(3)
203
204 @kb.add(
205 "'",
206 filter=focused_insert
207 & auto_match
208 & not_inside_unclosed_string
209 & preceding_text(r"^.*''$"),
210 )
211 def _(event):
212 event.current_buffer.insert_text("''''")
213 event.current_buffer.cursor_left(3)
214
181 # raw string
215 # raw string
182 @kb.add(
216 @kb.add(
183 "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
217 "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$")
@@ -7,6 +7,7 b' import warnings'
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10
10 class preserve_keys(object):
11 class preserve_keys(object):
11 """Preserve a set of keys in a dictionary.
12 """Preserve a set of keys in a dictionary.
12
13
@@ -1,4 +1,3 b''
1
2 from warnings import warn
1 from warnings import warn
3
2
4 warn("IPython.utils.eventful has moved to traitlets.eventful", stacklevel=2)
3 warn("IPython.utils.eventful has moved to traitlets.eventful", stacklevel=2)
@@ -1,4 +1,3 b''
1
2 from warnings import warn
1 from warnings import warn
3
2
4 warn("IPython.utils.log has moved to traitlets.log", stacklevel=2)
3 warn("IPython.utils.log has moved to traitlets.log", stacklevel=2)
@@ -83,12 +83,13 b' def get_py_filename(name):'
83 """
83 """
84
84
85 name = os.path.expanduser(name)
85 name = os.path.expanduser(name)
86 if not os.path.isfile(name) and not name.endswith('.py'):
87 name += '.py'
88 if os.path.isfile(name):
86 if os.path.isfile(name):
89 return name
87 return name
90 else:
88 if not name.endswith(".py"):
91 raise IOError('File `%r` not found.' % name)
89 py_name = name + ".py"
90 if os.path.isfile(py_name):
91 return py_name
92 raise IOError("File `%r` not found." % name)
92
93
93
94
94 def filefind(filename: str, path_dirs=None) -> str:
95 def filefind(filename: str, path_dirs=None) -> str:
@@ -48,6 +48,7 b' class TemporaryWorkingDirectory(TemporaryDirectory):'
48 with TemporaryWorkingDirectory() as tmpdir:
48 with TemporaryWorkingDirectory() as tmpdir:
49 ...
49 ...
50 """
50 """
51
51 def __enter__(self):
52 def __enter__(self):
52 self.old_wd = Path.cwd()
53 self.old_wd = Path.cwd()
53 _os.chdir(self.name)
54 _os.chdir(self.name)
@@ -25,6 +25,7 b' Need to be updated:'
25
25
26
26
27
27
28
28 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
29 .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT.
29
30
30 Backwards incompatible changes
31 Backwards incompatible changes
@@ -2,6 +2,122 b''
2 8.x Series
2 8.x Series
3 ============
3 ============
4
4
5 .. _version 8.5.0:
6
7 IPython 8.5.0
8 -------------
9
10 First release since a couple of month due to various reasons and timing preventing
11 me for sticking to the usual monthly release the last Friday of each month. This
12 is of non negligible size as it has more than two dozen PRs with various fixes
13 an bug fixes.
14
15 Many thanks to everybody who contributed PRs for your patience in review and
16 merges.
17
18 Here is a non exhaustive list of changes that have been implemented for IPython
19 8.5.0. As usual you can find the full list of issues and PRs tagged with `the
20 8.5 milestone
21 <https://github.com/ipython/ipython/pulls?q=is%3Aclosed+milestone%3A8.5+>`__.
22
23 - Added shortcut for accepting auto suggestion. The End key shortcut for
24 accepting auto-suggestion This binding works in Vi mode too, provided
25 ``TerminalInteractiveShell.emacs_bindings_in_vi_insert_mode`` is set to be
26 ``True`` :ghpull:`13566`.
27
28 - No popup in window for latex generation w hen generating latex (e.g. via
29 `_latex_repr_`) no popup window is shows under Windows. :ghpull:`13679`
30
31 - Fixed error raised when attempting to tab-complete an input string with
32 consecutive periods or forward slashes (such as "file:///var/log/...").
33 :ghpull:`13675`
34
35 - Relative filenames in Latex rendering :
36 The `latex_to_png_dvipng` command internally generates input and output file
37 arguments to `latex` and `dvipis`. These arguments are now generated as
38 relative files to the current working directory instead of absolute file
39 paths. This solves a problem where the current working directory contains
40 characters that are not handled properly by `latex` and `dvips`. There are
41 no changes to the user API. :ghpull:`13680`
42
43 - Stripping decorators bug: Fixed bug which meant that ipython code blocks in
44 restructured text documents executed with the ipython-sphinx extension
45 skipped any lines of code containing python decorators. :ghpull:`13612`
46
47 - Allow some modules with frozen dataclasses to be reloaded. :ghpull:`13732`
48 - Fix paste magic on wayland. :ghpull:`13671`
49 - show maxlen in deque's repr. :ghpull:`13648`
50
51 Restore line numbers for Input
52 ------------------------------
53
54 Line number information in tracebacks from input are restored.
55 Line numbers from input were removed during the transition to v8 enhanced traceback reporting.
56
57 So, instead of::
58
59 ---------------------------------------------------------------------------
60 ZeroDivisionError Traceback (most recent call last)
61 Input In [3], in <cell line: 1>()
62 ----> 1 myfunc(2)
63
64 Input In [2], in myfunc(z)
65 1 def myfunc(z):
66 ----> 2 foo.boo(z-1)
67
68 File ~/code/python/ipython/foo.py:3, in boo(x)
69 2 def boo(x):
70 ----> 3 return 1/(1-x)
71
72 ZeroDivisionError: division by zero
73
74 The error traceback now looks like::
75
76 ---------------------------------------------------------------------------
77 ZeroDivisionError Traceback (most recent call last)
78 Cell In [3], line 1
79 ----> 1 myfunc(2)
80
81 Cell In [2], line 2, in myfunc(z)
82 1 def myfunc(z):
83 ----> 2 foo.boo(z-1)
84
85 File ~/code/python/ipython/foo.py:3, in boo(x)
86 2 def boo(x):
87 ----> 3 return 1/(1-x)
88
89 ZeroDivisionError: division by zero
90
91 or, with xmode=Plain::
92
93 Traceback (most recent call last):
94 Cell In [12], line 1
95 myfunc(2)
96 Cell In [6], line 2 in myfunc
97 foo.boo(z-1)
98 File ~/code/python/ipython/foo.py:3 in boo
99 return 1/(1-x)
100 ZeroDivisionError: division by zero
101
102 :ghpull:`13560`
103
104 New setting to silence warning if working inside a virtual environment
105 ----------------------------------------------------------------------
106
107 Previously, when starting IPython in a virtual environment without IPython installed (so IPython from the global environment is used), the following warning was printed:
108
109 Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv.
110
111 This warning can be permanently silenced by setting ``c.InteractiveShell.warn_venv`` to ``False`` (the default is ``True``).
112
113 :ghpull:`13706`
114
115 -------
116
117 Thanks to the `D. E. Shaw group <https://deshaw.com/>`__ for sponsoring
118 work on IPython and related libraries.
119
120
5 .. _version 8.4.0:
121 .. _version 8.4.0:
6
122
7 IPython 8.4.0
123 IPython 8.4.0
@@ -39,7 +39,6 b' install_requires ='
39 pickleshare
39 pickleshare
40 prompt_toolkit>3.0.1,<3.1.0
40 prompt_toolkit>3.0.1,<3.1.0
41 pygments>=2.4.0
41 pygments>=2.4.0
42 setuptools>=18.5
43 stack_data
42 stack_data
44 traitlets>=5
43 traitlets>=5
45
44
@@ -80,7 +80,7 b' def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", p'
80 if pulls:
80 if pulls:
81 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
81 filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ]
82 # filter out PRs not against main (backports)
82 # filter out PRs not against main (backports)
83 filtered = [ i for i in filtered if i['base']['ref'] == 'main' ]
83 filtered = [i for i in filtered if i["base"]["ref"] == "main"]
84 else:
84 else:
85 filtered = [ i for i in filtered if not is_pull_request(i) ]
85 filtered = [ i for i in filtered if not is_pull_request(i) ]
86
86
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now