UI: Add UIDiffView for specialized diff/patch visualization

- Integrated dtl into thirdparty for native string diffing
- Implemented UIDiffView, a composite wrapper for UICodeEditor with a custom plugin (UIDiffEditorPlugin)
- Handles dynamic string diffing and standard .patch file parsing
- Automatically extracts target filename from patch headers for proper syntax highlighting
- Corrects line numbers in gutter to match the diff origins while hiding patch headers
- Wired into App::loadDiffFromPath and GitPlugin
- Added exhaustive unit tests in uidiffview_test.cpp
This commit is contained in:
Martín Lucas Golini
2026-03-23 18:12:23 -03:00
parent bfb1fd66b6
commit 0e923adea9
12 changed files with 1839 additions and 8 deletions

View File

@@ -11,6 +11,7 @@
#include "uitreeviewfs.hpp"
#include "uiwelcomescreen.hpp"
#include "version.hpp"
#include <eepp/ui/tools/uidiffview.hpp>
#include <algorithm>
#include <args/args.hxx>
#include <eepp/graphics/fontfamily.hpp>
@@ -2606,6 +2607,19 @@ void App::loadAudioFromPath( const std::string& path, bool autoPlay ) {
audioPlayer->loadFromPath( path, autoPlay );
}
void App::loadDiffFromPath( const std::string& path ) {
auto* diffView = Tools::UIDiffView::New();
auto [tab, iv] = mSplitter->createWidget( diffView, i18n( "diff_viewer", "Diff Viewer" ) );
tab->setText( FileSystem::fileNameFromPath( path ) )->setTooltipText( path );
auto icon = findIcon( "filetype-patch" );
tab->setIcon( icon ? icon : findIcon( "file" ) );
std::string text;
if ( FileSystem::fileGet( path, text ) ) {
diffView->loadFromPatch( text );
}
}
void App::openFileFromPath( const std::string& path ) {
std::string ext = FileSystem::fileExtension( path );
if ( !Image::isImageExtension( path ) && !SoundFileFactory::isKnownFileExtension( path ) &&
@@ -2658,6 +2672,8 @@ bool App::loadFileFromPath(
} else if ( ( SoundFileFactory::isKnownFileExtension( path ) || tryFindMimeType ) &&
SoundFileFactory::isValidAudioFile( path ) ) {
loadAudioFromPath( path );
} else if ( ext == "diff" || ext == "patch" ) {
loadDiffFromPath( path );
} else if ( !openBinaryAsDocument && PathHelper::isOpenExternalExtension( ext ) ) {
Engine::instance()->openURI( path );
} else if ( tryFindMimeType && TextDocument::fileMightBeBinary( path ) ) {