* Follow the TODO comment in TextSlotDiffRenderer ::getTextDiffInternal() by moving the code out to three parallel implementations, namely ExternalTextDiffer, PhpTextDiffer and Wikidiff2TextDiffer. * Add a container/factory class ManifoldTextDiffer to glue them together and collate available formats. * Move the inline legend to Wikidiff2TextDiffer. Not the toggle since the ability to toggle depends on the available format, not the current format. * Update the diff cache keys so that ManifoldTextDiffer can store the engine=>format map it used to generate the diff. * Drop support for the second parameter to TextSlotDiffRenderer ::setEngine(), since nothing used it anymore. * Provide a format batch API, since some engines are able to efficiently generate multiple formats. This might be used by DifferenceEngine in future. Needs risky change notification for the cache key change. Bug: T339184 Depends-On: I8a35b9b8ec1622c9a36d2496bdd24f51bc52c85f Change-Id: I5c506e39162855aff53dd420dd8145156739059c
165 lines
4.4 KiB
PHP
165 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace MediaWiki\Diff\TextDiffer;
|
|
|
|
use MediaWiki\Html\Html;
|
|
use TextSlotDiffRenderer;
|
|
|
|
/**
|
|
* @since 1.41
|
|
*/
|
|
class Wikidiff2TextDiffer extends BaseTextDiffer {
|
|
/** @var string */
|
|
private $version;
|
|
/** @var bool */
|
|
private $haveMoveSupport;
|
|
/** @var bool */
|
|
private $haveCutoffParameter;
|
|
|
|
/**
|
|
* Fake wikidiff2 extension version for PHPUnit testing
|
|
* @var string|null
|
|
*/
|
|
public static $fakeVersionForTesting = null;
|
|
|
|
/**
|
|
* Determine whether the extension is installed (or mocked for testing)
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function isInstalled() {
|
|
return self::$fakeVersionForTesting !== null
|
|
|| function_exists( 'wikidiff2_do_diff' );
|
|
}
|
|
|
|
public function __construct() {
|
|
$this->version = self::$fakeVersionForTesting ?? phpversion( 'wikidiff2' );
|
|
$this->haveMoveSupport = version_compare( $this->version, '1.5.0', '>=' );
|
|
$this->haveCutoffParameter = $this->haveMoveSupport
|
|
&& version_compare( $this->version, '1.8.0', '<' );
|
|
}
|
|
|
|
public function getName(): string {
|
|
return 'wikidiff2';
|
|
}
|
|
|
|
public function getFormatContext( string $format ) {
|
|
return $format === 'inline' ? self::CONTEXT_PLAIN : self::CONTEXT_ROW;
|
|
}
|
|
|
|
public function getCacheKeys( array $formats ): array {
|
|
return [ '20-wikidiff2-version' => $this->version ];
|
|
}
|
|
|
|
public function doRenderBatch( string $oldText, string $newText, array $formats ): array {
|
|
$result = [];
|
|
foreach ( $formats as $format ) {
|
|
switch ( $format ) {
|
|
case 'table':
|
|
$result['table'] = $this->doTableFormat( $oldText, $newText );
|
|
break;
|
|
|
|
case 'inline':
|
|
$result['inline'] = $this->doInlineFormat( $oldText, $newText );
|
|
break;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Do a table format diff
|
|
*
|
|
* @param string $old
|
|
* @param string $new
|
|
* @return string
|
|
*/
|
|
private function doTableFormat( $old, $new ) {
|
|
if ( $this->haveCutoffParameter ) {
|
|
return wikidiff2_do_diff(
|
|
$old,
|
|
$new,
|
|
2,
|
|
0
|
|
);
|
|
} else {
|
|
// Don't pass the 4th parameter introduced in version 1.5.0 and removed in version 1.8.0
|
|
return wikidiff2_do_diff(
|
|
$old,
|
|
$new,
|
|
2
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do an inline format diff
|
|
*
|
|
* @param string $oldText
|
|
* @param string $newText
|
|
* @return string
|
|
*/
|
|
private function doInlineFormat( $oldText, $newText ) {
|
|
return wikidiff2_inline_diff( $oldText, $newText, 2 );
|
|
}
|
|
|
|
public function getFormats(): array {
|
|
return [ 'table', 'inline' ];
|
|
}
|
|
|
|
public function getTablePrefixes( string $format ): array {
|
|
$localizer = $this->getLocalizer();
|
|
$ins = Html::element( 'span',
|
|
[ 'class' => 'mw-diff-inline-legend-ins' ],
|
|
$localizer->msg( 'diff-inline-tooltip-ins' )->plain()
|
|
);
|
|
$del = Html::element( 'span',
|
|
[ 'class' => 'mw-diff-inline-legend-del' ],
|
|
$localizer->msg( 'diff-inline-tooltip-del' )->plain()
|
|
);
|
|
$hideDiffClass = $format === 'inline' ? '' : 'oo-ui-element-hidden';
|
|
$legend = Html::rawElement( 'div',
|
|
[ 'class' => 'mw-diff-inline-legend ' . $hideDiffClass ], "$ins $del"
|
|
);
|
|
return [ TextSlotDiffRenderer::INLINE_LEGEND_KEY => $legend ];
|
|
}
|
|
|
|
public function localize( string $format, string $diff, array $options = [] ): string {
|
|
$diff = $this->localizeLineNumbers( $diff,
|
|
$options['reducedLineNumbers'] ?? false );
|
|
// FIXME: in the inline case, comments like <!-- LINES 25,25 --> remain in the output
|
|
if ( $this->haveMoveSupport ) {
|
|
$diff = $this->addLocalizedTitleTooltips( $format, $diff );
|
|
}
|
|
return $diff;
|
|
}
|
|
|
|
/**
|
|
* Add title attributes for tooltips on various diff elements
|
|
*
|
|
* @param string $format
|
|
* @param string $text
|
|
* @return string
|
|
*/
|
|
private function addLocalizedTitleTooltips( $format, $text ) {
|
|
// Moved paragraph indicators.
|
|
$localizer = $this->getLocalizer();
|
|
$replacements = [
|
|
'class="mw-diff-movedpara-right"' =>
|
|
'class="mw-diff-movedpara-right" title="' .
|
|
$localizer->msg( 'diff-paragraph-moved-toold' )->escaped() . '"',
|
|
'class="mw-diff-movedpara-left"' =>
|
|
'class="mw-diff-movedpara-left" title="' .
|
|
$localizer->msg( 'diff-paragraph-moved-tonew' )->escaped() . '"',
|
|
];
|
|
// For inline diffs, add tooltips to `<ins>` and `<del>`.
|
|
if ( $format == 'inline' ) {
|
|
$replacements['<ins>'] = Html::openElement( 'ins',
|
|
[ 'title' => $localizer->msg( 'diff-inline-tooltip-ins' )->plain() ] );
|
|
$replacements['<del>'] = Html::openElement( 'del',
|
|
[ 'title' => $localizer->msg( 'diff-inline-tooltip-del' )->plain() ] );
|
|
}
|
|
return strtr( $text, $replacements );
|
|
}
|
|
|
|
}
|