From f1268cac4697cff9c0cbfec80b5335bc925c218c Mon Sep 17 00:00:00 2001 From: Ahmet Ildirim Date: Sat, 12 Jul 2025 23:10:37 +0200 Subject: [PATCH] refactor: remove vibe coded refactor --- src/extension/fetcher.ts | 93 ------ src/extension/handlers.ts | 82 ------ src/extension/index.ts | 486 +++++++++++++++++++++++++++++-- src/extension/renderer.ts | 45 --- src/extension/session-state.ts | 142 --------- src/extension/text-strategies.ts | 48 --- src/extension/types.ts | 37 --- src/main.ts | 2 +- src/settings/tab.ts | 2 +- 9 files changed, 469 insertions(+), 468 deletions(-) delete mode 100644 src/extension/fetcher.ts delete mode 100644 src/extension/handlers.ts delete mode 100644 src/extension/renderer.ts delete mode 100644 src/extension/session-state.ts delete mode 100644 src/extension/text-strategies.ts delete mode 100644 src/extension/types.ts diff --git a/src/extension/fetcher.ts b/src/extension/fetcher.ts deleted file mode 100644 index 4baa287..0000000 --- a/src/extension/fetcher.ts +++ /dev/null @@ -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, - getDelay: () => number, - autoTriggerEnabled: boolean = true -) { - let activeRequest = true; - let timeoutId: ReturnType; - - // 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); - }, - }; -} diff --git a/src/extension/handlers.ts b/src/extension/handlers.ts deleted file mode 100644 index 6fbbc2f..0000000 --- a/src/extension/handlers.ts +++ /dev/null @@ -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, - 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; - }, - }, - ]) - ); -} diff --git a/src/extension/index.ts b/src/extension/index.ts index 1b39f46..ac98cb1 100644 --- a/src/extension/index.ts +++ b/src/extension/index.ts @@ -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 | Promise; + //(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({ + 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, + getDelay: () => number, + autoTriggerEnabled: boolean = true +) => { + let activeRequest = true; + let timeoutId: ReturnType; + + // 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, + 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); } diff --git a/src/extension/renderer.ts b/src/extension/renderer.ts deleted file mode 100644 index b9d6ade..0000000 --- a/src/extension/renderer.ts +++ /dev/null @@ -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 } -); diff --git a/src/extension/session-state.ts b/src/extension/session-state.ts deleted file mode 100644 index c06e184..0000000 --- a/src/extension/session-state.ts +++ /dev/null @@ -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({ - 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; - }, -}); diff --git a/src/extension/text-strategies.ts b/src/extension/text-strategies.ts deleted file mode 100644 index ea08be6..0000000 --- a/src/extension/text-strategies.ts +++ /dev/null @@ -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; diff --git a/src/extension/types.ts b/src/extension/types.ts deleted file mode 100644 index b8ccf1e..0000000 --- a/src/extension/types.ts +++ /dev/null @@ -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 | Promise; - // 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; -} diff --git a/src/main.ts b/src/main.ts index 1fec03f..6882fc9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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); } diff --git a/src/settings/tab.ts b/src/settings/tab.ts index af722c4..66e8699 100644 --- a/src/settings/tab.ts +++ b/src/settings/tab.ts @@ -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")