Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions src/MappedDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,15 @@ public function __construct(
parent::__construct($edits);

$xi = $yi = 0;
for ($i = 0; $i < count($this->edits); $i++) {
$orig = &$this->edits[$i]->orig;
if (is_array($orig)) {
$orig = array_slice($from_lines, $xi, count($orig));
$xi += count($orig);
foreach ($this->edits as $edit) {
if (is_array($edit->orig)) {
$edit->orig = array_slice($from_lines, $xi, count($edit->orig));
$xi += count($edit->orig);
}

$final = &$this->edits[$i]->final;
if (is_array($final)) {
$final = array_slice($to_lines, $yi, count($final));
$yi += count($final);
if (is_array($edit->final)) {
$edit->final = array_slice($to_lines, $yi, count($edit->final));
$yi += count($edit->final);
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions src/NativeEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ public function diff(): OperationList
$this->xchanged = $this->ychanged = [];
$this->xv = $this->yv = [];
$this->xind = $this->yind = [];
unset($this->seq);
unset($this->in_seq);
unset($this->lcs);
// In modern PHP we cannot unset as NativeEngine may be subclassed and subclasses may have hooks
$this->seq = null;
$this->in_seq = null;
$this->lcs = null;

// Skip leading common lines.
for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {
Expand Down Expand Up @@ -243,6 +244,8 @@ protected function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)

$numer = $xlim - $xoff + $nchunks - 1;
$x = $xoff;
// $k should be defined by the first iteration reaching an empty inseq(y)
$k = null;
for ($chunk = 0; $chunk < $nchunks; $chunk++) {
if ($chunk > 0) {
for ($i = 0; $i <= $this->lcs; $i++) {
Expand All @@ -263,7 +266,7 @@ protected function _diag($xoff, $xlim, $yoff, $ylim, $nchunks)
assert($k > 0);
$ymids[$k] = $ymids[$k - 1];
break;
} elseif ($y > $this->seq[$k - 1]) {
} elseif (is_int($k) && ($k > 0) && ($y > $this->seq[$k - 1])) {
/*
* Disable this assertion for now until we know why it
* causes https://github.com/maintaina-com/Text_Diff/issues/12
Expand Down
4 changes: 2 additions & 2 deletions src/ThreeWay.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ protected function _diff3($edits1, $edits2)

$ncopy = min($e1->norig(), $e2->norig());
assert($ncopy > 0);
$edits[] = new ThreeWayCopyOperation(array_slice($e1->getOrig(), 0, $ncopy));
$edits[] = new ThreeWayCopyOperation(array_slice($e1->orig, 0, $ncopy));

if ($e1->norig() > $ncopy) {
array_splice($e1->getOrig(), 0, $ncopy);
array_splice($e1->orig, 0, $ncopy);
array_splice($e1->final, 0, $ncopy);
} else {
$e1 = next($edits1);
Expand Down
166 changes: 166 additions & 0 deletions test/ColoredRendererTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php

/**
* @author Ralf Lang
* @license http://www.horde.org/licenses/gpl GPL
* @category Horde
* @package Text_Diff
* @subpackage UnitTests
*
* UnifiedColoredRenderer
*
* UnifiedColoredRenderer wraps added lines in $cli->color('lightgreen', ...),
* deleted lines in $cli->color('lightred', ...) and block headers in
* $cli->color('lightmagenta', ...). Context (unchanged) lines are NOT
* color-wrapped. They pass through the parent UnifiedRenderer unchanged.
*
* The renderer requires a Horde/Cli instance via $params['cli'].
* Tests use an anonymous-class stand-in that wraps text in predictable
* "[COLOR]text[/COLOR]" markers instead of real ANSI/HTML codes
**/

namespace Horde\Text\Diff\Test;

use BadMethodCallException;
use Horde\Text\Diff\Diff;
use Horde\Text\Diff\NativeEngine;
use Horde\Text\Diff\UnifiedColoredRenderer;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

/**
* @requires PHP >= 8.1
*/
#[CoversClass(UnifiedColoredRenderer::class)]
class ColoredRendererTest extends TestCase
{
/** Returns a minimal Horde_Cli stand-in that wraps text in [COLOR]...[/COLOR]. */
private function mockCli(): object
{
return new class {
public function color(string $name, string $text): string
{
return "[{$name}]{$text}[/{$name}]";
}
};
}

// -------------------------------------------------------------------------
// Constructor guard
// -------------------------------------------------------------------------

/**
* The constructor must throw BadMethodCallException when the 'cli' key
* is absent from $params.
*
* @see src/UnifiedColoredRenderer.php __construct()
*/
public function testConstructorThrowsWithoutCliParam(): void
{
$this->expectException(BadMethodCallException::class);
new UnifiedColoredRenderer([]);
}

// -------------------------------------------------------------------------
// Color wrapping for added / deleted / header lines
// -------------------------------------------------------------------------

/**
* Added lines must be wrapped via $cli->color('lightgreen', ...).
*
* Input: pure insertion (from = [], to = ['new line\n']).
* The "+" content line must be inside "[lightgreen]...[/lightgreen]".
*/
public function testColoredRendererAddedLinesColor(): void
{
$renderer = new UnifiedColoredRenderer(['cli' => $this->mockCli()]);
$diff = Diff::fromFileLineArrays([], ["new line\n"], NativeEngine::class);
$output = $renderer->render($diff);

$this->assertStringContainsString(
'[lightgreen]',
$output,
'Added lines must be wrapped in the lightgreen color marker'
);
$this->assertStringContainsString('new line', $output);
}

/**
* Deleted lines must be wrapped via $cli->color('lightred', ...).
*
* Input: pure deletion (from = ['old line\n'], to = []).
* The "-" content line must be inside "[lightred]...[/lightred]".
*/
public function testColoredRendererRemovedLinesColor(): void
{
$renderer = new UnifiedColoredRenderer(['cli' => $this->mockCli()]);
$diff = Diff::fromFileLineArrays(["old line\n"], [], NativeEngine::class);
$output = $renderer->render($diff);

$this->assertStringContainsString(
'[lightred]',
$output,
'Deleted lines must be wrapped in the lightred color marker'
);
$this->assertStringContainsString('old line', $output);
}

/**
* Block headers (@@ ... @@) must be wrapped via $cli->color('lightmagenta', ...).
*
* Any diff that produces at least one hunk will have a block header.
*/
public function testColoredRendererBlockHeaderColor(): void
{
$renderer = new UnifiedColoredRenderer(['cli' => $this->mockCli()]);
$diff = Diff::fromFileLineArrays(
["This line is the same.\n", "This line is different in 1.txt\n", "This line is the same.\n"],
["This line is the same.\n", "This line is different in 2.txt\n", "This line is the same.\n"],
NativeEngine::class
);
$output = $renderer->render($diff);

$this->assertStringContainsString(
'[lightmagenta]',
$output,
'Block headers must be wrapped in the lightmagenta color marker'
);
$this->assertStringContainsString(
'@@',
$output,
'The @@ hunk header text must still be present inside the color marker'
);
}

/**
* Unchanged context lines must NOT be wrapped in any color marker.
* UnifiedColoredRenderer does not override _context(), so context lines
* are emitted by the parent UnifiedRenderer as plain text.
*
* Input: 1-line context sandwiching the change (3-line files, default context=4).
* The "This line is the same." lines appear in context must not be colorized.
*/
public function testColoredRendererContextLinesUncolored(): void
{
$renderer = new UnifiedColoredRenderer(['cli' => $this->mockCli()]);
$from = ["unchanged\n", "old value\n", "unchanged\n"];
$to = ["unchanged\n", "new value\n", "unchanged\n"];

$diff = Diff::fromFileLineArrays($from, $to, NativeEngine::class);
$output = $renderer->render($diff);

// Check that the context line "unchanged" appears outside any color wrapper
// by verifying it is not immediately preceded by "]" from a color marker close.
// A simpler proxy: the raw text appears in the output at all.
$this->assertStringContainsString(
'unchanged',
$output,
'Context line content must appear in the output'
);

// Context lines are prefixed with " " (space) in unified format.
// They should NOT be wrapped in [lightgreen] or [lightred].
$this->assertStringNotContainsString('[lightgreen] unchanged', $output);
$this->assertStringNotContainsString('[lightred] unchanged', $output);
}
}
Loading
Loading