refactor: remove vibe coded refactor

This commit is contained in:
Ahmet Ildirim 2025-07-12 23:10:37 +02:00
parent eaffc10136
commit f1268cac46
9 changed files with 469 additions and 468 deletions

View file

@ -1,93 +0,0 @@
// Debounced suggestion fetching
import { ViewPlugin, EditorView, ViewUpdate } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { Suggestion } from './types';
import { suggestionSessionState, SuggestionUpdateEffect } from './session-state';
// Creates a debounced fetcher for suggestions.
export function createDebouncedFetcher(
fetch: (state: EditorState) => AsyncGenerator<Suggestion>,
getDelay: () => number,
autoTriggerEnabled: boolean = true
) {
let activeRequest = true;
let timeoutId: ReturnType<typeof setTimeout>;
// Throttled fetch that waits for the debounce interval.
const throttledFetch = async function* (state: EditorState) {
clearTimeout(timeoutId);
activeRequest = true;
await new Promise((resolve) => {
timeoutId = setTimeout(resolve, getDelay());
});
if (activeRequest) yield* fetch(state);
};
// Immediate fetch without debounce (for manual triggers).
const immediateFetch = async function* (state: EditorState) {
activeRequest = true;
if (activeRequest) yield* fetch(state);
};
// Plugin that initiates suggestion fetching on document changes.
const fetcherPlugin = ViewPlugin.fromClass(
class {
private currentRequestId = 0;
async update(update: ViewUpdate) {
const state = update.state;
// Only trigger fetch if auto-trigger is enabled and there is no active suggestion.
if (!autoTriggerEnabled || !update.docChanged || state.field(suggestionSessionState).remainingText)
return;
const requestId = ++this.currentRequestId;
for await (const suggestion of throttledFetch(state)) {
// Ignore stale requests.
if (requestId !== this.currentRequestId) return;
update.view.dispatch({
effects: SuggestionUpdateEffect.of({
content: suggestion.text,
document: state.doc,
anchor: state.selection.main.head,
}),
});
}
}
// Method to manually trigger suggestions (exposed for hotkey use).
async triggerSuggestion(view: EditorView) {
const state = view.state;
// Cancel any active suggestion first.
view.dispatch({
effects: SuggestionUpdateEffect.of({
content: null,
document: null,
anchor: null,
}),
});
const requestId = ++this.currentRequestId;
for await (const suggestion of immediateFetch(state)) {
// Ignore stale requests.
if (requestId !== this.currentRequestId) return;
view.dispatch({
effects: SuggestionUpdateEffect.of({
content: suggestion.text,
document: state.doc,
anchor: state.selection.main.head,
}),
});
}
}
}
);
return {
fetcherPlugin,
terminate: () => {
activeRequest = false;
clearTimeout(timeoutId);
},
};
}

View file

