Skip to content

fix(autocomplete): apply the replacement range of matches given to the popup - #6003

Open
boogie wants to merge 1 commit into
ajaxorg:masterfrom
boogie:fix/completion-range-without-initial-position
Open

fix(autocomplete): apply the replacement range of matches given to the popup#6003
boogie wants to merge 1 commit into
ajaxorg:masterfrom
boogie:fix/completion-range-without-initial-position

Conversation

@boogie

@boogie boogie commented Aug 14, 2026

Copy link
Copy Markdown

Problem

Inserting a completion that carries a range throws when the popup was opened with a prepared list of matches:

var editor = ace.edit(el, {enableBasicAutocompletion: true, value: "abcdefGHI"});
editor.moveCursorTo(0, 3);
editor.execCommand("startAutocomplete", {
    matches: [{value: "xyz", range: new Range(0, 0, 0, 6)}]
});
// press Enter
TypeError: Cannot read properties of undefined (reading 'prefix')
    at CompletionProvider.insertMatch (src/autocomplete.js:889)

The options.matches branch of updateCompletions builds the CompletionProvider without an initial position, and insertMatch reads this.initialPosition.prefix when converting a completion's range into the span to replace. The completion never makes it into the document.

range is part of the completion API and is what language server integrations use to honour a textEdit, so this path is reachable for anyone feeding Ace prepared matches.

Change

Give the provider the position the popup was opened at.

The position is taken from the cursor rather than from the start of the selection. Ranges are applied as offsets around that reference point, and the regular completion path uses the cursor, so anchoring on the start of a non-empty selection would widen the replacement by the length of the selection. With the fix both paths agree:

before after
matches path, collapsed cursor throws replaces the range
matches path, non-empty selection throws replaces the range
regular path, collapsed cursor replaces the range unchanged
regular path, non-empty selection replaces the range unchanged

The range handling is also guarded on the initial position, since CompletionProvider is exported and can be constructed directly.

Out of scope, unchanged by this PR: ranges spanning more than one row are still ignored by the surrounding condition, on this path as on any other.

Test

test: replacement range of matches passed to execCommand is applied covers a range that extends on both sides of the cursor, and a non-empty selection that must not widen the replacement. Without the change the first case throws; anchoring on the selection start instead of the cursor fails the second with 'xyz' == 'xyzGHI'.

Open kitchen-sink @ a3e40f4ae27a7f0d62c253a4a0be8a2430a12be1

…e popup

Opening the popup with a prepared list of matches, the way `startAutocomplete`
does when it is given a `matches` option, builds the completion provider
without an initial position. Inserting a completion that carries a `range`
then reads `prefix` off that missing position and throws a TypeError, so the
completion never makes it into the document.

Give the provider the position the popup was opened at, taken from the cursor
rather than from the start of the selection: ranges are applied as offsets
around that reference point, and the regular path uses the cursor, so
anchoring on the start of a non-empty selection would widen the replacement by
the length of the selection. Guard the range handling as well, since the
provider is exported and can be constructed without an initial position.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant