diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index e4a34f102..9115d325a 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -111,6 +111,10 @@ p, ol, ul, pre { margin: 1em 0; } +li > p { + margin: 0; +} + ol, ul { margin-left: 2em; } diff --git a/include/eepp/ui/tools/uidiffview.hpp b/include/eepp/ui/tools/uidiffview.hpp index 61d498293..c6d6e6ba2 100644 --- a/include/eepp/ui/tools/uidiffview.hpp +++ b/include/eepp/ui/tools/uidiffview.hpp @@ -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& plugin ); void syncScroll( UICodeEditor* source, UICodeEditor* target, bool emitEvent = false ); void updateModeButton(); diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 47544a490..36bc06195 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -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(); diff --git a/include/eepp/ui/uiscrollview.hpp b/include/eepp/ui/uiscrollview.hpp index 7c13b8068..4e449d388 100644 --- a/include/eepp/ui/uiscrollview.hpp +++ b/include/eepp/ui/uiscrollview.hpp @@ -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 ); diff --git a/src/eepp/scene/keyevent.cpp b/src/eepp/scene/keyevent.cpp index 4600283b0..6647eb9f5 100644 --- a/src/eepp/scene/keyevent.cpp +++ b/src/eepp/scene/keyevent.cpp @@ -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, diff --git a/src/eepp/ui/iconmanager.cpp b/src/eepp/ui/iconmanager.cpp index cd62f8318..8e18050ab 100644 --- a/src/eepp/ui/iconmanager.cpp +++ b/src/eepp/ui/iconmanager.cpp @@ -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 ) ); diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp index aa555d1cf..17f54c518 100644 --- a/src/eepp/ui/tools/uidiffview.cpp +++ b/src/eepp/ui/tools/uidiffview.cpp @@ -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( 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 diff --git a/src/eepp/ui/uiscrollview.cpp b/src/eepp/ui/uiscrollview.cpp index 031430cff..0eba1cdf8 100644 --- a/src/eepp/ui/uiscrollview.cpp +++ b/src/eepp/ui/uiscrollview.cpp @@ -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 diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 17b2fbdb9..1cc092d3e 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -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()->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()->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(); + } + + 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( 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( client ); + dialog->on( Event::OpenFile, [this, editor]( const Event* event ) { + if ( !getSplitter()->editorExists( editor ) ) + return; + auto files = event->getNode()->asType()->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( client ); + loadDiffFromStrings( editor->getDocument().getText().toUtf8(), + getWindow()->getClipboard()->getText() ); + } ); + + editor->on( Event::OnCreateContextMenu, [this]( const Event* event ) { + auto editor = event->getNode()->asType(); + auto ext = editor->getDocument().getFileInfo().getExtension(); + auto cevent = static_cast( 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 ); }; diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index a79b0b1d5..4fec292b3 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -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 );