@ -1,82 +0,0 @@
// User interaction handlers for accepting and triggering suggestions
import { ViewPlugin, EditorView, keymap } from '@codemirror/view';
import { EditorState, EditorSelection, Prec } from '@codemirror/state';
import { SplitStrategy, InlineCompletionOptions } from './types';
import { TextSplitStrategies } from './text-strategies';
import { suggestionSessionState, SuggestionUpdateEffect } from './session-state';
// Helper to create a transaction that inserts completion text.
function insertCompletion(state: EditorState, text: string) {
const cursorPos = state.selection.main.head;
return {
...state.changeByRange(() => ({
changes: { from: cursorPos, insert: text },
range: EditorSelection.cursor(cursorPos + text.length),
})),
userEvent: 'completion.accept',
};
}
// Returns a key binding that accepts the current suggestion.
export function createAcceptanceHandler(
terminateFetch: () => void,
hotkey: string,
getOptions: () => InlineCompletionOptions
) {
return Prec.highest(
keymap.of([
{
key: hotkey,
run: (view: EditorView) => {
const session = view.state.field(suggestionSessionState);
if (!session.remainingText) return false;
// Always obtain the current split strategy from getOptions.
const dynamicOptions = getOptions();
const segmentationKey = dynamicOptions.splitStrategy ?? 'word';
const { accepted, remaining } =
TextSplitStrategies[segmentationKey](session.remainingText);
if (!accepted) return false;
// Insert the accepted suggestion text.
view.dispatch({
...insertCompletion(view.state, accepted),
effects: SuggestionUpdateEffect.of({
content: remaining || null,
document: remaining ? session.baselineDocument : null,
anchor: remaining ? session.anchorPosition! + accepted.length : null,
}),
});
if (!remaining) terminateFetch();
return true;
},
},
])
);
}
// Returns a key binding that manually triggers suggestions.
export function createTriggerHandler(
fetcherPlugin: ViewPlugin<any>,
hotkey: string
) {
return Prec.highest(
keymap.of([
{
key: hotkey,
run: (view: EditorView) => {
// Get the fetcher plugin instance and trigger suggestion.
const pluginInstance = view.plugin(fetcherPlugin);
if (pluginInstance && 'triggerSuggestion' in pluginInstance) {
(pluginInstance as any).triggerSuggestion(view);
return true;
}
return false;
},
},
])
);
}

View file

