diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml index 6dd25df34..04ba49cad 100644 --- a/bin/assets/i18n/de.xml +++ b/bin/assets/i18n/de.xml @@ -373,6 +373,7 @@ Zweignamen eingeben: Lokalen Zweig erstellen? Löschen HEAD-Diff + Änderungs-Diff Stage-Diff Verwerfen Alles verwerfen diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml index 97081f4e4..36be4506e 100644 --- a/bin/assets/i18n/en.xml +++ b/bin/assets/i18n/en.xml @@ -357,6 +357,7 @@ Enter the name for the branch: Create local branch? Delete Diff HEAD + Diff Changed Diff Staged Discard Discard All diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml index 8c53a8b53..b1abe9313 100644 --- a/bin/assets/i18n/fr.xml +++ b/bin/assets/i18n/fr.xml @@ -356,6 +356,7 @@ Entrez le nom de la branche : Créer une branche locale ? Supprimer Diff HEAD + Diff des modifications Diff Staged Annuler Tout annuler diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml index 2614e7a91..d347ecc64 100644 --- a/bin/assets/i18n/zh.xml +++ b/bin/assets/i18n/zh.xml @@ -277,6 +277,7 @@ Enter the name for the branch: 新建本地分支? 删除 + 更改差异 Discard Drop Stash Drop Stash diff --git a/include/eepp/core/containers.hpp b/include/eepp/core/containers.hpp index db0524ed5..f595c0da6 100644 --- a/include/eepp/core/containers.hpp +++ b/include/eepp/core/containers.hpp @@ -3,7 +3,8 @@ #include -#if defined( EEPP_NO_THIRDPARTY_CONTAINERS ) || ( defined( EE_DEBUG ) && defined( EE_COMPILER_MSVC ) ) +#if defined( EEPP_NO_THIRDPARTY_CONTAINERS ) || \ + ( defined( EE_DEBUG ) && defined( EE_COMPILER_MSVC ) ) #include #include #else @@ -13,9 +14,12 @@ namespace EE { -#if defined( EEPP_NO_THIRDPARTY_CONTAINERS ) || ( defined( EE_DEBUG ) && defined( EE_COMPILER_MSVC ) ) +#if defined( EEPP_NO_THIRDPARTY_CONTAINERS ) || \ + ( defined( EE_DEBUG ) && defined( EE_COMPILER_MSVC ) ) -template using UnorderedMap = std::unordered_map; +template , + typename KeyEqual = std::equal_to> +using UnorderedMap = std::unordered_map; template using UnorderedSet = std::unordered_set; @@ -28,8 +32,9 @@ using SmallUnorderedSet = std::unordered_set; #else -template -using UnorderedMap = ankerl::unordered_dense::map; +template , + typename KeyEqual = std::equal_to> +using UnorderedMap = ankerl::unordered_dense::map; template using UnorderedSet = ankerl::unordered_dense::set; diff --git a/include/eepp/ui/doc/syntaxcolorscheme.hpp b/include/eepp/ui/doc/syntaxcolorscheme.hpp index 428a86699..af10c4114 100644 --- a/include/eepp/ui/doc/syntaxcolorscheme.hpp +++ b/include/eepp/ui/doc/syntaxcolorscheme.hpp @@ -2,6 +2,7 @@ #define EE_UI_DOC_SYNTAXCOLORSCHEME_HPP #include +#include #include namespace EE { namespace System { @@ -317,11 +318,17 @@ class EE_API SyntaxColorScheme { void setName( const std::string& name ); protected: - std::string mName; - UnorderedMap mSyntaxColors; - UnorderedMap mEditorColors; + struct Storage { + std::string name; + UnorderedMap syntaxColors; + UnorderedMap editorColors; + }; + + std::shared_ptr mStorage; mutable UnorderedMap mStyleCache; + void ensureUniqueStorage(); + template const SyntaxColorScheme::Style& getSyntaxStyleFromCache( const SyntaxStyleType& type ) const; }; diff --git a/include/eepp/ui/doc/syntaxdefinitionmanager.hpp b/include/eepp/ui/doc/syntaxdefinitionmanager.hpp index 328807d7f..50256903e 100644 --- a/include/eepp/ui/doc/syntaxdefinitionmanager.hpp +++ b/include/eepp/ui/doc/syntaxdefinitionmanager.hpp @@ -2,12 +2,14 @@ #define EE_UI_DOC_SYNTAXSTYLEMANAGER_HPP #include +#include #include #include #include #include #include #include +#include #include namespace EE { namespace System { @@ -127,6 +129,7 @@ class EE_API SyntaxDefinitionManager { std::vector> mDefinitions; std::vector mPreDefinitions; std::map mPriorities; + mutable UnorderedMap mExtensionManyLanguagesCache; FileAssociations mFileAssociations; mutable Mutex mMutex; mutable Mutex mFileAssociationsMutex; diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index ca9fe702c..86b30a6cc 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include using namespace EE::System; @@ -132,6 +133,7 @@ class EE_API TextDocument { typedef std::function DocumentCommand; typedef std::function DocumentRefCommand; + typedef UnorderedMap DocumentRefCommands; TextDocument( bool verbose = true ); @@ -450,6 +452,9 @@ class EE_API TextDocument { void setCommand( const std::string& command, const DocumentRefCommand& func ); + /** Installs an immutable command table shared by documents with the same client type. */ + void setSharedRefCommands( std::shared_ptr commands ); + bool hasCommand( const std::string& command ); bool removeCommand( const std::string& command ); @@ -818,6 +823,8 @@ class EE_API TextDocument { Uint32 mPageSize{ 10 }; UnorderedMap mCommands; UnorderedMap mRefCommands; + std::shared_ptr mSharedRefCommands; + std::unique_ptr> mRemovedDefaultCommands; String mNonWordChars; Client* mActiveClient{ nullptr }; mutable Mutex mLoadingMutex; @@ -831,6 +838,12 @@ class EE_API TextDocument { void initializeCommands(); + using BuiltinDocumentCommand = void ( * )( TextDocument* ); + + static const UnorderedMap& getBuiltinCommands(); + + bool isDefaultCommandRemoved( const std::string& command ) const; + void cleanChangeId(); void notifyDocumentLoaded(); diff --git a/include/eepp/ui/keyboardshortcut.hpp b/include/eepp/ui/keyboardshortcut.hpp index b448e1e34..55f0abbc4 100644 --- a/include/eepp/ui/keyboardshortcut.hpp +++ b/include/eepp/ui/keyboardshortcut.hpp @@ -2,8 +2,10 @@ #define EE_UI_KEYBOARDSHORTCUT_HPP #include +#include #include #include +#include #include #include #include @@ -20,8 +22,6 @@ class UIWidget; class EE_API KeyBindings { public: - typedef std::map ShortcutMap; - struct Shortcut { Shortcut() {} Shortcut( Keycode key, Uint32 mod ) : key( key ), mod( mod ) {} @@ -31,11 +31,30 @@ class EE_API KeyBindings { Uint32 mod{ 0 }; Uint64 toUint64() const { return (Uint64)mod << 32 | (Uint64)key; } operator Uint64() const { return toUint64(); } + bool operator<( const Shortcut& other ) const { return toUint64() < other.toUint64(); } + bool operator==( const Shortcut& other ) const { + return key == other.key && mod == other.mod; + } + bool operator!=( const Shortcut& other ) const { return !( *this == other ); } bool empty() const { return 0 == mod && 0 == key; } }; + struct ShortcutHash { + size_t operator()( const Shortcut& shortcut ) const noexcept { + return hashCombine( static_cast( shortcut.key ), + static_cast( shortcut.mod ) ); + } + }; + + typedef UnorderedMap ShortcutMap; + static KeyBindings::Shortcut sanitizeShortcut( const KeyBindings::Shortcut& shortcut ); + /** Returns the shortcut keys in ascending packed-value order. This provides deterministic + * iteration at boundaries where the order affects the selected reverse binding or serialized + * output, while keeping ShortcutMap optimized for lookup. */ + static std::vector getOrderedShortcuts( const ShortcutMap& bindings ); + static std::string keybindFormat( std::string str ); static Shortcut toShortcut( const Window::Input* input, const std::string& keys ); @@ -45,14 +64,15 @@ class EE_API KeyBindings { KeyBindings( const Window::Input* input ); + /** Shares @p bindings storage while keeping this instance's input source. */ + void setKeybinds( const KeyBindings& bindings ); + void addKeybindsString( const std::map& binds ); - void addKeybinds( const std::map& binds ); + void addKeybinds( const ShortcutMap& binds ); void addKeybindsStringUnordered( const std::unordered_map& binds ); - void addKeybindsUnordered( const std::unordered_map& binds ); - void addKeybindString( const std::string& key, const std::string& command ); void addKeybind( const Shortcut& key, const std::string& command ); @@ -85,23 +105,31 @@ class EE_API KeyBindings { const ShortcutMap& getShortcutMap() const; - const std::map& getKeybindings() const; + const std::map& getKeybindings() const; Shortcut getShortcutFromCommand( const std::string& cmd ) const; std::string getShortcutString( Shortcut shortcut, bool format = false ) const; protected: + struct Storage { + ShortcutMap shortcuts; + std::map keybindingsInvert; + }; + const Window::Input* mInput; - ShortcutMap mShortcuts; - std::map mKeybindingsInvert; + std::shared_ptr mStorage; + + static std::shared_ptr getEmptyStorage(); + + void ensureUniqueStorage(); }; }} // namespace EE::UI template <> struct std::hash { std::size_t operator()( EE::UI::KeyBindings::Shortcut const& s ) const noexcept { - return s.toUint64(); + return EE::UI::KeyBindings::ShortcutHash{}( s ); } }; diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 047832f9c..8f2b03c76 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -17,9 +17,9 @@ namespace EE { namespace UI { namespace Tools { class EE_API UICodeEditorSplitter { public: - static const std::map getDefaultKeybindings(); + static const KeyBindings::ShortcutMap getDefaultKeybindings(); - static const std::map getLocalDefaultKeybindings(); + static const KeyBindings::ShortcutMap getLocalDefaultKeybindings(); static Uint32 getDefaultSwitchToTabModifier(); diff --git a/include/eepp/ui/tools/uidiffview.hpp b/include/eepp/ui/tools/uidiffview.hpp index 3c2854966..a00b917ec 100644 --- a/include/eepp/ui/tools/uidiffview.hpp +++ b/include/eepp/ui/tools/uidiffview.hpp @@ -1,10 +1,12 @@ #ifndef EE_UI_TOOLS_UIDIFFVIEW_HPP #define EE_UI_TOOLS_UIDIFFVIEW_HPP +#include #include #include #include #include +#include namespace EE { @@ -23,6 +25,8 @@ class UIDiffEditorPlugin; class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter { public: + class PreparedMultiFileDiff; + enum class ViewMode { Unified, SideBySide }; enum class SubLineDiffAlgorithm { LCS, SES }; @@ -30,7 +34,21 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter { static UIScrollView* NewMultiFileDiffViewer( const std::string& patchText, const std::string& repoPath = "", - ViewMode viewMode = ViewMode::Unified ); + ViewMode viewMode = ViewMode::Unified, + bool interactiveFileHeaders = false ); + + static UIScrollView* + NewMultiFileDiffViewer( std::shared_ptr preparedDiff, + const std::string& repoPath = "", ViewMode viewMode = ViewMode::Unified, + bool interactiveFileHeaders = false ); + + /** + * Parses patches and computes sub-line changes without touching UI, documents, or syntax + * definitions. This is safe to run on a worker thread. Returns null when cancelled. + */ + static std::shared_ptr + prepareMultiFileDiff( const std::string& patchText, + const std::shared_ptr& cancelled = {} ); static std::vector splitDiff( const std::string& multiFileDiff ); @@ -109,6 +127,7 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter { bool isInteractiveFileHeader() const { return mInteractiveFileHeader; } void setCollapsed( bool collapsed ); + bool isCollapsed() const { return mCollapsed; } bool areHeadersVisible() const { return mHeadersVisible; } @@ -128,6 +147,8 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter { void setAutoDeleteOldTempImage( bool set ) { mAutoDeleteOldTempImage = set; } protected: + struct PreparedPatch; + UICodeEditor* mEditor{ nullptr }; UICodeEditor* mLeftEditor{ nullptr }; UICodeEditor* mRightEditor{ nullptr }; @@ -179,6 +200,14 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter { void computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ); + static PreparedPatch preparePatch( const std::string& patchText, + const std::string& originalFilePath, + SubLineDiffAlgorithm algorithm, + const std::shared_ptr& cancelled ); + + void loadPreparedPatch( PreparedPatch&& patch, const std::string& originalFilePath, + const std::string& oldFilePath, const std::string& repoPath ); + void updateEditorsText(); void updateButtonsText(); diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index d7a2f5405..be682f42b 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -219,7 +219,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { static UICodeEditor* NewOpt( const bool& autoRegisterBaseCommands, const bool& autoRegisterBaseKeybindings ); - static const std::map getDefaultKeybindings(); + static std::shared_ptr getDefaultKeybindings(); static const MouseBindings::ShortcutMap getDefaultMousebindings(); @@ -407,7 +407,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void addKeyBindsString( const std::map& binds, const bool& allowLocked = false ); - void addKeyBinds( const std::map& binds, + void addKeyBinds( const KeyBindings::ShortcutMap& binds, const bool& allowLocked = false ); const bool& getHighlightCurrentLine() const; @@ -896,6 +896,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { const Tools::UIDocFindReplace* getFindReplace() const { return mFindReplace; } protected: + static std::shared_ptr getDefaultEditorCommands(); + struct LastXOffset { TextPosition position{ 0, 0 }; Float offset{ 0.f }; diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index b2840353f..0e360ee46 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -668,7 +668,7 @@ class EE_API UISceneNode : public SceneNode { * * @param binds Map of KeyBindings::Shortcut to command strings. */ - void addKeyBinds( const std::map& binds ); + void addKeyBinds( const KeyBindings::ShortcutMap& binds ); typedef std::function KeyBindingCommand; diff --git a/include/eepp/ui/uiwindow.hpp b/include/eepp/ui/uiwindow.hpp index 86402c2e3..c8fbdc44a 100644 --- a/include/eepp/ui/uiwindow.hpp +++ b/include/eepp/ui/uiwindow.hpp @@ -191,9 +191,10 @@ class EE_API UIWindow : public UIWidget { void addKeyBindsString( const std::map& binds ); - void addKeyBinds( const std::map& binds ); + void addKeyBinds( const KeyBindings::ShortcutMap& binds ); void setKeyBindingCommand( const std::string& command, KeyBindingCommand func ); + void removeKeyBindingCommand( const std::string& command ); void executeKeyBindingCommand( const std::string& command ); diff --git a/src/eepp/ui/doc/syntaxcolorscheme.cpp b/src/eepp/ui/doc/syntaxcolorscheme.cpp index db699d894..dafb9af8e 100644 --- a/src/eepp/ui/doc/syntaxcolorscheme.cpp +++ b/src/eepp/ui/doc/syntaxcolorscheme.cpp @@ -37,7 +37,7 @@ namespace EE { namespace UI { namespace Doc { // "minimap_highlight" (Minimap text highlight color) // "minimap_visible_area" (Minimap visible area marker color) -SyntaxColorScheme SyntaxColorScheme::getDefaultDark() { +static SyntaxColorScheme createDefaultDarkSyntaxColorScheme() { return { "eepp", { @@ -86,7 +86,12 @@ SyntaxColorScheme SyntaxColorScheme::getDefaultDark() { { "minimap_visible_area"_sst, Color( "#FFFFFF0A" ) } } }; } -SyntaxColorScheme SyntaxColorScheme::getDefaultLight() { +SyntaxColorScheme SyntaxColorScheme::getDefaultDark() { + static const SyntaxColorScheme colorScheme = createDefaultDarkSyntaxColorScheme(); + return colorScheme; +} + +static SyntaxColorScheme createDefaultLightSyntaxColorScheme() { return { "github", { @@ -135,6 +140,11 @@ SyntaxColorScheme SyntaxColorScheme::getDefaultLight() { { "minimap_visible_area"_sst, Color( "#00000011" ) } } }; } +SyntaxColorScheme SyntaxColorScheme::getDefaultLight() { + static const SyntaxColorScheme colorScheme = createDefaultLightSyntaxColorScheme(); + return colorScheme; +} + SyntaxColorScheme::Style parseStyle( const std::string& value, bool* colorWasSet = nullptr, const UnorderedMap* syntaxColors = nullptr ) { @@ -212,11 +222,11 @@ std::vector SyntaxColorScheme::loadFromStream( IOStream& stre std::string value( ini.getValue( keyIdx, valueIdx ) ); if ( !value.empty() ) { SyntaxColorScheme::Style style = parseStyle( value ); - if ( refColorScheme.mSyntaxColors.find( toSyntaxStyleType( valueName ) ) != - refColorScheme.mSyntaxColors.end() ) { + if ( refColorScheme.mStorage->syntaxColors.find( toSyntaxStyleType( valueName ) ) != + refColorScheme.mStorage->syntaxColors.end() ) { colorScheme.setSyntaxStyle( toSyntaxStyleType( valueName ), style ); - } else if ( refColorScheme.mEditorColors.find( toSyntaxStyleType( valueName ) ) != - refColorScheme.mEditorColors.end() ) { + } else if ( refColorScheme.mStorage->editorColors.find( toSyntaxStyleType( + valueName ) ) != refColorScheme.mStorage->editorColors.end() ) { colorScheme.setEditorSyntaxStyle( toSyntaxStyleType( valueName ), style ); } } @@ -257,20 +267,26 @@ std::vector SyntaxColorScheme::loadFromPack( Pack* pack, return {}; } -SyntaxColorScheme::SyntaxColorScheme() {} +SyntaxColorScheme::SyntaxColorScheme() : mStorage( std::make_shared() ) {} SyntaxColorScheme::SyntaxColorScheme( const std::string& name, const UnorderedMap& syntaxColors, const UnorderedMap& editorColors ) : - mName( name ), mSyntaxColors( syntaxColors ), mEditorColors( editorColors ) {} + mStorage( std::make_shared( Storage{ name, syntaxColors, editorColors } ) ) {} + +void SyntaxColorScheme::ensureUniqueStorage() { + if ( !mStorage.unique() ) + mStorage = std::make_shared( *mStorage ); + mStyleCache.clear(); +} static const SyntaxColorScheme::Style StyleEmpty = { Color::Transparent }; static const SyntaxColorScheme StyleDefault = SyntaxColorScheme::getDefaultDark(); const SyntaxColorScheme::Style& SyntaxColorScheme::getSyntaxStyle( const SyntaxStyleType& type ) const { - auto it = mSyntaxColors.find( type ); - if ( it != mSyntaxColors.end() ) + auto it = mStorage->syntaxColors.find( type ); + if ( it != mStorage->syntaxColors.end() ) return it->second; else if ( type == "keyword2"_sst ) return getSyntaxStyle( "type"_sst ); @@ -292,22 +308,24 @@ SyntaxColorScheme::getSyntaxStyle( const SyntaxStyleType& type ) const { } bool SyntaxColorScheme::hasSyntaxStyle( const SyntaxStyleType& type ) const { - return mSyntaxColors.find( type ) != mSyntaxColors.end(); + return mStorage->syntaxColors.find( type ) != mStorage->syntaxColors.end(); } void SyntaxColorScheme::setSyntaxStyles( const UnorderedMap& styles ) { - mSyntaxColors.insert( styles.begin(), styles.end() ); + ensureUniqueStorage(); + mStorage->syntaxColors.insert( styles.begin(), styles.end() ); } void SyntaxColorScheme::setSyntaxStyle( const SyntaxStyleType& type, const SyntaxColorScheme::Style& style ) { - mSyntaxColors[type] = style; + ensureUniqueStorage(); + mStorage->syntaxColors[type] = style; } const SyntaxColorScheme::Style& SyntaxColorScheme::getEditorSyntaxStyle( const SyntaxStyleType& type ) const { - auto it = mEditorColors.find( type ); - if ( it != mEditorColors.end() ) + auto it = mStorage->editorColors.find( type ); + if ( it != mStorage->editorColors.end() ) return it->second; if ( type == "widget_background"_sst ) return getEditorSyntaxStyle( "gutter_background"_sst ); @@ -348,20 +366,23 @@ const Color& SyntaxColorScheme::getEditorColor( const SyntaxStyleType& type ) co void SyntaxColorScheme::setEditorSyntaxStyles( const EE::UnorderedMap& styles ) { - mEditorColors.insert( styles.begin(), styles.end() ); + ensureUniqueStorage(); + mStorage->editorColors.insert( styles.begin(), styles.end() ); } void SyntaxColorScheme::setEditorSyntaxStyle( const SyntaxStyleType& type, const SyntaxColorScheme::Style& style ) { - mEditorColors[type] = style; + ensureUniqueStorage(); + mStorage->editorColors[type] = style; } const std::string& SyntaxColorScheme::getName() const { - return mName; + return mStorage->name; } void SyntaxColorScheme::setName( const std::string& name ) { - mName = name; + ensureUniqueStorage(); + mStorage->name = name; } template @@ -369,18 +390,18 @@ const SyntaxColorScheme::Style& SyntaxColorScheme::getSyntaxStyleFromCache( const SyntaxStyleType& type ) const { bool colorWasSet = false; if constexpr ( std::is_same_v ) - mStyleCache[type] = parseStyle( type, &colorWasSet, &mSyntaxColors ); + mStyleCache[type] = parseStyle( type, &colorWasSet, &mStorage->syntaxColors ); else { auto cache = SyntaxPattern::SyntaxStyleTypeCache.find( type ); if ( cache != SyntaxPattern::SyntaxStyleTypeCache.end() ) { - mStyleCache[type] = parseStyle( cache->second, &colorWasSet, &mSyntaxColors ); + mStyleCache[type] = parseStyle( cache->second, &colorWasSet, &mStorage->syntaxColors ); } else { return StyleEmpty; } } if ( !colorWasSet ) { - auto normalStyle = mSyntaxColors.find( "normal"_sst ); - if ( normalStyle != mSyntaxColors.end() ) + auto normalStyle = mStorage->syntaxColors.find( "normal"_sst ); + if ( normalStyle != mStorage->syntaxColors.end() ) mStyleCache[type].color = normalStyle->second.color; } return mStyleCache[type]; diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index a02a32547..e8c8c7983 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -567,12 +567,14 @@ SyntaxDefinition& SyntaxDefinitionManager::add( SyntaxDefinition&& syntaxStyle ) syntaxStyle.mLanguageIndex = mDefinitions.size(); syntaxStyle.compile(); mDefinitions.emplace_back( std::make_shared( std::move( syntaxStyle ) ) ); + mExtensionManyLanguagesCache.clear(); return *mDefinitions.back().get(); } void SyntaxDefinitionManager::addPreDefinition( SyntaxPreDefinition&& preDefinition ) { Lock l( mMutex ); mPreDefinitions.emplace_back( std::move( preDefinition ) ); + mExtensionManyLanguagesCache.clear(); } const SyntaxDefinition& SyntaxDefinitionManager::getPlainDefinition() const { @@ -1104,6 +1106,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream, Lock l( mMutex ); mDefinitions[pos.value()] = std::make_shared( std::move( res ) ); + mExtensionManyLanguagesCache.clear(); } else { if ( addedLangs ) addedLangs->push_back( res.getLanguageName() ); @@ -1111,6 +1114,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream, res.mLanguageIndex = mDefinitions.size(); mDefinitions.emplace_back( std::make_shared( std::move( res ) ) ); + mExtensionManyLanguagesCache.clear(); } } } @@ -1125,6 +1129,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream, Lock l( mMutex ); mDefinitions[pos.value()] = std::make_shared( std::move( res ) ); + mExtensionManyLanguagesCache.clear(); } else { if ( addedLangs ) addedLangs->push_back( res.getLanguageName() ); @@ -1132,6 +1137,7 @@ bool SyntaxDefinitionManager::loadFromStream( IOStream& stream, res.mLanguageIndex = mDefinitions.size(); mDefinitions.emplace_back( std::make_shared( std::move( res ) ) ); + mExtensionManyLanguagesCache.clear(); } } } @@ -1259,55 +1265,52 @@ bool SyntaxDefinitionManager::extensionCanRepresentManyLanguages( std::string ex if ( extension[0] != '.' ) extension = '.' + extension; - std::unordered_set count; - { - Lock l( mMutex ); - for ( const auto& definition : mDefinitions ) { - for ( const auto& ext : definition->getFiles() ) { - if ( String::startsWith( ext, "%." ) || String::startsWith( ext, "^" ) || - String::endsWith( ext, "$" ) ) { - LuaPattern words( ext ); - int start, end; - if ( words.find( extension, start, end ) ) { - count.insert( definition->getLanguageName() ); - if ( count.size() > 1 ) - return true; - break; - } - } else if ( extension == ext ) { - count.insert( definition->getLanguageName() ); - if ( count.size() > 1 ) - return true; - break; - } + Lock l( mMutex ); + if ( auto it = mExtensionManyLanguagesCache.find( extension ); + it != mExtensionManyLanguagesCache.end() ) + return it->second; + + auto supportsExtension = [&extension]( const std::vector& files ) { + for ( const auto& ext : files ) { + if ( String::startsWith( ext, "%." ) || String::startsWith( ext, "^" ) || + String::endsWith( ext, "$" ) ) { + LuaPattern pattern( ext ); + int start, end; + if ( pattern.find( extension, start, end ) ) + return true; + } else if ( extension == ext ) { + return true; } } + return false; + }; + + const std::string* matchedLanguage = nullptr; + auto foundDifferentLanguage = [&matchedLanguage]( const std::string& language ) { + if ( !matchedLanguage ) { + matchedLanguage = &language; + return false; + } + return *matchedLanguage != language; + }; + + for ( const auto& definition : mDefinitions ) { + if ( supportsExtension( definition->getFiles() ) && + foundDifferentLanguage( definition->getLanguageName() ) ) { + mExtensionManyLanguagesCache.emplace( extension, true ); + return true; + } } - { - Lock l( mMutex ); - for ( const auto& preDefinition : mPreDefinitions ) { - for ( const auto& ext : preDefinition.getFiles() ) { - if ( String::startsWith( ext, "%." ) || String::startsWith( ext, "^" ) || - String::endsWith( ext, "$" ) ) { - LuaPattern words( ext ); - int start, end; - if ( words.find( extension, start, end ) ) { - count.insert( preDefinition.getLanguageName() ); - if ( count.size() > 1 ) - return true; - break; - } - } else if ( extension == ext ) { - count.insert( preDefinition.getLanguageName() ); - if ( count.size() > 1 ) - return true; - break; - } - } + for ( const auto& preDefinition : mPreDefinitions ) { + if ( supportsExtension( preDefinition.getFiles() ) && + foundDifferentLanguage( preDefinition.getLanguageName() ) ) { + mExtensionManyLanguagesCache.emplace( extension, true ); + return true; } } + mExtensionManyLanguagesCache.emplace( std::move( extension ), false ); return false; } diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index c4e5c1697..8beafcfed 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -302,10 +302,12 @@ bool TextDocument::fileMightBeBinary( const std::string& file ) { } bool TextDocument::isTextDocumentCommand( std::string_view cmd ) { + (void)getBuiltinCommands(); return TEXT_DOCUMENT_COMMANDS.contains( String::hash( cmd ) ); } bool TextDocument::isTextDocumentCommand( String::HashType cmdHash ) { + (void)getBuiltinCommands(); return TEXT_DOCUMENT_COMMANDS.contains( cmdHash ); } @@ -1475,7 +1477,16 @@ std::string TextDocument::toUtf8String() { std::vector TextDocument::getCommandList() const { std::vector cmds; - cmds.reserve( mCommands.size() + mRefCommands.size() ); + cmds.reserve( getBuiltinCommands().size() + mCommands.size() + mRefCommands.size() + + ( mSharedRefCommands ? mSharedRefCommands->size() : 0 ) ); + for ( const auto& cmd : getBuiltinCommands() ) { + if ( !isDefaultCommandRemoved( cmd.first ) ) + cmds.push_back( cmd.first ); + } + if ( mSharedRefCommands ) { + for ( const auto& cmd : *mSharedRefCommands ) + cmds.push_back( cmd.first ); + } for ( const auto& cmd : mRefCommands ) cmds.push_back( cmd.first ); for ( const auto& cmd : mCommands ) @@ -3205,25 +3216,42 @@ bool TextDocument::isDirty() const { void TextDocument::execute( const std::string& command ) { auto cmdIt = mCommands.find( command ); if ( cmdIt != mCommands.end() ) - cmdIt->second(); + return cmdIt->second(); + auto builtinIt = getBuiltinCommands().find( command ); + if ( builtinIt != getBuiltinCommands().end() && !isDefaultCommandRemoved( command ) ) + builtinIt->second( this ); } void TextDocument::execute( const std::string& command, Client* client ) { auto cmdRefIt = mRefCommands.find( command ); if ( cmdRefIt != mRefCommands.end() ) return cmdRefIt->second( client ); + if ( mSharedRefCommands ) { + auto sharedCmdIt = mSharedRefCommands->find( command ); + if ( sharedCmdIt != mSharedRefCommands->end() ) + return sharedCmdIt->second( client ); + } auto cmdIt = mCommands.find( command ); if ( cmdIt != mCommands.end() ) return cmdIt->second(); + auto builtinIt = getBuiltinCommands().find( command ); + if ( builtinIt != getBuiltinCommands().end() && !isDefaultCommandRemoved( command ) ) + builtinIt->second( this ); } void TextDocument::setCommands( const UnorderedMap& cmds ) { mCommands.insert( cmds.begin(), cmds.end() ); + if ( mRemovedDefaultCommands ) { + for ( const auto& cmd : cmds ) + mRemovedDefaultCommands->erase( cmd.first ); + } } void TextDocument::setCommand( const std::string& command, const TextDocument::DocumentCommand& func ) { mCommands[command] = func; + if ( mRemovedDefaultCommands ) + mRemovedDefaultCommands->erase( command ); } void TextDocument::setCommand( const std::string& command, @@ -3231,13 +3259,43 @@ void TextDocument::setCommand( const std::string& command, mRefCommands[command] = func; } +void TextDocument::setSharedRefCommands( + std::shared_ptr commands ) { + mSharedRefCommands = std::move( commands ); +} + bool TextDocument::hasCommand( const std::string& command ) { return mCommands.find( command ) != mCommands.end() || - mRefCommands.find( command ) != mRefCommands.end(); + mRefCommands.find( command ) != mRefCommands.end() || + ( mSharedRefCommands && + mSharedRefCommands->find( command ) != mSharedRefCommands->end() ) || + ( getBuiltinCommands().find( command ) != getBuiltinCommands().end() && + !isDefaultCommandRemoved( command ) ); } bool TextDocument::removeCommand( const std::string& command ) { - return mCommands.erase( command ) > 0 || mRefCommands.erase( command ) > 0; + if ( mCommands.erase( command ) > 0 ) { + if ( getBuiltinCommands().find( command ) != getBuiltinCommands().end() ) { + if ( !mRemovedDefaultCommands ) + mRemovedDefaultCommands = std::make_unique>(); + mRemovedDefaultCommands->insert( command ); + } + return true; + } + if ( mRefCommands.erase( command ) > 0 ) + return true; + if ( getBuiltinCommands().find( command ) != getBuiltinCommands().end() && + !isDefaultCommandRemoved( command ) ) { + if ( !mRemovedDefaultCommands ) + mRemovedDefaultCommands = std::make_unique>(); + mRemovedDefaultCommands->insert( command ); + return true; + } + return false; +} + +bool TextDocument::isDefaultCommandRemoved( const std::string& command ) const { + return mRemovedDefaultCommands && mRemovedDefaultCommands->contains( command ); } static constexpr auto MAX_CAPTURES = 12; @@ -4885,88 +4943,105 @@ void TextDocument::clearIndentation() { } } -void TextDocument::initializeCommands() { - // The built-in document commands and editor-specific commands share this table. Reserve their - // known steady-state capacity so every new editor does not repeatedly grow and rehash it. - mCommands.reserve( 128 ); - mCommands["reset-document"] = [this] { reset(); }; - mCommands["save-doc"] = [this] { save(); }; - mCommands["delete-to-previous-word"] = [this] { deleteToPreviousWord(); }; - mCommands["delete-to-previous-char"] = [this] { deleteToPreviousChar(); }; - mCommands["delete-to-next-word"] = [this] { deleteToNextWord(); }; - mCommands["delete-to-next-char"] = [this] { deleteToNextChar(); }; - mCommands["delete-current-line"] = [this] { deleteCurrentLine(); }; - mCommands["delete-to-start-of-line"] = [this] { deleteToStartOfLine(); }; - mCommands["delete-to-end-of-line"] = [this] { deleteToEndOfLine(); }; - mCommands["delete-selection"] = [this] { deleteSelection(); }; - mCommands["delete-word"] = [this] { deleteWord(); }; - mCommands["delete-paragraph"] = [this] { deleteCurrentParagraph(); }; - mCommands["move-to-previous-char"] = [this] { moveToPreviousChar(); }; - mCommands["move-to-previous-word"] = [this] { moveToPreviousWord(); }; - mCommands["move-to-next-char"] = [this] { moveToNextChar(); }; - mCommands["move-to-next-word"] = [this] { moveToNextWord(); }; - mCommands["move-to-previous-line"] = [this] { moveToPreviousLine(); }; - mCommands["move-to-next-line"] = [this] { moveToNextLine(); }; - mCommands["move-to-previous-page"] = [this] { moveToPreviousPage( mPageSize ); }; - mCommands["move-to-next-page"] = [this] { moveToNextPage( mPageSize ); }; - mCommands["move-to-start-of-doc"] = [this] { moveToStartOfDoc(); }; - mCommands["move-to-end-of-doc"] = [this] { moveToEndOfDoc(); }; - mCommands["move-to-start-of-line"] = [this] { moveToStartOfLine(); }; - mCommands["move-to-end-of-line"] = [this] { moveToEndOfLine(); }; - mCommands["move-to-start-of-content"] = [this] { moveToStartOfContent(); }; - mCommands["move-to-previous-paragraph"] = [this] { moveToPreviousParagraph(); }; - mCommands["move-to-next-paragraph"] = [this] { moveToNextParagraph(); }; - mCommands["move-lines-up"] = [this] { moveLinesUp(); }; - mCommands["move-lines-down"] = [this] { moveLinesDown(); }; - mCommands["select-to-previous-char"] = [this] { selectToPreviousChar(); }; - mCommands["select-to-previous-word"] = [this] { selectToPreviousWord(); }; - mCommands["select-to-previous-line"] = [this] { selectToPreviousLine(); }; - mCommands["select-to-next-char"] = [this] { selectToNextChar(); }; - mCommands["select-to-next-word"] = [this] { selectToNextWord(); }; - mCommands["select-to-next-line"] = [this] { selectToNextLine(); }; - mCommands["select-word"] = [this] { selectWord(); }; - mCommands["select-all-words"] = [this] { selectAllWords(); }; - mCommands["select-line"] = [this] { selectLine(); }; - mCommands["select-single-line"] = [this] { selectSingleLine(); }; - mCommands["select-to-start-of-line"] = [this] { selectToStartOfLine(); }; - mCommands["select-to-end-of-line"] = [this] { selectToEndOfLine(); }; - mCommands["select-to-start-of-doc"] = [this] { selectToStartOfDoc(); }; - mCommands["select-to-start-of-content"] = [this] { selectToStartOfContent(); }; - mCommands["select-to-end-of-doc"] = [this] { selectToEndOfDoc(); }; - mCommands["select-to-previous-page"] = [this] { selectToPreviousPage( mPageSize ); }; - mCommands["select-to-next-page"] = [this] { selectToNextPage( mPageSize ); }; - mCommands["select-paragraph"] = [this] { selectCurrentParagraph(); }; - mCommands["select-all"] = [this] { selectAll(); }; - mCommands["new-line"] = [this] { newLine(); }; - mCommands["new-line-above"] = [this] { newLineAbove(); }; - mCommands["indent"] = [this] { indent(); }; - mCommands["unindent"] = [this] { unindent(); }; - mCommands["undo"] = [this] { undo(); }; - mCommands["redo"] = [this] { redo(); }; - mCommands["toggle-line-comments"] = [this] { toggleLineComments(); }; - mCommands["toggle-block-comments"] = [this] { toggleBlockComments(); }; - mCommands["selection-to-upper"] = [this] { toUpperSelection(); }; - mCommands["selection-to-lower"] = [this] { toLowerSelection(); }; - mCommands["reset-cursor"] = [this] { resetSelection(); }; - mCommands["add-cursor-above"] = [this] { addCursorAbove(); }; - mCommands["add-cursor-below"] = [this] { addCursorBelow(); }; - mCommands["cursor-undo"] = [this] { cursorUndo(); }; - mCommands["select-all-matches"] = [this] { selectAllMatches(); }; - mCommands["escape"] = [this] { escape(); }; - mCommands["unescape"] = [this] { unescape(); }; - mCommands["to-base64"] = [this] { toBase64(); }; - mCommands["from-base64"] = [this] { fromBase64(); }; - mCommands["trim-trailing-whitespace"] = [this] { trimTrailingWhitespace(); }; - mCommands["join-lines"] = [this] { joinLines(); }; - mCommands["duplicate-line-or-selection"] = [this] { duplicateLineOrSelection(); }; - mCommands["convert-indentation-to-tabs"] = [this] { convertIndentationToTabs(); }; - mCommands["convert-indentation-to-spaces"] = [this] { convertIndentationToSpaces(); }; - mCommands["clear-indentation"] = [this] { clearIndentation(); }; - - if ( TEXT_DOCUMENT_COMMANDS.empty() ) { - for ( const auto& [cmd, _] : mCommands ) - TEXT_DOCUMENT_COMMANDS.insert( String::hash( cmd ) ); +const UnorderedMap& +TextDocument::getBuiltinCommands() { +#define EE_TEXT_DOCUMENT_COMMAND( Name, Method ) \ + { \ + Name, +[]( TextDocument* document ) { document->Method(); } \ } + static const UnorderedMap commands{ + EE_TEXT_DOCUMENT_COMMAND( "reset-document", reset ), + EE_TEXT_DOCUMENT_COMMAND( "save-doc", save ), + EE_TEXT_DOCUMENT_COMMAND( "delete-to-previous-word", deleteToPreviousWord ), + EE_TEXT_DOCUMENT_COMMAND( "delete-to-previous-char", deleteToPreviousChar ), + EE_TEXT_DOCUMENT_COMMAND( "delete-to-next-word", deleteToNextWord ), + EE_TEXT_DOCUMENT_COMMAND( "delete-to-next-char", deleteToNextChar ), + EE_TEXT_DOCUMENT_COMMAND( "delete-current-line", deleteCurrentLine ), + EE_TEXT_DOCUMENT_COMMAND( "delete-to-start-of-line", deleteToStartOfLine ), + EE_TEXT_DOCUMENT_COMMAND( "delete-to-end-of-line", deleteToEndOfLine ), + EE_TEXT_DOCUMENT_COMMAND( "delete-selection", deleteSelection ), + EE_TEXT_DOCUMENT_COMMAND( "delete-word", deleteWord ), + EE_TEXT_DOCUMENT_COMMAND( "delete-paragraph", deleteCurrentParagraph ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-char", moveToPreviousChar ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-word", moveToPreviousWord ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-next-char", moveToNextChar ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-next-word", moveToNextWord ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-line", moveToPreviousLine ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-next-line", moveToNextLine ), + { "move-to-previous-page", + +[]( TextDocument* document ) { document->moveToPreviousPage( document->mPageSize ); } }, + { "move-to-next-page", + +[]( TextDocument* document ) { document->moveToNextPage( document->mPageSize ); } }, + EE_TEXT_DOCUMENT_COMMAND( "move-to-start-of-doc", moveToStartOfDoc ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-end-of-doc", moveToEndOfDoc ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-start-of-line", moveToStartOfLine ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-end-of-line", moveToEndOfLine ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-start-of-content", moveToStartOfContent ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-previous-paragraph", moveToPreviousParagraph ), + EE_TEXT_DOCUMENT_COMMAND( "move-to-next-paragraph", moveToNextParagraph ), + EE_TEXT_DOCUMENT_COMMAND( "move-lines-up", moveLinesUp ), + EE_TEXT_DOCUMENT_COMMAND( "move-lines-down", moveLinesDown ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-previous-char", selectToPreviousChar ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-previous-word", selectToPreviousWord ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-previous-line", selectToPreviousLine ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-next-char", selectToNextChar ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-next-word", selectToNextWord ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-next-line", selectToNextLine ), + EE_TEXT_DOCUMENT_COMMAND( "select-word", selectWord ), + EE_TEXT_DOCUMENT_COMMAND( "select-all-words", selectAllWords ), + EE_TEXT_DOCUMENT_COMMAND( "select-line", selectLine ), + EE_TEXT_DOCUMENT_COMMAND( "select-single-line", selectSingleLine ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-start-of-line", selectToStartOfLine ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-end-of-line", selectToEndOfLine ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-start-of-doc", selectToStartOfDoc ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-start-of-content", selectToStartOfContent ), + EE_TEXT_DOCUMENT_COMMAND( "select-to-end-of-doc", selectToEndOfDoc ), + { "select-to-previous-page", + +[]( TextDocument* document ) { + document->selectToPreviousPage( document->mPageSize ); + } }, + { "select-to-next-page", + +[]( TextDocument* document ) { document->selectToNextPage( document->mPageSize ); } }, + EE_TEXT_DOCUMENT_COMMAND( "select-paragraph", selectCurrentParagraph ), + EE_TEXT_DOCUMENT_COMMAND( "select-all", selectAll ), + EE_TEXT_DOCUMENT_COMMAND( "new-line", newLine ), + EE_TEXT_DOCUMENT_COMMAND( "new-line-above", newLineAbove ), + EE_TEXT_DOCUMENT_COMMAND( "indent", indent ), + EE_TEXT_DOCUMENT_COMMAND( "unindent", unindent ), + EE_TEXT_DOCUMENT_COMMAND( "undo", undo ), + EE_TEXT_DOCUMENT_COMMAND( "redo", redo ), + EE_TEXT_DOCUMENT_COMMAND( "toggle-line-comments", toggleLineComments ), + EE_TEXT_DOCUMENT_COMMAND( "toggle-block-comments", toggleBlockComments ), + EE_TEXT_DOCUMENT_COMMAND( "selection-to-upper", toUpperSelection ), + EE_TEXT_DOCUMENT_COMMAND( "selection-to-lower", toLowerSelection ), + EE_TEXT_DOCUMENT_COMMAND( "reset-cursor", resetSelection ), + EE_TEXT_DOCUMENT_COMMAND( "add-cursor-above", addCursorAbove ), + EE_TEXT_DOCUMENT_COMMAND( "add-cursor-below", addCursorBelow ), + EE_TEXT_DOCUMENT_COMMAND( "cursor-undo", cursorUndo ), + EE_TEXT_DOCUMENT_COMMAND( "select-all-matches", selectAllMatches ), + EE_TEXT_DOCUMENT_COMMAND( "escape", escape ), + EE_TEXT_DOCUMENT_COMMAND( "unescape", unescape ), + EE_TEXT_DOCUMENT_COMMAND( "to-base64", toBase64 ), + EE_TEXT_DOCUMENT_COMMAND( "from-base64", fromBase64 ), + EE_TEXT_DOCUMENT_COMMAND( "trim-trailing-whitespace", trimTrailingWhitespace ), + EE_TEXT_DOCUMENT_COMMAND( "join-lines", joinLines ), + EE_TEXT_DOCUMENT_COMMAND( "duplicate-line-or-selection", duplicateLineOrSelection ), + EE_TEXT_DOCUMENT_COMMAND( "convert-indentation-to-tabs", convertIndentationToTabs ), + EE_TEXT_DOCUMENT_COMMAND( "convert-indentation-to-spaces", convertIndentationToSpaces ), + EE_TEXT_DOCUMENT_COMMAND( "clear-indentation", clearIndentation ), + }; +#undef EE_TEXT_DOCUMENT_COMMAND + static const bool commandHashesInitialized = [] { + for ( const auto& [command, _] : commands ) + TEXT_DOCUMENT_COMMANDS.insert( String::hash( command ) ); + return true; + }(); + (void)commandHashesInitialized; + return commands; +} + +void TextDocument::initializeCommands() { + (void)getBuiltinCommands(); } size_t TextDocument::getTopMostCursorIndex() { diff --git a/src/eepp/ui/keyboardshortcut.cpp b/src/eepp/ui/keyboardshortcut.cpp index f39042a96..83d9a5536 100644 --- a/src/eepp/ui/keyboardshortcut.cpp +++ b/src/eepp/ui/keyboardshortcut.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -21,7 +22,32 @@ KeyBindings::Shortcut KeyBindings::sanitizeShortcut( const KeyBindings::Shortcut return sanitized; } -KeyBindings::KeyBindings( const Window::Input* input ) : mInput( input ) {} +std::vector +KeyBindings::getOrderedShortcuts( const KeyBindings::ShortcutMap& bindings ) { + std::vector shortcuts; + shortcuts.reserve( bindings.size() ); + for ( const auto& binding : bindings ) + shortcuts.emplace_back( binding.first ); + std::sort( shortcuts.begin(), shortcuts.end() ); + return shortcuts; +} + +std::shared_ptr KeyBindings::getEmptyStorage() { + static auto storage = std::make_shared(); + return storage; +} + +KeyBindings::KeyBindings( const Window::Input* input ) : + mInput( input ), mStorage( getEmptyStorage() ) {} + +void KeyBindings::setKeybinds( const KeyBindings& bindings ) { + mStorage = bindings.mStorage; +} + +void KeyBindings::ensureUniqueStorage() { + if ( !mStorage.unique() ) + mStorage = std::make_shared( *mStorage ); +} void KeyBindings::addKeybindsString( const std::map& binds ) { for ( auto& bind : binds ) { @@ -29,9 +55,9 @@ void KeyBindings::addKeybindsString( const std::map& b } } -void KeyBindings::addKeybinds( const std::map& binds ) { - for ( auto& bind : binds ) { - addKeybind( bind.first, bind.second ); +void KeyBindings::addKeybinds( const KeyBindings::ShortcutMap& binds ) { + for ( const auto& shortcut : getOrderedShortcuts( binds ) ) { + addKeybind( shortcut, binds.find( shortcut )->second ); } } @@ -42,20 +68,15 @@ void KeyBindings::addKeybindsStringUnordered( } } -void KeyBindings::addKeybindsUnordered( - const std::unordered_map& binds ) { - for ( auto& bind : binds ) { - addKeybind( bind.first, bind.second ); - } -} - void KeyBindings::addKeybindString( const std::string& key, const std::string& command ) { addKeybind( getShortcutFromString( key ), command ); } void KeyBindings::addKeybind( const KeyBindings::Shortcut& key, const std::string& command ) { - mShortcuts[sanitizeShortcut( key )] = command; - mKeybindingsInvert[command] = sanitizeShortcut( key ); + ensureUniqueStorage(); + auto shortcut = sanitizeShortcut( key ); + mStorage->shortcuts[shortcut] = command; + mStorage->keybindingsInvert[command] = shortcut; } void KeyBindings::replaceKeybindString( const std::string& keys, const std::string& command ) { @@ -63,18 +84,19 @@ void KeyBindings::replaceKeybindString( const std::string& keys, const std::stri } void KeyBindings::replaceKeybind( const KeyBindings::Shortcut& keys, const std::string& command ) { + ensureUniqueStorage(); bool erased; do { erased = false; - auto it = mShortcuts.find( sanitizeShortcut( keys ) ); - if ( it != mShortcuts.end() ) { - mShortcuts.erase( it ); - mKeybindingsInvert.erase( it->second ); + auto it = mStorage->shortcuts.find( sanitizeShortcut( keys ) ); + if ( it != mStorage->shortcuts.end() ) { + mStorage->shortcuts.erase( it ); + mStorage->keybindingsInvert.erase( it->second ); erased = true; } } while ( erased ); - mShortcuts[sanitizeShortcut( keys )] = command; - mKeybindingsInvert[command] = sanitizeShortcut( keys ); + mStorage->shortcuts[sanitizeShortcut( keys )] = command; + mStorage->keybindingsInvert[command] = sanitizeShortcut( keys ); } KeyBindings::Shortcut KeyBindings::toShortcut( const Window::Input* input, @@ -102,9 +124,10 @@ KeyBindings::Shortcut KeyBindings::getShortcutFromString( const std::string& key } void KeyBindings::removeKeybind( const KeyBindings::Shortcut& keys ) { - auto it = mShortcuts.find( keys.toUint64() ); - if ( it != mShortcuts.end() ) { - mShortcuts.erase( it ); + ensureUniqueStorage(); + auto it = mStorage->shortcuts.find( keys ); + if ( it != mStorage->shortcuts.end() ) { + mStorage->shortcuts.erase( it ); } } @@ -113,25 +136,26 @@ void KeyBindings::removeKeybind( const std::string& kb ) { } bool KeyBindings::existsKeybind( const KeyBindings::Shortcut& keys ) { - return mShortcuts.find( keys.toUint64() ) != mShortcuts.end(); + return mStorage->shortcuts.find( keys ) != mStorage->shortcuts.end(); } bool KeyBindings::hasCommand( const std::string& command ) { - return mKeybindingsInvert.find( command ) != mKeybindingsInvert.end(); + return mStorage->keybindingsInvert.find( command ) != mStorage->keybindingsInvert.end(); } KeyBindings::Shortcut KeyBindings::getShortcutFromCommand( const std::string& cmd ) const { - auto it = mKeybindingsInvert.find( cmd ); - if ( it != mKeybindingsInvert.end() ) + auto it = mStorage->keybindingsInvert.find( cmd ); + if ( it != mStorage->keybindingsInvert.end() ) return it->second; return {}; } void KeyBindings::removeCommandKeybind( const std::string& command ) { - auto kbIt = mKeybindingsInvert.find( command ); - if ( kbIt != mKeybindingsInvert.end() ) { + ensureUniqueStorage(); + auto kbIt = mStorage->keybindingsInvert.find( command ); + if ( kbIt != mStorage->keybindingsInvert.end() ) { removeKeybind( kbIt->second ); - mKeybindingsInvert.erase( command ); + mStorage->keybindingsInvert.erase( command ); } } @@ -141,8 +165,8 @@ void KeyBindings::removeCommandsKeybind( const std::vector& command } std::string KeyBindings::getCommandFromKeyBind( const KeyBindings::Shortcut& keys ) { - auto it = mShortcuts.find( sanitizeShortcut( keys ) ); - if ( it != mShortcuts.end() ) { + auto it = mStorage->shortcuts.find( sanitizeShortcut( keys ) ); + if ( it != mStorage->shortcuts.end() ) { return it->second; } return ""; @@ -166,23 +190,22 @@ std::string KeyBindings::keybindFormat( std::string str ) { } std::string KeyBindings::getCommandKeybindString( const std::string& command ) const { - auto it = mKeybindingsInvert.find( command ); - if ( it == mKeybindingsInvert.end() ) + auto it = mStorage->keybindingsInvert.find( command ); + if ( it == mStorage->keybindingsInvert.end() ) return ""; - return keybindFormat( getShortcutString( Shortcut( it->second ) ) ); + return keybindFormat( getShortcutString( it->second ) ); } void KeyBindings::reset() { - mShortcuts.clear(); - mKeybindingsInvert.clear(); + mStorage = getEmptyStorage(); } const KeyBindings::ShortcutMap& KeyBindings::getShortcutMap() const { - return mShortcuts; + return mStorage->shortcuts; } -const std::map& KeyBindings::getKeybindings() const { - return mKeybindingsInvert; +const std::map& KeyBindings::getKeybindings() const { + return mStorage->keybindingsInvert; } std::string KeyBindings::fromShortcut( const Window::Input* input, KeyBindings::Shortcut shortcut, diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 91594e8e9..93fe9b6f0 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -11,10 +11,11 @@ using namespace EE::System; namespace EE { namespace UI { namespace Tools { -const std::map UICodeEditorSplitter::getDefaultKeybindings() { +const KeyBindings::ShortcutMap UICodeEditorSplitter::getDefaultKeybindings() { auto keybindings = UICodeEditor::getDefaultKeybindings(); auto localKeybindings = getLocalDefaultKeybindings(); - localKeybindings.insert( keybindings.begin(), keybindings.end() ); + for ( const auto& [shortcut, command] : keybindings->getShortcutMap() ) + localKeybindings.emplace( shortcut, command ); return localKeybindings; } @@ -28,7 +29,7 @@ Uint32 UICodeEditorSplitter::getDefaultSwitchToTabModifier() { return DefaultSwitchToTabModifier; } -const std::map +const KeyBindings::ShortcutMap UICodeEditorSplitter::getLocalDefaultKeybindings() { return { { { KEY_S, KeyMod::getDefaultModifier() }, "save-doc" }, diff --git a/src/eepp/ui/tools/uidiffview.cpp b/src/eepp/ui/tools/uidiffview.cpp index 6dec25f1e..b7b69e6cc 100644 --- a/src/eepp/ui/tools/uidiffview.cpp +++ b/src/eepp/ui/tools/uidiffview.cpp @@ -25,6 +25,21 @@ namespace EE { namespace UI { namespace Tools { +struct UIDiffView::PreparedPatch { + std::vector lines; + std::string filename; + std::string binaryOldPath; + std::string binaryNewPath; + std::string binaryFileName; + bool hasCompleteFile{ false }; + bool isBinaryImage{ false }; +}; + +class UIDiffView::PreparedMultiFileDiff { + public: + std::vector patches; +}; + static bool imagesHaveSameDimensions( const std::string& oldFilePath, const std::string& newFilePath ) { int oldWidth = 0; @@ -80,8 +95,19 @@ static Sprite* setImageViewerImage( UIImageViewer* viewer, Image* image ) { } UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText, - const std::string& repoPath, ViewMode viewMode ) { - auto diffs = UIDiffView::splitDiff( patchText ); + const std::string& repoPath, ViewMode viewMode, + bool interactiveFileHeaders ) { + return NewMultiFileDiffViewer( prepareMultiFileDiff( patchText ), repoPath, viewMode, + interactiveFileHeaders ); +} + +UIScrollView* +UIDiffView::NewMultiFileDiffViewer( std::shared_ptr preparedDiff, + const std::string& repoPath, ViewMode viewMode, + bool interactiveFileHeaders ) { + if ( !preparedDiff ) + return nullptr; + auto* uiSceneNode = SceneManager::instance()->getUISceneNode(); const bool wasLoading = uiSceneNode && uiSceneNode->isLoading(); if ( uiSceneNode ) @@ -92,7 +118,7 @@ UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText, vbox->setParent( scrollView ); vbox->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent ); - for ( const auto& diff : diffs ) { + for ( auto& patch : preparedDiff->patches ) { auto* diffView = UIDiffView::New(); diffView->setViewMode( viewMode ); diffView->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::WrapContent ); @@ -100,7 +126,8 @@ UIScrollView* UIDiffView::NewMultiFileDiffViewer( const std::string& patchText, diffView->setHeadersVisible( true ); diffView->setViewModeToggleVisible( false ); diffView->setCompleteViewToggleVisible( false ); - diffView->loadFromPatch( diff, "", "", repoPath ); + diffView->setInteractiveFileHeader( interactiveFileHeaders ); + diffView->loadPreparedPatch( std::move( patch ), "", "", repoPath ); } if ( uiSceneNode ) { @@ -506,9 +533,8 @@ UIDiffView::UIDiffView() : mLeftEditor->setFontSize( mRightEditor->getFontSize() ); mLeftPlugin->registerUpdate( mLeftEditor ); } ); - mRightEditor->getVScrollBar()->on( Event::OnSizeChange, [this] ( auto ) { - updateModeButton(); - } ); + mRightEditor->getVScrollBar()->on( Event::OnSizeChange, + [this]( auto ) { updateModeButton(); } ); for ( auto* editor : { mEditor, mLeftEditor, mRightEditor } ) { editor->on( Event::OnSizeChange, [this]( auto ) { onAutoSize(); } ); @@ -993,12 +1019,13 @@ void UIDiffView::updateImageDiffView() { } } -void UIDiffView::computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ) { +static void computeSubLineDiffImpl( UIDiffView::DiffLine& oldLine, UIDiffView::DiffLine& newLine, + UIDiffView::SubLineDiffAlgorithm algorithm ) { dtl::Diff diff( oldLine.text.view(), newLine.text.view() ); diff.compose(); - if ( mSubLineDiffAlgorithm == SubLineDiffAlgorithm::SES ) { + if ( algorithm == UIDiffView::SubLineDiffAlgorithm::SES ) { auto ses = diff.getSes().getSequence(); Int64 oldIdx = 0; Int64 newIdx = 0; @@ -1068,11 +1095,18 @@ void UIDiffView::computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ) { } } -static void applySubLineDiff( +void UIDiffView::computeSubLineDiff( DiffLine& oldLine, DiffLine& newLine ) { + computeSubLineDiffImpl( oldLine, newLine, mSubLineDiffAlgorithm ); +} + +static bool applySubLineDiff( std::vector& lines, - std::function computeSubLineDiff ) { + std::function computeSubLineDiff, + const std::shared_ptr& cancelled = {} ) { size_t i = 0; while ( i < lines.size() ) { + if ( cancelled && cancelled->load( std::memory_order_relaxed ) ) + return false; if ( lines[i].type == UIDiffView::DiffLineType::Removed ) { size_t j = i; while ( j < lines.size() && lines[j].type == UIDiffView::DiffLineType::Removed ) @@ -1085,6 +1119,8 @@ static void applySubLineDiff( size_t numAdded = k - j; size_t numToCompare = std::min( numRemoved, numAdded ); for ( size_t m = 0; m < numToCompare; m++ ) { + if ( cancelled && cancelled->load( std::memory_order_relaxed ) ) + return false; computeSubLineDiff( lines[i + m], lines[j + m] ); } i = k; @@ -1092,6 +1128,7 @@ static void applySubLineDiff( i++; } } + return true; } struct BinaryImagePatch { @@ -1212,19 +1249,21 @@ static BinaryImagePatch parseBinaryImagePatch( const std::vector& l return patch; } -void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& originalFilePath, - const std::string& oldFilePath, const std::string& repoPath ) { - resetToTextDiffView(); - mLines.clear(); +UIDiffView::PreparedPatch +UIDiffView::preparePatch( const std::string& patchText, const std::string& originalFilePath, + SubLineDiffAlgorithm algorithm, + const std::shared_ptr& cancelled ) { + PreparedPatch prepared; auto lines = String::split( patchText, '\n', true ); + if ( cancelled && cancelled->load( std::memory_order_relaxed ) ) + return prepared; std::string fileText; - bool hasCompleteFile = false; std::vector fileLines; if ( !originalFilePath.empty() && FileSystem::fileExists( originalFilePath ) ) { FileSystem::fileGet( originalFilePath, fileText ); fileLines = String::split( fileText, '\n', true ); - hasCompleteFile = true; + prepared.hasCompleteFile = true; } Int64 oldLineNum = 0; @@ -1234,27 +1273,16 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& std::string filename; auto imagePatch = parseBinaryImagePatch( lines, originalFilePath ); - if ( imagePatch.isBinary ) { - std::string oldImagePath( oldFilePath ); - if ( oldImagePath.empty() ) { - oldImagePath = - resolveImagePatchPath( imagePatch.oldPath, originalFilePath, false, repoPath ); - } - std::string newImagePath( - resolveImagePatchPath( imagePatch.newPath, originalFilePath, true, repoPath ) ); + prepared.isBinaryImage = imagePatch.isBinary; + prepared.binaryOldPath = std::move( imagePatch.oldPath ); + prepared.binaryNewPath = std::move( imagePatch.newPath ); + prepared.binaryFileName = std::move( imagePatch.fileName ); + prepared.lines.reserve( lines.size() + fileLines.size() ); - if ( oldImagePath == newImagePath ) - oldImagePath.clear(); - - if ( loadImageDiffFromPaths( oldImagePath, newImagePath ) ) { - if ( !imagePatch.fileName.empty() ) - mFileName = std::move( imagePatch.fileName ); - updateFileHeaderInfo(); - return; - } - } - - for ( const auto& line : lines ) { + for ( size_t lineIdx = 0; lineIdx < lines.size(); ++lineIdx ) { + if ( ( lineIdx & 0xFF ) == 0 && cancelled && cancelled->load( std::memory_order_relaxed ) ) + return PreparedPatch{}; + const auto& line = lines[lineIdx]; if ( String::startsWith( line, "diff " ) || String::startsWith( line, "index " ) || String::startsWith( line, "--- " ) || String::startsWith( line, "+++ " ) ) { if ( String::startsWith( line, "+++ " ) ) { @@ -1270,27 +1298,33 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& size_t minusPos = line.find( "-" ); size_t plusPos = line.find( "+" ); if ( minusPos != std::string::npos && plusPos != std::string::npos ) { + auto parseLineNumber = [&line]( size_t start, size_t end, Int64& lineNumber ) { + if ( end == std::string::npos || end <= start ) + return false; + Int64 parsedLineNumber; + if ( !String::fromString( parsedLineNumber, std::string_view{ line }.substr( + start, end - start ) ) ) + return false; + lineNumber = parsedLineNumber - 1; + return true; + }; size_t commaPos = line.find( ",", minusPos ); size_t spacePos = line.find( " ", minusPos ); if ( commaPos != std::string::npos && commaPos < spacePos ) { - oldLineNum = - std::stoll( line.substr( minusPos + 1, commaPos - minusPos - 1 ) ) - 1; + parseLineNumber( minusPos + 1, commaPos, oldLineNum ); } else if ( spacePos != std::string::npos ) { - oldLineNum = - std::stoll( line.substr( minusPos + 1, spacePos - minusPos - 1 ) ) - 1; + parseLineNumber( minusPos + 1, spacePos, oldLineNum ); } commaPos = line.find( ",", plusPos ); spacePos = line.find( " ", plusPos ); if ( commaPos != std::string::npos && commaPos < spacePos ) { - newLineNum = - std::stoll( line.substr( plusPos + 1, commaPos - plusPos - 1 ) ) - 1; + parseLineNumber( plusPos + 1, commaPos, newLineNum ); } else if ( spacePos != std::string::npos ) { - newLineNum = - std::stoll( line.substr( plusPos + 1, spacePos - plusPos - 1 ) ) - 1; + parseLineNumber( plusPos + 1, spacePos, newLineNum ); } - if ( hasCompleteFile ) { + if ( prepared.hasCompleteFile ) { while ( expectedNewLineNum < newLineNum + 1 && expectedNewLineNum <= (Int64)fileLines.size() ) { DiffLine dline; @@ -1298,7 +1332,7 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& dline.text = fileLines[expectedNewLineNum - 1]; dline.oldLineNum = expectedOldLineNum++; dline.newLineNum = expectedNewLineNum++; - mLines.push_back( dline ); + prepared.lines.push_back( std::move( dline ) ); } } expectedOldLineNum = oldLineNum + 1; @@ -1335,31 +1369,88 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& expectedNewLineNum = newLineNum + 1; } - mLines.push_back( dline ); + prepared.lines.push_back( std::move( dline ) ); } - if ( hasCompleteFile ) { + if ( prepared.hasCompleteFile ) { while ( expectedNewLineNum <= (Int64)fileLines.size() ) { + if ( ( expectedNewLineNum & 0xFF ) == 0 && cancelled && + cancelled->load( std::memory_order_relaxed ) ) + return PreparedPatch{}; DiffLine dline; dline.type = DiffLineType::Common; dline.text = fileLines[expectedNewLineNum - 1]; dline.oldLineNum = expectedOldLineNum++; dline.newLineNum = expectedNewLineNum++; - mLines.push_back( dline ); + prepared.lines.push_back( std::move( dline ) ); } } - applySubLineDiff( mLines, [this]( DiffLine& oldLine, DiffLine& newLine ) { - computeSubLineDiff( oldLine, newLine ); - } ); + if ( !applySubLineDiff( + prepared.lines, + [algorithm]( DiffLine& oldLine, DiffLine& newLine ) { + computeSubLineDiffImpl( oldLine, newLine, algorithm ); + }, + cancelled ) ) + return PreparedPatch{}; - setCompleteViewToggleVisible( hasCompleteFile ); + prepared.filename = std::move( filename ); + return prepared; +} - if ( !filename.empty() ) { - auto def = SyntaxDefinitionManager::instance()->getByExtension( filename ); +std::shared_ptr +UIDiffView::prepareMultiFileDiff( const std::string& patchText, + const std::shared_ptr& cancelled ) { + if ( cancelled && cancelled->load( std::memory_order_relaxed ) ) + return {}; + + auto diffs = splitDiff( patchText ); + auto prepared = std::make_shared(); + prepared->patches.reserve( diffs.size() ); + for ( const auto& diff : diffs ) { + if ( cancelled && cancelled->load( std::memory_order_relaxed ) ) + return {}; + prepared->patches.emplace_back( + preparePatch( diff, "", SubLineDiffAlgorithm::LCS, cancelled ) ); + } + + if ( cancelled && cancelled->load( std::memory_order_relaxed ) ) + return {}; + return prepared; +} + +void UIDiffView::loadPreparedPatch( PreparedPatch&& patch, const std::string& originalFilePath, + const std::string& oldFilePath, const std::string& repoPath ) { + resetToTextDiffView(); + mLines.clear(); + + if ( patch.isBinaryImage ) { + std::string oldImagePath( oldFilePath ); + if ( oldImagePath.empty() ) { + oldImagePath = + resolveImagePatchPath( patch.binaryOldPath, originalFilePath, false, repoPath ); + } + std::string newImagePath( + resolveImagePatchPath( patch.binaryNewPath, originalFilePath, true, repoPath ) ); + if ( oldImagePath == newImagePath ) + oldImagePath.clear(); + + if ( loadImageDiffFromPaths( oldImagePath, newImagePath ) ) { + if ( !patch.binaryFileName.empty() ) + mFileName = String::fromUtf8( patch.binaryFileName ); + updateFileHeaderInfo(); + return; + } + } + + mLines = std::move( patch.lines ); + setCompleteViewToggleVisible( patch.hasCompleteFile ); + + if ( !patch.filename.empty() ) { + auto def = SyntaxDefinitionManager::instance()->getByExtension( patch.filename ); mSyntaxDef = SyntaxDefinitionManager::instance()->getLanguageDefinition( def.getLanguageIndex() ); - mFileName = std::move( filename ); + mFileName = String::fromUtf8( patch.filename ); } updateFileHeaderInfo(); @@ -1368,6 +1459,12 @@ void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& onSizeChange(); } +void UIDiffView::loadFromPatch( const std::string& patchText, const std::string& originalFilePath, + const std::string& oldFilePath, const std::string& repoPath ) { + loadPreparedPatch( preparePatch( patchText, originalFilePath, mSubLineDiffAlgorithm, {} ), + originalFilePath, oldFilePath, repoPath ); +} + void UIDiffView::loadFromStrings( const std::string& oldText, const std::string& newText, const std::string& originalFilePath ) { resetToTextDiffView(); diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 31fc8227f..e603467c1 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -49,9 +49,7 @@ UICodeEditor* UICodeEditor::NewOpt( const bool& autoRegisterBaseCommands, return eeNew( UICodeEditor, ( autoRegisterBaseCommands, autoRegisterBaseKeybindings ) ); } -using CodeEditorKeyBindingMap = std::map; - -static CodeEditorKeyBindingMap createDefaultCodeEditorKeybindings() { +static KeyBindings::ShortcutMap createDefaultCodeEditorKeybindings() { return { { { KEY_BACKSPACE, KeyMod::getDefaultModifier() }, "delete-to-previous-word" }, { { KEY_BACKSPACE, KEYMOD_SHIFT }, "delete-to-previous-char" }, @@ -132,12 +130,12 @@ static CodeEditorKeyBindingMap createDefaultCodeEditorKeybindings() { }; } -static std::shared_ptr getCachedDefaultCodeEditorKeybindings() { +std::shared_ptr UICodeEditor::getDefaultKeybindings() { struct Cache { Mutex mutex; Uint32 defaultModifier{ 0 }; Uint32 secondaryModifier{ 0 }; - std::shared_ptr bindings; + std::shared_ptr bindings; }; static Cache cache; @@ -146,18 +144,15 @@ static std::shared_ptr getCachedDefaultCodeEditor Lock lock( cache.mutex ); if ( !cache.bindings || cache.defaultModifier != defaultModifier || cache.secondaryModifier != secondaryModifier ) { - cache.bindings = - std::make_shared( createDefaultCodeEditorKeybindings() ); + auto bindings = std::make_shared( nullptr ); + bindings->addKeybinds( createDefaultCodeEditorKeybindings() ); + cache.bindings = std::move( bindings ); cache.defaultModifier = defaultModifier; cache.secondaryModifier = secondaryModifier; } return cache.bindings; } -const std::map UICodeEditor::getDefaultKeybindings() { - return *getCachedDefaultCodeEditorKeybindings(); -} - const MouseBindings::ShortcutMap UICodeEditor::getDefaultMousebindings() { return { { { MouseAction::Down, EE_BUTTON_LMASK, KeyMod::getDefaultModifier() }, "add-cursor-at-mouse-position" }, @@ -2914,7 +2909,7 @@ void UICodeEditor::addKeyBindsString( const std::map& } } -void UICodeEditor::addKeyBinds( const std::map& binds, +void UICodeEditor::addKeyBinds( const KeyBindings::ShortcutMap& binds, const bool& allowLocked ) { mKeyBindings.addKeybinds( binds ); for ( const auto& bind : binds ) { @@ -4853,129 +4848,81 @@ void UICodeEditor::drawLineEndings( const DocumentLineRange& lineRange, const Ve } } -void UICodeEditor::registerCommands() { - mUnlockedCmd.reserve( 8 ); - mDoc->setCommand( "move-to-previous-line", []( Client* client ) { - static_cast( client )->moveToPreviousLine(); - } ); - mDoc->setCommand( "move-to-next-line", []( Client* client ) { - static_cast( client )->moveToNextLine(); - } ); - mDoc->setCommand( "move-to-previous-page", []( Client* client ) { - static_cast( client )->moveToPreviousPage(); - } ); - mDoc->setCommand( "move-to-next-page", []( Client* client ) { - static_cast( client )->moveToNextPage(); - } ); - mDoc->setCommand( "move-to-start-of-line", []( Client* client ) { - static_cast( client )->moveToStartOfLine(); - } ); - mDoc->setCommand( "move-to-end-of-line", []( Client* client ) { - static_cast( client )->moveToEndOfLine(); - } ); - mDoc->setCommand( "move-to-start-of-content", []( Client* client ) { - static_cast( client )->moveToStartOfContent(); - } ); - mDoc->setCommand( "select-to-previous-line", []( Client* client ) { - static_cast( client )->selectToPreviousLine(); - } ); - mDoc->setCommand( "select-to-next-line", []( Client* client ) { - static_cast( client )->selectToNextLine(); - } ); - mDoc->setCommand( "select-to-start-of-line", []( Client* client ) { - static_cast( client )->selectToStartOfLine(); - } ); - mDoc->setCommand( "select-to-end-of-line", []( Client* client ) { - static_cast( client )->selectToEndOfLine(); - } ); - mDoc->setCommand( "select-to-start-of-content", []( Client* client ) { - static_cast( client )->selectToStartOfContent(); - } ); - mDoc->setCommand( "move-scroll-up", []( Client* client ) { - static_cast( client )->moveScrollUp(); - } ); - mDoc->setCommand( "move-scroll-down", []( Client* client ) { - static_cast( client )->moveScrollDown(); - } ); - mDoc->setCommand( "jump-lines-up", []( Client* client ) { - static_cast( client )->jumpLinesUp(); - } ); - mDoc->setCommand( "jump-lines-down", []( Client* client ) { - static_cast( client )->jumpLinesDown(); - } ); - mDoc->setCommand( "indent", - []( Client* client ) { static_cast( client )->indent(); } ); - mDoc->setCommand( "unindent", - []( Client* client ) { static_cast( client )->unindent(); } ); - mDoc->setCommand( "copy", - []( Client* client ) { static_cast( client )->copy(); } ); - mDoc->setCommand( "cut", - []( Client* client ) { static_cast( client )->cut(); } ); - mDoc->setCommand( "paste", - []( Client* client ) { static_cast( client )->paste(); } ); - mDoc->setCommand( "font-size-grow", []( Client* client ) { - static_cast( client )->fontSizeGrow(); - } ); - mDoc->setCommand( "font-size-shrink", []( Client* client ) { - static_cast( client )->fontSizeShrink(); - } ); - mDoc->setCommand( "font-size-reset", []( Client* client ) { - static_cast( client )->fontSizeReset(); - } ); - mDoc->setCommand( - "lock", []( Client* client ) { static_cast( client )->setLocked( true ); } ); - mDoc->setCommand( "unlock", []( Client* client ) { - static_cast( client )->setLocked( false ); - } ); - mDoc->setCommand( "lock-toggle", []( Client* client ) { - UICodeEditor* editor = static_cast( client ); - editor->setLocked( !editor->isLocked() ); - } ); - mDoc->setCommand( "open-containing-folder", []( Client* client ) { - static_cast( client )->openContainingFolder(); - } ); - mDoc->setCommand( "copy-containing-folder-path", []( Client* client ) { - static_cast( client )->copyContainingFolderPath(); - } ); - mDoc->setCommand( "copy-file-path", []( Client* client ) { - static_cast( client )->copyFilePath(); - } ); - mDoc->setCommand( "copy-file-path-and-position", []( Client* client ) { - static_cast( client )->copyFilePath( true ); - } ); - mDoc->setCommand( "find-replace", [this] { showFindReplace(); } ); - mDoc->setCommand( "open-context-menu", []( Client* client ) { - static_cast( client )->createContextMenu(); - } ); - mDoc->setCommand( "add-cursor-at-mouse-position", []( Client* client ) { - static_cast( client )->addCursorAtMousePosition(); - } ); - mDoc->setCommand( "add-cursors-from-current-to-mouse-position", []( Client* client ) { - static_cast( client )->addCursorsFromCurrentToMousePosition(); - } ); - mDoc->setCommand( "toggle-fold", []( Client* client ) { - static_cast( client )->toggleFoldUnfold(); - } ); - mDoc->setCommand( "fold-all", - []( Client* client ) { static_cast( client )->foldAll(); } ); - mDoc->setCommand( "unfold-all", - []( Client* client ) { static_cast( client )->unfoldAll(); } ); - mDoc->setCommand( "fold", - []( Client* client ) { static_cast( client )->fold(); } ); - mDoc->setCommand( "unfold", - []( Client* client ) { static_cast( client )->unfold(); } ); - mDoc->setCommand( "open-hover-url", []( Client* client ) { - UICodeEditor* editor = static_cast( client ); - if ( !editor->mLink.empty() ) - Engine::instance()->openURI( editor->mLink ); - } ); - mDoc->setCommand( "add-cursor-above", []( Client* client ) { - static_cast( client )->addCursorAbove(); - } ); - mDoc->setCommand( "add-cursor-below", []( Client* client ) { - static_cast( client )->addCursorBelow(); - } ); +std::shared_ptr UICodeEditor::getDefaultEditorCommands() { +#define EE_CODE_EDITOR_COMMAND( Name, Method ) \ + { \ + Name, []( Client* client ) { static_cast( client )->Method(); } \ + } + static const auto commands = std::make_shared( + TextDocument::DocumentRefCommands{ + EE_CODE_EDITOR_COMMAND( "move-to-previous-line", moveToPreviousLine ), + EE_CODE_EDITOR_COMMAND( "move-to-next-line", moveToNextLine ), + EE_CODE_EDITOR_COMMAND( "move-to-previous-page", moveToPreviousPage ), + EE_CODE_EDITOR_COMMAND( "move-to-next-page", moveToNextPage ), + EE_CODE_EDITOR_COMMAND( "move-to-start-of-line", moveToStartOfLine ), + EE_CODE_EDITOR_COMMAND( "move-to-end-of-line", moveToEndOfLine ), + EE_CODE_EDITOR_COMMAND( "move-to-start-of-content", moveToStartOfContent ), + EE_CODE_EDITOR_COMMAND( "select-to-previous-line", selectToPreviousLine ), + EE_CODE_EDITOR_COMMAND( "select-to-next-line", selectToNextLine ), + EE_CODE_EDITOR_COMMAND( "select-to-start-of-line", selectToStartOfLine ), + EE_CODE_EDITOR_COMMAND( "select-to-end-of-line", selectToEndOfLine ), + EE_CODE_EDITOR_COMMAND( "select-to-start-of-content", selectToStartOfContent ), + EE_CODE_EDITOR_COMMAND( "move-scroll-up", moveScrollUp ), + EE_CODE_EDITOR_COMMAND( "move-scroll-down", moveScrollDown ), + EE_CODE_EDITOR_COMMAND( "jump-lines-up", jumpLinesUp ), + EE_CODE_EDITOR_COMMAND( "jump-lines-down", jumpLinesDown ), + EE_CODE_EDITOR_COMMAND( "indent", indent ), + EE_CODE_EDITOR_COMMAND( "unindent", unindent ), + EE_CODE_EDITOR_COMMAND( "copy", copy ), + EE_CODE_EDITOR_COMMAND( "cut", cut ), + EE_CODE_EDITOR_COMMAND( "paste", paste ), + EE_CODE_EDITOR_COMMAND( "font-size-grow", fontSizeGrow ), + EE_CODE_EDITOR_COMMAND( "font-size-shrink", fontSizeShrink ), + EE_CODE_EDITOR_COMMAND( "font-size-reset", fontSizeReset ), + { "lock", + []( Client* client ) { static_cast( client )->setLocked( true ); } }, + { "unlock", + []( Client* client ) { static_cast( client )->setLocked( false ); } }, + { "lock-toggle", + []( Client* client ) { + auto* editor = static_cast( client ); + editor->setLocked( !editor->isLocked() ); + } }, + EE_CODE_EDITOR_COMMAND( "open-containing-folder", openContainingFolder ), + EE_CODE_EDITOR_COMMAND( "copy-containing-folder-path", copyContainingFolderPath ), + EE_CODE_EDITOR_COMMAND( "copy-file-path", copyFilePath ), + { "copy-file-path-and-position", + []( Client* client ) { + static_cast( client )->copyFilePath( true ); + } }, + EE_CODE_EDITOR_COMMAND( "open-context-menu", createContextMenu ), + EE_CODE_EDITOR_COMMAND( "add-cursor-at-mouse-position", addCursorAtMousePosition ), + EE_CODE_EDITOR_COMMAND( "add-cursors-from-current-to-mouse-position", + addCursorsFromCurrentToMousePosition ), + EE_CODE_EDITOR_COMMAND( "toggle-fold", toggleFoldUnfold ), + EE_CODE_EDITOR_COMMAND( "fold-all", foldAll ), + EE_CODE_EDITOR_COMMAND( "unfold-all", unfoldAll ), + EE_CODE_EDITOR_COMMAND( "fold", fold ), + EE_CODE_EDITOR_COMMAND( "unfold", unfold ), + { "open-hover-url", + []( Client* client ) { + auto* editor = static_cast( client ); + if ( !editor->mLink.empty() ) + Engine::instance()->openURI( editor->mLink ); + } }, + EE_CODE_EDITOR_COMMAND( "add-cursor-above", addCursorAbove ), + EE_CODE_EDITOR_COMMAND( "add-cursor-below", addCursorBelow ), + } ); +#undef EE_CODE_EDITOR_COMMAND + return commands; +} +void UICodeEditor::registerCommands() { + mDoc->setSharedRefCommands( getDefaultEditorCommands() ); + // This is the only default editor command bound to a specific editor instance. + mDoc->setCommand( "find-replace", [this] { showFindReplace(); } ); + + mUnlockedCmd.reserve( 8 ); mUnlockedCmd.insert( { "copy", "select-all", "open-containing-folder", "copy-containing-folder-path", "copy-file-path", "copy-file-path-and-position", "open-context-menu", "find-replace" } ); @@ -5007,9 +4954,9 @@ Tools::UIDocFindReplace* UICodeEditor::getFindReplace() { } void UICodeEditor::registerKeybindings() { - // Editors keep mutable bindings, but the immutable defaults only need to be constructed once - // for each configured default-modifier pair. - mKeyBindings.addKeybinds( *getCachedDefaultCodeEditorKeybindings() ); + // Bindings use copy-on-write storage, so unmodified editors share both lookup maps and only + // allocate their own copy if an editor-specific binding is changed. + mKeyBindings.setKeybinds( *getDefaultKeybindings() ); mMouseBindings.addMousebinds( getDefaultMousebindings() ); } diff --git a/src/eepp/ui/uiscenenode.cpp b/src/eepp/ui/uiscenenode.cpp index d1a103597..5f13fb277 100644 --- a/src/eepp/ui/uiscenenode.cpp +++ b/src/eepp/ui/uiscenenode.cpp @@ -2071,7 +2071,7 @@ void UISceneNode::addKeyBindsString( const std::map& b mKeyBindings.addKeybindsString( binds ); } -void UISceneNode::addKeyBinds( const std::map& binds ) { +void UISceneNode::addKeyBinds( const KeyBindings::ShortcutMap& binds ) { mKeyBindings.addKeybinds( binds ); } diff --git a/src/eepp/ui/uiwindow.cpp b/src/eepp/ui/uiwindow.cpp index b33e040a9..a938ef201 100644 --- a/src/eepp/ui/uiwindow.cpp +++ b/src/eepp/ui/uiwindow.cpp @@ -1804,7 +1804,7 @@ void UIWindow::addKeyBindsString( const std::map& bind mKeyBindings.addKeybindsString( binds ); } -void UIWindow::addKeyBinds( const std::map& binds ) { +void UIWindow::addKeyBinds( const KeyBindings::ShortcutMap& binds ) { mKeyBindings.addKeybinds( binds ); } diff --git a/src/modules/eterm/include/eterm/ui/uiterminal.hpp b/src/modules/eterm/include/eterm/ui/uiterminal.hpp index 18751cc0c..ad7e2fc3e 100644 --- a/src/modules/eterm/include/eterm/ui/uiterminal.hpp +++ b/src/modules/eterm/include/eterm/ui/uiterminal.hpp @@ -67,7 +67,7 @@ class UITerminal : public UIWidget { void addKeyBindsString( const std::map& binds ); - void addKeyBinds( const std::map& binds ); + void addKeyBinds( const KeyBindings::ShortcutMap& binds ); bool execute( const std::string& command ); diff --git a/src/modules/eterm/src/eterm/ui/uiterminal.cpp b/src/modules/eterm/src/eterm/ui/uiterminal.cpp index dfd17e85f..ec7f44e67 100644 --- a/src/modules/eterm/src/eterm/ui/uiterminal.cpp +++ b/src/modules/eterm/src/eterm/ui/uiterminal.cpp @@ -412,7 +412,7 @@ void UITerminal::addKeyBindsString( const std::map& bi mKeyBindings.addKeybindsString( binds ); } -void UITerminal::addKeyBinds( const std::map& binds ) { +void UITerminal::addKeyBinds( const KeyBindings::ShortcutMap& binds ) { mKeyBindings.addKeybinds( binds ); } diff --git a/src/tests/ui_perf_test/ui_perf_test.cpp b/src/tests/ui_perf_test/ui_perf_test.cpp index 325bebc3a..e4a58b96e 100644 --- a/src/tests/ui_perf_test/ui_perf_test.cpp +++ b/src/tests/ui_perf_test/ui_perf_test.cpp @@ -201,7 +201,7 @@ int testTextRendering() { } EE_MAIN_FUNC int main( int, char*[] ) { - return testTextRendering(); + // return testTextRendering(); win = Engine::instance()->createWindow( WindowSettings( 1366, 768, "eepp - UI Perf Test" ) ); diff --git a/src/tests/unit_tests/keyboardshortcut_tests.cpp b/src/tests/unit_tests/keyboardshortcut_tests.cpp index 4dddda09f..30dec1ec6 100644 --- a/src/tests/unit_tests/keyboardshortcut_tests.cpp +++ b/src/tests/unit_tests/keyboardshortcut_tests.cpp @@ -8,6 +8,41 @@ using namespace EE; using namespace EE::UI; using namespace EE::Window; +UTEST( KeyboardShortcut, BulkBindingsPreserveLegacyOrderedReverseLookup ) { + const KeyBindings::Shortcut first{ KEY_A, KEYMOD_NONE }; + const KeyBindings::Shortcut second{ KEY_B, KEYMOD_CTRL }; + const KeyBindings::Shortcut third{ KEY_C, KEYMOD_CTRL | KEYMOD_SHIFT }; + KeyBindings::ShortcutMap defaults; + defaults.emplace( third, "duplicate-command" ); + defaults.emplace( first, "duplicate-command" ); + defaults.emplace( second, "duplicate-command" ); + + auto ordered = KeyBindings::getOrderedShortcuts( defaults ); + EXPECT_EQ( first.toUint64(), ordered.front().toUint64() ); + EXPECT_EQ( third.toUint64(), ordered.back().toUint64() ); + + KeyBindings bindings( nullptr ); + bindings.addKeybinds( defaults ); + EXPECT_EQ( third.toUint64(), + bindings.getShortcutFromCommand( "duplicate-command" ).toUint64() ); +} + +UTEST( KeyboardShortcut, SharedBindingsDetachOnMutation ) { + KeyBindings defaults( nullptr ); + defaults.addKeybind( { KEY_A, KEYMOD_CTRL }, "select-all" ); + + KeyBindings bindings( nullptr ); + bindings.setKeybinds( defaults ); + EXPECT_TRUE( &bindings.getShortcutMap() == &defaults.getShortcutMap() ); + EXPECT_TRUE( bindings.getCommandFromKeyBind( { KEY_A, KEYMOD_CTRL } ) == "select-all" ); + + bindings.addKeybind( { KEY_C, KEYMOD_CTRL }, "copy" ); + EXPECT_TRUE( &bindings.getShortcutMap() != &defaults.getShortcutMap() ); + EXPECT_TRUE( bindings.getCommandFromKeyBind( { KEY_C, KEYMOD_CTRL } ) == "copy" ); + EXPECT_TRUE( defaults.getCommandFromKeyBind( { KEY_C, KEYMOD_CTRL } ).empty() ); + EXPECT_TRUE( defaults.getCommandFromKeyBind( { KEY_A, KEYMOD_CTRL } ) == "select-all" ); +} + UTEST( KeyboardShortcut, AggregateAltUsesPrimaryAltAndKeepsAltGrDistinct ) { UIApplication app( WindowSettings{ 320, 240, "eepp - keyboard shortcut tests" } ); auto& bindings = app.getUI()->getKeyBindings(); diff --git a/src/tests/unit_tests/textdocument_tests.cpp b/src/tests/unit_tests/textdocument_tests.cpp index 8656117b4..a3da9537c 100644 --- a/src/tests/unit_tests/textdocument_tests.cpp +++ b/src/tests/unit_tests/textdocument_tests.cpp @@ -1,4 +1,5 @@ #include "utest.hpp" +#include #include #include #include @@ -6,6 +7,28 @@ using namespace EE::UI::Doc; using namespace EE::System; +UTEST( TextDocument, BuiltinCommandsRemainAvailableWithoutPerDocumentClosures ) { + TextDocument doc; + doc.textInput( "content" ); + EXPECT_TRUE( doc.hasCommand( "select-all" ) ); + EXPECT_TRUE( doc.hasCommand( "delete-selection" ) ); + + doc.execute( "select-all" ); + doc.execute( "delete-selection" ); + EXPECT_TRUE( doc.isEmpty() ); + + auto commands = doc.getCommandList(); + EXPECT_TRUE( std::find( commands.begin(), commands.end(), "undo" ) != commands.end() ); +} + +UTEST( TextDocument, RemovingBuiltinCommandIsLocalToDocument ) { + TextDocument first; + TextDocument second; + EXPECT_TRUE( first.removeCommand( "undo" ) ); + EXPECT_FALSE( first.hasCommand( "undo" ) ); + EXPECT_TRUE( second.hasCommand( "undo" ) ); +} + UTEST( TextRanges, keepsCommonSelectionsInline ) { TextRanges ranges; EXPECT_TRUE( ranges.is_small() ); diff --git a/src/tests/unit_tests/uicodeeditor_tests.cpp b/src/tests/unit_tests/uicodeeditor_tests.cpp index 4c3601438..7c25f6a54 100644 --- a/src/tests/unit_tests/uicodeeditor_tests.cpp +++ b/src/tests/unit_tests/uicodeeditor_tests.cpp @@ -25,6 +25,43 @@ class TestableCodeEditor : public UICodeEditor { void clearLongestLineWidthDirtyForTest() { mLongestLineWidthDirty = false; } }; +UTEST( SyntaxColorScheme, CopiesShareStorageAndDetachOnMutation ) { + auto defaults = SyntaxColorScheme::getDefaultDark(); + auto copy = defaults; + EXPECT_TRUE( &defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ) == + ©.getSyntaxStyle( SyntaxStyleTypes::Keyword ) ); + + const auto defaultKeyword = defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ).color; + copy.setSyntaxStyle( SyntaxStyleTypes::Keyword, SyntaxColorScheme::Style{ Color::Red } ); + EXPECT_TRUE( copy.getSyntaxStyle( SyntaxStyleTypes::Keyword ).color == Color::Red ); + EXPECT_TRUE( defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ).color == defaultKeyword ); + EXPECT_TRUE( &defaults.getSyntaxStyle( SyntaxStyleTypes::Keyword ) != + ©.getSyntaxStyle( SyntaxStyleTypes::Keyword ) ); +} + +UTEST( SyntaxDefinitionManager, ManyLanguageExtensionCacheInvalidatesOnAdd ) { + auto* manager = SyntaxDefinitionManager::instance(); + const std::string extension( ".eepp-many-languages-cache-test" ); + const std::string preDefinitionExtension( ".eepp-many-languages-predefinition-cache-test" ); + + EXPECT_FALSE( manager->extensionCanRepresentManyLanguages( extension ) ); + manager->add( { "EEPP Cache Test A", { extension }, {} } ); + EXPECT_FALSE( manager->extensionCanRepresentManyLanguages( extension ) ); + manager->add( { "EEPP Cache Test B", { extension }, {} } ); + EXPECT_TRUE( manager->extensionCanRepresentManyLanguages( extension ) ); + EXPECT_TRUE( manager->extensionCanRepresentManyLanguages( extension ) ); + + manager->add( { "EEPP Cache Test C", { preDefinitionExtension }, {} } ); + EXPECT_FALSE( manager->extensionCanRepresentManyLanguages( preDefinitionExtension ) ); + manager->addPreDefinition( { "EEPP Cache Test PreDefinition", + []() -> SyntaxDefinition& { + return SyntaxDefinitionManager::instance()->add( + { "EEPP Cache Test PreDefinition", {}, {} } ); + }, + { preDefinitionExtension } } ); + EXPECT_TRUE( manager->extensionCanRepresentManyLanguages( preDefinitionExtension ) ); +} + UTEST( MainThreadLifetime, InvalidatedCallbacksDoNotRun ) { UIApplication app( WindowSettings{ 320, 240, "eepp - main thread lifetime test" } ); int owner = 42; @@ -101,25 +138,28 @@ UTEST( UICodeEditor, DefaultKeybindingCacheTracksConfiguredModifiers ) { const Uint32 originalSecondaryModifier = KeyMod::getDefaultSecondaryModifier(); auto defaultBindings = UICodeEditor::getDefaultKeybindings(); - auto copy = defaultBindings.find( { KEY_C, originalDefaultModifier } ); - EXPECT_TRUE( copy != defaultBindings.end() ); - if ( copy != defaultBindings.end() ) + const auto& defaultShortcutMap = defaultBindings->getShortcutMap(); + auto copy = defaultShortcutMap.find( KeyBindings::Shortcut{ KEY_C, originalDefaultModifier } ); + EXPECT_TRUE( copy != defaultShortcutMap.end() ); + if ( copy != defaultShortcutMap.end() ) EXPECT_STREQ( "copy", copy->second.c_str() ); KeyMod::setDefaultModifier( KEYMOD_LALT ); KeyMod::setDefaultSecondaryModifier( KEYMOD_META ); auto reconfiguredBindings = UICodeEditor::getDefaultKeybindings(); - copy = reconfiguredBindings.find( { KEY_C, KEYMOD_LALT } ); - EXPECT_TRUE( copy != reconfiguredBindings.end() ); - if ( copy != reconfiguredBindings.end() ) + const auto& reconfiguredShortcutMap = reconfiguredBindings->getShortcutMap(); + copy = reconfiguredShortcutMap.find( KeyBindings::Shortcut{ KEY_C, KEYMOD_LALT } ); + EXPECT_TRUE( copy != reconfiguredShortcutMap.end() ); + if ( copy != reconfiguredShortcutMap.end() ) EXPECT_STREQ( "copy", copy->second.c_str() ); KeyMod::setDefaultModifier( originalDefaultModifier ); KeyMod::setDefaultSecondaryModifier( originalSecondaryModifier ); auto restoredBindings = UICodeEditor::getDefaultKeybindings(); - copy = restoredBindings.find( { KEY_C, originalDefaultModifier } ); - EXPECT_TRUE( copy != restoredBindings.end() ); - if ( copy != restoredBindings.end() ) + const auto& restoredShortcutMap = restoredBindings->getShortcutMap(); + copy = restoredShortcutMap.find( KeyBindings::Shortcut{ KEY_C, originalDefaultModifier } ); + EXPECT_TRUE( copy != restoredShortcutMap.end() ); + if ( copy != restoredShortcutMap.end() ) EXPECT_STREQ( "copy", copy->second.c_str() ); } @@ -236,7 +276,7 @@ UTEST( KeybindingsHelper, PreservesUserShortcutWhenAddingBinding ) { std::unordered_map keybindings; std::unordered_map invertedKeybindings; - const std::map defaultKeybindings{ + const KeyBindings::ShortcutMap defaultKeybindings{ { { KEY_D, KeyMod::getDefaultModifier() }, "select-word" }, { { KEY_X, KeyMod::getDefaultModifier() }, "cut" }, }; @@ -275,7 +315,7 @@ UTEST( KeybindingsHelper, RestoresMissingCommandWhenShortcutIsFree ) { std::unordered_map keybindings; std::unordered_map invertedKeybindings; - const std::map defaultKeybindings{ + const KeyBindings::ShortcutMap defaultKeybindings{ { { KEY_D, KeyMod::getDefaultModifier() }, "select-word" }, { { KEY_X, KeyMod::getDefaultModifier() }, "cut" }, }; diff --git a/src/tests/unit_tests/uidiffview_tests.cpp b/src/tests/unit_tests/uidiffview_tests.cpp index 7a5da1e36..e500b9022 100644 --- a/src/tests/unit_tests/uidiffview_tests.cpp +++ b/src/tests/unit_tests/uidiffview_tests.cpp @@ -1,4 +1,5 @@ #include "utest.h" +#include #include #include #include @@ -8,6 +9,7 @@ #include #include #include +#include using namespace EE; using namespace EE::UI; @@ -124,11 +126,15 @@ diff --git a/second.txt b/second.txt )patch"; auto* viewer = - UIDiffView::NewMultiFileDiffViewer( patchText, "", UIDiffView::ViewMode::SideBySide ); + UIDiffView::NewMultiFileDiffViewer( patchText, "", UIDiffView::ViewMode::SideBySide, true ); auto diffViews = viewer->findAllByType( UI_TYPE_DIFF_VIEW ); ASSERT_EQ( size_t{ 2 }, diffViews.size() ); - for ( const auto* diffView : diffViews ) + for ( const auto* diffView : diffViews ) { EXPECT_EQ( UIDiffView::ViewMode::SideBySide, diffView->getViewMode() ); + EXPECT_FALSE( diffView->isViewModeToggleVisible() ); + EXPECT_FALSE( diffView->isCompleteViewToggleVisible() ); + EXPECT_TRUE( diffView->isInteractiveFileHeader() ); + } UIDiffView::setMultiFileViewMode( viewer, UIDiffView::ViewMode::Unified ); UIDiffView::setMultiFileCollapsed( viewer, true ); @@ -146,6 +152,44 @@ diff --git a/second.txt b/second.txt eeDelete( viewer ); } +UTEST( UIDiffView, PreparedMultiFileDiffBuildsViewer ) { + std::string patchText = R"patch(diff --git a/first.txt b/first.txt +--- a/first.txt ++++ b/first.txt +@@ -1 +1 @@ +-old ++new +diff --git a/second.txt b/second.txt +--- a/second.txt ++++ b/second.txt +@@ -1 +1 @@ +-before ++after +)patch"; + + std::shared_ptr prepared; + std::thread worker( + [&prepared, &patchText] { prepared = UIDiffView::prepareMultiFileDiff( patchText ); } ); + worker.join(); + ASSERT_TRUE( prepared ); + UIApplication app( WindowSettings{ 800, 600, "eepp - unit tests" } ); + auto* viewer = UIDiffView::NewMultiFileDiffViewer( std::move( prepared ), "", + UIDiffView::ViewMode::SideBySide ); + ASSERT_TRUE( viewer ); + auto diffViews = viewer->findAllByType( UI_TYPE_DIFF_VIEW ); + ASSERT_EQ( size_t{ 2 }, diffViews.size() ); + EXPECT_TRUE( diffViews[0]->getFileName().toUtf8() == "first.txt" ); + EXPECT_TRUE( diffViews[1]->getFileName().toUtf8() == "second.txt" ); + EXPECT_EQ( UIDiffView::ViewMode::SideBySide, diffViews[0]->getViewMode() ); + + eeDelete( viewer ); +} + +UTEST( UIDiffView, PreparedMultiFileDiffHonorsCancellation ) { + auto cancelled = std::make_shared( true ); + EXPECT_FALSE( UIDiffView::prepareMultiFileDiff( "diff --git a/a b/a\n", cancelled ) ); +} + UTEST( UIDiffView, MultiFileViewerHandlesLargePatches ) { UIApplication app( WindowSettings{ 800, 600, "eepp - unit tests" } ); app.getUI()->flushDirtyStyleAndLayout(); diff --git a/src/thirdparty/efsw b/src/thirdparty/efsw index 5df6a032e..41ddf6822 160000 --- a/src/thirdparty/efsw +++ b/src/thirdparty/efsw @@ -1 +1 @@ -Subproject commit 5df6a032e231add2c7d342d47bac838bd23fa49f +Subproject commit 41ddf6822f2d0dec7e14fafa09c4cef391137b20 diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index 668e95453..e39f23b20 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -1231,15 +1231,15 @@ UITabWidget* App::getSidePanel() const { return mSidePanel; } -const std::map& App::getRealLocalKeybindings() const { +const KeyBindings::ShortcutMap& App::getRealLocalKeybindings() const { return mRealLocalKeybindings; } -const std::map& App::getRealSplitterKeybindings() const { +const KeyBindings::ShortcutMap& App::getRealSplitterKeybindings() const { return mRealSplitterKeybindings; } -const std::map& App::getRealTerminalKeybindings() const { +const KeyBindings::ShortcutMap& App::getRealTerminalKeybindings() const { return mRealTerminalKeybindings; } @@ -1510,32 +1510,35 @@ void App::loadKeybindings() { getMigrateKeybindings(), mConfig.iniState ); auto localKeybindings = getLocalKeybindings(); - for ( const auto& kb : localKeybindings ) { - auto found = mKeybindingsInvert.find( kb.second ); + for ( const auto& shortcut : KeyBindings::getOrderedShortcuts( localKeybindings ) ) { + const auto& command = localKeybindings.find( shortcut )->second; + auto found = mKeybindingsInvert.find( command ); if ( found != mKeybindingsInvert.end() ) { - mRealLocalKeybindings[bindings.getShortcutFromString( found->second )] = kb.second; + mRealLocalKeybindings[bindings.getShortcutFromString( found->second )] = command; } else { - mRealLocalKeybindings[kb.first] = kb.second; + mRealLocalKeybindings[shortcut] = command; } } auto localSplitterKeybindings = UICodeEditorSplitter::getLocalDefaultKeybindings(); - for ( const auto& kb : localSplitterKeybindings ) { - auto found = mKeybindingsInvert.find( kb.second ); + for ( const auto& shortcut : KeyBindings::getOrderedShortcuts( localSplitterKeybindings ) ) { + const auto& command = localSplitterKeybindings.find( shortcut )->second; + auto found = mKeybindingsInvert.find( command ); if ( found != mKeybindingsInvert.end() ) { - mRealSplitterKeybindings[bindings.getShortcutFromString( found->second )] = kb.second; + mRealSplitterKeybindings[bindings.getShortcutFromString( found->second )] = command; } else { - mRealSplitterKeybindings[kb.first] = kb.second; + mRealSplitterKeybindings[shortcut] = command; } } auto localTerminalKeybindings = TerminalManager::getTerminalKeybindings(); - for ( const auto& kb : localTerminalKeybindings ) { - auto found = mKeybindingsInvert.find( kb.second ); + for ( const auto& shortcut : KeyBindings::getOrderedShortcuts( localTerminalKeybindings ) ) { + const auto& command = localTerminalKeybindings.find( shortcut )->second; + auto found = mKeybindingsInvert.find( command ); if ( found != mKeybindingsInvert.end() ) { - mRealTerminalKeybindings[bindings.getShortcutFromString( found->second )] = kb.second; + mRealTerminalKeybindings[bindings.getShortcutFromString( found->second )] = command; } else { - mRealTerminalKeybindings[kb.first] = kb.second; + mRealTerminalKeybindings[shortcut] = command; } } } @@ -2100,7 +2103,7 @@ const AppConfig& App::getConfig() const { return mConfig; } -const std::map& App::getRealDefaultKeybindings() { +const KeyBindings::ShortcutMap& App::getRealDefaultKeybindings() { if ( mRealDefaultKeybindings.empty() ) { mRealDefaultKeybindings.insert( mRealLocalKeybindings.begin(), mRealLocalKeybindings.end() ); @@ -2112,7 +2115,7 @@ const std::map& App::getRealDefaultKeybindin return mRealDefaultKeybindings; } -std::map App::getDefaultKeybindings() { +KeyBindings::ShortcutMap App::getDefaultKeybindings() { auto bindings = UICodeEditorSplitter::getDefaultKeybindings(); auto local = getLocalKeybindings(); auto app = TerminalManager::getTerminalKeybindings(); @@ -2127,7 +2130,7 @@ static Uint32 DefaultSwitchToStatusPanelModifier = KeyMod::getDefaultModifier(); static Uint32 DefaultSwitchToStatusPanelModifier = KeyMod::getDefaultSecondaryModifier(); #endif -std::map App::getLocalKeybindings() { +KeyBindings::ShortcutMap App::getLocalKeybindings() { return { { { KEY_PRINTSCREEN, KEYMOD_NONE }, "take-screenshot" }, { { KEY_RETURN, KeyMod::getDefaultSecondaryModifier() | KeyMod::getDefaultModifier() }, @@ -2626,15 +2629,16 @@ void App::loadAudioFromPath( const std::string& path, bool autoPlay ) { } void App::loadDiffFromMemory( const std::string& content, const std::string& originalFilePath, - const std::string& oldFilePath, const std::string& repoPath ) { + const std::string& oldFilePath, const std::string& repoPath, + bool interactiveFileHeaders ) { if ( UIDiffView::isMultiFileDiff( content ) ) { auto diffViewTitle = i18n( "diff_viewer", "Diff Viewer" ) + ": " + originalFilePath; UIIcon* icon = getUISceneNode()->findIcon( "filetype-diff" ); if ( !icon ) icon = getUISceneNode()->findIcon( "file" ); - auto scrollView = - UIDiffView::NewMultiFileDiffViewer( content, repoPath, mConfig.editor.diffViewMode ); + auto scrollView = UIDiffView::NewMultiFileDiffViewer( + content, repoPath, mConfig.editor.diffViewMode, interactiveFileHeaders ); auto [tab, iv] = getSplitter()->createWidget( scrollView, diffViewTitle ); if ( icon ) tab->setIcon( icon->createDrawable( getMenuIconSize() ) ); diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 9aa276c07..f2f240c2e 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -179,11 +179,11 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { DrawablePtr findIcon( const std::string& name, const size_t iconSize ); - const std::map& getRealDefaultKeybindings(); + const KeyBindings::ShortcutMap& getRealDefaultKeybindings(); - std::map getDefaultKeybindings(); + KeyBindings::ShortcutMap getDefaultKeybindings(); - std::map getLocalKeybindings(); + KeyBindings::ShortcutMap getLocalKeybindings(); std::map getMigrateKeybindings(); @@ -581,8 +581,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { void loadDiffFromPaths( const std::string& oldPath, const std::string& newPath ); void loadDiffFromMemory( const std::string& content, const std::string& originalFilePath = "", - const std::string& oldFilePath = "", - const std::string& repoPath = "" ); + const std::string& oldFilePath = "", const std::string& repoPath = "", + bool interactiveFileHeaders = false ); void loadDiffFromStrings( const std::string& str, const std::string& otherStr ); void configureDiffView( UIDiffView* diffView ); @@ -619,11 +619,11 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { UITabWidget* getSidePanel() const; - const std::map& getRealLocalKeybindings() const; + const KeyBindings::ShortcutMap& getRealLocalKeybindings() const; - const std::map& getRealSplitterKeybindings() const; + const KeyBindings::ShortcutMap& getRealSplitterKeybindings() const; - const std::map& getRealTerminalKeybindings() const; + const KeyBindings::ShortcutMap& getRealTerminalKeybindings() const; const std::string& getFileToOpen() const; @@ -699,10 +699,10 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::unordered_map mGlobalSearchKeybindings; std::unordered_map mDocumentSearchKeybindings; std::unordered_map mStatusBarKeybindings; - std::map mRealLocalKeybindings; - std::map mRealSplitterKeybindings; - std::map mRealTerminalKeybindings; - std::map mRealDefaultKeybindings; + KeyBindings::ShortcutMap mRealLocalKeybindings; + KeyBindings::ShortcutMap mRealSplitterKeybindings; + KeyBindings::ShortcutMap mRealTerminalKeybindings; + KeyBindings::ShortcutMap mRealDefaultKeybindings; std::unordered_map mMousebindings; std::unordered_map mMousebindingsInvert; std::string mConfigPath; diff --git a/src/tools/ecode/keybindingshelper.cpp b/src/tools/ecode/keybindingshelper.cpp index bff72cc1c..f3c1f970f 100644 --- a/src/tools/ecode/keybindingshelper.cpp +++ b/src/tools/ecode/keybindingshelper.cpp @@ -72,17 +72,19 @@ void KeybindingsHelper::updateKeybindings( IniFile& ini, const std::string& group, Input* input, std::unordered_map& keybindings, std::unordered_map& invertedKeybindings, - const std::map& defKeybindings, bool forceRebind, + const KeyBindings::ShortcutMap& defKeybindings, bool forceRebind, const std::map& migrateKeyindings, IniFile& iniState ) { KeyBindings bindings( input ); bool added = false; bool migrated = false; + const auto orderedDefaultShortcuts = KeyBindings::getOrderedShortcuts( defKeybindings ); if ( ini.findKey( group ) != IniFile::noID ) { keybindings = ini.getKeyUnorderedMap( group ); } else { - for ( const auto& it : defKeybindings ) - ini.setValue( group, bindings.getShortcutString( it.first ), it.second ); + for ( const auto& shortcut : orderedDefaultShortcuts ) + ini.setValue( group, bindings.getShortcutString( shortcut ), + defKeybindings.find( shortcut )->second ); added = true; } for ( const auto& key : keybindings ) @@ -93,9 +95,9 @@ void KeybindingsHelper::updateKeybindings( auto foundCmd = invertedKeybindings.find( migrate.first ); if ( foundCmd != invertedKeybindings.end() && foundCmd->second == migrate.second ) { KeyBindings::Shortcut shortcut; - for ( const auto& defKb : defKeybindings ) { - if ( defKb.second == foundCmd->first ) { - shortcut = defKb.first; + for ( const auto& defaultShortcut : orderedDefaultShortcuts ) { + if ( defKeybindings.find( defaultShortcut )->second == foundCmd->first ) { + shortcut = defaultShortcut; break; } } @@ -121,15 +123,16 @@ void KeybindingsHelper::updateKeybindings( bool keybindingsWereEmpty = keybindings.empty(); if ( defKeybindings.size() != keybindings.size() || forceRebind ) { - for ( auto& key : defKeybindings ) { - auto foundCmd = invertedKeybindings.find( key.second ); - auto shortcutStr = bindings.getShortcutString( key.first ); + for ( const auto& shortcut : orderedDefaultShortcuts ) { + const auto& command = defKeybindings.find( shortcut )->second; + auto foundCmd = invertedKeybindings.find( command ); + auto shortcutStr = bindings.getShortcutString( shortcut ); if ( ( foundCmd == invertedKeybindings.end() || keybindingsWereEmpty ) && keybindings.find( shortcutStr ) == keybindings.end() ) { - keybindings[shortcutStr] = key.second; - invertedKeybindings[key.second] = shortcutStr; - ini.setValue( group, shortcutStr, key.second ); + keybindings[shortcutStr] = command; + invertedKeybindings[command] = shortcutStr; + ini.setValue( group, shortcutStr, command ); added = true; } } diff --git a/src/tools/ecode/keybindingshelper.hpp b/src/tools/ecode/keybindingshelper.hpp index f583966aa..f1dac1866 100644 --- a/src/tools/ecode/keybindingshelper.hpp +++ b/src/tools/ecode/keybindingshelper.hpp @@ -23,7 +23,7 @@ class KeybindingsHelper { IniFile& ini, const std::string& group, Input* input, std::unordered_map& keybindings, std::unordered_map& invertedKeybindings, - const std::map& defKeybindings, bool forceRebind, + const KeyBindings::ShortcutMap& defKeybindings, bool forceRebind, const std::map& migrateKeyindings, IniFile& iniState ); static void diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp index 5817ff8e1..cb84ec090 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.cpp @@ -124,7 +124,7 @@ UIWidget* UIBreakpointsTableView::createCell( UIWidget* rowWidget, const ModelIn return UITableView::createCell( rowWidget, index ); } -const std::map +const KeyBindings::ShortcutMap StatusDebuggerController::getLocalDefaultKeybindings() { return { { { KEY_TAB, UICodeEditorSplitter::getDefaultSwitchToTabModifier() }, "next-tab" }, diff --git a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp index 8d2756658..b730c1ff6 100644 --- a/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp +++ b/src/tools/ecode/plugins/debugger/statusdebuggercontroller.hpp @@ -42,7 +42,7 @@ class StatusDebuggerController : public StatusBarElement, public UITabWidgetSpli public: enum class State { NotStarted, Running, Paused }; - static const std::map getLocalDefaultKeybindings(); + static const KeyBindings::ShortcutMap getLocalDefaultKeybindings(); StatusDebuggerController( UISplitter* mainSplitter, UISceneNode* uiSceneNode, PluginContextProvider* pluginContext ); diff --git a/src/tools/ecode/plugins/git/git.cpp b/src/tools/ecode/plugins/git/git.cpp index bbfd0b67d..494685016 100644 --- a/src/tools/ecode/plugins/git/git.cpp +++ b/src/tools/ecode/plugins/git/git.cpp @@ -877,6 +877,8 @@ Git::Result Git::diff( DiffMode mode, const std::string& projectDir ) { modeTxt = "--staged"; break; } + case DiffChanged: + break; } return gitSimple( String::format( "diff %s", modeTxt ), projectDir ); } diff --git a/src/tools/ecode/plugins/git/git.hpp b/src/tools/ecode/plugins/git/git.hpp index 423315afb..8caba559c 100644 --- a/src/tools/ecode/plugins/git/git.hpp +++ b/src/tools/ecode/plugins/git/git.hpp @@ -306,7 +306,7 @@ class Git { bool isEmpty() const { return name.empty(); } }; - enum DiffMode { DiffHead, DiffStaged }; + enum DiffMode { DiffHead, DiffStaged, DiffChanged }; Git( const std::string& projectDir = "", const std::string& gitPath = "" ); diff --git a/src/tools/ecode/plugins/git/gitplugin.cpp b/src/tools/ecode/plugins/git/gitplugin.cpp index b76522198..4aa90e9cc 100644 --- a/src/tools/ecode/plugins/git/gitplugin.cpp +++ b/src/tools/ecode/plugins/git/gitplugin.cpp @@ -212,6 +212,8 @@ GitPlugin::~GitPlugin() { mLifetime.invalidate(); waitUntilLoaded(); mShuttingDown = true; + mCommitDetails.cancelDiffPreparation(); + mDetachedHistory.details.cancelDiffPreparation(); mConflictViewCloseConnection.disconnect(); mConflictView = nullptr; mConflictSessions.clear(); @@ -2152,10 +2154,13 @@ void GitPlugin::diff( const Git::DiffMode mode, const std::string& repoPath ) { case Git::DiffStaged: modeName = "staged"; break; + case Git::DiffChanged: + modeName = "changed"; + break; } plugin->getPluginContext()->loadDiffFromMemory( - res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "", "", - repoPath ); + res.result, UIDiffView::isMultiFileDiff( res.result ) ? modeName : "", "", repoPath, + true ); } ); } ); } @@ -2992,6 +2997,8 @@ void GitPlugin::CommitDetailsState::openCommitDetails( GitPlugin& plugin, const if ( commit.hash.empty() && !isWorkingTree ) return; const std::string selectedRepo = plugin.repoSelected(); + cancelDiffPreparation(); + diffPreparationCancelled = std::make_shared( false ); const Uint64 requestGeneration = ++generation; this->commit = commit; workingTree = isWorkingTree; @@ -3163,14 +3170,31 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det const std::string repo = this->repo; const Git::Commit commit = this->commit; const bool workingTree = this->workingTree; + const auto diffPreparationCancelled = this->diffPreparationCancelled; auto git = plugin.mGit; const auto lifetime = plugin.mLifetime.weakHandle(); plugin.runAsyncTask( [git = std::move( git ), lifetime, generation, repo, commit, detached, - workingTree] { + workingTree, diffPreparationCancelled] { + if ( diffPreparationCancelled && + diffPreparationCancelled->load( std::memory_order_relaxed ) ) + return; auto result = workingTree ? git->workingTreeFiles( repo ) : git->commitFiles( commit, repo ); + if ( diffPreparationCancelled && + diffPreparationCancelled->load( std::memory_order_relaxed ) ) + return; + std::shared_ptr preparedDiff; + if ( result.success() && !result.patch.empty() ) { + preparedDiff = + UIDiffView::prepareMultiFileDiff( result.patch, diffPreparationCancelled ); + if ( diffPreparationCancelled && + diffPreparationCancelled->load( std::memory_order_relaxed ) ) + return; + std::string{}.swap( result.patch ); + } lifetime.run( [generation, repo, commit, detached, workingTree, - result = std::move( result )]( GitPlugin* plugin ) mutable { + result = std::move( result ), + preparedDiff = std::move( preparedDiff )]( GitPlugin* plugin ) mutable { auto& details = detached ? plugin->mDetachedHistory.details : plugin->mCommitDetails; if ( plugin->mShuttingDown || generation != details.generation || repo != details.repo || repo != plugin->repoSelected() || @@ -3230,9 +3254,9 @@ void GitPlugin::CommitDetailsState::loadCommitFiles( GitPlugin& plugin, bool det details.gitHub->setVisible( !details.url.empty() ); details.diffContainer->closeAllChildren(); details.diff = nullptr; - if ( !result.patch.empty() ) { - details.diff = - UIDiffView::NewMultiFileDiffViewer( result.patch, repo, details.viewMode ); + if ( preparedDiff ) { + details.diff = UIDiffView::NewMultiFileDiffViewer( std::move( preparedDiff ), repo, + details.viewMode ); details.diff->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); details.diff->setParent( details.diffContainer ); @@ -3750,6 +3774,8 @@ void GitPlugin::buildSidePanelTab() { "diff-added" ); if ( type == Git::GitStatusType::Changed ) { + menuAdd( menu, "git-diff-changed", + i18n( "git_diff_changed", "Diff Changed" ), "diff-multiple" ); menu->addSeparator(); menuAdd( menu, "git-discard-all", i18n( "git_discard_all", "Discard All" ) ); @@ -3775,6 +3801,8 @@ void GitPlugin::buildSidePanelTab() { (Uint32)Git::GitStatusType::Changed ) ); } else if ( id == "git-diff-staged" ) { diff( Git::DiffMode::DiffStaged, repoPath ); + } else if ( id == "git-diff-changed" ) { + diff( Git::DiffMode::DiffChanged, repoPath ); } } ); diff --git a/src/tools/ecode/plugins/git/gitplugin.hpp b/src/tools/ecode/plugins/git/gitplugin.hpp index b551ce7a2..9cfc56fb6 100644 --- a/src/tools/ecode/plugins/git/gitplugin.hpp +++ b/src/tools/ecode/plugins/git/gitplugin.hpp @@ -188,6 +188,7 @@ class GitPlugin : public PluginBase { Git::Commit commit; std::string repo; std::atomic generation{ 0 }; + std::shared_ptr diffPreparationCancelled; EventConnection closeConnection; Tools::UIDiffView::ViewMode viewMode{ Tools::UIDiffView::ViewMode::Unified }; bool messageExpanded{ false }; @@ -199,7 +200,14 @@ class GitPlugin : public PluginBase { void loadCommitFiles( GitPlugin& plugin, bool detached ); + void cancelDiffPreparation() { + if ( diffPreparationCancelled ) + diffPreparationCancelled->store( true, std::memory_order_relaxed ); + diffPreparationCancelled.reset(); + } + void reset() { + cancelDiffPreparation(); ++generation; view = nullptr; subject = nullptr; diff --git a/src/tools/ecode/plugins/plugincontextprovider.hpp b/src/tools/ecode/plugins/plugincontextprovider.hpp index 3b4608097..92f61ac03 100644 --- a/src/tools/ecode/plugins/plugincontextprovider.hpp +++ b/src/tools/ecode/plugins/plugincontextprovider.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -170,7 +171,8 @@ class PluginContextProvider { virtual void loadDiffFromMemory( const std::string& content, const std::string& originalFilePath = "", const std::string& oldFilePath = "", - const std::string& repoPath = "" ) = 0; + const std::string& repoPath = "", + bool interactiveFileHeaders = false ) = 0; virtual void loadFolder( std::string path, bool forceNewWindow = false ) = 0; diff --git a/src/tools/ecode/terminalmanager.cpp b/src/tools/ecode/terminalmanager.cpp index ce937ceea..17b68bfae 100644 --- a/src/tools/ecode/terminalmanager.cpp +++ b/src/tools/ecode/terminalmanager.cpp @@ -131,7 +131,7 @@ void TerminalManager::loadTerminalColorSchemes() { mTerminalCurrentColorScheme = mTerminalColorSchemes.begin()->first; } -std::map TerminalManager::getTerminalKeybindings() { +KeyBindings::ShortcutMap TerminalManager::getTerminalKeybindings() { return { { { KEY_T, KeyMod::getDefaultModifier() | KEYMOD_SHIFT }, "create-new-terminal" }, { { KEY_E, diff --git a/src/tools/ecode/terminalmanager.hpp b/src/tools/ecode/terminalmanager.hpp index afc02083d..099d5c5ea 100644 --- a/src/tools/ecode/terminalmanager.hpp +++ b/src/tools/ecode/terminalmanager.hpp @@ -34,7 +34,7 @@ class TerminalManager { void loadTerminalColorSchemes(); - static std::map getTerminalKeybindings(); + static KeyBindings::ShortcutMap getTerminalKeybindings(); const std::string& getTerminalColorSchemesPath() const; diff --git a/src/tools/ecode/uitreeviewfs.cpp b/src/tools/ecode/uitreeviewfs.cpp index 28018e459..c161341f6 100644 --- a/src/tools/ecode/uitreeviewfs.cpp +++ b/src/tools/ecode/uitreeviewfs.cpp @@ -10,7 +10,7 @@ namespace ecode { -static const std::map getDefaultKeybindings() { +static const KeyBindings::ShortcutMap getDefaultKeybindings() { return { { { KEY_C, KeyMod::getDefaultModifier() }, "copy" }, { { KEY_X, KeyMod::getDefaultModifier() }, "cut" },