import { createInlineFormatVirtualDocument, normalizeInlineFormatSemanticTokens, type InlineFormatInspectionResult, type InlineFormatSemanticToken, type InlineFormatVirtualDocument, } from "@pi-inline-format/intel"; import { extractInlineFormatHeredocOpenerCommand, findInlineFormatHeredocRange, findInlineFormatHeredocRanges, type InlineFormatMatch, type InlineFormatPlugin, } from "@pi-inline-format/shared-contract"; export const PYTHON_HEREDOC_MARKERS = ["<<'PY'", '<<"PY"', "< Promise; export type PythonSemanticTokensRenderEntrypoint = ( payload: PythonSemanticTokensRenderHandoffPayload, ) => TResult | Promise; export interface PythonSemanticTokensRenderEntrypointReference { render: PythonSemanticTokensRenderEntrypoint; } export function createPythonSemanticTokensRenderEntrypointReference( render: PythonSemanticTokensRenderEntrypoint, ): PythonSemanticTokensRenderEntrypointReference { return { render }; } const PYTHON_HEREDOC_COMMAND_PATTERN = /^\s*python(?:3)?(?:\s|$)/u; export function findPythonHeredocRanges(command: string): { startLineIndex: number; endLineIndex: number; }[] { const explicitRanges = findInlineFormatHeredocRanges(command, { terminator: PYTHON_HEREDOC_TERMINATOR, }); if (explicitRanges.length > 0) { return explicitRanges; } return findInlineFormatHeredocRanges(command).filter((range) => { const openerCommand = extractInlineFormatHeredocOpenerCommand( range.openerLine, ); return PYTHON_HEREDOC_COMMAND_PATTERN.test(openerCommand); }); } export function findPythonHeredocRange(command: string): { startLineIndex: number; endLineIndex: number; } | null { return findPythonHeredocRanges(command)[0] ?? null; } export function extractPythonHeredocSource(command: string): string | null { const heredocRange = findPythonHeredocRange(command); if (heredocRange === null) { return null; } const source = command .split("\n") .slice(heredocRange.startLineIndex + 1, heredocRange.endLineIndex) .join("\n"); return source.length === 0 ? null : source; } export function describePythonHeredoc(command: string): { startLineIndex: number; endLineIndex: number; source: string; } | null { const heredocRange = findPythonHeredocRange(command); if (heredocRange === null) { return null; } const source = extractPythonHeredocSource(command); if (source === null) { return null; } return { startLineIndex: heredocRange.startLineIndex, endLineIndex: heredocRange.endLineIndex, source, }; } function inferPythonFilePathHint(command: string): string | undefined { const fileWriteMatch = /cat\s*>\s*(?\S+)\s*< { const context = createPythonSemanticTokensBoundaryContext( command, projectRoot, ); if (context === null) { return null; } return { context, rawResult: await inspect(context.document, "semantic-tokens"), }; } export async function collectNormalizedPythonSemanticTokensBoundaryPayload( command: string, inspect: PythonSemanticTokensInspector, projectRoot: string = process.cwd(), ): Promise { const collected = await collectPythonSemanticTokensBoundaryPayload( command, inspect, projectRoot, ); if (collected === null) { return null; } return { ...collected, tokens: collected.rawResult === null ? [] : normalizeInlineFormatSemanticTokens(collected.rawResult), }; } export async function collectPythonSemanticTokensRenderSlicePayload( command: string, inspect: PythonSemanticTokensInspector, projectRoot: string = process.cwd(), ): Promise { const collected = await collectNormalizedPythonSemanticTokensBoundaryPayload( command, inspect, projectRoot, ); if (collected === null) { return null; } return { ...collected, sourceLines: collected.context.source.split("\n"), }; } export async function collectPythonSemanticTokensRenderHandoffPayload( command: string, inspect: PythonSemanticTokensInspector, projectRoot: string = process.cwd(), ): Promise { const collected = await collectPythonSemanticTokensRenderSlicePayload( command, inspect, projectRoot, ); if (collected === null) { return null; } return { ...collected, language: "python", }; } export async function renderPythonSemanticTokensAtBoundary( command: string, inspect: PythonSemanticTokensInspector, entrypoint: PythonSemanticTokensRenderEntrypointReference, projectRoot: string = process.cwd(), ): Promise { const payload = await collectPythonSemanticTokensRenderHandoffPayload( command, inspect, projectRoot, ); if (payload === null) { return null; } return await entrypoint.render(payload); } function detectPythonHeredoc(command: string): InlineFormatMatch[] | null { const heredocRanges = findPythonHeredocRanges(command); if (heredocRanges.length === 0) { return null; } return heredocRanges.map((heredocRange) => ({ pluginName: "python", language: "python", startLineIndex: heredocRange.startLineIndex + 1, endLineIndex: heredocRange.endLineIndex - 1, })); } export const pythonInlineFormatPlugin: InlineFormatPlugin = { name: "python", language: "python", detect: detectPythonHeredoc, };