mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-16 08:25:14 +02:00
Normalize semantic layer descriptions
This commit is contained in:
parent
c82989119b
commit
86c818a454
21 changed files with 498 additions and 37 deletions
|
|
@ -22,7 +22,7 @@ export interface KtxCliPackageInfo {
|
|||
}
|
||||
|
||||
export interface KtxCliIo {
|
||||
stdout: { isTTY?: boolean; write(chunk: string): void };
|
||||
stdout: { isTTY?: boolean; columns?: number; write(chunk: string): void };
|
||||
stderr: { write(chunk: string): void };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
|
|||
import type { KtxPublicIngestProject, KtxPublicIngestTargetResult } from './public-ingest.js';
|
||||
import {
|
||||
extractProgressMessage,
|
||||
createRepainter,
|
||||
initViewState,
|
||||
parseIngestSummary,
|
||||
parseScanSummary,
|
||||
|
|
@ -11,13 +12,14 @@ import {
|
|||
viewStateFromSourceProgress,
|
||||
} from './context-build-view.js';
|
||||
|
||||
function makeIo(options: { isTTY?: boolean } = {}) {
|
||||
function makeIo(options: { isTTY?: boolean; columns?: number } = {}) {
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
return {
|
||||
io: {
|
||||
stdout: {
|
||||
isTTY: options.isTTY,
|
||||
columns: options.columns,
|
||||
write: (chunk: string) => {
|
||||
stdout += chunk;
|
||||
},
|
||||
|
|
@ -305,6 +307,31 @@ describe('renderContextBuildView', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('createRepainter', () => {
|
||||
it('moves up visual rows, not just newline count, when content wraps', () => {
|
||||
const io = makeIo({ isTTY: true, columns: 5 });
|
||||
const repainter = createRepainter(io.io);
|
||||
|
||||
repainter.paint('abcdefghijk\n');
|
||||
repainter.paint('updated\n');
|
||||
repainter.paint('done\n');
|
||||
|
||||
const cursorMoves = [...io.stdout().matchAll(/\u001b\[(\d+)A\r/g)].map((match) => Number(match[1]));
|
||||
expect(cursorMoves).toEqual([3, 2]);
|
||||
});
|
||||
|
||||
it('returns to the start of a single-line frame without moving up when content has no newline', () => {
|
||||
const io = makeIo({ isTTY: true, columns: 80 });
|
||||
const repainter = createRepainter(io.io);
|
||||
|
||||
repainter.paint('hello');
|
||||
repainter.paint('bye');
|
||||
|
||||
expect(io.stdout()).toContain('\rbye');
|
||||
expect(io.stdout()).not.toContain('\u001b[1A\rbye');
|
||||
});
|
||||
});
|
||||
|
||||
describe('runContextBuild', () => {
|
||||
it('executes scan targets before source-ingest targets', async () => {
|
||||
const io = makeIo();
|
||||
|
|
|
|||
|
|
@ -226,6 +226,7 @@ export function renderContextBuildView(
|
|||
// --- IO Capture ---
|
||||
|
||||
const ESC_K_RE = new RegExp(`${ESC.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\[K`, 'g');
|
||||
const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
||||
|
||||
export function extractProgressMessage(chunk: string): string | null {
|
||||
const cleaned = chunk.replace(/^\r/, '').replace(ESC_K_RE, '').replace(/\n$/, '').trim();
|
||||
|
|
@ -342,16 +343,45 @@ export function viewStateFromSourceProgress(
|
|||
// --- Repaint ---
|
||||
|
||||
export function createRepainter(io: KtxCliIo) {
|
||||
let lastLineCount = 0;
|
||||
let hasPainted = false;
|
||||
let lastCursorUpRows = 0;
|
||||
|
||||
const terminalColumns = () => {
|
||||
for (const columns of [io.stdout.columns, process.stdout.columns]) {
|
||||
if (typeof columns === 'number' && Number.isFinite(columns) && columns > 0) return columns;
|
||||
}
|
||||
return 80;
|
||||
};
|
||||
|
||||
const visualRows = (line: string, columns: number) => {
|
||||
const plainLength = line.replace(ANSI_RE, '').length;
|
||||
return Math.max(1, Math.ceil(plainLength / columns));
|
||||
};
|
||||
|
||||
const cursorUpRowsAfterWrite = (content: string) => {
|
||||
const columns = terminalColumns();
|
||||
const endsWithNewline = content.endsWith('\n');
|
||||
const lines = content.split('\n');
|
||||
return lines.reduce((sum, line, index) => {
|
||||
if (index === lines.length - 1) {
|
||||
return endsWithNewline ? sum : sum + Math.max(0, visualRows(line, columns) - 1);
|
||||
}
|
||||
return sum + visualRows(line, columns);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
return {
|
||||
paint(content: string) {
|
||||
if (lastLineCount > 0) {
|
||||
io.stdout.write(`${ESC}[${lastLineCount}A\r`);
|
||||
if (hasPainted) {
|
||||
if (lastCursorUpRows > 0) {
|
||||
io.stdout.write(`${ESC}[${lastCursorUpRows}A`);
|
||||
}
|
||||
io.stdout.write('\r');
|
||||
}
|
||||
io.stdout.write(content.replaceAll('\n', `${ESC}[K\n`));
|
||||
io.stdout.write(`${ESC}[J`);
|
||||
lastLineCount = (content.match(/\n/g) ?? []).length;
|
||||
hasPainted = true;
|
||||
lastCursorUpRows = cursorUpRowsAfterWrite(content);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue