From ee22fa2e156333399b99c004db3ae90f70e1e870 Mon Sep 17 00:00:00 2001 From: Marijn Haverbeke Date: Mon, 4 Mar 2024 15:51:55 +0100 Subject: [PATCH] Add support for CompletionResult.map FEATURE: Completion results can now define a `map` method that can be used to adjust position-dependent information for document changes. Issue https://github.com/codemirror/dev/issues/1347 --- src/completion.ts | 8 +++++++- src/state.ts | 24 ++++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/completion.ts b/src/completion.ts index f0cdcbf..a1bb0fb 100644 --- a/src/completion.ts +++ b/src/completion.ts @@ -1,5 +1,5 @@ import {EditorView} from "@codemirror/view" -import {EditorState, StateEffect, Annotation, EditorSelection, TransactionSpec} from "@codemirror/state" +import {EditorState, StateEffect, Annotation, EditorSelection, TransactionSpec, ChangeDesc} from "@codemirror/state" import {syntaxTree} from "@codemirror/language" import {SyntaxNode} from "@lezer/common" @@ -232,6 +232,12 @@ export interface CompletionResult { /// [`validFor`](#autocomplete.CompletionResult.validFor)) that the /// completion still applies in the new state. update?: (current: CompletionResult, from: number, to: number, context: CompletionContext) => CompletionResult | null + /// When results contain position-dependent information in, for + /// example, `apply` methods, you can provide this method to update + /// the result for transactions that happen after the query. It is + /// not necessary to update `from` and `to`—those are tracked + /// automatically. + map?: (current: CompletionResult, changes: ChangeDesc) => CompletionResult | null /// Set a default set of [commit /// characters](#autocomplete.Completion.commitCharacters) for all /// options in this result. diff --git a/src/state.ts b/src/state.ts index 9fc10aa..5867976 100644 --- a/src/state.ts +++ b/src/state.ts @@ -240,18 +240,20 @@ export class ActiveResult extends ActiveSource { hasResult(): this is ActiveResult { return true } handleUserEvent(tr: Transaction, type: "input" | "delete", conf: Required): ActiveSource { + let result = this.result as CompletionResult | null + if (result!.map && !tr.changes.empty) result = result!.map(result!, tr.changes) let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1) let pos = cur(tr.state) if ((this.explicitPos < 0 ? pos <= from : pos < this.from) || - pos > to || + pos > to || !result || type == "delete" && cur(tr.startState) == this.from) return new ActiveSource(this.source, type == "input" && conf.activateOnTyping ? State.Pending : State.Inactive) - let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated - if (checkValid(this.result.validFor, tr.state, from, to)) - return new ActiveResult(this.source, explicitPos, this.result, from, to) - if (this.result.update && - (updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0)))) - return new ActiveResult(this.source, explicitPos, updated, updated.from, updated.to ?? cur(tr.state)) + let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos) + if (checkValid(result.validFor, tr.state, from, to)) + return new ActiveResult(this.source, explicitPos, result, from, to) + if (result.update && + (result = result.update(result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0)))) + return new ActiveResult(this.source, explicitPos, result, result.from, result.to ?? cur(tr.state)) return new ActiveSource(this.source, State.Pending, explicitPos) } @@ -260,9 +262,11 @@ export class ActiveResult extends ActiveSource { } map(mapping: ChangeDesc) { - return mapping.empty ? this : - new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, - mapping.mapPos(this.from), mapping.mapPos(this.to, 1)) + if (mapping.empty) return this + let result = this.result.map ? this.result.map(this.result, mapping) : this.result + if (!result) return new ActiveSource(this.source, State.Inactive) + return new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, + mapping.mapPos(this.from), mapping.mapPos(this.to, 1)) } }