diff --git a/demo/index.ts b/demo/index.ts index 7ef532a..db3caed 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -2,12 +2,12 @@ import { EditorView, basicSetup } from "codemirror"; import { javascript } from "@codemirror/lang-javascript"; import { codeiumOtherDocumentsConfig, + startCompletion, Language, copilotPlugin, } from "../src/plugin.js"; import { python } from "@codemirror/lang-python"; import { keymap } from "@codemirror/view"; -import { startCompletion } from "../src/commands.js"; new EditorView({ doc: "// Factorial function", diff --git a/src/commands.ts b/src/commands.ts index 9f9ea13..d80c65a 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -7,7 +7,7 @@ import { addSuggestions, clearSuggestion, } from "./effects.js"; -import { requestCompletion } from "./completionRequester.js"; +import { requestCompletion } from "./requestCompletion.js"; /** * Accepting a suggestion: we remove the ghost text, which diff --git a/src/completionRequester.ts b/src/completionRequester.ts index 3adf537..9488bef 100644 --- a/src/completionRequester.ts +++ b/src/completionRequester.ts @@ -1,15 +1,10 @@ import { CompletionContext, completionStatus } from "@codemirror/autocomplete"; -import { ChangeSet, Transaction } from "@codemirror/state"; import { EditorView, type ViewUpdate } from "@codemirror/view"; -import { completionsToChangeSpec, getCodeiumCompletions } from "./codeium.js"; -import { - acceptSuggestion, - addSuggestions, - clearSuggestion, -} from "./effects.js"; +import { acceptSuggestion, clearSuggestion } from "./effects.js"; import { completionDecoration } from "./completionDecoration.js"; -import { copilotEvent, copilotIgnore } from "./annotations.js"; -import { codeiumConfig, codeiumOtherDocumentsConfig } from "./config.js"; +import { copilotEvent } from "./annotations.js"; +import { codeiumConfig } from "./config.js"; +import { requestCompletion } from "./requestCompletion.js"; /** * To request a completion, the document needs to have been @@ -48,80 +43,6 @@ function shouldIgnoreUpdate(update: ViewUpdate) { } } -/** - * Inner 'requestCompletion' API, which can optionally - * be run all the time if you set `alwaysOn` - */ -export async function requestCompletion(view: EditorView, lastPos?: number) { - const config = view.state.facet(codeiumConfig); - const { override } = view.state.facet(codeiumOtherDocumentsConfig); - - const otherDocuments = await override(); - - // Get the current position and source - const state = view.state; - const pos = state.selection.main.head; - const source = state.doc.toString(); - - // Request completion from the server - try { - const completionResult = await getCodeiumCompletions({ - text: source, - cursorOffset: pos, - config, - otherDocuments, - }); - - if (!completionResult || completionResult.completionItems.length === 0) { - return; - } - - // Check if the position is still the same. If - // it has changed, ignore the code that we just - // got from the API and don't show anything. - if ( - !( - (lastPos === undefined || pos === lastPos) && - completionStatus(view.state) !== "active" && - view.hasFocus - ) - ) { - return; - } - - // Dispatch an effect to add the suggestion - // If the completion starts before the end of the line, - // check the end of the line with the end of the completion - const changeSpecs = completionsToChangeSpec(completionResult); - - const index = 0; - const firstSpec = changeSpecs.at(index); - if (!firstSpec) return; - const insertChangeSet = ChangeSet.of(firstSpec, state.doc.length); - const reverseChangeSet = insertChangeSet.invert(state.doc); - - view.dispatch({ - changes: insertChangeSet, - effects: addSuggestions.of({ - index, - reverseChangeSet, - changeSpecs, - }), - annotations: [ - copilotIgnore.of(null), - copilotEvent.of(null), - Transaction.addToHistory.of(false), - ], - }); - } catch (error) { - console.warn("copilot completion failed", error); - // Javascript wait for 500ms for some reason is necessary here. - // TODO - FIGURE OUT WHY THIS RESOLVES THE BUG - - await new Promise((resolve) => setTimeout(resolve, 300)); - } -} - /** * A view plugin that requests completions from the server after a delay */ diff --git a/src/plugin.ts b/src/plugin.ts index 413938a..e245cc0 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -7,6 +7,7 @@ import { rejectSuggestionCommand, acceptSuggestionCommand, nextSuggestionCommand, + startCompletion, } from "./commands.js"; import { type CodeiumConfig, @@ -112,6 +113,7 @@ export { codeiumConfig, codeiumOtherDocumentsConfig, nextSuggestionCommand, + startCompletion, type CodeiumOtherDocumentsConfig, type CodeiumConfig, }; diff --git a/src/requestCompletion.ts b/src/requestCompletion.ts new file mode 100644 index 0000000..3cb57a3 --- /dev/null +++ b/src/requestCompletion.ts @@ -0,0 +1,81 @@ +import { completionStatus } from "@codemirror/autocomplete"; +import { ChangeSet, Transaction } from "@codemirror/state"; +import type { EditorView } from "@codemirror/view"; +import { completionsToChangeSpec, getCodeiumCompletions } from "./codeium.js"; +import { addSuggestions } from "./effects.js"; +import { copilotEvent, copilotIgnore } from "./annotations.js"; +import { codeiumConfig, codeiumOtherDocumentsConfig } from "./config.js"; + +/** + * Inner 'requestCompletion' API, which can optionally + * be run all the time if you set `alwaysOn` + */ +export async function requestCompletion(view: EditorView, lastPos?: number) { + const config = view.state.facet(codeiumConfig); + const { override } = view.state.facet(codeiumOtherDocumentsConfig); + + const otherDocuments = await override(); + + // Get the current position and source + const state = view.state; + const pos = state.selection.main.head; + const source = state.doc.toString(); + + // Request completion from the server + try { + const completionResult = await getCodeiumCompletions({ + text: source, + cursorOffset: pos, + config, + otherDocuments, + }); + + if (!completionResult || completionResult.completionItems.length === 0) { + return; + } + + // Check if the position is still the same. If + // it has changed, ignore the code that we just + // got from the API and don't show anything. + if ( + !( + (lastPos === undefined || pos === lastPos) && + completionStatus(view.state) !== "active" && + view.hasFocus + ) + ) { + return; + } + + // Dispatch an effect to add the suggestion + // If the completion starts before the end of the line, + // check the end of the line with the end of the completion + const changeSpecs = completionsToChangeSpec(completionResult); + + const index = 0; + const firstSpec = changeSpecs.at(index); + if (!firstSpec) return; + const insertChangeSet = ChangeSet.of(firstSpec, state.doc.length); + const reverseChangeSet = insertChangeSet.invert(state.doc); + + view.dispatch({ + changes: insertChangeSet, + effects: addSuggestions.of({ + index, + reverseChangeSet, + changeSpecs, + }), + annotations: [ + copilotIgnore.of(null), + copilotEvent.of(null), + Transaction.addToHistory.of(false), + ], + }); + } catch (error) { + console.warn("copilot completion failed", error); + // Javascript wait for 500ms for some reason is necessary here. + // TODO - FIGURE OUT WHY THIS RESOLVES THE BUG + + await new Promise((resolve) => setTimeout(resolve, 300)); + } +}