git-tweezers
Version:
Advanced git staging tool with hunk and line-level control
118 lines (117 loc) • 3.78 kB
JavaScript
import chalk from 'chalk';
export class DiffRenderer {
/**
* Render a hunk with optional context lines
*/
renderHunk(hunk, options = {}) {
const { context = 3, maxLines = 120, color = true } = options;
const lines = [];
let displayedLines = 0;
let inContext = false;
let contextCount = 0;
for (let i = 0; i < hunk.changes.length; i++) {
const change = hunk.changes[i];
// Check if we should show this line
if (change.type === 'UnchangedLine') {
// Show context lines around actual changes
const hasChangeBefore = this.hasChangeWithin(hunk.changes, i, -context);
const hasChangeAfter = this.hasChangeWithin(hunk.changes, i, context);
if (hasChangeBefore || hasChangeAfter) {
inContext = true;
contextCount = 0;
}
else if (inContext) {
contextCount++;
if (contextCount > context) {
inContext = false;
continue;
}
}
else {
continue;
}
}
else {
inContext = true;
contextCount = 0;
}
// Render the line
const line = this.renderLine(change, color);
lines.push(line);
displayedLines++;
// Check if we've hit the max lines limit
if (displayedLines >= maxLines && i < hunk.changes.length - 1) {
lines.push(chalk.dim('... (truncated)'));
break;
}
}
return lines.join('\n');
}
/**
* Render a single line change
*/
renderLine(change, color) {
const prefix = this.getPrefix(change.type);
const content = change.content;
if (!color) {
return prefix + content;
}
switch (change.type) {
case 'AddedLine':
return chalk.green(prefix + content);
case 'DeletedLine':
return chalk.red(prefix + content);
case 'UnchangedLine':
return chalk.dim(prefix + content);
default:
return prefix + content;
}
}
/**
* Get the prefix character for a change type
*/
getPrefix(type) {
switch (type) {
case 'AddedLine':
return '+';
case 'DeletedLine':
return '-';
case 'UnchangedLine':
return ' ';
default:
return ' ';
}
}
/**
* Check if there's a change within the given distance
*/
hasChangeWithin(changes, index, distance) {
const start = Math.max(0, index + (distance < 0 ? distance : 0));
const end = Math.min(changes.length, index + (distance > 0 ? distance + 1 : 1));
for (let i = start; i < end; i++) {
if (i !== index && changes[i].type !== 'UnchangedLine') {
return true;
}
}
return false;
}
/**
* Render a summary for a hunk (for inline display)
*/
renderHunkSummary(hunk) {
const stats = hunk.stats;
if (!stats)
return '';
const parts = [];
if (stats.additions > 0) {
parts.push(chalk.green(`+${stats.additions}`));
}
if (stats.deletions > 0) {
parts.push(chalk.red(`-${stats.deletions}`));
}
if (hunk.summary) {
parts.push(chalk.dim(`| ${hunk.summary}`));
}
return parts.join(' ');
}
}