diff --git a/demo/index.ts b/demo/index.ts index 0f2bf93..4281232 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -19,6 +19,14 @@ increment('not a number');`, }), copilotPlugin({ apiKey: "d49954eb-cfba-4992-980f-d8fb37f0e942", + otherDocuments: [ + { + absolutePath: "https://esm.town/v/foo.ts", + text: "export const foo = 10;", + language: Language.TYPESCRIPT, + editorLanguage: "typescript", + }, + ], }), ], parent: document.querySelector("#editor")!, diff --git a/src/annotations.ts b/src/annotations.ts index e1e42ea..7d208fe 100644 --- a/src/annotations.ts +++ b/src/annotations.ts @@ -1,3 +1,10 @@ import { Annotation } from "@codemirror/state"; export const copilotEvent = Annotation.define(); + +/** + * Annotation that signals to upstream integrations + * that this transaction should not be included + * in history or treated otherwise as a user edit. + */ +export const copilotIgnore = Annotation.define(); diff --git a/src/codeium.ts b/src/codeium.ts index 167ff87..1a14771 100644 --- a/src/codeium.ts +++ b/src/codeium.ts @@ -49,7 +49,7 @@ export async function getCodeiumCompletions({ tabSize: 2n, insertSpaces: true, }, - otherDocuments: [], + otherDocuments: config.otherDocuments, multilineConfig: undefined, }, { diff --git a/src/commands.ts b/src/commands.ts index 30c375d..01c1a9d 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,6 +1,6 @@ import { Transaction, EditorSelection } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; -import { copilotEvent } from "./annotations.js"; +import { copilotEvent, copilotIgnore } from "./annotations.js"; import { completionDecoration } from "./completionDecoration.js"; import { acceptSuggestion, clearSuggestion } from "./effects.js"; @@ -18,12 +18,20 @@ export function acceptSuggestionCommand(view: EditorView) { view.state.doc, ); - // This is removing the previous ghost text. Don't - // add this to history. + // This is removing the previous ghost text. view.dispatch({ changes: stateField.reverseChangeSet, effects: acceptSuggestion.of(null), - annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)], + annotations: [ + // Tell upstream integrations to ignore this + // change. + copilotIgnore.of(null), + // Tell ourselves not to request a completion + // because of this change. + copilotEvent.of(null), + // Don't add this to history. + Transaction.addToHistory.of(false), + ], }); let lastIndex = 0; @@ -52,7 +60,14 @@ export function rejectSuggestionCommand(view: EditorView) { view.dispatch({ changes: stateField.reverseChangeSet, effects: clearSuggestion.of(null), - annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)], + annotations: [ + // Tell upstream integrations to ignore this + // change. This was never really in the document + // in the first place - we were just showing ghost text. + copilotIgnore.of(null), + copilotEvent.of(null), + Transaction.addToHistory.of(false), + ], }); return false; diff --git a/src/completionRequester.ts b/src/completionRequester.ts index b3fa183..6e2e3e6 100644 --- a/src/completionRequester.ts +++ b/src/completionRequester.ts @@ -12,7 +12,7 @@ import { clearSuggestion, } from "./effects.js"; import { completionDecoration } from "./completionDecoration.js"; -import { copilotEvent } from "./annotations.js"; +import { copilotEvent, copilotIgnore } from "./annotations.js"; import { codeiumConfig } from "./config.js"; /** @@ -135,6 +135,7 @@ export function completionRequester() { })), }), annotations: [ + copilotIgnore.of(null), copilotEvent.of(null), Transaction.addToHistory.of(false), ], diff --git a/src/config.ts b/src/config.ts index 73fb13b..c6dc5a4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,7 @@ import { Facet, combineConfig } from "@codemirror/state"; import { Language } from "./api/proto/exa/codeium_common_pb/codeium_common_pb.js"; +import { Document } from "./api/proto/exa/language_server_pb/language_server_pb.js"; +import { type PartialMessage } from "@bufbuild/protobuf"; export interface CodeiumConfig { /** @@ -14,6 +16,8 @@ export interface CodeiumConfig { timeout?: number; authSource?: number; + + otherDocuments?: PartialMessage[]; } export const codeiumConfig = Facet.define< @@ -26,6 +30,7 @@ export const codeiumConfig = Facet.define< { language: Language.TYPESCRIPT, timeout: 150, + otherDocuments: [], }, {}, ); diff --git a/src/plugin.ts b/src/plugin.ts index 9b6e7d8..537c303 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,4 +1,4 @@ -import { EditorView } from "@codemirror/view"; +import { EditorView, ViewUpdate } from "@codemirror/view"; import { Extension, Prec } from "@codemirror/state"; import { completionDecoration } from "./completionDecoration.js"; import { completionRequester } from "./completionRequester.js"; @@ -9,6 +9,7 @@ import { } from "./commands.js"; import { CodeiumConfig, codeiumConfig } from "./config.js"; import { Language } from "./api/proto/exa/codeium_common_pb/codeium_common_pb.js"; +import { copilotIgnore } from "./annotations.js"; function isDecorationClicked(view: EditorView) { let inRange = false; @@ -67,3 +68,18 @@ export function copilotPlugin(config: CodeiumConfig): Extension { completionRequester(), ]; } + +/** + * Returns false if this ViewUpdate is just the plugin + * adding or removing ghost text, and it should not be + * considered when saving this CodeMirror state into other + * systems, like draft recovery. + */ +export function shouldTakeUpdate(update: ViewUpdate) { + for (const tr of update.transactions) { + if (tr.annotation(copilotIgnore) !== undefined) { + return false; + } + } + return true; +}