From 8358456d38849fe899aa531b38e8f3ca8579fd2c Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 4 Apr 2024 11:54:08 -0400 Subject: [PATCH] refactor: Simplify stateField type Fixes #20 --- src/commands.ts | 10 ++++------ src/completionDecoration.ts | 36 ++++++------------------------------ src/completionRequester.ts | 26 +++++++++++++++++--------- src/plugin.ts | 11 +++++------ src/types.ts | 9 ++++----- 5 files changed, 36 insertions(+), 56 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 01c1a9d..95ff798 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -8,9 +8,8 @@ export function acceptSuggestionCommand(view: EditorView) { // We delete the ghost text and insert the suggestion. // We also set the cursor to the end of the suggestion. const stateField = view.state.field(completionDecoration)!; - const ghostTexts = stateField.ghostTexts; - if (!ghostTexts) { + if (!stateField) { return false; } @@ -51,9 +50,8 @@ export function acceptSuggestionCommand(view: EditorView) { export function rejectSuggestionCommand(view: EditorView) { // We delete the suggestion, then carry through with the original keypress const stateField = view.state.field(completionDecoration)!; - const ghostTexts = stateField.ghostTexts; - if (!ghostTexts?.length) { + if (!stateField) { return false; } @@ -76,9 +74,9 @@ export function rejectSuggestionCommand(view: EditorView) { // TODO: this isn't full reimplemented yet. export function sameKeyCommand(view: EditorView, key: string) { // When we type a key that is the same as the first letter of the suggestion, we delete the first letter of the suggestion and carry through with the original keypress - const ghostTexts = view.state.field(completionDecoration)!.ghostTexts; + const stateField = view.state.field(completionDecoration); - if (!ghostTexts || !ghostTexts.length) { + if (!stateField) { return false; } diff --git a/src/completionDecoration.ts b/src/completionDecoration.ts index 2e24a5a..4c608d5 100644 --- a/src/completionDecoration.ts +++ b/src/completionDecoration.ts @@ -11,7 +11,7 @@ const ghostMark = Decoration.mark({ class: "cm-ghostText" }); export const completionDecoration = StateField.define({ create(_state: EditorState) { - return { ghostTexts: null }; + return null; }, update(state: CompletionState, transaction: Transaction) { for (const effect of transaction.effects) { @@ -19,16 +19,10 @@ export const completionDecoration = StateField.define({ // NOTE: here we're adjusting the decoration range // to refer to locations in the document _after_ we've // inserted the text. - let decorationOffset = 0; const decorations = Decoration.set( effect.value.suggestions.map((suggestion) => { - const endGhostText = - suggestion.startPos + suggestion.displayText.length; - let range = ghostMark.range( - decorationOffset + suggestion.startPos, - decorationOffset + endGhostText, - ); - decorationOffset += suggestion.displayText.length; + console.log(suggestion.startPos, suggestion.endPos); + let range = ghostMark.range(suggestion.startPos, suggestion.endPos); return range; }), ); @@ -37,29 +31,11 @@ export const completionDecoration = StateField.define({ return { decorations, reverseChangeSet: effect.value.reverseChangeSet, - ghostTexts: effect.value.suggestions.map((suggestion) => { - const endGhostText = - suggestion.cursorPos + suggestion.displayText.length; - return { - text: suggestion.text, - displayText: suggestion.text, - startPos: suggestion.startPos, - endPos: suggestion.endPos, - decorations, - // TODO: what's the difference between this - // and startPos? - displayPos: suggestion.cursorPos, - endReplacement: suggestion.endReplacement, - endGhostText, - }; - }), }; } else if (effect.is(acceptSuggestion)) { - if (state.ghostTexts) { - return { ghostTexts: null }; - } + return null; } else if (effect.is(clearSuggestion)) { - return { ghostTexts: null }; + return null; } } @@ -67,6 +43,6 @@ export const completionDecoration = StateField.define({ }, provide: (field) => EditorView.decorations.from(field, (value) => { - return value.decorations || Decoration.none; + return value?.decorations || Decoration.none; }), }); diff --git a/src/completionRequester.ts b/src/completionRequester.ts index 6e2e3e6..7c34782 100644 --- a/src/completionRequester.ts +++ b/src/completionRequester.ts @@ -39,7 +39,7 @@ function shouldIgnoreUpdate(update: ViewUpdate) { if (!update.view.hasFocus) return true; // contains ghost text - if (update.state.field(completionDecoration).ghostTexts != null) return true; + if (update.state.field(completionDecoration)) return true; // is autocompleting if (completionStatus(update.state) === "active") return true; @@ -121,18 +121,26 @@ export function completionRequester() { const reverseChangeSet = insertChangeSet.invert(state.doc); + let combinedOffset = 0; update.view.dispatch({ changes: insertChangeSet, effects: addSuggestions.of({ reverseChangeSet, - suggestions: simplifyCompletions(completionResult).map((part) => ({ - displayText: part.text, - endReplacement: 0, // "", - text: part.text, - cursorPos: pos, - startPos: Number(part.offset), - endPos: Number(part.offset) + part.text.length, - })), + suggestions: simplifyCompletions(completionResult).map((part) => { + try { + return { + displayText: part.text, + endReplacement: 0, // "", + text: part.text, + cursorPos: pos, + startPos: combinedOffset + Number(part.offset), + endPos: + combinedOffset + Number(part.offset) + part.text.length, + }; + } finally { + combinedOffset += part.text.length; + } + }), }), annotations: [ copilotIgnore.of(null), diff --git a/src/plugin.ts b/src/plugin.ts index 2a800f0..b79c30e 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -14,12 +14,11 @@ import { copilotIgnore } from "./annotations.js"; function isDecorationClicked(view: EditorView) { let inRange = false; const head = view.state.selection.asSingle().ranges.at(0)?.head; - if (head !== undefined) { - view.state - .field(completionDecoration) - .decorations?.between(head, head, () => { - inRange = true; - }); + const stateField = view.state.field(completionDecoration); + if (head !== undefined && stateField) { + stateField.decorations?.between(head, head, () => { + inRange = true; + }); return inRange; } return false; diff --git a/src/types.ts b/src/types.ts index 835331d..034cab6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,11 +10,10 @@ export interface Suggestion { endReplacement: number; } -export interface CompletionState { - ghostTexts: GhostText[] | null; - reverseChangeSet?: ChangeSet; - decorations?: DecorationSet; -} +export type CompletionState = null | { + reverseChangeSet: ChangeSet; + decorations: DecorationSet; +}; export interface GhostText { text: string;