diff --git a/demo/index.html b/demo/index.html index 9e08e8b..bbd943d 100644 --- a/demo/index.html +++ b/demo/index.html @@ -24,6 +24,9 @@

TypeScript AI autocompletion

Python AI autocompletion

+ +

TypeScript AI autocompletion (cmd+k to trigger completion)

+
diff --git a/demo/index.ts b/demo/index.ts index fbb4e25..7ef532a 100644 --- a/demo/index.ts +++ b/demo/index.ts @@ -6,6 +6,8 @@ import { 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", @@ -41,6 +43,47 @@ const hiddenValue = "https://macwright.com/"`, parent: document.querySelector("#editor")!, }); +new EditorView({ + doc: "// Factorial function (explicit trigger)", + extensions: [ + basicSetup, + javascript({ + typescript: true, + jsx: true, + }), + codeiumOtherDocumentsConfig.of({ + override: () => [ + { + absolutePath: "https://esm.town/v/foo.ts", + text: `export const foo = 10; + +const hiddenValue = "https://macwright.com/"`, + language: Language.TYPESCRIPT, + editorLanguage: "typescript", + }, + ], + }), + copilotPlugin({ + apiKey: "d49954eb-cfba-4992-980f-d8fb37f0e942", + shouldComplete(context) { + if (context.tokenBefore(["String"])) { + return true; + } + const match = context.matchBefore(/(@(?:\w*))(?:[./](\w*))?/); + return !match; + }, + alwaysOn: false, + }), + keymap.of([ + { + key: "Cmd-k", + run: startCompletion, + }, + ]), + ], + parent: document.querySelector("#editor-explicit")!, +}); + new EditorView({ doc: "def hi_python():", extensions: [ diff --git a/src/commands.ts b/src/commands.ts index 4a6403a..9f9ea13 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -7,6 +7,7 @@ import { addSuggestions, clearSuggestion, } from "./effects.js"; +import { requestCompletion } from "./completionRequester.js"; /** * Accepting a suggestion: we remove the ghost text, which @@ -174,3 +175,8 @@ export function sameKeyCommand(view: EditorView, key: string) { } return rejectSuggestionCommand(view); } + +export const startCompletion: Command = (view: EditorView) => { + requestCompletion(view); + return true; +}; diff --git a/src/completionRequester.ts b/src/completionRequester.ts index 6dd6874..3adf537 100644 --- a/src/completionRequester.ts +++ b/src/completionRequester.ts @@ -48,14 +48,18 @@ function shouldIgnoreUpdate(update: ViewUpdate) { } } -async function requestCompletion(update: ViewUpdate, lastPos?: number) { - const config = update.view.state.facet(codeiumConfig); - const { override } = update.view.state.facet(codeiumOtherDocumentsConfig); +/** + * 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 = update.state; + const state = view.state; const pos = state.selection.main.head; const source = state.doc.toString(); @@ -78,8 +82,8 @@ async function requestCompletion(update: ViewUpdate, lastPos?: number) { if ( !( (lastPos === undefined || pos === lastPos) && - completionStatus(update.view.state) !== "active" && - update.view.hasFocus + completionStatus(view.state) !== "active" && + view.hasFocus ) ) { return; @@ -96,7 +100,7 @@ async function requestCompletion(update: ViewUpdate, lastPos?: number) { const insertChangeSet = ChangeSet.of(firstSpec, state.doc.length); const reverseChangeSet = insertChangeSet.invert(state.doc); - update.view.dispatch({ + view.dispatch({ changes: insertChangeSet, effects: addSuggestions.of({ index, @@ -160,7 +164,7 @@ export function completionRequester() { // Check if the position has changed if (pos !== lastPos) return; - await requestCompletion(update, lastPos); + await requestCompletion(update.view, lastPos); }, config.timeout); // Update the last position