[tab] src/preview/inlineProvider.ts (typescript)
import * as vscode from 'vscode';
import { Action, toVscodeRange, toVscodePosition } from './types';

/**
 * Provides inline completion items (ghost text) for code edit actions.
 * This takes priority over Cursor's hints and works on empty lines.
 */
export class CrowdPilotInlineProvider implements vscode.InlineCompletionItemProvider {
    private action: Action | null = null;
    private enabled: boolean = true;

    /**
     * Set the current action to display as inline completion.
     */
    setAction(action: Action): void {
        this.action = action;
        // Trigger VS Code to re-query inline completions
        vscode.commands.executeCommand('editor.action.inlineSuggest.trigger');
    }

    /**
     * Clear the current action.
     */
    clearAction(): void {
        this.action = null;
    }

    /**
     * Get the current action.
     */
    getAction(): Action | null {
        return this.action;
    }

    /**
     * Enable or disable the provider.
     */
    setEnabled(enabled: boolean): void {
        this.enabled = enabled;
    }

    /**
     * Provide inline completion items.
     */
    provideInlineCompletionItems(
        document: vscode.TextDocument,
        position: vscode.Position,
        context: vscode.InlineCompletionContext,
        token: vscode.CancellationToken
    ): vscode.ProviderResult<vscode.InlineCompletionList | vscode.InlineCompletionItem[]> {
        if (!this.enabled || !this.action) {
            return [];
        }

        // Handle insertions
        if (this.action.kind === 'editInsert') {
            const insertPos = toVscodePosition(this.action.position);
            
            // Always provide the completion - VS Code will show it at the insert position
            // The ghost text appears at the range specified, not necessarily at cursor
            const item = new vscode.InlineCompletionItem(
                this.action.text,
                new vscode.Range(insertPos, insertPos)
            );
            
            return [item];
        }

        // Handle replacements
        if (this.action.kind === 'editReplace') {
            const range = toVscodeRange(this.action.range);
            
            // Always provide the completion for replacements
            const item = new vscode.InlineCompletionItem(
                this.action.text,
                range  // This will replace the content in range when accepted
            );
            
            return [item];
        }

        return [];
    }

    /**
     * Check if the current action's position is near the given cursor position.
     * Used to determine if we need a fallback indicator.
     */
    isActionNearCursor(cursorLine: number): boolean {
        if (!this.action) return false;
        
        if (this.action.kind === 'editInsert') {
            return Math.abs(this.action.position[0] - cursorLine) <= 1;
        }
        
        if (this.action.kind === 'editReplace') {
            return cursorLine >= this.action.range.start[0] - 1 && 
                   cursorLine <= this.action.range.end[0] + 1;
        }
        
        return false;
    }
}