UNPKG

@valtown/codemirror-codeium

Version:

codemirror integration for codeium

148 lines 5.54 kB
import { Transaction, EditorSelection, ChangeSet } from "@codemirror/state"; import { copilotEvent, copilotIgnore } from "./annotations.js"; import { completionDecoration } from "./completionDecoration.js"; import { acceptSuggestion, addSuggestions, clearSuggestion, } from "./effects.js"; import { requestCompletion } from "./requestCompletion.js"; /** * Accepting a suggestion: we remove the ghost text, which * was not part of CodeMirror history, and then re-add it, * making sure that it _is_ added to history, and we remove * the Decoration that was making that ghost text look ghostly. */ export function acceptSuggestionCommand(view) { // 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); if (!stateField) { return false; } const reverseReverseChangeSet = stateField.reverseChangeSet?.invert(view.state.doc); // This is removing the previous ghost text. view.dispatch({ changes: stateField.reverseChangeSet, effects: acceptSuggestion.of(null), 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; reverseReverseChangeSet?.iterChangedRanges((_fromA, _toA, _fromB, toB) => { lastIndex = toB; }); view.dispatch({ changes: reverseReverseChangeSet, selection: EditorSelection.cursor(lastIndex), annotations: [copilotEvent.of(null), Transaction.addToHistory.of(true)], }); return true; } /** * Cycle through suggested AI completed code. */ export const nextSuggestionCommand = (view) => { const { state } = view; // We delete the suggestion, then carry through with the original keypress const stateField = state.field(completionDecoration); if (!stateField) { return false; } const { changeSpecs } = stateField; if (changeSpecs.length < 2) { return false; } // Loop through next suggestion. const index = (stateField.index + 1) % changeSpecs.length; const nextSpec = changeSpecs.at(index); if (!nextSpec) { return false; } /** * First, get the original document, by applying the stored * reverse changeset against the currently-displayed document. */ const originalDocument = stateField.reverseChangeSet.apply(state.doc); /** * Get the changeset that we will apply that will * * 1. Reverse the currently-displayed suggestion, to get us back to * the original document * 2. Apply the next suggestion. * * It does both in the same changeset, so there is no flickering. */ const reverseCurrentSuggestionAndApplyNext = ChangeSet.of(stateField.reverseChangeSet, state.doc.length).compose(ChangeSet.of(nextSpec, originalDocument.length)); /** * Generate the next changeset */ const nextSet = ChangeSet.of(nextSpec, originalDocument.length); const reverseChangeSet = nextSet.invert(originalDocument); view.dispatch({ changes: reverseCurrentSuggestionAndApplyNext, effects: addSuggestions.of({ index, reverseChangeSet, changeSpecs, }), 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), ], }); return true; }; /** * Rejecting a suggestion: this looks at the currently-shown suggestion * and reverses it, clears the suggestion, and makes sure * that we don't add that clearing transaction to history and we don't * trigger a new suggestion because of it. */ export function rejectSuggestionCommand(view) { // We delete the suggestion, then carry through with the original keypress const stateField = view.state.field(completionDecoration); if (!stateField) { return false; } view.dispatch({ changes: stateField.reverseChangeSet, effects: clearSuggestion.of(null), 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; } // TODO: this isn't full reimplemented yet. export function sameKeyCommand(view, key) { // 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 stateField = view.state.field(completionDecoration); if (!stateField) { return false; } if (key === "Tab") { return acceptSuggestionCommand(view); } return rejectSuggestionCommand(view); } export const startCompletion = (view) => { requestCompletion(view); return true; }; //# sourceMappingURL=commands.js.map