91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
export const MESSAGE_PREVIEW_MAX_LINES = 200;
|
|
export const MESSAGE_PREVIEW_MAX_CODE_UNITS = 16_000;
|
|
|
|
export type MessagePreviewLimits = {
|
|
maxLines?: number;
|
|
maxCodeUnits?: number;
|
|
};
|
|
|
|
export type MessagePreview = {
|
|
content: string;
|
|
truncated: boolean;
|
|
logicalLines: number;
|
|
codeUnits: number;
|
|
};
|
|
|
|
function isSurrogatePair(text: string, index: number): boolean {
|
|
const first = text.charCodeAt(index);
|
|
const second = text.charCodeAt(index + 1);
|
|
return first >= 0xd800 && first <= 0xdbff && second >= 0xdc00 && second <= 0xdfff;
|
|
}
|
|
|
|
function isLineBreak(text: string, index: number): boolean {
|
|
return text[index] === "\n" || text[index] === "\r";
|
|
}
|
|
|
|
function characterLengthAt(text: string, index: number): number {
|
|
if (text[index] === "\r" && text[index + 1] === "\n") return 2;
|
|
if (isSurrogatePair(text, index)) return 2;
|
|
return 1;
|
|
}
|
|
|
|
function previewResult(
|
|
source: string,
|
|
end: number,
|
|
truncated: boolean,
|
|
logicalLines: number,
|
|
): MessagePreview {
|
|
return {
|
|
content: source.slice(0, end),
|
|
truncated,
|
|
logicalLines,
|
|
codeUnits: end,
|
|
};
|
|
}
|
|
|
|
/** Select a source prefix without splitting Unicode pairs or CRLF endings. */
|
|
export function getMessagePreview(
|
|
source: string,
|
|
{
|
|
maxLines = MESSAGE_PREVIEW_MAX_LINES,
|
|
maxCodeUnits = MESSAGE_PREVIEW_MAX_CODE_UNITS,
|
|
}: MessagePreviewLimits = {},
|
|
): MessagePreview {
|
|
if (source.length === 0) return previewResult(source, 0, false, 0);
|
|
|
|
let index = 0;
|
|
let codeUnits = 0;
|
|
let completedLines = 0;
|
|
let currentLineHasContent = false;
|
|
|
|
while (index < source.length) {
|
|
const lineBreak = isLineBreak(source, index);
|
|
const characterLength = characterLengthAt(source, index);
|
|
|
|
if (codeUnits + characterLength > maxCodeUnits) {
|
|
return previewResult(source, index, true, completedLines + (currentLineHasContent ? 1 : 0));
|
|
}
|
|
|
|
if (lineBreak) {
|
|
if (completedLines >= maxLines) {
|
|
return previewResult(source, index, true, completedLines);
|
|
}
|
|
index += characterLength;
|
|
codeUnits += characterLength;
|
|
completedLines += 1;
|
|
currentLineHasContent = false;
|
|
continue;
|
|
}
|
|
|
|
if (completedLines >= maxLines) {
|
|
return previewResult(source, index, true, completedLines);
|
|
}
|
|
|
|
index += characterLength;
|
|
codeUnits += characterLength;
|
|
currentLineHasContent = true;
|
|
}
|
|
|
|
return previewResult(source, index, false, completedLines + (currentLineHasContent ? 1 : 0));
|
|
}
|