diff --git a/.phan/internal_stubs/wikidiff.php b/.phan/internal_stubs/wikidiff.php index 8b904fb049d..a57fa8d34ad 100644 --- a/.phan/internal_stubs/wikidiff.php +++ b/.phan/internal_stubs/wikidiff.php @@ -45,3 +45,12 @@ function wikidiff2_inline_diff( $text1, $text2, $numContextLines ) { */ function wikidiff2_inline_json_diff( $text1, $text2, $numContextLines ) { } + +/** + * @param string $text1 + * @param string $text2 + * @param array $options + * @return array + */ +function wikidiff2_multi_format_diff( $text1, $text2, $options ) { +} diff --git a/docs/config-schema.yaml b/docs/config-schema.yaml index c3bf7e88609..7f8b75f3b48 100755 --- a/docs/config-schema.yaml +++ b/docs/config-schema.yaml @@ -2125,6 +2125,29 @@ config-schema: - string - boolean description: 'Name of the external diff engine to use.' + Wikidiff2Options: + default: { } + type: object + description: |- + Options for wikidiff2: + - useMultiFormat: (bool) Whether to use wikidiff2_multi_format_diff() + if it is available. This temporarily defaults to false, during + migration to the new code. It is available in wikidiff2 1.14.0+. + The following options are only effective if wikidiff2_multi_format_diff() + is enabled. See README.md in wikidiff2 for details: + - numContextLines + - changeThreshold + - movedLineThreshold + - maxMovedLines + - maxWordLevelDiffComplexity + - maxSplitSize + - initialSplitThreshold + - finalSplitThreshold + Also: + - formatOptions: An array of format-specific overrides. The key may + be "inline" or "table" and the value is an array with keys + numContextLines, changeThreshold, etc. + @since 1.41 RequestTimeLimit: default: null type: diff --git a/docs/config-vars.php b/docs/config-vars.php index 36bc082c702..fbe72a92d6e 100755 --- a/docs/config-vars.php +++ b/docs/config-vars.php @@ -1307,6 +1307,12 @@ $wgDiffEngine = null; */ $wgExternalDiffEngine = null; +/** + * Config variable stub for the Wikidiff2Options setting, for use by phpdoc and IDEs. + * @see MediaWiki\MainConfigSchema::Wikidiff2Options + */ +$wgWikidiff2Options = null; + /** * Config variable stub for the RequestTimeLimit setting, for use by phpdoc and IDEs. * @see MediaWiki\MainConfigSchema::RequestTimeLimit diff --git a/includes/MainConfigNames.php b/includes/MainConfigNames.php index b482abeafa4..1ac11146f9a 100755 --- a/includes/MainConfigNames.php +++ b/includes/MainConfigNames.php @@ -1322,6 +1322,12 @@ class MainConfigNames { */ public const ExternalDiffEngine = 'ExternalDiffEngine'; + /** + * Name constant for the Wikidiff2Options setting, for use with Config::get() + * @see MainConfigSchema::Wikidiff2Options + */ + public const Wikidiff2Options = 'Wikidiff2Options'; + /** * Name constant for the RequestTimeLimit setting, for use with Config::get() * @see MainConfigSchema::RequestTimeLimit diff --git a/includes/MainConfigSchema.php b/includes/MainConfigSchema.php index 059835fb798..f3a5530e8af 100644 --- a/includes/MainConfigSchema.php +++ b/includes/MainConfigSchema.php @@ -3467,6 +3467,35 @@ class MainConfigSchema { 'type' => 'string|false', ]; + /** + * Options for wikidiff2: + * - useMultiFormat: (bool) Whether to use wikidiff2_multi_format_diff() + * if it is available. This temporarily defaults to false, during + * migration to the new code. It is available in wikidiff2 1.14.0+. + * + * The following options are only effective if wikidiff2_multi_format_diff() + * is enabled. See README.md in wikidiff2 for details: + * + * - numContextLines + * - changeThreshold + * - movedLineThreshold + * - maxMovedLines + * - maxWordLevelDiffComplexity + * - maxSplitSize + * - initialSplitThreshold + * - finalSplitThreshold + * + * Also: + * - formatOptions: An array of format-specific overrides. The key may + * be "inline" or "table" and the value is an array with keys + * numContextLines, changeThreshold, etc. + * @since 1.41 + */ + public const Wikidiff2Options = [ + 'default' => [], + 'type' => 'map' + ]; + // endregion -- end of Content handlers and storage /***************************************************************************/ diff --git a/includes/config-schema.php b/includes/config-schema.php index bf0eedfe990..c43b3a138df 100755 --- a/includes/config-schema.php +++ b/includes/config-schema.php @@ -422,6 +422,8 @@ return [ 'PageLanguageUseDB' => false, 'DiffEngine' => null, 'ExternalDiffEngine' => false, + 'Wikidiff2Options' => [ + ], 'RequestTimeLimit' => null, 'TransactionalTimeLimit' => 120, 'CriticalSectionTimeLimit' => 180.0, @@ -2621,6 +2623,7 @@ return [ 0 => 'string', 1 => 'boolean', ], + 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 0 => 'integer', 1 => 'null', diff --git a/includes/content/ContentHandler.php b/includes/content/ContentHandler.php index 1de61d4d37e..9bf1d2615a9 100644 --- a/includes/content/ContentHandler.php +++ b/includes/content/ContentHandler.php @@ -711,7 +711,8 @@ abstract class ContentHandler { RequestContext::getMain(), $language, $config->get( MainConfigNames::DiffEngine ), - $config->get( MainConfigNames::ExternalDiffEngine ) + $config->get( MainConfigNames::ExternalDiffEngine ), + $config->get( MainConfigNames::Wikidiff2Options ) ); } $format = $options['diff-type'] ?? 'table'; diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index 317ccaa3862..f7fd340f066 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -2241,7 +2241,8 @@ class DifferenceEngine extends ContextSource { $this->getContext(), $this->getDiffLang(), $this->getConfig()->get( MainConfigNames::DiffEngine ), - $this->getConfig()->get( MainConfigNames::ExternalDiffEngine ) + $this->getConfig()->get( MainConfigNames::ExternalDiffEngine ), + $this->getConfig()->get( MainConfigNames::Wikidiff2Options ) ); } return $this->textDiffer; diff --git a/includes/diff/TextDiffer/ManifoldTextDiffer.php b/includes/diff/TextDiffer/ManifoldTextDiffer.php index 60b21223afd..75540b2206f 100644 --- a/includes/diff/TextDiffer/ManifoldTextDiffer.php +++ b/includes/diff/TextDiffer/ManifoldTextDiffer.php @@ -27,6 +27,8 @@ class ManifoldTextDiffer implements TextDiffer { private $differs; /** @var TextDiffer[]|null The differ to use for each format */ private $differsByFormat; + /** @var array */ + private $wikidiff2Options; /** * @internal For use by DifferenceEngine, ContentHandler @@ -35,17 +37,20 @@ class ManifoldTextDiffer implements TextDiffer { * @param Language|null $contentLanguage * @param string|null $diffEngine The DiffEngine config variable * @param string|false|null $externalPath The ExternalDiffEngine config variable + * @param array $wikidiff2Options The Wikidiff2Options config variable */ public function __construct( MessageLocalizer $localizer, ?Language $contentLanguage, $diffEngine, - $externalPath + $externalPath, + $wikidiff2Options ) { $this->localizer = $localizer; $this->contentLanguage = $contentLanguage; $this->diffEngine = $diffEngine; $this->externalPath = $externalPath; + $this->wikidiff2Options = $wikidiff2Options; } public function getName(): string { @@ -243,7 +248,9 @@ class ManifoldTextDiffer implements TextDiffer { case 'wikidiff2': if ( Wikidiff2TextDiffer::isInstalled() ) { - return new Wikidiff2TextDiffer; + return new Wikidiff2TextDiffer( + $this->wikidiff2Options + ); } $failureReason = 'wikidiff2 is not available'; return null; diff --git a/includes/diff/TextDiffer/Wikidiff2TextDiffer.php b/includes/diff/TextDiffer/Wikidiff2TextDiffer.php index 08933797b71..0e1b049047f 100644 --- a/includes/diff/TextDiffer/Wikidiff2TextDiffer.php +++ b/includes/diff/TextDiffer/Wikidiff2TextDiffer.php @@ -14,7 +14,26 @@ class Wikidiff2TextDiffer extends BaseTextDiffer { /** @var bool */ private $haveMoveSupport; /** @var bool */ + private $haveMultiFormatSupport; + /** @var bool */ private $haveCutoffParameter; + /** @var bool */ + private $useMultiFormat; + /** @var array */ + private $defaultOptions; + /** @var array[] */ + private $formatOptions; + + private const OPT_NAMES = [ + 'numContextLines', + 'changeThreshold', + 'movedLineThreshold', + 'maxMovedLines', + 'maxWordLevelDiffComplexity', + 'maxSplitSize', + 'initialSplitThreshold', + 'finalSplitThreshold', + ]; /** * Fake wikidiff2 extension version for PHPUnit testing @@ -32,11 +51,23 @@ class Wikidiff2TextDiffer extends BaseTextDiffer { || function_exists( 'wikidiff2_do_diff' ); } - public function __construct() { + /** + * @param array $options + */ + public function __construct( $options ) { $this->version = self::$fakeVersionForTesting ?? phpversion( 'wikidiff2' ); $this->haveMoveSupport = version_compare( $this->version, '1.5.0', '>=' ); + $this->haveMultiFormatSupport = version_compare( $this->version, '1.14.0', '>=' ); $this->haveCutoffParameter = $this->haveMoveSupport && version_compare( $this->version, '1.8.0', '<' ); + + $this->useMultiFormat = $this->haveMultiFormatSupport && !empty( $options['useMultiFormat'] ); + $validOpts = array_fill_keys( self::OPT_NAMES, true ); + $this->defaultOptions = array_intersect_key( $options, $validOpts ); + $this->formatOptions = []; + foreach ( $options['formatOptions'] ?? [] as $format => $formatOptions ) { + $this->formatOptions[$format] = array_intersect_key( $formatOptions, $validOpts ); + } } public function getName(): string { @@ -52,16 +83,38 @@ class Wikidiff2TextDiffer extends BaseTextDiffer { } 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; + if ( $this->useMultiFormat ) { + if ( !$this->formatOptions ) { + /** @var array $result */ + $result = wikidiff2_multi_format_diff( + $oldText, + $newText, + [ 'formats' => $formats ] + $this->defaultOptions + ); + } else { + $result = []; + foreach ( $formats as $format ) { + $result[$format] = wikidiff2_multi_format_diff( + $oldText, + $newText, + [ 'formats' => $formats ] + + ( $this->formatOptions[$format] ?? [] ) + + $this->defaultOptions + ); + } + } + } else { + $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; + case 'inline': + $result['inline'] = $this->doInlineFormat( $oldText, $newText ); + break; + } } } return $result; @@ -162,4 +215,11 @@ class Wikidiff2TextDiffer extends BaseTextDiffer { return strtr( $text, $replacements ); } + public function getPreferredFormatBatch( string $format ): array { + if ( $this->formatOptions ) { + return [ $format ]; + } else { + return [ 'table', 'inline' ]; + } + } } diff --git a/tests/phpunit/includes/diff/TextDiffer/ManifoldTextDifferTest.php b/tests/phpunit/includes/diff/TextDiffer/ManifoldTextDifferTest.php index bae1290762f..d882f539c51 100644 --- a/tests/phpunit/includes/diff/TextDiffer/ManifoldTextDifferTest.php +++ b/tests/phpunit/includes/diff/TextDiffer/ManifoldTextDifferTest.php @@ -14,7 +14,8 @@ class ManifoldTextDifferTest extends MediaWikiIntegrationTestCase { RequestContext::getMain(), $services->getLanguageFactory()->getLanguage( 'en' ), $configVars['DiffEngine'] ?? null, - $configVars['ExternalDiffEngine'] ?? null + $configVars['ExternalDiffEngine'] ?? null, + $configVars['Wikidiff2Options'] ?? [] ); } diff --git a/tests/phpunit/includes/diff/TextDiffer/Wikidiff2TextDifferTest.php b/tests/phpunit/includes/diff/TextDiffer/Wikidiff2TextDifferTest.php index af389c4ec53..906346cdded 100644 --- a/tests/phpunit/includes/diff/TextDiffer/Wikidiff2TextDifferTest.php +++ b/tests/phpunit/includes/diff/TextDiffer/Wikidiff2TextDifferTest.php @@ -10,7 +10,7 @@ use Wikimedia\TestingAccessWrapper; */ class Wikidiff2TextDifferTest extends MediaWikiIntegrationTestCase { private function createDiffer() { - $differ = new Wikidiff2TextDiffer(); + $differ = new Wikidiff2TextDiffer( [] ); $localizer = RequestContext::getMain(); $localizer->setLanguage( 'qqx' ); $differ->setLocalizer( $localizer ); @@ -18,13 +18,25 @@ class Wikidiff2TextDifferTest extends MediaWikiIntegrationTestCase { return $differ; } + public static function provideRenderBatch() { + return [ + [ false ], + [ true ] + ]; + } + /** * @requires extension wikidiff2 + * @dataProvider provideRenderBatch + * @param bool $useMultiFormat */ - public function testRenderBatch() { + public function testRenderBatch( $useMultiFormat ) { + if ( !function_exists( 'wikidiff2_multi_format_diff' ) && $useMultiFormat ) { + $this->markTestSkipped( 'Need wikidiff2 1.14.0+' ); + } $oldText = 'foo'; $newText = 'bar'; - $differ = new Wikidiff2TextDiffer(); + $differ = new Wikidiff2TextDiffer( [ 'useMultiFormat' => $useMultiFormat ] ); // Should not need a MessageLocalizer $result = $differ->renderBatch( $oldText, $newText, [ 'table', 'inline' ] ); $this->assertSame( @@ -37,12 +49,12 @@ class Wikidiff2TextDifferTest extends MediaWikiIntegrationTestCase { } public function testGetName() { - $differ = new Wikidiff2TextDiffer(); + $differ = new Wikidiff2TextDiffer( [] ); $this->assertSame( 'wikidiff2', $differ->getName() ); } public function testGetFormatContext() { - $differ = new Wikidiff2TextDiffer(); + $differ = new Wikidiff2TextDiffer( [] ); $this->assertSame( TextDiffer::CONTEXT_ROW, $differ->getFormatContext( 'table' ) ); }