@valtown/codemirror-codeium
Version:
codemirror integration for codeium
120 lines • 4.93 kB
JavaScript
import { completionStatus } from "@codemirror/autocomplete";
import { ChangeSet, Transaction } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { simplifyCompletions, completionsToChangeSpec, getCodeiumCompletions, } from "./codeium.js";
import { acceptSuggestion, addSuggestions, clearSuggestion, } from "./effects.js";
import { completionDecoration } from "./completionDecoration.js";
import { copilotEvent } from "./annotations.js";
import { codeiumConfig } from "./config.js";
/**
* To request a completion, the document needs to have been
* updated and the update should not have been because
* of accepting or clearing a suggestion.
*/
function shouldRequestCompletion(update) {
return (update.docChanged &&
!update.transactions.some((tr) => tr.effects.some((e) => e.is(acceptSuggestion) || e.is(clearSuggestion))));
}
/**
* Don't request a completion if we've already
* done so, or it's a copilot event we're responding
* to, or if the view is not focused.
*/
function shouldIgnoreUpdate(update) {
// not focused
if (!update.view.hasFocus)
return true;
// contains ghost text
if (update.state.field(completionDecoration).ghostTexts != null)
return true;
// is autocompleting
if (completionStatus(update.state) === "active")
return true;
// bad update
for (const tr of update.transactions) {
if (tr.annotation(copilotEvent) !== undefined) {
return true;
}
}
}
/**
* A view plugin that requests completions from the server after a delay
*/
export function completionRequester() {
let timeout = null;
let lastPos = 0;
return EditorView.updateListener.of((update) => {
const config = update.view.state.facet(codeiumConfig);
if (!shouldRequestCompletion(update))
return;
// Cancel the previous timeout
if (timeout) {
clearTimeout(timeout);
}
if (shouldIgnoreUpdate(update)) {
return;
}
// Get the current position and source
const state = update.state;
const pos = state.selection.main.head;
const source = state.doc.toString();
// Set a new timeout to request completion
timeout = setTimeout(async () => {
// Check if the position has changed
if (pos !== lastPos)
return;
// Request completion from the server
try {
const completionResult = await getCodeiumCompletions({
text: source,
cursorOffset: pos,
config,
});
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 (!(pos === lastPos &&
completionStatus(update.view.state) !== "active" &&
update.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 insertChangeSet = ChangeSet.of(completionsToChangeSpec(completionResult), state.doc.length);
const reverseChangeSet = insertChangeSet.invert(state.doc);
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,
})),
}),
annotations: [
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));
}
}, config.timeout);
// Update the last position
lastPos = pos;
});
}
//# sourceMappingURL=completionRequester.js.map