Integration of the diff/patch viewing and file comparison in ecode (SpartanJ/ecode#130).

This commit is contained in:
Martín Lucas Golini
2026-03-25 23:44:57 -03:00
parent 527c1dc56c
commit 571ed644f5
10 changed files with 192 additions and 25 deletions

View File

@@ -111,6 +111,10 @@ p, ol, ul, pre {
margin: 1em 0;
}
li > p {
margin: 0;
}
ol, ul {
margin-left: 2em;
}

View File

@@ -103,6 +103,8 @@ class EE_API UIDiffView : public UIWidget {
virtual void onSizeChange() override;
virtual Uint32 onKeyDown( const KeyEvent& event ) override;
void createEditor( UICodeEditor*& editor, std::unique_ptr<UIDiffEditorPlugin>& plugin );
void syncScroll( UICodeEditor* source, UICodeEditor* target, bool emitEvent = false );
void updateModeButton();

View File

@@ -838,6 +838,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void setDisableScrollInvalidation( bool disable ) { mDisableScrollInvalidation = disable; }
size_t getTotalVisibleLines() const;
protected:
struct LastXOffset {
TextPosition position{ 0, 0 };
@@ -1170,8 +1172,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void drawLockedIcon( const Vector2f start );
size_t getTotalVisibleLines() const;
void invalidateLineWrapMaxWidth( bool force );
void findRegionsDelayed();

View File

@@ -52,6 +52,10 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
void setAnchorScroll( bool anchor );
void setEnableDefaultKeybindings( bool enable );
bool areDefaultKeybindingsEnabled() const { return mDefaultKeybindings; }
protected:
ScrollViewType mViewType;
ScrollBarMode mVScrollMode;
@@ -64,6 +68,7 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
Uint32 mPosChangeCb;
bool mAutoSetClipStep{ true };
bool mAnchorScroll{ false };
bool mDefaultKeybindings{ true };
Sizef mLastScrollViewSize;
Node* mParentRef{ nullptr };
Uint32 mParentSizeChangeCb{ 0 };
@@ -83,6 +88,8 @@ class EE_API UIScrollView : public UITouchDraggableWidget {
virtual void onSizePolicyChange();
virtual Uint32 onKeyDown( const KeyEvent& event );
void onValueChangeCb( const Event* Event );
void onScrollViewSizeChange( const Event* Event );

View File

@@ -37,18 +37,7 @@ const Uint32& KeyEvent::getMod() const {
}
Uint32 KeyEvent::getSanitizedMod() const {
Uint32 mod = 0;
if ( mMod & KEYMOD_CTRL )
mod |= KEYMOD_CTRL;
if ( mMod & KEYMOD_SHIFT )
mod |= KEYMOD_SHIFT;
if ( mMod & KEYMOD_META )
mod |= KEYMOD_META;
if ( mMod & KEYMOD_LALT )
mod |= KEYMOD_LALT;
if ( mMod & KEYMOD_RALT )
mod |= KEYMOD_RALT;
return mod;
return mMod & KEYMOD_CTRL_SHIFT_ALT_META;
}
TextInputEvent::TextInputEvent( Node* node, const Uint32& eventNum, const Uint32& chr,

View File

@@ -300,6 +300,7 @@ UIIconTheme* IconManager::init( const std::string& iconThemeName, FontTrueType*
{ "inspect", 0xebd1 },
{ "link", 0xeb15 },
{ "agent", 0xec67 },
{ "diff", 0xeae1 },
} ) {
iconTheme->add( UIGlyphIcon::New( icon.first, codIconFont, icon.second ) );

View File

@@ -880,4 +880,33 @@ void UIDiffView::setHeadersVisible( bool visible ) {
updateModeButton();
}
Uint32 UIDiffView::onKeyDown( const KeyEvent& event ) {
auto editor = mViewMode == ViewMode::Unified ? mEditor : mRightEditor;
const auto moveLinesOffset = [editor]( int numLines ) {
Int64 curLine = static_cast<Int64>( editor->getVisibleLineRange().first );
Int64 line = curLine + numLines;
editor->scrollToVisibleIndex(
std::clamp( line, (Int64)0, (Int64)editor->getTotalVisibleLines() ), false, true );
};
if ( !event.getSanitizedMod() ) {
if ( event.getKeyCode() == Window::KEY_PAGEDOWN ) {
moveLinesOffset( editor->getViewPortLineCount().y );
} else if ( event.getKeyCode() == Window::KEY_PAGEUP ) {
moveLinesOffset( -editor->getViewPortLineCount().y );
} else if ( event.getKeyCode() == Window::KEY_DOWN ) {
moveLinesOffset( 1 );
} else if ( event.getKeyCode() == Window::KEY_UP ) {
moveLinesOffset( -1 );
} else if ( event.getKeyCode() == Window::KEY_HOME ) {
editor->scrollToVisibleIndex( 0, false, true );
} else if ( event.getKeyCode() == Window::KEY_END ) {
editor->scrollToVisibleIndex( (Int64)editor->getTotalVisibleLines(), false, true );
}
}
return UIWidget::onKeyDown( event );
}
}}} // namespace EE::UI::Tools

View File

@@ -469,4 +469,31 @@ void UIScrollView::setAnchorScroll( bool anchor ) {
mAnchorScroll = anchor;
}
Uint32 UIScrollView::onKeyDown( const KeyEvent& event ) {
if ( !mDefaultKeybindings || event.getSanitizedMod() )
return UITouchDraggableWidget::onKeyDown( event );
if ( event.getKeyCode() == Window::KEY_UP ) {
mVScroll->setValue( mVScroll->getValue() - mVScroll->getClickStep() );
return 1;
} else if ( event.getKeyCode() == Window::KEY_DOWN ) {
mVScroll->setValue( mVScroll->getValue() + mVScroll->getClickStep() );
return 1;
} else if ( event.getKeyCode() == Window::KEY_PAGEDOWN ) {
mVScroll->setValue( mVScroll->getValue() + mVScroll->getPageStep() );
return 1;
} else if ( event.getKeyCode() == Window::KEY_PAGEUP ) {
mVScroll->setValue( mVScroll->getValue() - mVScroll->getPageStep() );
return 1;
} else if ( event.getKeyCode() == Window::KEY_HOME ) {
mVScroll->setValue( mVScroll->getMinValue() );
return 1;
} else if ( event.getKeyCode() == Window::KEY_END ) {
mVScroll->setValue( mVScroll->getMaxValue() );
return 1;
}
return UITouchDraggableWidget::onKeyDown( event );
}
}} // namespace EE::UI

