Conversation
…s per request Two changes on the inline completion path. The Tab shortcut for inline-completer:accept used the selector .jp-Notebook.jp-mod-editMode .jp-mod-completer-enabled. That class means only that the completer is available in this editor, which is true of every code cell in edit mode. JupyterLab gates its own Tab binding for the same command on .jp-mod-inline-completer-active, the class it sets while a suggestion is on screen. This adopts that guard, so Tab accepts a suggestion when there is one to accept and indents otherwise. The guard matters because accept() replaces the text between the cursor recorded when the completion was requested and the cursor as it stands now, then moves the cursor to the end of what it inserted. Anything typed in between sits inside that replaced range. The provider also identified its own response by reading _lastRequestInfo, a single field that the next keystroke overwrites. A callback reading it is asking about whichever request started most recently rather than about itself, so a slow answer could be matched against a newer request, and a mismatch called reject() with no argument, which the completer logs as a warning with nothing in it. The request identity is now captured per call and the decision lives in a small predicate that can be tested directly. Scope worth stating: this narrows when Tab can reach the accept path and removes the shared-state ambiguity around late responses. The exact sequence in the report has not been reproduced locally, since doing so needs an inline completion model configured.
|
The The details, in case they are useful. The same job on #440 failed in the same window with A durable fix would be moving the devDependency from |
The previous check_release run failed on a transient GitHub API rate limit while resolving @jupyterlab/core-meta, unrelated to this change.
Closes #442
The report
The reporter recorded a short video of typing in a cell and the text not taking. Stepping through it at 15 frames per second:
inp,inpu,inputs,inpu,inpu[,inpu[1,inpu], and the cell finally executes asinpu, producingNameError: name inpu is not defined.Ln 1, Col 5in every frame across roughly six seconds, while the text on screen changes five times. The cursor column never advances.Why Tab is involved
JupyterLab applies an accepted inline suggestion by replacing the range from the cursor recorded when the completion was requested up to the cursor as it stands now, then moving the cursor to the end of the inserted text (
InlineCompleter.acceptin@jupyterlab/completer). Anything typed after the request went out sits inside that replaced range.The Tab shortcut for
inline-completer:acceptused the selector.jp-Notebook.jp-mod-editMode .jp-mod-completer-enabled. That class means only that the completer is available in this editor, which is true of every code cell in edit mode. JupyterLab gates its own Tab binding for the same command on.jp-mod-inline-completer-active, the class it sets while a suggestion is actually on screen. This change adopts that guard, so Tab accepts a suggestion when there is one and indents otherwise.Response identity
The provider also identified its own response by reading
_lastRequestInfo, a single field that the next keystroke overwrites. A callback reading that field is asking about whichever request started most recently rather than about itself, so a slow answer could be compared against a newer request. A mismatch then calledreject()with no argument, which the completer surfaces asconsole.warn(undefined).Request identity is now captured per call, and the decision moved into
classifyInlineCompletionResponseso it can be tested without standing up the extension. A superseded or empty request resolves with no items instead of rejecting.Scope, stated plainly
This narrows when Tab can reach the accept path and removes the shared-state ambiguity around late responses. I was not able to reproduce the exact sequence in the report locally, because doing so needs an inline completion model configured, and with no provider configured there is never a suggestion to accept. So this is a narrowing plus a correctness fix rather than a demonstrated cure, and the reporter has been asked for versions, the configured model, and console output to confirm it.
Two observations from the investigation that are not addressed here:
jupyter labextension listreports the extension as incompatible with JupyterLab 4.6 because of the@jupyterlab/launcherrange being~4.2.0. The pin was deliberate, for a build-timeToken<ILauncher>type mismatch, but the same field advertises runtime compatibility. The extension does load and work on 4.6 in testing. Worth separating the dev pin from the advertised range in its own change.TypeErrorfrom a CodeMirror decoration height measurement insidedispatchTransactionsappeared once and never recurred.Tests
tests/ts/inline-completion-request.test.ts: 7 tests over the predicate, covering ownership, supersede, stream end, and ordering of those checks.tests/test_inline_completion_shortcuts.py: 4 tests over the shipped schema, asserting positively that a guarded notebook Tab binding exists and that no Tab binding accepts on the merely-enabled class.Verified: pytest 1833 passed, tsc clean, jest 430 passed, eslint and prettier clean. Six mutants all fail a named test, including reverting the selector, deleting the binding, and removing either the ownership or the supersede check. The deletion mutant initially survived, which is why the schema test asserts the guarded binding exists rather than only that no unguarded one does.