Update padding logic in CodeViewer component

This commit updates the padding logic in the CodeViewer component to handle highlighted lines more effectively. The renderRowContent function now returns an object with an html property and a highlighted property, allowing for more precise controls when rendering rows. Additionally, new functions have been introduced to manage chunks of lines and apply padding, helping to optimize the display of code diffs.

Changes include:
- Modify `renderRowContent` to return an object containing html and highlighted properties
- Add a `rows` computed variable that maps the original rows to the newly rendered rows
- Introduce new `RenderedRow` type and `applyPadding` function to manage chunks and padding logic
- Update Row interface to include a size property
This commit is contained in:
Nikita Galaiko 2023-03-23 14:07:27 +01:00
parent cbc82a93cc
commit f842829583
3 changed files with 225 additions and 137 deletions

View File

@ -47,9 +47,9 @@
$: currentHighlighter = create(diffRows.currentLines.join('\n'), filepath);
$: currentMap = documentMap(diffRows.currentLines);
const renderRowContent = (row: Row) => {
const renderRowContent = (row: Row): { html: string[]; highlighted: boolean } => {
if (row.type === RowType.Spacer) {
return row.tokens.map((tok) => `${tok.text}`);
return { html: row.tokens.map((tok) => `${tok.text}`), highlighted: false };
}
const [doc, startPos] =
@ -60,6 +60,7 @@
const content: string[] = [];
let pos = startPos;
let highlighted = false;
for (const token of row.tokens) {
let tokenContent = '';
@ -72,6 +73,8 @@
(row.type === RowType.Deletion || row.type === RowType.Addition) &&
highlight.find((h) => text.includes(h));
if (shouldHighlight) highlighted = true;
tokenContent += shouldHighlight ? `<mark>${token}</mark>` : token;
});
@ -84,12 +87,91 @@
pos += token.text.length;
}
return content;
return { html: content, highlighted };
};
$: rows = diffRows.rows.map((row) => ({ ...row, render: renderRowContent(row) }));
type RenderedRow = (typeof rows)[0];
const applyPadding = (rows: RenderedRow[]): RenderedRow[] => {
const chunks: (RenderedRow[] | RenderedRow)[] = [];
const mergeChunk = (rows: RenderedRow[], isFirst: boolean, isLast: boolean): RenderedRow[] => {
const spacerIndex = rows.findIndex((row) => row.type === RowType.Spacer);
if (spacerIndex === -1) {
if (isFirst) {
return rows.slice(-paddingLines);
} else if (isLast) {
return rows.slice(0, paddingLines);
} else {
return [
...rows.slice(0, paddingLines),
{
originalLineNumber: -1,
currentLineNumber: -1,
type: RowType.Spacer,
tokens: [{ text: '...' }],
render: { html: ['...'], highlighted: false }
},
...rows.slice(-paddingLines)
] as RenderedRow[];
}
} else {
let beforeSpacer = rows.slice(0, spacerIndex);
let afterSpacer = rows.slice(spacerIndex + 1);
if (isFirst) {
return afterSpacer.slice(-paddingLines);
} else if (isLast) {
return beforeSpacer.slice(0, paddingLines);
} else {
return [
...beforeSpacer.slice(0, paddingLines),
{
originalLineNumber: -1,
currentLineNumber: -1,
type: RowType.Spacer,
tokens: [{ text: '...' }],
render: { html: ['...'], highlighted: false }
},
...afterSpacer.slice(-paddingLines)
] as RenderedRow[];
}
}
};
for (const row of rows) {
if (row.render.highlighted) {
if (chunks.length > 0) {
const lastChunk = chunks[chunks.length - 1];
if (Array.isArray(lastChunk)) {
chunks[chunks.length - 1] = mergeChunk(lastChunk, chunks.length === 1, false);
}
}
chunks.push(row);
} else {
if (chunks.length === 0) {
chunks.push([row]);
} else {
const lastChunk = chunks[chunks.length - 1];
if (Array.isArray(lastChunk)) {
lastChunk.push(row);
} else {
chunks.push([row]);
}
}
}
}
const lastChunk = chunks[chunks.length - 1];
if (Array.isArray(lastChunk)) {
chunks[chunks.length - 1] = mergeChunk(lastChunk, false, true);
}
return chunks.flatMap((chunk) => chunk);
};
</script>
<div class="diff-listing w-full select-text whitespace-pre font-mono">
{#each diffRows.rows as row}
{#each applyPadding(rows) as row}
{@const baseNumber =
row.type === RowType.Equal || row.type === RowType.Deletion
? String(row.originalLineNumber)
@ -112,7 +194,7 @@
class="px-1 diff-line-{row.type}"
data-line-number={curNumber}
>
{#each renderRowContent(row) as content}
{#each row.render.html as content}
{@html content}
{/each}
</div>

View File

@ -1,159 +1,165 @@
import { Operation, type DiffArray, charDiff } from './diff';
export interface Token {
text: string;
className: string;
text: string;
className: string;
}
export interface Row {
originalLineNumber: number;
currentLineNumber: number;
tokens: Token[];
type: RowType;
originalLineNumber: number;
currentLineNumber: number;
tokens: Token[];
type: RowType;
size: number;
}
export const enum RowType {
Deletion = 'deletion',
Addition = 'addition',
Equal = 'equal',
Spacer = 'spacer'
Deletion = 'deletion',
Addition = 'addition',
Equal = 'equal',
Spacer = 'spacer'
}
export function buildDiffRows(
diff: DiffArray,
opts = { paddingLines: 10000 }
diff: DiffArray,
opts = { paddingLines: 10000 }
): {
originalLines: readonly string[];
currentLines: readonly string[];
rows: readonly Row[];
originalLines: readonly string[];
currentLines: readonly string[];
rows: readonly Row[];
} {
const { paddingLines } = opts;
const { paddingLines } = opts;
let currentLineNumber = 0;
let originalLineNumber = 0;
let currentLineNumber = 0;
let originalLineNumber = 0;
const originalLines: string[] = [];
const currentLines: string[] = [];
const rows: Row[] = [];
const originalLines: string[] = [];
const currentLines: string[] = [];
const rows: Row[] = [];
for (let i = 0; i < diff.length; ++i) {
const token = diff[i];
switch (token[0]) {
case Operation.Equal:
rows.push(...createEqualRows(token[1], i === 0, i === diff.length - 1));
originalLines.push(...token[1]);
currentLines.push(...token[1]);
break;
case Operation.Insert:
for (const line of token[1]) {
rows.push(createRow(line, RowType.Addition));
}
currentLines.push(...token[1]);
break;
case Operation.Delete:
originalLines.push(...token[1]);
if (diff[i + 1] && diff[i + 1][0] === Operation.Insert) {
i++;
rows.push(...createModifyRows(token[1].join('\n'), diff[i][1].join('\n')));
currentLines.push(...diff[i][1]);
} else {
for (const line of token[1]) {
rows.push(createRow(line, RowType.Deletion));
}
}
break;
}
}
for (let i = 0; i < diff.length; ++i) {
const token = diff[i];
switch (token[0]) {
case Operation.Equal:
rows.push(...createEqualRows(token[1], i === 0, i === diff.length - 1));
originalLines.push(...token[1]);
currentLines.push(...token[1]);
break;
case Operation.Insert:
for (const line of token[1]) {
rows.push(createRow(line, RowType.Addition));
}
currentLines.push(...token[1]);
break;
case Operation.Delete:
originalLines.push(...token[1]);
if (diff[i + 1] && diff[i + 1][0] === Operation.Insert) {
i++;
rows.push(...createModifyRows(token[1].join('\n'), diff[i][1].join('\n')));
currentLines.push(...diff[i][1]);
} else {
for (const line of token[1]) {
rows.push(createRow(line, RowType.Deletion));
}
}
break;
}
}
return { originalLines, currentLines, rows };
return { originalLines, currentLines, rows };
function createEqualRows(lines: string[], atStart: boolean, atEnd: boolean): Row[] {
const equalRows = [];
if (!atStart) {
for (let i = 0; i < paddingLines && i < lines.length; i++) {
equalRows.push(createRow(lines[i], RowType.Equal));
}
if (lines.length > paddingLines * 2 + 1 && !atEnd) {
equalRows.push(
createRow(`skipping ${lines.length - paddingLines * 2} matching lines`, RowType.Spacer)
);
}
}
if (!atEnd) {
const start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : paddingLines);
let skip = lines.length - paddingLines - 1;
if (!atStart) {
skip -= paddingLines;
}
if (skip > 0) {
originalLineNumber += skip;
currentLineNumber += skip;
}
function createEqualRows(lines: string[], atStart: boolean, atEnd: boolean): Row[] {
const equalRows = [];
if (!atStart) {
for (let i = 0; i < paddingLines && i < lines.length; i++) {
equalRows.push(createRow(lines[i], RowType.Equal));
}
if (lines.length > paddingLines * 2 + 1 && !atEnd) {
equalRows.push(
createRow(
`skipping ${lines.length - paddingLines * 2} matching lines`,
RowType.Spacer,
lines.length - paddingLines * 2
)
);
}
}
if (!atEnd) {
const start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : paddingLines);
let skip = lines.length - paddingLines - 1;
if (!atStart) {
skip -= paddingLines;
}
if (skip > 0) {
originalLineNumber += skip;
currentLineNumber += skip;
}
for (let i = start; i < lines.length; i++) {
equalRows.push(createRow(lines[i], RowType.Equal));
}
}
return equalRows;
}
for (let i = start; i < lines.length; i++) {
equalRows.push(createRow(lines[i], RowType.Equal));
}
}
return equalRows;
}
function createModifyRows(before: string, after: string): Row[] {
const internalDiff = charDiff(before, after, true /* cleanup diff */);
const deletionRows = [createRow('', RowType.Deletion)];
const insertionRows = [createRow('', RowType.Addition)];
function createModifyRows(before: string, after: string): Row[] {
const internalDiff = charDiff(before, after, true /* cleanup diff */);
const deletionRows = [createRow('', RowType.Deletion)];
const insertionRows = [createRow('', RowType.Addition)];
for (const token of internalDiff) {
const text = token[1];
const type = token[0];
const className = type === Operation.Equal ? '' : 'inner-diff';
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
if (i > 0 && type !== Operation.Insert) {
deletionRows.push(createRow('', RowType.Deletion));
}
if (i > 0 && type !== Operation.Delete) {
insertionRows.push(createRow('', RowType.Addition));
}
if (!lines[i]) {
continue;
}
if (type !== Operation.Insert) {
deletionRows[deletionRows.length - 1].tokens.push({ text: lines[i], className });
}
if (type !== Operation.Delete) {
insertionRows[insertionRows.length - 1].tokens.push({ text: lines[i], className });
}
}
}
return deletionRows.concat(insertionRows);
}
for (const token of internalDiff) {
const text = token[1];
const type = token[0];
const className = type === Operation.Equal ? '' : 'inner-diff';
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
if (i > 0 && type !== Operation.Insert) {
deletionRows.push(createRow('', RowType.Deletion));
}
if (i > 0 && type !== Operation.Delete) {
insertionRows.push(createRow('', RowType.Addition));
}
if (!lines[i]) {
continue;
}
if (type !== Operation.Insert) {
deletionRows[deletionRows.length - 1].tokens.push({ text: lines[i], className });
}
if (type !== Operation.Delete) {
insertionRows[insertionRows.length - 1].tokens.push({ text: lines[i], className });
}
}
}
return deletionRows.concat(insertionRows);
}
function createRow(text: string, type: RowType): Row {
if (type === RowType.Addition) {
currentLineNumber++;
}
if (type === RowType.Deletion) {
originalLineNumber++;
}
if (type === RowType.Equal) {
originalLineNumber++;
currentLineNumber++;
}
function createRow(text: string, type: RowType, size = 1): Row {
if (type === RowType.Addition) {
currentLineNumber++;
}
if (type === RowType.Deletion) {
originalLineNumber++;
}
if (type === RowType.Equal) {
originalLineNumber++;
currentLineNumber++;
}
return {
originalLineNumber,
currentLineNumber,
tokens: text ? [{ text, className: 'inner-diff' }] : [],
type
};
}
return {
originalLineNumber,
currentLineNumber,
tokens: text ? [{ text, className: 'inner-diff' }] : [],
type,
size
};
}
}
export function documentMap(lines: readonly string[]): Map<number, number> {
const map = new Map<number, number>();
for (let pos = 0, lineNo = 0; lineNo < lines.length; lineNo++) {
map.set(lineNo + 1, pos);
pos += lines[lineNo].length + 1;
}
return map;
const map = new Map<number, number>();
for (let pos = 0, lineNo = 0; lineNo < lines.length; lineNo++) {
map.set(lineNo + 1, pos);
pos += lines[lineNo].length + 1;
}
return map;
}

View File

@ -81,7 +81,7 @@
<div
class="flex-auto overflow-auto rounded-lg border border-zinc-700 bg-[#2F2F33] text-[#EBDBB2] drop-shadow-lg"
>
<CodeViewer {doc} {deltas} {filepath} paddingLines={4} {highlight} />
<CodeViewer {doc} {deltas} {filepath} paddingLines={2} {highlight} />
</div>
</div>
</li>