@ -6,29 +6,478 @@
// - Debounced network requests
// - Non-invasive suggestion rendering
import { EditorState } from '@codemirror/state';
import { InlineCompletionConfig, Suggestion } from './types';
import { suggestionSessionState } from './session-state';
import { suggestionRenderer } from './renderer';
import { createDebouncedFetcher } from './fetcher';
import { createAcceptanceHandler, createTriggerHandler } from './handlers';
import {
ViewPlugin,
EditorView,
ViewUpdate,
Decoration,
WidgetType,
keymap,
} from '@codemirror/view';
import {
StateEffect,
Text,
StateField,
EditorState,
EditorSelection,
Transaction,
Prec,
} from '@codemirror/state';
// Re-export types for external use
export type { SplitStrategy, Suggestion, InlineCompletionConfig, InlineCompletionOptions } from './types';
/* --------------------------------------------------------------------------
Type Definitions
---------------------------------------------------------------------------- */
/**
* Creates an inline suggestions extension for CodeMirror.
*
* The main extension function that wires up session state management,
* suggestion fetching, rendering, and user interaction.
* The split strategy is always obtained dynamically via `getOptions()`.
*/
// Supported segmentation strategies
export type SplitStrategy = keyof typeof TextSplitStrategies;
// Inline suggestion structure now only carries text.
export interface Suggestion {
text: string;
}
// Inline completion configuration.
export interface InlineCompletionConfig {
fetchFunc: (
state: EditorState
) => AsyncGenerator<Suggestion> | Promise<Suggestion>;
//(Optional) A static hotkey for accepting suggestions.
acceptanceHotkey?: string;
//(Optional) A static hotkey for manually triggering suggestions.
triggerHotkey?: string;
// A function that returns current options.
getOptions: () => InlineCompletionOptions;
}
export interface InlineCompletionOptions {
delayMs?: number;
splitStrategy?: SplitStrategy;
}
// Internal state for the current suggestion session.
interface SuggestionSession {
fullText: string | null;
remainingText: string | null;
baselineDocument: Text | null;
anchorPosition: number | null;
}
/* --------------------------------------------------------------------------
Text Segmentation Strategies
---------------------------------------------------------------------------- */
// A set of text splitting functions used to determine how much of the
// suggestion to accept when triggered.
const TextSplitStrategies = {
// Word-level segmentation (space-delimited).
// Accepts text until (and including) the first space.
word: (text: string) => {
const nextSpace = text.indexOf(' ');
return nextSpace === -1
? { accepted: text, remaining: '' }
: {
accepted: text.slice(0, nextSpace + 1),
remaining: text.slice(nextSpace + 1),
};
},
// Sentence-level segmentation (punctuation followed by whitespace).
sentence: (text: string) => {
const match = text.match(/[.!?]\s+/);
if (match && match.index !== undefined) {
return {
accepted: text.slice(0, match.index + 1),
remaining: text.slice(match.index + 1),
};
}
return { accepted: text, remaining: '' };
},
// Paragraph-level segmentation (double newline).
paragraph: (text: string) => {
const paragraphEnd = text.indexOf('\n\n');
return paragraphEnd === -1
? { accepted: text, remaining: '' }
: {
accepted: text.slice(0, paragraphEnd + 2),
remaining: text.slice(paragraphEnd + 2),
};
},
// Atomic acceptance consume the entire suggestion.
full: (text: string) => ({ accepted: text, remaining: '' }),
} as const;
/* --------------------------------------------------------------------------
Suggestion Session State Management
---------------------------------------------------------------------------- */
// Effect to update the suggestion session state.
// A `null` content signals a reset.
const SuggestionUpdateEffect = StateEffect.define<{
content: string | null;
document: Text | null;
anchor: number | null;
}>();
// The state field that holds the current suggestion session.
const suggestionSessionState = StateField.define<SuggestionSession>({
create: () => getResetSession(),
update(session, transaction) {
// Process explicit session update effects.
const effect = transaction.effects.find((e) =>
e.is(SuggestionUpdateEffect)
);
if (effect) return updateSessionFromEffect(effect.value);
// If the document has changed, adjust the session.
if (
transaction.docChanged &&
session.remainingText &&
session.anchorPosition !== null
) {
return updateSessionOnDocumentChange(session, transaction);
}
// If there is an active suggestion but the cursor has moved, cancel it.
if (session.remainingText !== null && session.anchorPosition !== null) {
return updateSessionOnCursorDrift(session, transaction);
}
return session;
},
});
// Creates a fresh, empty suggestion session.
function getResetSession(): SuggestionSession {
return {
fullText: null,
remainingText: null,
baselineDocument: null,
anchorPosition: null,
};
}
// Update session state based on an incoming effect.
function updateSessionFromEffect(effect: {
content: string | null;
document: Text | null;
anchor: number | null;
}): SuggestionSession {
return effect.content === null
? getResetSession()
: initializeSession(effect as {
content: string;
document: Text;
anchor: number;
});
}
// Initializes a new suggestion session with provided effect data.
function initializeSession(effect: {
content: string;
document: Text;
anchor: number;
}): SuggestionSession {
return {
fullText: effect.content,
remainingText: effect.content,
baselineDocument: effect.document,
anchorPosition: effect.anchor,
};
}
// Adjust the suggestion session in response to document changes.
function updateSessionOnDocumentChange(
session: SuggestionSession,
transaction: Transaction
): SuggestionSession {
let insertedContent = '';
let insertionAtAnchor = false;
// Iterate over document changes to detect an insertion at the suggestion's anchor.
transaction.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
if (fromA === session.anchorPosition && toA === fromA) {
insertedContent = inserted.toString();
insertionAtAnchor = true;
}
});
if (!insertionAtAnchor || !session.remainingText) {
return invalidateSession(session);
}
// Verify the inserted text matches the pending suggestion.
if (session.remainingText.startsWith(insertedContent)) {
return advanceSession(session, insertedContent.length);
}
return invalidateSession(session);
}
// Advance the session by consuming accepted text.
function advanceSession(
session: SuggestionSession,
consumedLength: number
): SuggestionSession {
return {
...session,
remainingText:
session.remainingText!.slice(consumedLength).length > 0
? session.remainingText!.slice(consumedLength)
: null,
anchorPosition: session.anchorPosition! + consumedLength,
};
}
// Invalidate the session, effectively cancelling any pending suggestion.
function invalidateSession(session: SuggestionSession): SuggestionSession {
return {
...session,
remainingText: null,
anchorPosition: null,
};
}
// Cancel the suggestion if the cursor has drifted away.
function updateSessionOnCursorDrift(
session: SuggestionSession,
transaction: Transaction
): SuggestionSession {
return transaction.state.selection.main.head !== session.anchorPosition
? invalidateSession(session)
: session;
}
/* --------------------------------------------------------------------------
Suggestion Rendering (Visualization Layer)
---------------------------------------------------------------------------- */
// Widget for rendering inline suggestion text.
class SuggestionWidget extends WidgetType {
static readonly CSS_CLASSES = ['cm-inline-prediction', 'inscribe-inline-prediction'];
constructor(private readonly content: string) {
super();
}
toDOM(): HTMLElement {
const span = document.createElement('span');
span.classList.add(...SuggestionWidget.CSS_CLASSES);
span.textContent = this.content;
return span;
}
}
// Plugin that renders inline suggestion decorations.
const suggestionRenderer = ViewPlugin.fromClass(
class {
decorations = Decoration.none;
update(update: ViewUpdate) {
const session = update.state.field(suggestionSessionState);
this.decorations = session.remainingText
? this.createDecoration(update.view, session.remainingText)
: Decoration.none;
}
private createDecoration(view: EditorView, suggestionText: string) {
const cursorPosition = view.state.selection.main.head;
return Decoration.set([
Decoration.widget({
widget: new SuggestionWidget(suggestionText),
side: 1,
}).range(cursorPosition),
]);
}
},
{ decorations: (v) => v.decorations }
);
/* --------------------------------------------------------------------------
Suggestion Fetching (Debounced Fetcher)
---------------------------------------------------------------------------- */
// Creates a debounced fetcher for suggestions.
const createDebouncedFetcher = (
fetch: (state: EditorState) => AsyncGenerator<Suggestion>,
getDelay: () => number,
autoTriggerEnabled: boolean = true
) => {
let activeRequest = true;
let timeoutId: ReturnType<typeof setTimeout>;
// Throttled fetch that waits for the debounce interval.
const throttledFetch = async function* (state: EditorState) {
clearTimeout(timeoutId);
activeRequest = true;
await new Promise((resolve) => {
timeoutId = setTimeout(resolve, getDelay());
});
if (activeRequest) yield* fetch(state);
};
// Immediate fetch without debounce (for manual triggers).
const immediateFetch = async function* (state: EditorState) {
activeRequest = true;
if (activeRequest) yield* fetch(state);
};
// Plugin that initiates suggestion fetching on document changes.
const fetcherPlugin = ViewPlugin.fromClass(
class {
private currentRequestId = 0;
async update(update: ViewUpdate) {
const state = update.state;
// Only trigger fetch if auto-trigger is enabled and there is no active suggestion.
if (!autoTriggerEnabled || !update.docChanged || state.field(suggestionSessionState).remainingText)
return;
const requestId = ++this.currentRequestId;
for await (const suggestion of throttledFetch(state)) {
// Ignore stale requests.
if (requestId !== this.currentRequestId) return;
update.view.dispatch({
effects: SuggestionUpdateEffect.of({
content: suggestion.text,
document: state.doc,
anchor: state.selection.main.head,
}),
});
}
}
// Method to manually trigger suggestions (exposed for hotkey use).
async triggerSuggestion(view: EditorView) {
const state = view.state;
// Cancel any active suggestion first.
view.dispatch({
effects: SuggestionUpdateEffect.of({
content: null,
document: null,
anchor: null,
}),
});
const requestId = ++this.currentRequestId;
for await (const suggestion of immediateFetch(state)) {
// Ignore stale requests.
if (requestId !== this.currentRequestId) return;
view.dispatch({
effects: SuggestionUpdateEffect.of({
content: suggestion.text,
document: state.doc,
anchor: state.selection.main.head,
}),
});
}
}
}
);
return {
fetcherPlugin,
terminate: () => {
activeRequest = false;
clearTimeout(timeoutId);
},
};
};
/* --------------------------------------------------------------------------
User Interaction (Acceptance Handler)
---------------------------------------------------------------------------- */
// Returns a key binding that accepts the current suggestion.
const createAcceptanceHandler = (
terminateFetch: () => void,
hotkey: string,
getOptions: () => { delayMs?: number; splitStrategy?: SplitStrategy }
) =>
Prec.highest(
keymap.of([
{
key: hotkey,
run: (view: EditorView) => {
const session = view.state.field(suggestionSessionState);
if (!session.remainingText) return false;
// Always obtain the current split strategy from getOptions.
const dynamicOptions = getOptions();
const segmentationKey = dynamicOptions.splitStrategy ?? 'word';
const { accepted, remaining } =
TextSplitStrategies[segmentationKey](session.remainingText);
if (!accepted) return false;
// Insert the accepted suggestion text.
view.dispatch({
...insertCompletion(view.state, accepted),
effects: SuggestionUpdateEffect.of({
content: remaining || null,
document: remaining ? session.baselineDocument : null,
anchor: remaining ? session.anchorPosition! + accepted.length : null,
}),
});
if (!remaining) terminateFetch();
return true;
},
},
])
);
// Returns a key binding that manually triggers suggestions.
const createTriggerHandler = (
fetcherPlugin: ViewPlugin<any>,
hotkey: string
) =>
Prec.highest(
keymap.of([
{
key: hotkey,
run: (view: EditorView) => {
// Get the fetcher plugin instance and trigger suggestion.
const pluginInstance = view.plugin(fetcherPlugin);
if (pluginInstance && 'triggerSuggestion' in pluginInstance) {
(pluginInstance as any).triggerSuggestion(view);
return true;
}
return false;
},
},
])
);
// Helper to create a transaction that inserts completion text.
const insertCompletion = (state: EditorState, text: string) => {
const cursorPos = state.selection.main.head;
return {
...state.changeByRange(() => ({
changes: { from: cursorPos, insert: text },
range: EditorSelection.cursor(cursorPos + text.length),
})),
userEvent: 'completion.accept',
};
};
/* --------------------------------------------------------------------------
Public API
---------------------------------------------------------------------------- */
// The main extension function. It wires up session state management,
// suggestion fetching, rendering, and user interaction.
//
// The split strategy is always obtained dynamically via `getOptions()`.
export function inlineSuggestions(config: InlineCompletionConfig) {
const { fetchFunc, getOptions } = config;
// Use the hotkey from the config if provided; otherwise, default to "Tab".
const staticHotkey = config.acceptanceHotkey || 'Tab';
// Determine if auto-trigger should be disabled when trigger hotkey is set.
const autoTriggerEnabled = !config.manualActivationKey;
const autoTriggerEnabled = !config.triggerHotkey;
// Normalize the fetch function to always return an async generator.
const normalizeFetch = async function* (state: EditorState) {
@ -53,9 +502,8 @@ export function inlineSuggestions(config: InlineCompletionConfig) {
acceptanceHandler,
];
if (autoTriggerEnabled) {
const hotkey = String(config.manualActivationKey);
const triggerHandler = createTriggerHandler(fetcherPlugin, hotkey);
if (config.triggerHotkey) {
const triggerHandler = createTriggerHandler(fetcherPlugin, config.triggerHotkey);
extensions.push(triggerHandler);
}

View file

@ -1,45 +0,0 @@
// Suggestion rendering (visualization layer)
import { ViewPlugin, EditorView, ViewUpdate, Decoration, WidgetType } from '@codemirror/view';
import { suggestionSessionState } from './session-state';
// Widget for rendering inline suggestion text.
class SuggestionWidget extends WidgetType {
static readonly CSS_CLASSES = ['cm-inline-prediction', 'inscribe-inline-prediction'];
constructor(private readonly content: string) {
super();
}
toDOM(): HTMLElement {
const span = document.createElement('span');
span.classList.add(...SuggestionWidget.CSS_CLASSES);
span.textContent = this.content;
return span;
}
}
// Plugin that renders inline suggestion decorations.
export const suggestionRenderer = ViewPlugin.fromClass(
class {
decorations = Decoration.none;
update(update: ViewUpdate) {
const session = update.state.field(suggestionSessionState);
this.decorations = session.remainingText
? this.createDecoration(update.view, session.remainingText)
: Decoration.none;
}
private createDecoration(view: EditorView, suggestionText: string) {
const cursorPosition = view.state.selection.main.head;
return Decoration.set([
Decoration.widget({
widget: new SuggestionWidget(suggestionText),
side: 1,
}).range(cursorPosition),
]);
}
},
{ decorations: (v) => v.decorations }
);

View file

@ -1,142 +0,0 @@
// State management for suggestion sessions
import { StateEffect, Text, StateField, Transaction } from '@codemirror/state';
import { SuggestionSession } from './types';
// Effect to update the suggestion session state.
// A `null` content signals a reset.
export const SuggestionUpdateEffect = StateEffect.define<{
content: string | null;
document: Text | null;
anchor: number | null;
}>();
// Creates a fresh, empty suggestion session.
export function getResetSession(): SuggestionSession {
return {
fullText: null,
remainingText: null,
baselineDocument: null,
anchorPosition: null,
};
}
// Update session state based on an incoming effect.
export function updateSessionFromEffect(effect: {
content: string | null;
document: Text | null;
anchor: number | null;
}): SuggestionSession {
return effect.content === null
? getResetSession()
: initializeSession(effect as {
content: string;
document: Text;
anchor: number;
});
}
// Initializes a new suggestion session with provided effect data.
function initializeSession(effect: {
content: string;
document: Text;
anchor: number;
}): SuggestionSession {
return {
fullText: effect.content,
remainingText: effect.content,
baselineDocument: effect.document,
anchorPosition: effect.anchor,
};
}
// Adjust the suggestion session in response to document changes.
export function updateSessionOnDocumentChange(
session: SuggestionSession,
transaction: Transaction
): SuggestionSession {
let insertedContent = '';
let insertionAtAnchor = false;
// Iterate over document changes to detect an insertion at the suggestion's anchor.
transaction.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
if (fromA === session.anchorPosition && toA === fromA) {
insertedContent = inserted.toString();
insertionAtAnchor = true;
}
});
if (!insertionAtAnchor || !session.remainingText) {
return invalidateSession(session);
}
// Verify the inserted text matches the pending suggestion.
if (session.remainingText.startsWith(insertedContent)) {
return advanceSession(session, insertedContent.length);
}
return invalidateSession(session);
}
// Advance the session by consuming accepted text.
function advanceSession(
session: SuggestionSession,
consumedLength: number
): SuggestionSession {
return {
...session,
remainingText:
session.remainingText!.slice(consumedLength).length > 0
? session.remainingText!.slice(consumedLength)
: null,
anchorPosition: session.anchorPosition! + consumedLength,
};
}
// Invalidate the session, effectively cancelling any pending suggestion.
function invalidateSession(session: SuggestionSession): SuggestionSession {
return {
...session,
remainingText: null,
anchorPosition: null,
};
}
// Cancel the suggestion if the cursor has drifted away.
export function updateSessionOnCursorDrift(
session: SuggestionSession,
transaction: Transaction
): SuggestionSession {
return transaction.state.selection.main.head !== session.anchorPosition
? invalidateSession(session)
: session;
}
// The state field that holds the current suggestion session.
export const suggestionSessionState = StateField.define<SuggestionSession>({
create: () => getResetSession(),
update(session, transaction) {
// Process explicit session update effects.
const effect = transaction.effects.find((e) =>
e.is(SuggestionUpdateEffect)
);
if (effect) return updateSessionFromEffect(effect.value);
// If the document has changed, adjust the session.
if (
transaction.docChanged &&
session.remainingText &&
session.anchorPosition !== null
) {
return updateSessionOnDocumentChange(session, transaction);
}
// If there is an active suggestion but the cursor has moved, cancel it.
if (session.remainingText !== null && session.anchorPosition !== null) {
return updateSessionOnCursorDrift(session, transaction);
}
return session;
},
});

View file

@ -1,48 +0,0 @@
// Text segmentation strategies for inline completions
export type SplitResult = {
accepted: string;
remaining: string;
};
// A set of text splitting functions used to determine how much of the
// suggestion to accept when triggered.
export const TextSplitStrategies = {
// Word-level segmentation (space-delimited).
// Accepts text until (and including) the first space.
word: (text: string): SplitResult => {
const nextSpace = text.indexOf(' ');
return nextSpace === -1
? { accepted: text, remaining: '' }
: {
accepted: text.slice(0, nextSpace + 1),
remaining: text.slice(nextSpace + 1),
};
},
// Sentence-level segmentation (punctuation followed by whitespace).
sentence: (text: string): SplitResult => {
const match = text.match(/[.!?]\s+/);
if (match && match.index !== undefined) {
return {
accepted: text.slice(0, match.index + 1),
remaining: text.slice(match.index + 1),
};
}
return { accepted: text, remaining: '' };
},
// Paragraph-level segmentation (double newline).
paragraph: (text: string): SplitResult => {
const paragraphEnd = text.indexOf('\n\n');
return paragraphEnd === -1
? { accepted: text, remaining: '' }
: {
accepted: text.slice(0, paragraphEnd + 2),
remaining: text.slice(paragraphEnd + 2),
};
},
// Atomic acceptance consume the entire suggestion.
full: (text: string): SplitResult => ({ accepted: text, remaining: '' }),
} as const;

View file

@ -1,37 +0,0 @@
// Type definitions for inline completions extension
import { EditorState } from '@codemirror/state';
// Supported segmentation strategies
export type SplitStrategy = 'word' | 'sentence' | 'paragraph' | 'full';
// Inline suggestion structure
export interface Suggestion {
text: string;
}
// Inline completion configuration
export interface InlineCompletionConfig {
fetchFunc: (
state: EditorState
) => AsyncGenerator<Suggestion> | Promise<Suggestion>;
// Optional hotkey for accepting suggestions
acceptanceHotkey?: string;
// Optional hotkey for manually triggering suggestions
manualActivationKey?: string;
// Function that returns current options
getOptions: () => InlineCompletionOptions;
}
export interface InlineCompletionOptions {
delayMs?: number;
splitStrategy?: SplitStrategy;
}
// Internal state for the current suggestion session
export interface SuggestionSession {
fullText: string | null;
remainingText: string | null;
baselineDocument: import('@codemirror/state').Text | null;
anchorPosition: number | null;
}

View file

@ -33,7 +33,7 @@ export default class Inscribe extends Plugin {
fetchFunc: () => this.completionService.fetchCompletion(),
getOptions: () => this.profileService.getOptions(),
acceptanceHotkey: this.settings.suggestionSettings.acceptanceHotkey,
manualActivationKey: this.settings.suggestionSettings.manualActivationKey,
triggerHotkey: "Shift-Ctrl-Enter",
});
this.registerEditorExtension(extension);
}

View file

@ -138,7 +138,7 @@ class SuggestionSettingsSection {
// Manual Activation Key
new Setting(this.container)
.setName("Manual activation key")
.setDesc("Key to manually trigger suggestions (if enabled)")
.setDesc("Key to manually trigger suggestions (if enabled), disables auto-triggering")
.addText((text) => {
text
.setPlaceholder("e.g. Ctrl+Space")