summaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython/py3/IPython/terminal/shortcuts
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-11-15 14:03:29 +0300
committerrobot-piglet <[email protected]>2025-11-15 14:12:49 +0300
commitd35be2771487b4b96c52b23a0a8f85038cfcf3a0 (patch)
tree3920c72c44d46836c533e35937e712045e6c13a6 /contrib/python/ipython/py3/IPython/terminal/shortcuts
parent51b9f2ff639f425a192ae22e7666f264ecacca51 (diff)
Intermediate changes
commit_hash:7416cece282d94989ec949091de6e6674136bb5b
Diffstat (limited to 'contrib/python/ipython/py3/IPython/terminal/shortcuts')
-rw-r--r--contrib/python/ipython/py3/IPython/terminal/shortcuts/__init__.py9
-rw-r--r--contrib/python/ipython/py3/IPython/terminal/shortcuts/auto_suggest.py127
2 files changed, 84 insertions, 52 deletions
diff --git a/contrib/python/ipython/py3/IPython/terminal/shortcuts/__init__.py b/contrib/python/ipython/py3/IPython/terminal/shortcuts/__init__.py
index ba6d4055262..7b2dd8885b4 100644
--- a/contrib/python/ipython/py3/IPython/terminal/shortcuts/__init__.py
+++ b/contrib/python/ipython/py3/IPython/terminal/shortcuts/__init__.py
@@ -24,9 +24,9 @@ from prompt_toolkit.key_binding.vi_state import InputMode, ViState
from prompt_toolkit.filters import Condition
from IPython.core.getipython import get_ipython
-from IPython.terminal.shortcuts import auto_match as match
-from IPython.terminal.shortcuts import auto_suggest
-from IPython.terminal.shortcuts.filters import filter_from_string
+from . import auto_match as match
+from . import auto_suggest
+from .filters import filter_from_string
from IPython.utils.decorators import undoc
from prompt_toolkit.enums import DEFAULT_BUFFER
@@ -203,7 +203,7 @@ AUTO_SUGGEST_BINDINGS = [
Binding(
auto_suggest.accept,
["right"],
- "has_suggestion & default_buffer_focused & emacs_like_insert_mode",
+ "has_suggestion & default_buffer_focused & emacs_like_insert_mode & is_cursor_at_the_end_of_line",
),
Binding(
auto_suggest.accept_word,
@@ -630,6 +630,7 @@ KEY_BINDINGS = [
]
UNASSIGNED_ALLOWED_COMMANDS = [
+ auto_suggest.llm_autosuggestion,
nc.beginning_of_buffer,
nc.end_of_buffer,
nc.end_of_line,
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 bcba5622e49..81737c50943 100644
--- a/contrib/python/ipython/py3/IPython/terminal/shortcuts/auto_suggest.py
+++ b/contrib/python/ipython/py3/IPython/terminal/shortcuts/auto_suggest.py
@@ -9,7 +9,7 @@ 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, AutoSuggest
+from prompt_toolkit.auto_suggest import AutoSuggestFromHistory, Suggestion
from prompt_toolkit.document import Document
from prompt_toolkit.history import History
from prompt_toolkit.shortcuts import PromptSession
@@ -24,12 +24,6 @@ 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]
@@ -109,29 +103,32 @@ class AppendAutoSuggestionInAnyLine(Processor):
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 prompt_toolkit.VERSION < (3, 0, 49):
+ 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)
+ elif len(suggestions) == 1:
+ if ti.lineno == ti.document.cursor_position_row:
+ return Transformation(
+ fragments=ti.fragments + [(self.style, suggestions[0])]
+ )
+ return Transformation(fragments=ti.fragments)
- 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)])
@@ -170,19 +167,24 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
_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
+ # current buffer; we keep a handle to it in order to cancel 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
+ # This is the constructor of the LLM provider from jupyter-ai
+ # to which we forward the request to generate inline completions.
+ _init_llm_provider: Callable | None
+
+ _llm_provider_instance: Any | None
+ _llm_prefixer: Callable = lambda self, x: "wrong"
def __init__(self):
super().__init__()
self.skip_lines = 0
self._connected_apps = []
- self._llm_provider = None
+ self._llm_provider_instance = None
+ self._init_llm_provider = None
+ self._request_number = 0
def reset_history_position(self, _: Buffer):
self.skip_lines = 0
@@ -304,7 +306,7 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
def _cancel_running_llm_task(self) -> None:
"""
- Try to cancell the currently running llm_task if exists, and set it to None.
+ Try to cancel the currently running llm_task if exists, and set it to None.
"""
if self._llm_task is not None:
if self._llm_task.done():
@@ -318,6 +320,16 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
"LLM task not cancelled, does your provider support cancellation?"
)
+ @property
+ def _llm_provider(self):
+ """Lazy-initialized instance of the LLM provider.
+
+ Do not use in the constructor, as `_init_llm_provider` can trigger slow side-effects.
+ """
+ if self._llm_provider_instance is None and self._init_llm_provider:
+ self._llm_provider_instance = self._init_llm_provider()
+ return self._llm_provider_instance
+
async def _trigger_llm(self, buffer) -> None:
"""
This will ask the current llm provider a suggestion for the current buffer.
@@ -325,13 +337,15 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
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.
+ try:
+ import jupyter_ai_magics
+ except ModuleNotFoundError:
+ jupyter_ai_magics = None
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"
- )
+ if jupyter_ai_magics is None:
+ warnings.warn("LLM Completion requires `jupyter_ai_magics` to be installed")
self._cancel_running_llm_task()
@@ -343,10 +357,10 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
try:
await self._trigger_llm_core(buffer)
except Exception as e:
- get_ipython().log.error("error")
+ get_ipython().log.error("error %s", e)
raise
- # here we need a cancellable task so we can't just await the error catched
+ # here we need a cancellable task so we can't just await the error caught
self._llm_task = asyncio.create_task(error_catcher(buffer))
await self._llm_task
@@ -358,11 +372,10 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
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.
+ Unlike with JupyterAi, as we do not have multiple cells, the cell id
+ is always set to `None`.
- We set the prefix to the current cell content, but could also inset the
+ We set the prefix to the current cell content, but could also insert the
rest of the history or even just the non-fail history.
In the same way, we do not have cell id.
@@ -374,11 +387,28 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
stream_inline_completions, I'm not sure it is the case for all
providers.
"""
+ try:
+ import jupyter_ai.completions.models as jai_models
+ except ModuleNotFoundError:
+ jai_models = None
+
+ if not jai_models:
+ raise ValueError("jupyter-ai is not installed")
+
+ if not self._llm_provider:
+ raise ValueError("No LLM provider found, cannot trigger LLM completions")
+
+ hm = buffer.history.shell.history_manager
+ prefix = self._llm_prefixer(hm)
+ get_ipython().log.debug("prefix: %s", prefix)
+
+ self._request_number += 1
+ request_number = self._request_number
request = jai_models.InlineCompletionRequest(
- number=0,
- prefix=buffer.document.text,
- suffix="",
+ number=request_number,
+ prefix=prefix + buffer.document.text_before_cursor,
+ suffix=buffer.document.text_after_cursor,
mime="text/x-python",
stream=True,
path=None,
@@ -389,6 +419,9 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
async for reply_and_chunks in self._llm_provider.stream_inline_completions(
request
):
+ if self._request_number != request_number:
+ # If a new suggestion was requested, skip processing this one.
+ return
if isinstance(reply_and_chunks, jai_models.InlineCompletionReply):
if len(reply_and_chunks.list.items) > 1:
raise ValueError(
@@ -404,9 +437,6 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory):
return
-_MIN_LINES = 5
-
-
async def llm_autosuggestion(event: KeyPressEvent):
"""
Ask the AutoSuggester from history to delegate to ask an LLM for completion
@@ -414,16 +444,17 @@ async def llm_autosuggestion(event: KeyPressEvent):
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
+ Provisional as of 8.32, may change without warnings
"""
+ _MIN_LINES = 5
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)
+ event.current_buffer.insert_text("\n", move_cursor=False, fire_event=False)
await provider._trigger_llm(event.current_buffer)