View File

@@ -357,7 +357,7 @@ void App::onDocumentUndoRedo( UICodeEditor* editor, TextDocument& doc ) {
onDocumentModified( editor, doc );
}
void App::openFileDialog() {
UIFileDialog* App::openFileDialog( bool registerEvents ) {
UIFileDialog* dialog = UIFileDialog::New(
UIFileDialog::DefaultFlags |
( mConfig.ui.nativeFileDialogs ? UIFileDialog::UseNativeFileDialog : 0 ),
@@ -366,14 +366,16 @@ void App::openFileDialog() {
dialog->setTitle( i18n( "open_file", "Open File" ) );
dialog->setCloseShortcut( KEY_ESCAPE );
dialog->setSingleClickNavigation( mConfig.editor.singleClickNavigation );
dialog->setAllowsMultiFileSelect( true );
dialog->on( Event::OpenFile, [this]( const Event* event ) {
auto files = event->getNode()->asType<UIFileDialog>()->getFullPaths();
for ( const auto& file : files ) {
mLastFileFolder = FileSystem::fileRemoveFileName( file );
loadFileFromPath( file );
}
} );
if ( registerEvents ) {
dialog->setAllowsMultiFileSelect( true );
dialog->on( Event::OpenFile, [this]( const Event* event ) {
auto files = event->getNode()->asType<UIFileDialog>()->getFullPaths();
for ( const auto& file : files ) {
mLastFileFolder = FileSystem::fileRemoveFileName( file );
loadFileFromPath( file );
}
} );
}
dialog->on( Event::OnWindowClose, [this]( const Event* ) {
if ( App::instance() && mSplitter && mSplitter->getCurWidget() &&
!SceneManager::instance()->isShuttingDown() )
@@ -381,6 +383,7 @@ void App::openFileDialog() {
} );
dialog->center();
dialog->show();
return dialog;
}
std::string App::getDefaultFileDialogFolder() const {
@@ -1712,6 +1715,8 @@ void App::onTabCreated( UITab* tab, UIWidget* ) {
menuAdd( "split_top", "Split Top", "split-vertical", "split-top" );
menuAdd( "split_bottom", "Split Bottom", "split-vertical", "split-bottom" );
menu->addSeparator();
menuAdd( "open_containing_folder_in_fm", "Open Containing Folder in File Manager",
"folder-open", "open-containing-folder" );
@@ -1728,6 +1733,12 @@ void App::onTabCreated( UITab* tab, UIWidget* ) {
menuAdd( "open_in_new_window", "Open in New Window", "window", "open-in-new-window" );
menuAdd( "move_to_new_window", "Move to New Window", "window", "move-to-new-window" );
if ( !tab->isSelected() ) {
menu->addSeparator();
menuAdd( "compare_with_active_tab", "Compare with Active Tab", "diff",
"compare-with-active-tab" );
}
}
if ( tab->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ||
@@ -2715,6 +2726,18 @@ void App::loadDiffFromPaths( const std::string& oldPath, const std::string& newP
diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
}
void App::loadDiffFromStrings( const std::string& str, const std::string& otherStr ) {
auto diffViewTitle = i18n( "diff_viewer", "Diff Viewer" );
auto* diffView = Tools::UIDiffView::New();
auto [tab, iv] = mSplitter->createWidget( diffView, i18n( "diff_viewer", "Diff Viewer" ) );
tab->setText( diffViewTitle );
auto icon = findIcon( "filetype-diff" );
tab->setIcon( icon ? icon : findIcon( "file" ) );
diffView->setHeadersVisible( true );
diffView->loadFromStrings( str, otherStr );
diffView->setSyntaxColorScheme( *getCurrentColorScheme() );
}
void App::openFileFromPath( const std::string& path ) {
std::string ext = FileSystem::fileExtension( path );
if ( !Image::isImageExtension( path ) && !SoundFileFactory::isKnownFileExtension( path ) &&
@@ -3010,6 +3033,26 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
}
}
} );
doc.setCommand( "compare-with-active-tab", [this] {
if ( mSplitter->curEditorExistsAndFocused() && mSplitter->getCurEditor() ) {
UICodeEditor* editor = mSplitter->getCurEditor();
UICodeEditor* otherEditor = nullptr;
auto tabWidget = mSplitter->getCurTabWidget();
if ( tabWidget && tabWidget->getTabSelected() &&
tabWidget->getTabSelected()->getOwnedWidget() != editor &&
tabWidget->getTabSelected()->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) {
otherEditor = mSplitter->getCurTabWidget()
->getTabSelected()
->getOwnedWidget()
->asType<UICodeEditor>();
}
if ( otherEditor ) {
loadDiffFromStrings( editor->getDocument().getText().toUtf8(),
otherEditor->getDocument().getText().toUtf8() );
}
}
} );
registerUnlockedCommands( doc );
editor->on( Event::OnDocumentSave, [this]( const Event* event ) {
@@ -3150,7 +3193,8 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
};
auto docLoaded = [this, editor, docChanged]( const Event* event ) {
if ( editor->getDocument().getFileInfo().getExtension() == "svg" ) {
auto ext = editor->getDocument().getFileInfo().getExtension();
if ( ext == "svg" ) {
editor->getDocument().setCommand(
"show-image-preview", [this]( TextDocument::Client* client ) {
loadImageFromMedium(
@@ -3164,8 +3208,70 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
findIcon( "filetype-jpg" ) )
->setId( "show-image-preview" );
} );
} else if ( ext == "md" || ext == "markdown" ) {
editor->getDocument().setCommand(
"show-markdown-preview", [this]( TextDocument::Client* client ) {
auto doc = static_cast<UICodeEditor*>( client )->getDocumentRef();
auto mdView = UIMarkdownView::New();
mdView->loadFromString( doc->getText().toUtf8() );
auto title = i18n( "markdown_preview_ellipsis", "Markdown Preview:" ) + " " +
doc->getFilename();
auto [tab, _] = getSplitter()->createWidget( mdView, title );
tab->setIcon( findIcon( "filetype-md" ) );
} );
}
editor->getDocument().setCommand( "compare-to", [this]( TextDocument::Client* client ) {
auto dialog = openFileDialog( false );
auto editor = static_cast<UICodeEditor*>( client );
dialog->on( Event::OpenFile, [this, editor]( const Event* event ) {
if ( !getSplitter()->editorExists( editor ) )
return;
auto files = event->getNode()->asType<UIFileDialog>()->getFullPaths();
if ( !files.empty() ) {
if ( editor->isDirty() ) {
std::string otherStr;
if ( FileSystem::fileGet( files[0], otherStr ) ) {
loadDiffFromStrings( editor->getDocument().getText().toUtf8(),
otherStr );
}
} else {
loadDiffFromPaths( editor->getDocument().getFilePath(), files[0] );
}
}
} );
} );
editor->getDocument().setCommand(
"compare-with-clipboard", [this]( TextDocument::Client* client ) {
auto editor = static_cast<UICodeEditor*>( client );
loadDiffFromStrings( editor->getDocument().getText().toUtf8(),
getWindow()->getClipboard()->getText() );
} );
editor->on( Event::OnCreateContextMenu, [this]( const Event* event ) {
auto editor = event->getNode()->asType<UICodeEditor>();
auto ext = editor->getDocument().getFileInfo().getExtension();
auto cevent = static_cast<const ContextMenuEvent*>( event );
auto menu = cevent->getMenu();
UIPopUpMenu* compareMenu = UIPopUpMenu::New();
compareMenu->add( i18n( "compare_to_ellipsis", "Compare to..." ) )
->setId( "compare-to" );
compareMenu->add( i18n( "compare_with_clipboard", "Compare with clipboard" ) )
->setId( "compare-with-clipboard" );
menu->addSeparator();
menu->addSubMenu( i18n( "compare", "Compare" ), findIcon( "diff" ), compareMenu );
if ( ext == "md" || ext == "markdown" ) {
menu->addSeparator();
menu->add( i18n( "show_markdown_preview", "Show Markdown Preview" ),
findIcon( "filetype-md" ) )
->setId( "show-markdown-preview" );
}
} );
docChanged( event );
};

View File

@@ -70,7 +70,7 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
void setAppTitle( const std::string& title );
void openFileDialog();
UIFileDialog* openFileDialog( bool registerEvents = true );
std::string getDefaultFileDialogFolder() const;
@@ -563,6 +563,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider {
void loadDiffFromMemory( const std::string& content, const std::string& originalFilePath = "" );
void loadDiffFromStrings( const std::string& str, const std::string& otherStr );
void createAndShowRecentFolderPopUpMenu( Node* recentFoldersBut );
void createAndShowRecentFilesPopUpMenu( Node* recentFilesBut );