diff --git a/src/Controller/SnipContentController.php b/src/Controller/SnipContentController.php
new file mode 100644
index 0000000..af57b9c
--- /dev/null
+++ b/src/Controller/SnipContentController.php
@@ -0,0 +1,41 @@
+denyAccessUnlessGranted(SnipVoter::VIEW, $to->getSnip());
+
+ if ($from === null) {
+ $from = $to->getParent();
+ }
+
+ $diff = MyersDiff::buildDiffLines(
+ $this->contentService->rebuildText($from),
+ $this->contentService->rebuildText($to),
+ );
+
+ dump($diff);
+
+ return $this->render('content/compare.html.twig', [
+ 'snip' => $to->getSnip(),
+ 'diff' => $diff,
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/src/Service/SnipContent/DiffTypeEnum.php b/src/Service/SnipContent/DiffTypeEnum.php
new file mode 100644
index 0000000..cfc1abc
--- /dev/null
+++ b/src/Service/SnipContent/DiffTypeEnum.php
@@ -0,0 +1,15 @@
+value === $diffType;
+ }
+}
diff --git a/src/Service/SnipContent/MyersDiff.php b/src/Service/SnipContent/MyersDiff.php
index b7342f6..155d804 100644
--- a/src/Service/SnipContent/MyersDiff.php
+++ b/src/Service/SnipContent/MyersDiff.php
@@ -52,7 +52,7 @@ class MyersDiff
$x++;
$count++;
}
- $solution[] = ['D', $count];
+ $solution[] = [DiffTypeEnum::DELETE->value, $count];
}
// Insertions
@@ -63,10 +63,10 @@ class MyersDiff
$y++;
}
$solutionKey = count($solution) - 1;
- if ($solutionKey >= 0 && $solution[$solutionKey][0] === 'I') {
+ if ($solutionKey >= 0 && DiffTypeEnum::INSERT->is($solution[$solutionKey][0])) {
$solution[$solutionKey][1] = array_merge($solution[$solutionKey][1], $values);
} else {
- $solution[] = ['I', $values];
+ $solution[] = [DiffTypeEnum::INSERT->value, $values];
}
}
@@ -78,7 +78,7 @@ class MyersDiff
$count++;
}
if ($count > 0) {
- $solution[] = ['K', $count];
+ $solution[] = [DiffTypeEnum::KEEP->value, $count];
}
}
@@ -88,16 +88,24 @@ class MyersDiff
/**
* Calculate the shortest edit sequence to convert $x into $y.
*
- * @param string $textFrom - tokens (characters, words or lines)
- * @param string $textTo - tokens (characters, words or lines)
- * @param ?callable $compare - comparison function for tokens. Signature is compare($x, $y):bool. If null, === is used.
+ * @param string|array $textFrom - tokens (characters, words or lines)
+ * @param string|array $textTo - tokens (characters, words or lines)
+ * @param ?callable $compare - comparison function for tokens. Signature is compare($x, $y):bool. If null, === is used.
*
* @return array[] - pairs of token and edit (-1 for delete, 0 for keep, +1 for insert)
*/
- public static function calculate(string $textFrom, string $textTo, ?callable $compare = null): array
+ public static function calculate(string|array $textFrom, string|array $textTo, ?callable $compare = null): array
{
- $a = self::explode($textFrom);
- $b = self::explode($textTo);
+ if (is_string($textFrom)) {
+ $a = self::explode($textFrom);
+ } else {
+ $a = $textFrom;
+ }
+ if (is_string($textTo)) {
+ $b = self::explode($textTo);
+ } else {
+ $b = $textTo;
+ }
if ($compare === null) {
$compare = function ($x, $y) {
@@ -144,22 +152,75 @@ class MyersDiff
$x = 0;
foreach ($diff as [$op, $data]) {
- if ($op === 'K') {
- for ($i = 0; $i < $data; $i++) {
- $b[] = $a[$x++];
- }
- } elseif ($op === 'D') {
- $x += $data; // skip deleted
- } elseif ($op === 'I') {
- foreach ($data as $v) {
- $b[] = $v;
- }
+ switch ($op) {
+ case DiffTypeEnum::KEEP->value:
+ for ($i = 0; $i < $data; $i++) {
+ $b[] = $a[$x++];
+ }
+ break;
+ case DiffTypeEnum::DELETE->value:
+ $x += $data; // skip deleted
+ break;
+ case DiffTypeEnum::INSERT->value:
+ foreach ($data as $v) {
+ $b[] = $v;
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid diff operation');
}
}
return self::implode($b);
}
+ public static function buildDiffLines(string $textFrom, string $textTo): array
+ {
+ $a = self::explode($textFrom);
+ $b = self::explode($textTo);
+ $diff = MyersDiff::calculate($a, $b);
+
+ $lines = [];
+ $x = 0;
+ foreach ($diff as [$op, $data]) {
+ switch ($op) {
+ case DiffTypeEnum::KEEP->value:
+ for ($i = 0; $i < $data; $i++) {
+ $lines[] = [
+ 'type' => 'keep',
+ 'from' => $a[$x],
+ 'to' => $a[$x],
+ ];
+ $x++;
+ }
+ break;
+ case DiffTypeEnum::DELETE->value:
+ for ($i = 0; $i < $data; $i++) {
+ $lines[] = [
+ 'type' => 'delete',
+ 'from' => $a[$x],
+ 'to' => '',
+ ];
+ $x++;
+ }
+ break;
+ case DiffTypeEnum::INSERT->value:
+ foreach ($data as $v) {
+ $lines[] = [
+ 'type' => 'insert',
+ 'from' => '',
+ 'to' => $v,
+ ];
+ }
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid diff operation');
+ }
+ }
+
+ return $lines;
+ }
+
private static function explode(string $text): array
{
return explode(self::NEWLINE, $text);
diff --git a/src/Service/SnipContent/SnipContentService.php b/src/Service/SnipContent/SnipContentService.php
index 09b4ba6..a615684 100644
--- a/src/Service/SnipContent/SnipContentService.php
+++ b/src/Service/SnipContent/SnipContentService.php
@@ -41,8 +41,7 @@ readonly class SnipContentService
public function getActiveText(Snip $snip): string
{
- $contentRepo = $this->em->getRepository(SnipContent::class);
- return $this->rebuildText($contentRepo->find($snip->getActiveVersion()));
+ return $this->rebuildText($snip->getActiveVersion());
}
public function rebuildText(?SnipContent $snipContent): string
diff --git a/templates/content/compare.html.twig b/templates/content/compare.html.twig
new file mode 100644
index 0000000..ac00bfd
--- /dev/null
+++ b/templates/content/compare.html.twig
@@ -0,0 +1,38 @@
+{% extends 'base/single.column.html.twig' %}
+
+{% set title = 'Snip compare ' ~ snip %}
+
+{% block body %}
+
+ Back
+
+
+
Line | +Old | +New | +
---|---|---|
+ {{ line.type }} + | ++ {% if line.from %} + {{ line.from }} + {% endif %} + | ++ {% if line.to %} + {{ line.to }} + {% endif %} + | +