From b0daec19ee2d19e037893c48e1ed891972daa0af 2022-09-07 03:07:55 From: Michał Krassowski <5832902+krassowski@users.noreply.github.com> Date: 2022-09-07 03:07:55 Subject: [PATCH] Merge branch 'main' into completion-matcher --- diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 96ed217..e4be71c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,11 +7,11 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.8 - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.x - name: Install Graphviz run: | sudo apt-get update diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 309d03a..ae2dbe5 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -21,9 +21,9 @@ jobs: python-version: "3.9" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Update Python installer diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 8b1a4c3..2725c92 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -15,9 +15,9 @@ jobs: python-version: [3.8] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 37d0218..fc28ac8 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -14,18 +14,14 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 - strategy: - matrix: - python-version: [3.8] - steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + - name: Set up Python + uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: 3.x - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f57425a..53ccb6f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,9 +49,9 @@ jobs: deps: test steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} cache: pip diff --git a/IPython/core/completer.py b/IPython/core/completer.py index e00d81e..b4a0215 100644 --- a/IPython/core/completer.py +++ b/IPython/core/completer.py @@ -906,19 +906,23 @@ class Completer(Configurable): matches = [] match_append = matches.append n = len(text) - for lst in [keyword.kwlist, - builtin_mod.__dict__.keys(), - self.namespace.keys(), - self.global_namespace.keys()]: + for lst in [ + keyword.kwlist, + builtin_mod.__dict__.keys(), + list(self.namespace.keys()), + list(self.global_namespace.keys()), + ]: for word in lst: if word[:n] == text and word != "__builtins__": match_append(word) snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") - for lst in [self.namespace.keys(), - self.global_namespace.keys()]: - shortened = {"_".join([sub[0] for sub in word.split('_')]) : word - for word in lst if snake_case_re.match(word)} + for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]: + shortened = { + "_".join([sub[0] for sub in word.split("_")]): word + for word in lst + if snake_case_re.match(word) + } for word in shortened.keys(): if word[:n] == text and word != "__builtins__": match_append(shortened[word]) diff --git a/IPython/core/history.py b/IPython/core/history.py index 9b0b2cb..1a89060 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -202,7 +202,6 @@ class HistoryAccessor(HistoryAccessorBase): config : :class:`~traitlets.config.loader.Config` Config object. hist_file can also be set through this. """ - # We need a pointer back to the shell for various tasks. super(HistoryAccessor, self).__init__(**traits) # defer setting hist_file from kwarg until after init, # otherwise the default kwarg value would clobber any value @@ -344,11 +343,6 @@ class HistoryAccessor(HistoryAccessorBase): def get_tail(self, n=10, raw=True, output=False, include_latest=False): """Get the last n lines from the history database. - Most recent entry last. - - Completion will be reordered so that that the last ones are when - possible from current session. - Parameters ---------- n : int @@ -367,31 +361,12 @@ class HistoryAccessor(HistoryAccessorBase): self.writeout_cache() if not include_latest: n += 1 - # cursor/line/entry - this_cur = list( - self._run_sql( - "WHERE session == ? ORDER BY line DESC LIMIT ? ", - (self.session_number, n), - raw=raw, - output=output, - ) - ) - other_cur = list( - self._run_sql( - "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?", - (self.session_number, n), - raw=raw, - output=output, - ) + cur = self._run_sql( + "ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output ) - - everything = this_cur + other_cur - - everything = everything[:n] - if not include_latest: - return list(everything)[:0:-1] - return list(everything)[::-1] + return reversed(list(cur)[1:]) + return reversed(list(cur)) @catch_corrupt_db def search(self, pattern="*", raw=True, search_raw=True, @@ -560,7 +535,6 @@ class HistoryManager(HistoryAccessor): def __init__(self, shell=None, config=None, **traits): """Create a new history manager associated with a shell instance. """ - # We need a pointer back to the shell for various tasks. super(HistoryManager, self).__init__(shell=shell, config=config, **traits) self.save_flag = threading.Event() @@ -656,6 +630,59 @@ class HistoryManager(HistoryAccessor): return super(HistoryManager, self).get_session_info(session=session) + @catch_corrupt_db + def get_tail(self, n=10, raw=True, output=False, include_latest=False): + """Get the last n lines from the history database. + + Most recent entry last. + + Completion will be reordered so that that the last ones are when + possible from current session. + + Parameters + ---------- + n : int + The number of lines to get + raw, output : bool + See :meth:`get_range` + include_latest : bool + If False (default), n+1 lines are fetched, and the latest one + is discarded. This is intended to be used where the function + is called by a user command, which it should not return. + + Returns + ------- + Tuples as :meth:`get_range` + """ + self.writeout_cache() + if not include_latest: + n += 1 + # cursor/line/entry + this_cur = list( + self._run_sql( + "WHERE session == ? ORDER BY line DESC LIMIT ? ", + (self.session_number, n), + raw=raw, + output=output, + ) + ) + other_cur = list( + self._run_sql( + "WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?", + (self.session_number, n), + raw=raw, + output=output, + ) + ) + + everything = this_cur + other_cur + + everything = everything[:n] + + if not include_latest: + return list(everything)[:0:-1] + return list(everything)[::-1] + def _get_range_session(self, start=1, stop=None, raw=True, output=False): """Get input and output history from the current session. Called by get_range, and takes similar parameters.""" diff --git a/IPython/core/hooks.py b/IPython/core/hooks.py index 09b08d9..f73c565 100644 --- a/IPython/core/hooks.py +++ b/IPython/core/hooks.py @@ -155,15 +155,17 @@ def clipboard_get(self): """ Get text from the clipboard. """ from ..lib.clipboard import ( - osx_clipboard_get, tkinter_clipboard_get, - win32_clipboard_get + osx_clipboard_get, + tkinter_clipboard_get, + win32_clipboard_get, + wayland_clipboard_get, ) if sys.platform == 'win32': chain = [win32_clipboard_get, tkinter_clipboard_get] elif sys.platform == 'darwin': chain = [osx_clipboard_get, tkinter_clipboard_get] else: - chain = [tkinter_clipboard_get] + chain = [wayland_clipboard_get, tkinter_clipboard_get] dispatcher = CommandChainDispatcher() for func in chain: dispatcher.add(func) diff --git a/IPython/core/magics/osm.py b/IPython/core/magics/osm.py index 41957a2..f3fa246 100644 --- a/IPython/core/magics/osm.py +++ b/IPython/core/magics/osm.py @@ -8,6 +8,7 @@ builtin. import io import os +import pathlib import re import sys from pprint import pformat @@ -409,7 +410,7 @@ class OSMagics(Magics): except OSError: print(sys.exc_info()[1]) else: - cwd = os.getcwd() + cwd = pathlib.Path.cwd() dhist = self.shell.user_ns['_dh'] if oldcwd != cwd: dhist.append(cwd) @@ -419,7 +420,7 @@ class OSMagics(Magics): os.chdir(self.shell.home_dir) if hasattr(self.shell, 'term_title') and self.shell.term_title: set_term_title(self.shell.term_title_format.format(cwd="~")) - cwd = os.getcwd() + cwd = pathlib.Path.cwd() dhist = self.shell.user_ns['_dh'] if oldcwd != cwd: diff --git a/IPython/core/release.py b/IPython/core/release.py index 1d6483c..a7e48a1 100644 --- a/IPython/core/release.py +++ b/IPython/core/release.py @@ -16,7 +16,7 @@ # release. 'dev' as a _version_extra string means this is a development # version _version_major = 8 -_version_minor = 5 +_version_minor = 6 _version_patch = 0 _version_extra = ".dev" # _version_extra = "rc1" diff --git a/IPython/core/tests/print_argv.py b/IPython/core/tests/print_argv.py index 0e92bdd..4ec9e27 100644 --- a/IPython/core/tests/print_argv.py +++ b/IPython/core/tests/print_argv.py @@ -1,2 +1,3 @@ import sys + print(sys.argv[1:]) diff --git a/IPython/core/tests/test_autocall.py b/IPython/core/tests/test_autocall.py index ded9f78..925a1cc 100644 --- a/IPython/core/tests/test_autocall.py +++ b/IPython/core/tests/test_autocall.py @@ -8,6 +8,7 @@ with better-isolated tests that don't rely on the global instance in iptest. from IPython.core.splitinput import LineInfo from IPython.core.prefilter import AutocallChecker + def doctest_autocall(): """ In [1]: def f1(a,b,c): diff --git a/IPython/core/tests/test_displayhook.py b/IPython/core/tests/test_displayhook.py index 6ad8979..22899f3 100644 --- a/IPython/core/tests/test_displayhook.py +++ b/IPython/core/tests/test_displayhook.py @@ -28,7 +28,7 @@ def test_output_quiet(): with AssertNotPrints('2'): ip.run_cell('1+1;\n#commented_out_function()', store_history=True) -def test_underscore_no_overrite_user(): +def test_underscore_no_overwrite_user(): ip.run_cell('_ = 42', store_history=True) ip.run_cell('1+1', store_history=True) @@ -41,7 +41,7 @@ def test_underscore_no_overrite_user(): ip.run_cell('_', store_history=True) -def test_underscore_no_overrite_builtins(): +def test_underscore_no_overwrite_builtins(): ip.run_cell("import gettext ; gettext.install('foo')", store_history=True) ip.run_cell('3+3', store_history=True) diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index 73d50c8..a9ebafd 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -17,7 +17,7 @@ from tempfile import TemporaryDirectory # our own packages from traitlets.config.loader import Config -from IPython.core.history import HistoryManager, extract_hist_ranges +from IPython.core.history import HistoryAccessor, HistoryManager, extract_hist_ranges def test_proper_default_encoding(): @@ -227,3 +227,81 @@ def test_histmanager_disabled(): # hist_file should not be created assert hist_file.exists() is False + + +def test_get_tail_session_awareness(): + """Test .get_tail() is: + - session specific in HistoryManager + - session agnostic in HistoryAccessor + same for .get_last_session_id() + """ + ip = get_ipython() + with TemporaryDirectory() as tmpdir: + tmp_path = Path(tmpdir) + hist_file = tmp_path / "history.sqlite" + get_source = lambda x: x[2] + hm1 = None + hm2 = None + ha = None + try: + # hm1 creates a new session and adds history entries, + # ha catches up + hm1 = HistoryManager(shell=ip, hist_file=hist_file) + hm1_last_sid = hm1.get_last_session_id + ha = HistoryAccessor(hist_file=hist_file) + ha_last_sid = ha.get_last_session_id + + hist1 = ["a=1", "b=1", "c=1"] + for i, h in enumerate(hist1 + [""], start=1): + hm1.store_inputs(i, h) + assert list(map(get_source, hm1.get_tail())) == hist1 + assert list(map(get_source, ha.get_tail())) == hist1 + sid1 = hm1_last_sid() + assert sid1 is not None + assert ha_last_sid() == sid1 + + # hm2 creates a new session and adds entries, + # ha catches up + hm2 = HistoryManager(shell=ip, hist_file=hist_file) + hm2_last_sid = hm2.get_last_session_id + + hist2 = ["a=2", "b=2", "c=2"] + for i, h in enumerate(hist2 + [""], start=1): + hm2.store_inputs(i, h) + tail = hm2.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + tail = ha.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + sid2 = hm2_last_sid() + assert sid2 is not None + assert ha_last_sid() == sid2 + assert sid2 != sid1 + + # but hm1 still maintains its point of reference + # and adding more entries to it doesn't change others + # immediate perspective + assert hm1_last_sid() == sid1 + tail = hm1.get_tail(n=3) + assert list(map(get_source, tail)) == hist1 + + hist3 = ["a=3", "b=3", "c=3"] + for i, h in enumerate(hist3 + [""], start=5): + hm1.store_inputs(i, h) + tail = hm1.get_tail(n=7) + assert list(map(get_source, tail)) == hist1 + [""] + hist3 + tail = hm2.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + tail = ha.get_tail(n=3) + assert list(map(get_source, tail)) == hist2 + assert hm1_last_sid() == sid1 + assert hm2_last_sid() == sid2 + assert ha_last_sid() == sid2 + finally: + if hm1: + hm1.save_thread.stop() + hm1.db.close() + if hm2: + hm2.save_thread.stop() + hm2.db.close() + if ha: + ha.db.close() diff --git a/IPython/core/tests/test_splitinput.py b/IPython/core/tests/test_splitinput.py index 8969da2..1462e7f 100644 --- a/IPython/core/tests/test_splitinput.py +++ b/IPython/core/tests/test_splitinput.py @@ -32,6 +32,7 @@ tests.append(("Pérez Fernando", ("", "", "Pérez", "Fernando"))) def test_split_user_input(): return tt.check_pairs(split_user_input, tests) + def test_LineInfo(): """Simple test for LineInfo construction and str()""" linfo = LineInfo(" %cd /home") diff --git a/IPython/extensions/autoreload.py b/IPython/extensions/autoreload.py index 816d2f3..a0a8c27 100644 --- a/IPython/extensions/autoreload.py +++ b/IPython/extensions/autoreload.py @@ -300,7 +300,7 @@ def update_instances(old, new): for ref in refs: if type(ref) is old: - ref.__class__ = new + object.__setattr__(ref, "__class__", new) def update_class(old, new): diff --git a/IPython/lib/clipboard.py b/IPython/lib/clipboard.py index 95a6b0a..1d691a7 100644 --- a/IPython/lib/clipboard.py +++ b/IPython/lib/clipboard.py @@ -1,14 +1,16 @@ """ Utilities for accessing the platform's clipboard. """ - +import os import subprocess from IPython.core.error import TryNext import IPython.utils.py3compat as py3compat + class ClipboardEmpty(ValueError): pass + def win32_clipboard_get(): """ Get the current clipboard's text on Windows. @@ -32,6 +34,7 @@ def win32_clipboard_get(): win32clipboard.CloseClipboard() return text + def osx_clipboard_get() -> str: """ Get the clipboard's text on OS X. """ @@ -43,6 +46,7 @@ def osx_clipboard_get() -> str: text = py3compat.decode(bytes_) return text + def tkinter_clipboard_get(): """ Get the clipboard's text using Tkinter. @@ -67,3 +71,31 @@ def tkinter_clipboard_get(): return text +def wayland_clipboard_get(): + """Get the clipboard's text under Wayland using wl-paste command. + + This requires Wayland and wl-clipboard installed and running. + """ + if os.environ.get("XDG_SESSION_TYPE") != "wayland": + raise TryNext("wayland is not detected") + + try: + with subprocess.Popen(["wl-paste"], stdout=subprocess.PIPE) as p: + raw, err = p.communicate() + if p.wait(): + raise TryNext(err) + except FileNotFoundError as e: + raise TryNext( + "Getting text from the clipboard under Wayland requires the wl-clipboard " + "extension: https://github.com/bugaevc/wl-clipboard" + ) from e + + if not raw: + raise ClipboardEmpty + + try: + text = py3compat.decode(raw) + except UnicodeDecodeError as e: + raise ClipboardEmpty from e + + return text diff --git a/IPython/lib/latextools.py b/IPython/lib/latextools.py index 27aeef5..b90a5d7 100644 --- a/IPython/lib/latextools.py +++ b/IPython/lib/latextools.py @@ -144,19 +144,30 @@ def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0): find_cmd('dvipng') except FindCmdError: return None + + startupinfo = None + if os.name == "nt": + # prevent popup-windows + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + try: workdir = Path(tempfile.mkdtemp()) - tmpfile = workdir.joinpath("tmp.tex") - dvifile = workdir.joinpath("tmp.dvi") - outfile = workdir.joinpath("tmp.png") + tmpfile = "tmp.tex" + dvifile = "tmp.dvi" + outfile = "tmp.png" - with tmpfile.open("w", encoding="utf8") as f: + with workdir.joinpath(tmpfile).open("w", encoding="utf8") as f: f.writelines(genelatex(s, wrap)) with open(os.devnull, 'wb') as devnull: subprocess.check_call( ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile], - cwd=workdir, stdout=devnull, stderr=devnull) + cwd=workdir, + stdout=devnull, + stderr=devnull, + startupinfo=startupinfo, + ) resolution = round(150*scale) subprocess.check_call( @@ -179,9 +190,10 @@ def latex_to_png_dvipng(s, wrap, color='Black', scale=1.0): cwd=workdir, stdout=devnull, stderr=devnull, + startupinfo=startupinfo, ) - with outfile.open("rb") as f: + with workdir.joinpath(outfile).open("rb") as f: return f.read() except subprocess.CalledProcessError: return None diff --git a/IPython/lib/pretty.py b/IPython/lib/pretty.py index 72f1435..f7feff9 100644 --- a/IPython/lib/pretty.py +++ b/IPython/lib/pretty.py @@ -908,6 +908,8 @@ def _deque_pprint(obj, p, cycle): cls_ctor = CallExpression.factory(obj.__class__.__name__) if cycle: p.pretty(cls_ctor(RawText("..."))) + elif obj.maxlen is not None: + p.pretty(cls_ctor(list(obj), maxlen=obj.maxlen)) else: p.pretty(cls_ctor(list(obj))) diff --git a/IPython/lib/tests/test_clipboard.py b/IPython/lib/tests/test_clipboard.py index 802f753..6597c94 100644 --- a/IPython/lib/tests/test_clipboard.py +++ b/IPython/lib/tests/test_clipboard.py @@ -2,6 +2,7 @@ from IPython.core.error import TryNext from IPython.lib.clipboard import ClipboardEmpty from IPython.testing.decorators import skip_if_no_x11 + @skip_if_no_x11 def test_clipboard_get(): # Smoketest for clipboard access - we can't easily guarantee that the diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py index 06724be..b7739c8 100644 --- a/IPython/terminal/interactiveshell.py +++ b/IPython/terminal/interactiveshell.py @@ -91,7 +91,12 @@ def get_default_editor(): # - no isatty method for _name in ('stdin', 'stdout', 'stderr'): _stream = getattr(sys, _name) - if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty(): + try: + if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty(): + _is_tty = False + break + except ValueError: + # stream is closed _is_tty = False break else: diff --git a/IPython/terminal/ptutils.py b/IPython/terminal/ptutils.py index c390d49..39bc2e1 100644 --- a/IPython/terminal/ptutils.py +++ b/IPython/terminal/ptutils.py @@ -48,10 +48,17 @@ def _elide_point(string:str, *, min_elide=30)->str: file_parts.pop() if len(object_parts) > 3: - return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1]) + return "{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}".format( + object_parts[0], + object_parts[1][:1], + object_parts[-2][-1:], + object_parts[-1], + ) elif len(file_parts) > 3: - return ('{}' + os.sep + '{}\N{HORIZONTAL ELLIPSIS}{}' + os.sep + '{}').format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1]) + return ("{}" + os.sep + "{}\N{HORIZONTAL ELLIPSIS}{}" + os.sep + "{}").format( + file_parts[0], file_parts[1][:1], file_parts[-2][-1:], file_parts[-1] + ) return string diff --git a/IPython/terminal/shortcuts.py b/IPython/terminal/shortcuts.py index 6c1ba04..a68be9a 100644 --- a/IPython/terminal/shortcuts.py +++ b/IPython/terminal/shortcuts.py @@ -140,6 +140,18 @@ def create_ipython_shortcuts(shell): _following_text_cache[pattern] = condition return condition + @Condition + def not_inside_unclosed_string(): + app = get_app() + s = app.current_buffer.document.text_before_cursor + # remove escaped quotes + s = s.replace('\\"', "").replace("\\'", "") + # remove triple-quoted string literals + s = re.sub(r"(?:\"\"\"[\s\S]*\"\"\"|'''[\s\S]*''')", "", s) + # remove single-quoted string literals + s = re.sub(r"""(?:"[^"]*["\n]|'[^']*['\n])""", "", s) + return not ('"' in s or "'" in s) + # auto match @kb.add("(", filter=focused_insert & auto_match & following_text(r"[,)}\]]|$")) def _(event): @@ -160,7 +172,7 @@ def create_ipython_shortcuts(shell): '"', filter=focused_insert & auto_match - & preceding_text(r'^([^"]+|"[^"]*")*$') + & not_inside_unclosed_string & following_text(r"[,)}\]]|$"), ) def _(event): @@ -171,13 +183,35 @@ def create_ipython_shortcuts(shell): "'", filter=focused_insert & auto_match - & preceding_text(r"^([^']+|'[^']*')*$") + & not_inside_unclosed_string & following_text(r"[,)}\]]|$"), ) def _(event): event.current_buffer.insert_text("''") event.current_buffer.cursor_left() + @kb.add( + '"', + filter=focused_insert + & auto_match + & not_inside_unclosed_string + & preceding_text(r'^.*""$'), + ) + def _(event): + event.current_buffer.insert_text('""""') + event.current_buffer.cursor_left(3) + + @kb.add( + "'", + filter=focused_insert + & auto_match + & not_inside_unclosed_string + & preceding_text(r"^.*''$"), + ) + def _(event): + event.current_buffer.insert_text("''''") + event.current_buffer.cursor_left(3) + # raw string @kb.add( "(", filter=focused_insert & auto_match & preceding_text(r".*(r|R)[\"'](-*)$") diff --git a/IPython/utils/contexts.py b/IPython/utils/contexts.py index 7f95d44..73c3f2e 100644 --- a/IPython/utils/contexts.py +++ b/IPython/utils/contexts.py @@ -7,6 +7,7 @@ import warnings # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. + class preserve_keys(object): """Preserve a set of keys in a dictionary. diff --git a/IPython/utils/eventful.py b/IPython/utils/eventful.py index 661851e..837c6e0 100644 --- a/IPython/utils/eventful.py +++ b/IPython/utils/eventful.py @@ -1,4 +1,3 @@ - from warnings import warn warn("IPython.utils.eventful has moved to traitlets.eventful", stacklevel=2) diff --git a/IPython/utils/log.py b/IPython/utils/log.py index bb262ed..f9dea91 100644 --- a/IPython/utils/log.py +++ b/IPython/utils/log.py @@ -1,4 +1,3 @@ - from warnings import warn warn("IPython.utils.log has moved to traitlets.log", stacklevel=2) diff --git a/IPython/utils/path.py b/IPython/utils/path.py index 3db33e4..a3a417d 100644 --- a/IPython/utils/path.py +++ b/IPython/utils/path.py @@ -83,12 +83,13 @@ def get_py_filename(name): """ name = os.path.expanduser(name) - if not os.path.isfile(name) and not name.endswith('.py'): - name += '.py' if os.path.isfile(name): return name - else: - raise IOError('File `%r` not found.' % name) + if not name.endswith(".py"): + py_name = name + ".py" + if os.path.isfile(py_name): + return py_name + raise IOError("File `%r` not found." % name) def filefind(filename: str, path_dirs=None) -> str: diff --git a/IPython/utils/tempdir.py b/IPython/utils/tempdir.py index 5afc5d6..a233c73 100644 --- a/IPython/utils/tempdir.py +++ b/IPython/utils/tempdir.py @@ -48,6 +48,7 @@ class TemporaryWorkingDirectory(TemporaryDirectory): with TemporaryWorkingDirectory() as tmpdir: ... """ + def __enter__(self): self.old_wd = Path.cwd() _os.chdir(self.name) diff --git a/docs/source/whatsnew/development.rst b/docs/source/whatsnew/development.rst index 502b0e0..9969680 100644 --- a/docs/source/whatsnew/development.rst +++ b/docs/source/whatsnew/development.rst @@ -25,6 +25,7 @@ Need to be updated: + .. DO NOT EDIT THIS LINE BEFORE RELEASE. FEATURE INSERTION POINT. Backwards incompatible changes diff --git a/docs/source/whatsnew/pr/end-shortcut-accept-suggestion.rst b/docs/source/whatsnew/pr/end-shortcut-accept-suggestion.rst deleted file mode 100644 index c04998e..0000000 --- a/docs/source/whatsnew/pr/end-shortcut-accept-suggestion.rst +++ /dev/null @@ -1,7 +0,0 @@ -Added shortcut for accepting auto suggestion -============================================ - -Added End key shortcut for accepting auto-suggestion -This binding works in Vi mode too, provided -TerminalInteractiveShell.emacs_bindings_in_vi_insert_mode is set to be True. - diff --git a/docs/source/whatsnew/pr/restore-line-numbers.rst b/docs/source/whatsnew/pr/restore-line-numbers.rst deleted file mode 100644 index fb07292..0000000 --- a/docs/source/whatsnew/pr/restore-line-numbers.rst +++ /dev/null @@ -1,50 +0,0 @@ -Restore line numbers for Input -================================== - -Line number information in tracebacks from input are restored. -Line numbers from input were removed during the transition to v8 enhanced traceback reporting. - -So, instead of:: - - --------------------------------------------------------------------------- - ZeroDivisionError Traceback (most recent call last) - Input In [3], in () - ----> 1 myfunc(2) - - Input In [2], in myfunc(z) - 1 def myfunc(z): - ----> 2 foo.boo(z-1) - - File ~/code/python/ipython/foo.py:3, in boo(x) - 2 def boo(x): - ----> 3 return 1/(1-x) - - ZeroDivisionError: division by zero - -The error traceback now looks like:: - - --------------------------------------------------------------------------- - ZeroDivisionError Traceback (most recent call last) - Cell In [3], line 1 - ----> 1 myfunc(2) - - Cell In [2], line 2, in myfunc(z) - 1 def myfunc(z): - ----> 2 foo.boo(z-1) - - File ~/code/python/ipython/foo.py:3, in boo(x) - 2 def boo(x): - ----> 3 return 1/(1-x) - - ZeroDivisionError: division by zero - -or, with xmode=Plain:: - - Traceback (most recent call last): - Cell In [12], line 1 - myfunc(2) - Cell In [6], line 2 in myfunc - foo.boo(z-1) - File ~/code/python/ipython/foo.py:3 in boo - return 1/(1-x) - ZeroDivisionError: division by zero diff --git a/docs/source/whatsnew/pr/silence-running-in-venv-warning.rst b/docs/source/whatsnew/pr/silence-running-in-venv-warning.rst deleted file mode 100644 index ed245bd..0000000 --- a/docs/source/whatsnew/pr/silence-running-in-venv-warning.rst +++ /dev/null @@ -1,8 +0,0 @@ -New setting to silence warning if working inside a virtual environment -====================================================================== - -Previously, when starting IPython in a virtual environment without IPython installed (so IPython from the global environment is used), the following warning was printed: - - Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv. - -This warning can be permanently silenced by setting ``c.InteractiveShell.warn_venv`` to ``False`` (the default is ``True``). diff --git a/docs/source/whatsnew/pr/stripping-decorators-bug.rst b/docs/source/whatsnew/pr/stripping-decorators-bug.rst deleted file mode 100644 index 2ea03d7..0000000 --- a/docs/source/whatsnew/pr/stripping-decorators-bug.rst +++ /dev/null @@ -1,4 +0,0 @@ -Stripping decorators bug -======================== - -Fixed bug which meant that ipython code blocks in restructured text documents executed with the ipython-sphinx extension skipped any lines of code containing python decorators. diff --git a/docs/source/whatsnew/version8.rst b/docs/source/whatsnew/version8.rst index c4b7b15..59e7165 100644 --- a/docs/source/whatsnew/version8.rst +++ b/docs/source/whatsnew/version8.rst @@ -2,6 +2,122 @@ 8.x Series ============ +.. _version 8.5.0: + +IPython 8.5.0 +------------- + +First release since a couple of month due to various reasons and timing preventing +me for sticking to the usual monthly release the last Friday of each month. This +is of non negligible size as it has more than two dozen PRs with various fixes +an bug fixes. + +Many thanks to everybody who contributed PRs for your patience in review and +merges. + +Here is a non exhaustive list of changes that have been implemented for IPython +8.5.0. As usual you can find the full list of issues and PRs tagged with `the +8.5 milestone +`__. + + - Added shortcut for accepting auto suggestion. The End key shortcut for + accepting auto-suggestion This binding works in Vi mode too, provided + ``TerminalInteractiveShell.emacs_bindings_in_vi_insert_mode`` is set to be + ``True`` :ghpull:`13566`. + + - No popup in window for latex generation w hen generating latex (e.g. via + `_latex_repr_`) no popup window is shows under Windows. :ghpull:`13679` + + - Fixed error raised when attempting to tab-complete an input string with + consecutive periods or forward slashes (such as "file:///var/log/..."). + :ghpull:`13675` + + - Relative filenames in Latex rendering : + The `latex_to_png_dvipng` command internally generates input and output file + arguments to `latex` and `dvipis`. These arguments are now generated as + relative files to the current working directory instead of absolute file + paths. This solves a problem where the current working directory contains + characters that are not handled properly by `latex` and `dvips`. There are + no changes to the user API. :ghpull:`13680` + + - Stripping decorators bug: Fixed bug which meant that ipython code blocks in + restructured text documents executed with the ipython-sphinx extension + skipped any lines of code containing python decorators. :ghpull:`13612` + + - Allow some modules with frozen dataclasses to be reloaded. :ghpull:`13732` + - Fix paste magic on wayland. :ghpull:`13671` + - show maxlen in deque's repr. :ghpull:`13648` + +Restore line numbers for Input +------------------------------ + +Line number information in tracebacks from input are restored. +Line numbers from input were removed during the transition to v8 enhanced traceback reporting. + +So, instead of:: + + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + Input In [3], in () + ----> 1 myfunc(2) + + Input In [2], in myfunc(z) + 1 def myfunc(z): + ----> 2 foo.boo(z-1) + + File ~/code/python/ipython/foo.py:3, in boo(x) + 2 def boo(x): + ----> 3 return 1/(1-x) + + ZeroDivisionError: division by zero + +The error traceback now looks like:: + + --------------------------------------------------------------------------- + ZeroDivisionError Traceback (most recent call last) + Cell In [3], line 1 + ----> 1 myfunc(2) + + Cell In [2], line 2, in myfunc(z) + 1 def myfunc(z): + ----> 2 foo.boo(z-1) + + File ~/code/python/ipython/foo.py:3, in boo(x) + 2 def boo(x): + ----> 3 return 1/(1-x) + + ZeroDivisionError: division by zero + +or, with xmode=Plain:: + + Traceback (most recent call last): + Cell In [12], line 1 + myfunc(2) + Cell In [6], line 2 in myfunc + foo.boo(z-1) + File ~/code/python/ipython/foo.py:3 in boo + return 1/(1-x) + ZeroDivisionError: division by zero + +:ghpull:`13560` + +New setting to silence warning if working inside a virtual environment +---------------------------------------------------------------------- + +Previously, when starting IPython in a virtual environment without IPython installed (so IPython from the global environment is used), the following warning was printed: + + Attempting to work in a virtualenv. If you encounter problems, please install IPython inside the virtualenv. + +This warning can be permanently silenced by setting ``c.InteractiveShell.warn_venv`` to ``False`` (the default is ``True``). + +:ghpull:`13706` + +------- + +Thanks to the `D. E. Shaw group `__ for sponsoring +work on IPython and related libraries. + + .. _version 8.4.0: IPython 8.4.0 diff --git a/setup.cfg b/setup.cfg index 8d5136d..99b3498 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,6 @@ install_requires = pickleshare prompt_toolkit>3.0.1,<3.1.0 pygments>=2.4.0 - setuptools>=18.5 stack_data traitlets>=5 diff --git a/tools/github_stats.py b/tools/github_stats.py index 6a2f8f1..af00a7b 100644 --- a/tools/github_stats.py +++ b/tools/github_stats.py @@ -80,7 +80,7 @@ def issues_closed_since(period=timedelta(days=365), project="ipython/ipython", p if pulls: filtered = [ i for i in filtered if _parse_datetime(i['merged_at']) > since ] # filter out PRs not against main (backports) - filtered = [ i for i in filtered if i['base']['ref'] == 'main' ] + filtered = [i for i in filtered if i["base"]["ref"] == "main"] else: filtered = [ i for i in filtered if not is_pull_request(i) ]