diff options
author | Alexander Smirnov <alex@ydb.tech> | 2025-02-16 00:51:45 +0000 |
---|---|---|
committer | Alexander Smirnov <alex@ydb.tech> | 2025-02-16 00:51:45 +0000 |
commit | 436fda2cd94c199f31d890959fbc5e58a509c0a1 (patch) | |
tree | a6db297b18588c20ea26490b4d6bcc3dc8c32113 /contrib | |
parent | 0fc5672f94482c8d6e6afba9e34db7ed66a6c0fb (diff) | |
parent | 802da2736bf00631aa408e495b80d6e125f10a9f (diff) | |
download | ydb-436fda2cd94c199f31d890959fbc5e58a509c0a1.tar.gz |
Merge branch 'rightlib' into merge-libs-250216-0050
Diffstat (limited to 'contrib')
15 files changed, 392 insertions, 57 deletions
diff --git a/contrib/libs/simdjson/.yandex_meta/devtools.licenses.report b/contrib/libs/simdjson/.yandex_meta/devtools.licenses.report index 2aad6abcfa..e29fcfdfcb 100644 --- a/contrib/libs/simdjson/.yandex_meta/devtools.licenses.report +++ b/contrib/libs/simdjson/.yandex_meta/devtools.licenses.report @@ -42,7 +42,7 @@ BELONGS ya.make Match type : REFERENCE Links : http://www.boost.org/LICENSE_1_0.txt, http://www.boost.org/users/license.html, https://spdx.org/licenses/BSL-1.0 Files with this license: - README.md [235:235] + README.md [234:234] KEEP MIT 0a00f0d66f4f37595306dd8c6a25c63c BELONGS ya.make @@ -54,7 +54,7 @@ BELONGS ya.make Match type : NOTICE Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT Files with this license: - README.md [231:231] + README.md [230:230] SKIP LicenseRef-scancode-unknown-license-reference 0d48e0b09865a98a90db20ea37b36bb8 BELONGS ya.make @@ -66,7 +66,7 @@ BELONGS ya.make Match type : INTRO Links : https://github.com/nexB/scancode-toolkit/tree/develop/src/licensedcode/data/licenses/unknown-license-reference.LICENSE Files with this license: - README.md [239:239] + README.md [238:238] KEEP Apache-2.0 13ec3cccf3036f38df47d2051a825972 BELONGS ya.make @@ -102,7 +102,7 @@ BELONGS ya.make Match type : REFERENCE Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 Files with this license: - README.md [215:215] + README.md [214:214] SKIP BSL-1.0 2a9212d785cde4078c2f6803e544de21 BELONGS ya.make @@ -113,7 +113,7 @@ BELONGS ya.make Match type : REFERENCE Links : http://www.boost.org/LICENSE_1_0.txt, http://www.boost.org/users/license.html, https://spdx.org/licenses/BSL-1.0 Files with this license: - README.md [235:235] + README.md [234:234] KEEP MIT 3e1ede6948a97e7ee3d75e0204a567f3 BELONGS ya.make @@ -125,7 +125,7 @@ BELONGS ya.make Match type : TAG Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT Files with this license: - README.md [219:219] + README.md [218:218] SKIP Apache-2.0 500a503129337bb5adf5977ce11879cd BELONGS ya.make @@ -137,7 +137,7 @@ BELONGS ya.make Match type : NOTICE Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 Files with this license: - README.md [231:231] + README.md [230:230] SKIP BSL-1.0 77dd56e30840a227692d435b4aecdb95 BELONGS ya.make @@ -148,11 +148,11 @@ BELONGS ya.make Match type : REFERENCE Links : http://www.boost.org/LICENSE_1_0.txt, http://www.boost.org/users/license.html, https://spdx.org/licenses/BSL-1.0 Files with this license: - README.md [235:235] + README.md [234:234] KEEP MIT 7f0bdbc0a0545831259b66259ac6b604 BELONGS ya.make -FILE_INCLUDE LICENSE-MIT found in files: README.md at line 218 +FILE_INCLUDE LICENSE-MIT found in files: README.md at line 217 License text: [licensemit]: LICENSE-MIT Scancode info: @@ -161,7 +161,7 @@ FILE_INCLUDE LICENSE-MIT found in files: README.md at line 218 Match type : TAG Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT Files with this license: - README.md [218:218] + README.md [217:217] KEEP Apache-2.0 82e76bbc1841bd5886297e795c72bfa5 BELONGS ya.make @@ -173,7 +173,7 @@ BELONGS ya.make Match type : REFERENCE Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 Files with this license: - README.md [231:231] + README.md [230:230] KEEP MIT a6e9f2d79eb73e6e422759b53da6152a BELONGS ya.make @@ -208,7 +208,7 @@ BELONGS ya.make Match type : NOTICE Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT Files with this license: - README.md [237:237] + README.md [236:236] SKIP Apache-2.0 c23a044f4165feb9568f486ca3b30fc8 BELONGS ya.make @@ -219,7 +219,7 @@ BELONGS ya.make Match type : NOTICE Links : http://www.apache.org/licenses/, http://www.apache.org/licenses/LICENSE-2.0, https://spdx.org/licenses/Apache-2.0 Files with this license: - README.md [235:235] + README.md [234:234] SKIP BSD-3-Clause d77bd60dc7ee5f9c3b221f6edd94bbac BELONGS ya.make @@ -231,7 +231,7 @@ BELONGS ya.make Match type : REFERENCE Links : http://www.opensource.org/licenses/BSD-3-Clause, https://spdx.org/licenses/BSD-3-Clause Files with this license: - README.md [239:239] + README.md [238:238] SKIP MIT dd09705e3ec59af63c705c8f5f3eadb2 BELONGS ya.make @@ -243,7 +243,7 @@ BELONGS ya.make Match type : REFERENCE Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT Files with this license: - README.md [233:233] + README.md [232:232] KEEP MIT f0fe4686586f118327c3bc63fe4027de BELONGS ya.make diff --git a/contrib/libs/simdjson/.yandex_meta/override.nix b/contrib/libs/simdjson/.yandex_meta/override.nix index 386e6cc798..a3e6ea867c 100644 --- a/contrib/libs/simdjson/.yandex_meta/override.nix +++ b/contrib/libs/simdjson/.yandex_meta/override.nix @@ -1,11 +1,11 @@ pkgs: attrs: with pkgs; rec { - version = "3.12.1"; + version = "3.12.2"; src = fetchFromGitHub { owner = "simdjson"; repo = "simdjson"; rev = "v${version}"; - hash = "sha256-ujeG3yidZJZV6x4RQQYXwbslQcRx3HaqjzgaU2A4cQU="; + hash = "sha256-TjUPySFwwTlD4fLpHoUywAeWvVvi7Hg1wxzgE9vohrs="; }; cmakeFlags = attrs.cmakeFlags ++ [ diff --git a/contrib/libs/simdjson/README.md b/contrib/libs/simdjson/README.md index d0cae0caa4..a9ca9a08da 100644 --- a/contrib/libs/simdjson/README.md +++ b/contrib/libs/simdjson/README.md @@ -1,5 +1,4 @@ -[/badge.svg)](https://simdjson.org/plots.html) [](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:simdjson) [![][license img]][license] [![][licensemit img]][licensemit] diff --git a/contrib/libs/simdjson/include/simdjson/icelake/simd.h b/contrib/libs/simdjson/include/simdjson/icelake/simd.h index 04203f4b9a..a37ef957c9 100644 --- a/contrib/libs/simdjson/include/simdjson/icelake/simd.h +++ b/contrib/libs/simdjson/include/simdjson/icelake/simd.h @@ -148,14 +148,18 @@ namespace simd { // Copies to 'output" all bytes corresponding to a 0 in the mask (interpreted as a bitset). // Passing a 0 value for mask would be equivalent to writing out every byte to output. - // Only the first 32 - count_ones(mask) bytes of the result are significant but 32 bytes + // Only the first 64 - count_ones(mask) bytes of the result are significant but 64 bytes // get written. // Design consideration: it seems like a function with the // signature simd8<L> compress(uint32_t mask) would be // sensible, but the AVX ISA makes this kind of approach difficult. template<typename L> simdjson_inline void compress(uint64_t mask, L * output) const { - _mm512_mask_compressstoreu_epi8 (output,~mask,*this); + // we deliberately avoid _mm512_mask_compressstoreu_epi8 for portability + // (AMD Zen4 has terrible performance with it, it is effectively broken) + // _mm512_mask_compressstoreu_epi8 (output,~mask,*this); + __m512i compressed = _mm512_maskz_compress_epi8(~mask, *this); + _mm512_storeu_si512(output, compressed); // could use a mask } template<typename L> diff --git a/contrib/libs/simdjson/include/simdjson/simdjson_version.h b/contrib/libs/simdjson/include/simdjson/simdjson_version.h index ae27901639..d61c07c01f 100644 --- a/contrib/libs/simdjson/include/simdjson/simdjson_version.h +++ b/contrib/libs/simdjson/include/simdjson/simdjson_version.h @@ -4,7 +4,7 @@ #define SIMDJSON_SIMDJSON_VERSION_H /** The version of simdjson being used (major.minor.revision) */ -#define SIMDJSON_VERSION "3.12.1" +#define SIMDJSON_VERSION "3.12.2" namespace simdjson { enum { @@ -19,7 +19,7 @@ enum { /** * The revision (major.minor.REVISION) of simdjson being used. */ - SIMDJSON_VERSION_REVISION = 1 + SIMDJSON_VERSION_REVISION = 2 }; } // namespace simdjson diff --git a/contrib/libs/simdjson/ya.make b/contrib/libs/simdjson/ya.make index b4c95dae92..1f2e8bb1ea 100644 --- a/contrib/libs/simdjson/ya.make +++ b/contrib/libs/simdjson/ya.make @@ -10,9 +10,9 @@ LICENSE( LICENSE_TEXTS(.yandex_meta/licenses.list.txt) -VERSION(3.12.1) +VERSION(3.12.2) -ORIGINAL_SOURCE(https://github.com/simdjson/simdjson/archive/v3.12.1.tar.gz) +ORIGINAL_SOURCE(https://github.com/simdjson/simdjson/archive/v3.12.2.tar.gz) ADDINCL( GLOBAL contrib/libs/simdjson/include diff --git a/contrib/python/ipython/py3/.dist-info/METADATA b/contrib/python/ipython/py3/.dist-info/METADATA index b6a222768c..590a77f53f 100644 --- a/contrib/python/ipython/py3/.dist-info/METADATA +++ b/contrib/python/ipython/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.2 Name: ipython -Version: 8.31.0 +Version: 8.32.0 Summary: IPython: Productive Interactive Computing Author: The IPython Development Team Author-email: ipython-dev@python.org @@ -85,6 +85,9 @@ Requires-Dist: matplotlib; extra == "matplotlib" Provides-Extra: all Requires-Dist: ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]; extra == "all" Requires-Dist: ipython[test,test_extra]; extra == "all" +Dynamic: author +Dynamic: author-email +Dynamic: license IPython provides a rich toolkit to help you make the most out of using Python interactively. Its main components are: diff --git a/contrib/python/ipython/py3/IPython/core/interactiveshell.py b/contrib/python/ipython/py3/IPython/core/interactiveshell.py index 07fb807760..a341ab053a 100644 --- a/contrib/python/ipython/py3/IPython/core/interactiveshell.py +++ b/contrib/python/ipython/py3/IPython/core/interactiveshell.py @@ -900,7 +900,7 @@ class InteractiveShell(SingletonConfigurable): return p = Path(sys.executable) - p_venv = Path(os.environ["VIRTUAL_ENV"]) + p_venv = Path(os.environ["VIRTUAL_ENV"]).resolve() # fallback venv detection: # stdlib venv may symlink sys.executable, so we can't use realpath. @@ -913,7 +913,7 @@ class InteractiveShell(SingletonConfigurable): drive_name = p_venv.parts[2] p_venv = (drive_name + ":/") / Path(*p_venv.parts[3:]) - if any(p_venv == p.parents[1] for p in paths): + if any(p_venv == p.parents[1].resolve() for p in paths): # Our exe is inside or has access to the virtualenv, don't need to do anything. return @@ -2093,6 +2093,8 @@ class InteractiveShell(SingletonConfigurable): sys.last_type = etype sys.last_value = value sys.last_traceback = tb + if sys.version_info >= (3, 12): + sys.last_exc = value return etype, value, tb diff --git a/contrib/python/ipython/py3/IPython/core/magics/execution.py b/contrib/python/ipython/py3/IPython/core/magics/execution.py index 3aa0a27fc2..ec17d0a497 100644 --- a/contrib/python/ipython/py3/IPython/core/magics/execution.py +++ b/contrib/python/ipython/py3/IPython/core/magics/execution.py @@ -977,7 +977,21 @@ class ExecutionMagics(Magics): break finally: sys.settrace(trace) - + + # Perform proper cleanup of the session in case if + # it exited with "continue" and not "quit" command + if hasattr(deb, "rcLines"): + # Run this code defensively in case if custom debugger + # class does not implement rcLines, which although public + # is an implementation detail of `pdb.Pdb` and not part of + # the more generic basic debugger framework (`bdb.Bdb`). + deb.set_quit() + deb.rcLines.extend(["q"]) + try: + deb.run("", code_ns, local_ns) + except StopIteration: + # Stop iteration is raised on quit command + pass except: etype, value, tb = sys.exc_info() diff --git a/contrib/python/ipython/py3/IPython/core/magics/script.py b/contrib/python/ipython/py3/IPython/core/magics/script.py index 0c405ef420..3bfc4d8d67 100644 --- a/contrib/python/ipython/py3/IPython/core/magics/script.py +++ b/contrib/python/ipython/py3/IPython/core/magics/script.py @@ -67,6 +67,10 @@ def script_args(f): return f +class RaiseAfterInterrupt(Exception): + pass + + @magics_class class ScriptMagics(Magics): """Magics for talking to scripts @@ -176,6 +180,10 @@ class ScriptMagics(Magics): The rest of the cell is run by that program. + .. versionchanged:: 9.0 + Interrupting the script executed without `--bg` will end in + raising an exception (unless `--no-raise-error` is passed). + Examples -------- :: @@ -212,7 +220,7 @@ class ScriptMagics(Magics): async def _readchunk(stream): try: - return await stream.readuntil(b"\n") + return await stream.read(100) except asyncio.exceptions.IncompleteReadError as e: return e.partial except asyncio.exceptions.LimitOverrunError as e: @@ -292,20 +300,33 @@ class ScriptMagics(Magics): p.send_signal(signal.SIGINT) in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) if p.returncode is not None: - print("Process is interrupted.") - return + print("Process was interrupted.") + if args.raise_error: + raise RaiseAfterInterrupt() + else: + return p.terminate() in_thread(asyncio.wait_for(p.wait(), timeout=0.1)) if p.returncode is not None: - print("Process is terminated.") - return + print("Process was terminated.") + if args.raise_error: + raise RaiseAfterInterrupt() + else: + return p.kill() - print("Process is killed.") + print("Process was killed.") + if args.raise_error: + raise RaiseAfterInterrupt() + except RaiseAfterInterrupt: + pass except OSError: pass except Exception as e: print("Error while terminating subprocess (pid=%i): %s" % (p.pid, e)) - return + if args.raise_error: + raise CalledProcessError(p.returncode, cell) from None + else: + return if args.raise_error and p.returncode != 0: # If we get here and p.returncode is still None, we must have diff --git a/contrib/python/ipython/py3/IPython/core/release.py b/contrib/python/ipython/py3/IPython/core/release.py index 06917bb8ae..a21f446949 100644 --- a/contrib/python/ipython/py3/IPython/core/release.py +++ b/contrib/python/ipython/py3/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 = 31 +_version_minor = 32 _version_patch = 0 _version_extra = ".dev" # _version_extra = "rc1" diff --git a/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py b/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py index ef4f5cd3f6..ba9a31135a 100644 --- a/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py +++ b/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py @@ -26,7 +26,10 @@ from traitlets import ( Any, validate, Float, + DottedObjectName, ) +from traitlets.utils.importstring import import_item + from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode @@ -214,7 +217,9 @@ class TerminalInteractiveShell(InteractiveShell): pt_app: UnionType[PromptSession, None] = None auto_suggest: UnionType[ - AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None + AutoSuggestFromHistory, + NavigableAutoSuggestFromHistory, + None, ] = None debugger_history = None @@ -421,6 +426,37 @@ class TerminalInteractiveShell(InteractiveShell): allow_none=True, ).tag(config=True) + llm_provider_class = DottedObjectName( + None, + allow_none=True, + help="""\ + Provisional: + This is a provisinal API in IPython 8.32, before stabilisation + in 9.0, it may change without warnings. + + class to use for the `NavigableAutoSuggestFromHistory` to request + completions from a LLM, this should inherit from + `jupyter_ai_magics:BaseProvider` and implement + `stream_inline_completions` + """, + ).tag(config=True) + + @observe("llm_provider_class") + def _llm_provider_class_changed(self, change): + provider_class = change.new + if provider_class is not None: + warn( + "TerminalInteractiveShell.llm_provider_class is a provisional" + " API as of IPython 8.32, and may change without warnings." + ) + if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory): + self.auto_suggest._llm_provider = provider_class() + else: + self.log.warn( + "llm_provider_class only has effects when using" + "`NavigableAutoSuggestFromHistory` as auto_suggest." + ) + def _set_autosuggestions(self, provider): # disconnect old handler if self.auto_suggest and isinstance( @@ -432,7 +468,15 @@ class TerminalInteractiveShell(InteractiveShell): elif provider == "AutoSuggestFromHistory": self.auto_suggest = AutoSuggestFromHistory() elif provider == "NavigableAutoSuggestFromHistory": + # LLM stuff are all Provisional in 8.32 + if self.llm_provider_class: + llm_provider_constructor = import_item(self.llm_provider_class) + llm_provider = llm_provider_constructor() + else: + llm_provider = None self.auto_suggest = NavigableAutoSuggestFromHistory() + # Provisinal in 8.32 + self.auto_suggest._llm_provider = llm_provider else: raise ValueError("No valid provider.") if self.pt_app: @@ -815,7 +859,8 @@ class TerminalInteractiveShell(InteractiveShell): & ~IsDone() & Condition( lambda: isinstance( - self.auto_suggest, NavigableAutoSuggestFromHistory + self.auto_suggest, + NavigableAutoSuggestFromHistory, ) ), ), diff --git a/contrib/python/ipython/py3/IPython/terminal/shortcuts/auto_suggest.py b/contrib/python/ipython/py3/IPython/terminal/shortcuts/auto_suggest.py index 94a94a88c1..bcba5622e4 100644 --- a/contrib/python/ipython/py3/IPython/terminal/shortcuts/auto_suggest.py +++ b/contrib/python/ipython/py3/IPython/terminal/shortcuts/auto_suggest.py @@ -1,13 +1,15 @@ import re +import asyncio import tokenize from io import StringIO -from typing import Callable, List, Optional, Union, Generator, Tuple +from typing import Callable, List, Optional, Union, Generator, Tuple, ClassVar, Any import warnings +import prompt_toolkit from prompt_toolkit.buffer import Buffer from prompt_toolkit.key_binding import KeyPressEvent from prompt_toolkit.key_binding.bindings import named_commands as nc -from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion, AutoSuggest from prompt_toolkit.document import Document from prompt_toolkit.history import History from prompt_toolkit.shortcuts import PromptSession @@ -22,6 +24,12 @@ from IPython.utils.tokenutil import generate_tokens from .filters import pass_through +try: + import jupyter_ai_magics + import jupyter_ai.completions.models as jai_models +except ModuleNotFoundError: + jai_models = None + def _get_query(document: Document): return document.lines[document.cursor_position_row] @@ -31,26 +39,124 @@ class AppendAutoSuggestionInAnyLine(Processor): """ Append the auto suggestion to lines other than the last (appending to the last line is natively supported by the prompt toolkit). + + This has a private `_debug` attribute that can be set to True to display + debug information as virtual suggestion on the end of any line. You can do + so with: + + >>> from IPython.terminal.shortcuts.auto_suggest import AppendAutoSuggestionInAnyLine + >>> AppendAutoSuggestionInAnyLine._debug = True + """ + _debug: ClassVar[bool] = False + def __init__(self, style: str = "class:auto-suggestion") -> None: self.style = style def apply_transformation(self, ti: TransformationInput) -> Transformation: - is_last_line = ti.lineno == ti.document.line_count - 1 - is_active_line = ti.lineno == ti.document.cursor_position_row + """ + Apply transformation to the line that is currently being edited. - if not is_last_line and is_active_line: - buffer = ti.buffer_control.buffer + This is a variation of the original implementation in prompt toolkit + that allows to not only append suggestions to any line, but also to show + multi-line suggestions. - if buffer.suggestion and ti.document.is_cursor_at_the_end_of_line: - suggestion = buffer.suggestion.text - else: - suggestion = "" + As transformation are applied on a line-by-line basis; we need to trick + a bit, and elide any line that is after the line we are currently + editing, until we run out of completions. We cannot shift the existing + lines + + There are multiple cases to handle: + + The completions ends before the end of the buffer: + We can resume showing the normal line, and say that some code may + be hidden. + + The completions ends at the end of the buffer + We can just say that some code may be hidden. + + And separately: + + The completions ends beyond the end of the buffer + We need to both say that some code may be hidden, and that some + lines are not shown. + + """ + last_line_number = ti.document.line_count - 1 + is_last_line = ti.lineno == last_line_number + + noop = lambda text: Transformation( + fragments=ti.fragments + [(self.style, " " + text if self._debug else "")] + ) + if ti.document.line_count == 1: + return noop("noop:oneline") + if ti.document.cursor_position_row == last_line_number and is_last_line: + # prompt toolkit already appends something; just leave it be + return noop("noop:last line and cursor") + + # first everything before the current line is unchanged. + if ti.lineno < ti.document.cursor_position_row: + return noop("noop:before cursor") + + buffer = ti.buffer_control.buffer + if not buffer.suggestion or not ti.document.is_cursor_at_the_end_of_line: + return noop("noop:not eol") + + delta = ti.lineno - ti.document.cursor_position_row + suggestions = buffer.suggestion.text.splitlines() + if len(suggestions) == 0: + return noop("noop: no suggestions") + + suggestions_longer_than_buffer: bool = ( + len(suggestions) + ti.document.cursor_position_row > ti.document.line_count + ) + + if len(suggestions) >= 1 and prompt_toolkit.VERSION < (3, 0, 49): + if ti.lineno == ti.document.cursor_position_row: + return Transformation( + fragments=ti.fragments + + [ + ( + "red", + "(Cannot show multiline suggestion; requires prompt_toolkit > 3.0.49)", + ) + ] + ) + else: + return Transformation(fragments=ti.fragments) + if delta == 0: + suggestion = suggestions[0] return Transformation(fragments=ti.fragments + [(self.style, suggestion)]) + if is_last_line: + if delta < len(suggestions): + extra = f"; {len(suggestions) - delta} line(s) hidden" + suggestion = f"… rest of suggestion ({len(suggestions) - delta} lines) and code hidden" + return Transformation([(self.style, suggestion)]) + + n_elided = len(suggestions) + for i in range(len(suggestions)): + ll = ti.get_line(last_line_number - i) + el = "".join(l[1] for l in ll).strip() + if el: + break + else: + n_elided -= 1 + if n_elided: + return Transformation([(self.style, f"… {n_elided} line(s) hidden")]) + else: + return Transformation( + ti.get_line(last_line_number - len(suggestions) + 1) + + ([(self.style, "shift-last-line")] if self._debug else []) + ) + + elif delta < len(suggestions): + suggestion = suggestions[delta] + return Transformation([(self.style, suggestion)]) else: - return Transformation(fragments=ti.fragments) + shift = ti.lineno - len(suggestions) + 1 + return Transformation(ti.get_line(shift)) class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): @@ -60,16 +166,29 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): state need to carefully be cleared on the right events. """ - def __init__( - self, - ): + skip_lines: int + _connected_apps: list[PromptSession] + + # handle to the currently running llm task that appends suggestions to the + # current buffer; we keep a handle to it in order to cancell it when there is a cursor movement, or + # another request. + _llm_task: asyncio.Task | None = None + + # This is the instance of the LLM provider from jupyter-ai to which we forward the request + # to generate inline completions. + _llm_provider: Any | None + + def __init__(self): + super().__init__() self.skip_lines = 0 self._connected_apps = [] + self._llm_provider = None def reset_history_position(self, _: Buffer): self.skip_lines = 0 - def disconnect(self): + def disconnect(self) -> None: + self._cancel_running_llm_task() for pt_app in self._connected_apps: text_insert_event = pt_app.default_buffer.on_text_insert text_insert_event.remove_handler(self.reset_history_position) @@ -94,7 +213,8 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): return None - def _dismiss(self, buffer, *args, **kwargs): + def _dismiss(self, buffer, *args, **kwargs) -> None: + self._cancel_running_llm_task() buffer.suggestion = None def _find_match( @@ -149,6 +269,7 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): ) def up(self, query: str, other_than: str, history: History) -> None: + self._cancel_running_llm_task() for suggestion, line_number in self._find_next_match( query, self.skip_lines, history ): @@ -165,6 +286,7 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): self.skip_lines = 0 def down(self, query: str, other_than: str, history: History) -> None: + self._cancel_running_llm_task() for suggestion, line_number in self._find_previous_match( query, self.skip_lines, history ): @@ -180,6 +302,131 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): self.skip_lines = line_number break + def _cancel_running_llm_task(self) -> None: + """ + Try to cancell the currently running llm_task if exists, and set it to None. + """ + if self._llm_task is not None: + if self._llm_task.done(): + self._llm_task = None + return + cancelled = self._llm_task.cancel() + if cancelled: + self._llm_task = None + if not cancelled: + warnings.warn( + "LLM task not cancelled, does your provider support cancellation?" + ) + + async def _trigger_llm(self, buffer) -> None: + """ + This will ask the current llm provider a suggestion for the current buffer. + + If there is a currently running llm task, it will cancel it. + """ + # we likely want to store the current cursor position, and cancel if the cursor has moved. + if not self._llm_provider: + warnings.warn("No LLM provider found, cannot trigger LLM completions") + return + if jai_models is None: + warnings.warn( + "LLM Completion requires `jupyter_ai_magics` and `jupyter_ai` to be installed" + ) + + self._cancel_running_llm_task() + + async def error_catcher(buffer): + """ + This catches and log any errors, as otherwise this is just + lost in the void of the future running task. + """ + try: + await self._trigger_llm_core(buffer) + except Exception as e: + get_ipython().log.error("error") + raise + + # here we need a cancellable task so we can't just await the error catched + self._llm_task = asyncio.create_task(error_catcher(buffer)) + await self._llm_task + + async def _trigger_llm_core(self, buffer: Buffer): + """ + This is the core of the current llm request. + + Here we build a compatible `InlineCompletionRequest` and ask the llm + provider to stream it's response back to us iteratively setting it as + the suggestion on the current buffer. + + Unlike with JupyterAi, as we do not have multiple cell, the cell number + is always set to `0`, note that we _could_ set it to a new number each + time and ignore threply from past numbers. + + We set the prefix to the current cell content, but could also inset the + rest of the history or even just the non-fail history. + + In the same way, we do not have cell id. + + LLM provider may return multiple suggestion stream, but for the time + being we only support one. + + Here we make the assumption that the provider will have + stream_inline_completions, I'm not sure it is the case for all + providers. + """ + + request = jai_models.InlineCompletionRequest( + number=0, + prefix=buffer.document.text, + suffix="", + mime="text/x-python", + stream=True, + path=None, + language="python", + cell_id=None, + ) + + async for reply_and_chunks in self._llm_provider.stream_inline_completions( + request + ): + if isinstance(reply_and_chunks, jai_models.InlineCompletionReply): + if len(reply_and_chunks.list.items) > 1: + raise ValueError( + "Terminal IPython cannot deal with multiple LLM suggestions at once" + ) + buffer.suggestion = Suggestion( + reply_and_chunks.list.items[0].insertText + ) + buffer.on_suggestion_set.fire() + elif isinstance(reply_and_chunks, jai_models.InlineCompletionStreamChunk): + buffer.suggestion = Suggestion(reply_and_chunks.response.insertText) + buffer.on_suggestion_set.fire() + return + + +_MIN_LINES = 5 + + +async def llm_autosuggestion(event: KeyPressEvent): + """ + Ask the AutoSuggester from history to delegate to ask an LLM for completion + + This will first make sure that the current buffer have _MIN_LINES (7) + available lines to insert the LLM completion + + Provisional as of 8.32, may change without warnigns + + """ + provider = get_ipython().auto_suggest + if not isinstance(provider, NavigableAutoSuggestFromHistory): + return + doc = event.current_buffer.document + lines_to_insert = max(0, _MIN_LINES - doc.line_count + doc.cursor_position_row) + for _ in range(lines_to_insert): + event.current_buffer.insert_text("\n", move_cursor=False) + + await provider._trigger_llm(event.current_buffer) + def accept_or_jump_to_end(event: KeyPressEvent): """Apply autosuggestion or jump to end of line.""" diff --git a/contrib/python/ipython/py3/IPython/utils/_sysinfo.py b/contrib/python/ipython/py3/IPython/utils/_sysinfo.py index 44fbbc4530..fbb89d3aa8 100644 --- a/contrib/python/ipython/py3/IPython/utils/_sysinfo.py +++ b/contrib/python/ipython/py3/IPython/utils/_sysinfo.py @@ -1,2 +1,2 @@ # GENERATED BY setup.py -commit = "22d6a1c16" +commit = "56a70e42d" diff --git a/contrib/python/ipython/py3/ya.make b/contrib/python/ipython/py3/ya.make index 950e693736..adc99f5b64 100644 --- a/contrib/python/ipython/py3/ya.make +++ b/contrib/python/ipython/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(8.31.0) +VERSION(8.32.0) LICENSE(BSD-3-Clause) |