mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
Optimize bulk editor setup, deferred UI resources and Git plugin fixes
- Lazily compute document line hashes, avoid redundant multiline insertion
walks, and defer longest-line measurement for large or exceptionally long
text changes.
- Cache default code editor keybindings, reserve common command storage, and
create UIDiffView image viewers only when an image diff requires them.
- Keep cached screen positions coherent when descendants are queried before
dirty ancestors, and add regression coverage for ancestor movement.
- Relocate font-rendering golden images into the unit-test asset directory
and resolve image fixtures independently of the current working directory.
- Fix spurious status entries for files moved between directories. Preserve the suffix after Git's abbreviated brace notation when parsing renamed paths from numstat output. This prevents directory moves such as `bin/{ => unit_tests}/assets/...` from producing truncated synthetic untracked entries like `unit_tests`. Add regression coverage for staged files moved into a different directory.
- Git Plugin now remembers between sessions the uncommited commit message.
This commit is contained in:
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
@@ -1,6 +1,7 @@
|
||||
#ifndef EE_UI_DOC_TEXTDOCUMENTLINE_HPP
|
||||
#define EE_UI_DOC_TEXTDOCUMENTLINE_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <eepp/core/string.hpp>
|
||||
#include <eepp/system/lock.hpp>
|
||||
#include <eepp/system/mutex.hpp>
|
||||
@@ -14,25 +15,54 @@ class EE_API TextDocumentLine {
|
||||
public:
|
||||
TextDocumentLine( const String& text, std::shared_ptr<Mutex> docMutex ) :
|
||||
mText( text ), mDocMutex( docMutex ) {
|
||||
updateState();
|
||||
updateTextHints();
|
||||
}
|
||||
|
||||
TextDocumentLine( String&& text, std::shared_ptr<Mutex> docMutex ) :
|
||||
mText( std::move( text ) ), mDocMutex( std::move( docMutex ) ) {
|
||||
updateState();
|
||||
updateTextHints();
|
||||
}
|
||||
|
||||
TextDocumentLine( const TextDocumentLine& ) = default;
|
||||
TextDocumentLine( const TextDocumentLine& other ) : mDocMutex( other.mDocMutex ) {
|
||||
ConditionalLock lock( mDocMutex != nullptr, mDocMutex.get() );
|
||||
mText = other.mText;
|
||||
mHash.store( other.mHash.load( std::memory_order_relaxed ), std::memory_order_relaxed );
|
||||
mFlags.store( other.mFlags.load( std::memory_order_acquire ), std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
TextDocumentLine( TextDocumentLine&& other ) noexcept : mDocMutex( other.mDocMutex ) {
|
||||
ConditionalLock lock( mDocMutex != nullptr, mDocMutex.get() );
|
||||
mText = std::move( other.mText );
|
||||
mHash = other.mHash;
|
||||
mFlags = other.mFlags;
|
||||
mHash.store( other.mHash.load( std::memory_order_relaxed ), std::memory_order_relaxed );
|
||||
mFlags.store( other.mFlags.load( std::memory_order_acquire ), std::memory_order_relaxed );
|
||||
other.mDocMutex.reset();
|
||||
}
|
||||
|
||||
TextDocumentLine& operator=( const TextDocumentLine& ) = default;
|
||||
TextDocumentLine& operator=( const TextDocumentLine& other ) {
|
||||
if ( this == &other )
|
||||
return *this;
|
||||
|
||||
String text;
|
||||
String::HashType hash;
|
||||
Uint32 flags;
|
||||
auto docMutex = other.mDocMutex;
|
||||
{
|
||||
ConditionalLock lock( docMutex != nullptr, docMutex.get() );
|
||||
text = other.mText;
|
||||
hash = other.mHash.load( std::memory_order_relaxed );
|
||||
flags = other.mFlags.load( std::memory_order_acquire );
|
||||
}
|
||||
|
||||
auto oldDocMutex = mDocMutex;
|
||||
{
|
||||
ConditionalLock lock( oldDocMutex != nullptr, oldDocMutex.get() );
|
||||
mText = std::move( text );
|
||||
mHash.store( hash, std::memory_order_relaxed );
|
||||
mFlags.store( flags, std::memory_order_release );
|
||||
mDocMutex = std::move( docMutex );
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
~TextDocumentLine() {
|
||||
if ( mDocMutex ) {
|
||||
@@ -44,11 +74,13 @@ class EE_API TextDocumentLine {
|
||||
void setText( String&& text ) {
|
||||
if ( mDocMutex ) {
|
||||
Lock lock( *mDocMutex );
|
||||
invalidateHash();
|
||||
mText = std::move( text );
|
||||
updateState();
|
||||
updateTextHints();
|
||||
} else {
|
||||
invalidateHash();
|
||||
mText = std::move( text );
|
||||
updateState();
|
||||
updateTextHints();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,22 +119,26 @@ class EE_API TextDocumentLine {
|
||||
void append( const String& text ) {
|
||||
if ( mDocMutex ) {
|
||||
Lock lock( *mDocMutex );
|
||||
invalidateHash();
|
||||
mText.append( text );
|
||||
updateState();
|
||||
updateTextHints();
|
||||
} else {
|
||||
invalidateHash();
|
||||
mText.append( text );
|
||||
updateState();
|
||||
updateTextHints();
|
||||
}
|
||||
}
|
||||
|
||||
void insert( std::size_t position, const String& text ) {
|
||||
if ( mDocMutex ) {
|
||||
Lock lock( *mDocMutex );
|
||||
invalidateHash();
|
||||
mText.insert( position, text );
|
||||
updateState();
|
||||
updateTextHints();
|
||||
} else {
|
||||
invalidateHash();
|
||||
mText.insert( position, text );
|
||||
updateState();
|
||||
updateTextHints();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,30 +166,40 @@ class EE_API TextDocumentLine {
|
||||
return mText.size();
|
||||
}
|
||||
|
||||
String::HashType getHash() const { return mHash; }
|
||||
String::HashType getHash() const {
|
||||
Uint32 flags = mFlags.load( std::memory_order_acquire );
|
||||
if ( flags & HashValid )
|
||||
return mHash.load( std::memory_order_relaxed );
|
||||
|
||||
bool isAscii() const { return ( mFlags & TextHints::AllAscii ) != 0; }
|
||||
|
||||
bool isLatin1() const { return ( mFlags & TextHints::AllLatin1 ) != 0; }
|
||||
|
||||
Uint32 getTextHints() const {
|
||||
if ( mDocMutex ) {
|
||||
Lock lock( *mDocMutex );
|
||||
return mFlags;
|
||||
ConditionalLock lock( mDocMutex != nullptr, mDocMutex.get() );
|
||||
flags = mFlags.load( std::memory_order_acquire );
|
||||
if ( !( flags & HashValid ) ) {
|
||||
mHash.store( mText.getHash(), std::memory_order_relaxed );
|
||||
mFlags.store( flags | HashValid, std::memory_order_release );
|
||||
}
|
||||
return mFlags;
|
||||
return mHash.load( std::memory_order_relaxed );
|
||||
}
|
||||
|
||||
bool isAscii() const {
|
||||
return ( mFlags.load( std::memory_order_acquire ) & TextHints::AllAscii ) != 0;
|
||||
}
|
||||
|
||||
bool isLatin1() const {
|
||||
return ( mFlags.load( std::memory_order_acquire ) & TextHints::AllLatin1 ) != 0;
|
||||
}
|
||||
|
||||
Uint32 getTextHints() const { return mFlags.load( std::memory_order_acquire ) & ~HashValid; }
|
||||
|
||||
protected:
|
||||
static constexpr Uint32 HashValid = 1u << 31;
|
||||
String mText;
|
||||
String::HashType mHash{ 0 };
|
||||
Uint32 mFlags{ 0 };
|
||||
mutable std::atomic<String::HashType> mHash{ 0 };
|
||||
mutable std::atomic<Uint32> mFlags{ 0 };
|
||||
std::shared_ptr<Mutex> mDocMutex;
|
||||
|
||||
void updateState() {
|
||||
mHash = mText.getHash();
|
||||
mFlags = mText.getTextHints();
|
||||
}
|
||||
void invalidateHash() { mFlags.fetch_and( ~HashValid, std::memory_order_release ); }
|
||||
|
||||
void updateTextHints() { mFlags.store( mText.getTextHints(), std::memory_order_release ); }
|
||||
};
|
||||
|
||||
}}} // namespace EE::UI::Doc
|
||||
|
||||
@@ -185,7 +185,9 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
|
||||
|
||||
void updateButtonsVisibility();
|
||||
|
||||
void createImageViewers();
|
||||
UIImageViewer* createImageViewer();
|
||||
|
||||
void resetImageViewers();
|
||||
|
||||
bool loadImageDiffFromPaths( const std::string& oldFilePath, const std::string& newFilePath );
|
||||
|
||||
|
||||
@@ -533,6 +533,8 @@ Uint32 Node::forceTextInput( const TextInputEvent& event ) {
|
||||
}
|
||||
|
||||
const Vector2f& Node::getScreenPos() const {
|
||||
if ( mNodeFlags & NODE_FLAG_POSITION_DIRTY )
|
||||
const_cast<Node*>( this )->updateScreenPos();
|
||||
return mScreenPos;
|
||||
}
|
||||
|
||||
@@ -1195,6 +1197,12 @@ void Node::updateScreenPos() {
|
||||
if ( !( mNodeFlags & NODE_FLAG_POSITION_DIRTY ) )
|
||||
return;
|
||||
|
||||
// Keep the dirty-tree invariant intact when a descendant is queried before its ancestors are
|
||||
// drawn. Once this node becomes position-clean, every ancestor must also be position-clean;
|
||||
// otherwise a later ancestor move can early-out in setDirty() without reaching this node.
|
||||
if ( mParentNode && ( mParentNode->mNodeFlags & NODE_FLAG_POSITION_DIRTY ) )
|
||||
mParentNode->updateScreenPos();
|
||||
|
||||
Vector2f Pos( mPosition );
|
||||
|
||||
nodeToWorldTranslation( Pos );
|
||||
|
||||
@@ -1572,6 +1572,7 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio
|
||||
position = sanitizePosition( position );
|
||||
size_t lineCount = linesCount();
|
||||
Int64 linesAdd = 0;
|
||||
Int64 lastInsertedLineLength = 0;
|
||||
bool multiline = text.find( '\n' ) != String::InvalidPos;
|
||||
|
||||
{
|
||||
@@ -1596,6 +1597,8 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio
|
||||
for ( Int64 i = 0; i <= linesAdd; ++i ) {
|
||||
size_t newLine = textView.find( '\n', lineStart );
|
||||
size_t lineEnd = newLine == String::InvalidPos ? textView.size() : newLine + 1;
|
||||
if ( i == linesAdd )
|
||||
lastInsertedLineLength = static_cast<Int64>( lineEnd - lineStart );
|
||||
String line( textView.substr( lineStart, lineEnd - lineStart ) );
|
||||
if ( i == 0 )
|
||||
line.insert( 0, before );
|
||||
@@ -1616,7 +1619,12 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio
|
||||
}
|
||||
}
|
||||
|
||||
TextPosition cursor = positionOffset( position, text.size() );
|
||||
// The multiline construction above already knows the final line and column. Start from that
|
||||
// line instead of walking every inserted line again, while still letting positionOffset() move
|
||||
// the endpoint to a grapheme boundary when the inserted text joins the existing suffix.
|
||||
TextPosition cursor =
|
||||
multiline ? positionOffset( { position.line() + linesAdd, 0 }, lastInsertedLineLength )
|
||||
: positionOffset( position, text.size() );
|
||||
|
||||
mUndoStack.pushSelection( undoStack, cursorIdx, mSelection, time );
|
||||
mUndoStack.pushRemove( undoStack, cursorIdx, { position, cursor }, time );
|
||||
@@ -4878,6 +4886,9 @@ 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(); };
|
||||
|
||||
@@ -494,7 +494,6 @@ UIDiffView::UIDiffView() :
|
||||
createEditor( mEditor, mPlugin );
|
||||
createEditor( mLeftEditor, mLeftPlugin );
|
||||
createEditor( mRightEditor, mRightPlugin );
|
||||
createImageViewers();
|
||||
|
||||
mEditor->on( Event::OnFontChanged, [this]( auto ) { mPlugin->registerUpdate( mEditor ); } );
|
||||
mLeftEditor->on( Event::OnFontChanged, [this]( auto ) {
|
||||
@@ -507,6 +506,9 @@ UIDiffView::UIDiffView() :
|
||||
mLeftEditor->setFontSize( mRightEditor->getFontSize() );
|
||||
mLeftPlugin->registerUpdate( mLeftEditor );
|
||||
} );
|
||||
mRightEditor->getVScrollBar()->on( Event::OnSizeChange, [this] ( auto ) {
|
||||
updateModeButton();
|
||||
} );
|
||||
|
||||
for ( auto* editor : { mEditor, mLeftEditor, mRightEditor } ) {
|
||||
editor->on( Event::OnSizeChange, [this]( auto ) { onAutoSize(); } );
|
||||
@@ -552,7 +554,6 @@ UIDiffView::UIDiffView() :
|
||||
mCompleteViewToggle->on( Event::OnSizeChange, [this]( auto ) { updateModeButton(); } );
|
||||
|
||||
updateButtonsText();
|
||||
updateImagesPosAndSize();
|
||||
}
|
||||
|
||||
UIDiffView::~UIDiffView() {
|
||||
@@ -589,33 +590,31 @@ void UIDiffView::createEditor( UICodeEditor*& editor,
|
||||
editor->registerPlugin( plugin.get() );
|
||||
}
|
||||
|
||||
void UIDiffView::createImageViewers() {
|
||||
const auto initImageView = [this] {
|
||||
auto iv = UIImageViewer::New();
|
||||
iv->setParent( this );
|
||||
iv->setVisible( false );
|
||||
iv->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
iv->setDisplayOptions( UIImageViewer::DisplayDimensions );
|
||||
iv->setUseNativeImageSize( true );
|
||||
return iv;
|
||||
};
|
||||
mLeftImageViewer = initImageView();
|
||||
mRightImageViewer = initImageView();
|
||||
mDiffImageViewer = initImageView();
|
||||
UIImageViewer* UIDiffView::createImageViewer() {
|
||||
auto* imageViewer = UIImageViewer::New();
|
||||
imageViewer->setParent( this );
|
||||
imageViewer->setVisible( false );
|
||||
imageViewer->setLayoutSizePolicy( SizePolicy::Fixed, SizePolicy::Fixed );
|
||||
imageViewer->setDisplayOptions( UIImageViewer::DisplayDimensions );
|
||||
imageViewer->setUseNativeImageSize( true );
|
||||
return imageViewer;
|
||||
}
|
||||
|
||||
void UIDiffView::resetImageViewers() {
|
||||
for ( auto* imageViewer : { mLeftImageViewer, mRightImageViewer, mDiffImageViewer } ) {
|
||||
if ( imageViewer ) {
|
||||
imageViewer->reset();
|
||||
imageViewer->setVisible( false );
|
||||
}
|
||||
}
|
||||
mSprite = nullptr;
|
||||
}
|
||||
|
||||
void UIDiffView::resetToTextDiffView() {
|
||||
mIsImageDiff = false;
|
||||
mImageDiffOldPath.clear();
|
||||
mImageDiffNewPath.clear();
|
||||
if ( mLeftImageViewer ) {
|
||||
mLeftImageViewer->reset();
|
||||
mLeftImageViewer->setVisible( false );
|
||||
}
|
||||
if ( mRightImageViewer ) {
|
||||
mRightImageViewer->reset();
|
||||
mRightImageViewer->setVisible( false );
|
||||
}
|
||||
resetImageViewers();
|
||||
mEditor->setVisible( mViewMode == ViewMode::Unified );
|
||||
mLeftEditor->setVisible( mViewMode == ViewMode::SideBySide );
|
||||
mRightEditor->setVisible( mViewMode == ViewMode::SideBySide );
|
||||
@@ -628,8 +627,8 @@ void UIDiffView::setViewMode( ViewMode mode ) {
|
||||
mViewMode = mode;
|
||||
|
||||
if ( mIsImageDiff ) {
|
||||
onSizeChange();
|
||||
updateImageDiffView();
|
||||
onSizeChange();
|
||||
updateButtonsText();
|
||||
return;
|
||||
}
|
||||
@@ -668,6 +667,7 @@ void UIDiffView::setCompleteView( bool complete ) {
|
||||
updateButtonsText();
|
||||
if ( mIsImageDiff ) {
|
||||
updateImageDiffView();
|
||||
onSizeChange();
|
||||
return;
|
||||
}
|
||||
updateEditorsText();
|
||||
@@ -696,7 +696,8 @@ void UIDiffView::updateModeButton() {
|
||||
auto vmargin = mHeadersVisible ? emptySpace * 0.5f : margin;
|
||||
|
||||
Float currentX = getPixelsSize().getWidth() - margin;
|
||||
currentX -= mRightEditor->getVScrollBar()->getPixelsSize().getWidth();
|
||||
Float vScrollWidth = mRightEditor->getVScrollBar()->getPixelsSize().getWidth();
|
||||
currentX -= vScrollWidth;
|
||||
|
||||
if ( mViewModeToggleVisible && mModeToggle ) {
|
||||
currentX -= mModeToggle->getPixelsSize().getWidth();
|
||||
@@ -734,7 +735,7 @@ void UIDiffView::onAutoSize() {
|
||||
: mLeftEditor->getPixelsSize().getHeight() ) );
|
||||
}
|
||||
|
||||
if ( mIsImageDiff && mLeftImageViewer && mRightImageViewer && mDiffImageViewer ) {
|
||||
if ( mIsImageDiff ) {
|
||||
bool displayDiffImage;
|
||||
bool displayLeftImage;
|
||||
imageDisplayState( displayDiffImage, displayLeftImage );
|
||||
@@ -745,39 +746,47 @@ void UIDiffView::onAutoSize() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
height = std::max( height, viewImageHeight( mLeftImageViewer ) );
|
||||
height = std::max( height, viewImageHeight( mRightImageViewer ) );
|
||||
|
||||
if ( displayDiffImage )
|
||||
if ( displayDiffImage ) {
|
||||
height = std::ceil( std::max( height, viewImageHeight( mDiffImageViewer ) ) );
|
||||
} else {
|
||||
if ( displayLeftImage )
|
||||
height = std::max( height, viewImageHeight( mLeftImageViewer ) );
|
||||
height = std::max( height, viewImageHeight( mRightImageViewer ) );
|
||||
}
|
||||
|
||||
setPixelsSize( getPixelsSize().getWidth(), height );
|
||||
}
|
||||
}
|
||||
|
||||
void UIDiffView::updateImagesPosAndSize() {
|
||||
if ( !mIsImageDiff )
|
||||
return;
|
||||
|
||||
const Sizef size( getPixelsSize() );
|
||||
|
||||
bool displayDiffImage;
|
||||
bool displayLeftImage;
|
||||
imageDisplayState( displayDiffImage, displayLeftImage );
|
||||
|
||||
mLeftImageViewer->setVisible( true );
|
||||
mLeftImageViewer->setPixelsPosition( 0, 0 );
|
||||
mLeftImageViewer->setPixelsSize( { size.getWidth() * 0.5f, size.getHeight() } );
|
||||
setImageViewerImageSize( mLeftImageViewer );
|
||||
if ( mLeftImageViewer ) {
|
||||
mLeftImageViewer->setPixelsPosition( 0, 0 );
|
||||
mLeftImageViewer->setPixelsSize( { size.getWidth() * 0.5f, size.getHeight() } );
|
||||
setImageViewerImageSize( mLeftImageViewer );
|
||||
}
|
||||
|
||||
mRightImageViewer->setVisible( true );
|
||||
mRightImageViewer->setPixelsSize(
|
||||
displayLeftImage ? Sizef{ size.getWidth() * 0.5f, size.getHeight() } : size );
|
||||
mRightImageViewer->setPixelsPosition(
|
||||
displayLeftImage ? std::floor( size.getWidth() * 0.5f ) : 0.f, 0.f );
|
||||
setImageViewerImageSize( mRightImageViewer );
|
||||
if ( mRightImageViewer ) {
|
||||
mRightImageViewer->setPixelsSize(
|
||||
displayLeftImage ? Sizef{ size.getWidth() * 0.5f, size.getHeight() } : size );
|
||||
mRightImageViewer->setPixelsPosition(
|
||||
displayLeftImage ? std::floor( size.getWidth() * 0.5f ) : 0.f, 0.f );
|
||||
setImageViewerImageSize( mRightImageViewer );
|
||||
}
|
||||
|
||||
mDiffImageViewer->setVisible( true );
|
||||
mDiffImageViewer->setPixelsPosition( 0, 0 );
|
||||
mDiffImageViewer->setPixelsSize( size );
|
||||
setImageViewerImageSize( mDiffImageViewer );
|
||||
if ( mDiffImageViewer ) {
|
||||
mDiffImageViewer->setPixelsPosition( 0, 0 );
|
||||
mDiffImageViewer->setPixelsSize( size );
|
||||
setImageViewerImageSize( mDiffImageViewer );
|
||||
}
|
||||
|
||||
onAutoSize();
|
||||
updateModeButton();
|
||||
@@ -879,6 +888,7 @@ bool UIDiffView::loadImageDiffFromPaths( const std::string& oldFilePath,
|
||||
if ( !hasOldImage && !hasNewImage )
|
||||
return false;
|
||||
|
||||
resetImageViewers();
|
||||
mLines.clear();
|
||||
mViewLines.clear();
|
||||
mSyntaxDef.reset();
|
||||
@@ -902,8 +912,8 @@ bool UIDiffView::loadImageDiffFromPaths( const std::string& oldFilePath,
|
||||
|
||||
setCompleteViewToggleVisible( !mImageDiffOldPath.empty() && !mImageDiffNewPath.empty() );
|
||||
updateButtonsText();
|
||||
onSizeChange();
|
||||
updateImageDiffView();
|
||||
onSizeChange();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -933,18 +943,23 @@ void UIDiffView::updateImageDiffView() {
|
||||
mLeftEditor->setVisible( false );
|
||||
mRightEditor->setVisible( false );
|
||||
|
||||
mDiffImageViewer->setVisible( false );
|
||||
if ( mDiffImageViewer )
|
||||
mDiffImageViewer->setVisible( false );
|
||||
|
||||
if ( displayLeftImage ) {
|
||||
if ( !mLeftImageViewer )
|
||||
mLeftImageViewer = createImageViewer();
|
||||
mLeftImageViewer->setVisible( true );
|
||||
if ( !mLeftImageViewer->hasImage() )
|
||||
mLeftImageViewer->loadImageAsync( mImageDiffOldPath, false, false );
|
||||
} else {
|
||||
} else if ( mLeftImageViewer ) {
|
||||
mLeftImageViewer->reset();
|
||||
mLeftImageViewer->setVisible( false );
|
||||
}
|
||||
|
||||
if ( displayDiffImage ) {
|
||||
if ( !mDiffImageViewer )
|
||||
mDiffImageViewer = createImageViewer();
|
||||
if ( nullptr == mSprite ) {
|
||||
Image oldImage( mImageDiffOldPath, 4 );
|
||||
Image newImage( mImageDiffNewPath, 4 );
|
||||
@@ -955,8 +970,10 @@ void UIDiffView::updateImageDiffView() {
|
||||
}
|
||||
}
|
||||
|
||||
mLeftImageViewer->setVisible( false );
|
||||
mRightImageViewer->setVisible( false );
|
||||
if ( mLeftImageViewer )
|
||||
mLeftImageViewer->setVisible( false );
|
||||
if ( mRightImageViewer )
|
||||
mRightImageViewer->setVisible( false );
|
||||
mDiffImageViewer->setVisible( true );
|
||||
return;
|
||||
}
|
||||
@@ -965,10 +982,12 @@ void UIDiffView::updateImageDiffView() {
|
||||
mImageDiffNewPath.empty() ? mImageDiffOldPath : mImageDiffNewPath;
|
||||
|
||||
if ( !displayPath.empty() ) {
|
||||
if ( !mRightImageViewer )
|
||||
mRightImageViewer = createImageViewer();
|
||||
mRightImageViewer->setVisible( true );
|
||||
if ( !mRightImageViewer->hasImage() )
|
||||
mRightImageViewer->loadImageAsync( displayPath, false, false );
|
||||
} else {
|
||||
} else if ( mRightImageViewer ) {
|
||||
mRightImageViewer->reset();
|
||||
mRightImageViewer->setVisible( false );
|
||||
}
|
||||
|
||||
@@ -49,7 +49,9 @@ UICodeEditor* UICodeEditor::NewOpt( const bool& autoRegisterBaseCommands,
|
||||
return eeNew( UICodeEditor, ( autoRegisterBaseCommands, autoRegisterBaseKeybindings ) );
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string> UICodeEditor::getDefaultKeybindings() {
|
||||
using CodeEditorKeyBindingMap = std::map<KeyBindings::Shortcut, std::string>;
|
||||
|
||||
static CodeEditorKeyBindingMap createDefaultCodeEditorKeybindings() {
|
||||
return {
|
||||
{ { KEY_BACKSPACE, KeyMod::getDefaultModifier() }, "delete-to-previous-word" },
|
||||
{ { KEY_BACKSPACE, KEYMOD_SHIFT }, "delete-to-previous-char" },
|
||||
@@ -130,6 +132,32 @@ const std::map<KeyBindings::Shortcut, std::string> UICodeEditor::getDefaultKeybi
|
||||
};
|
||||
}
|
||||
|
||||
static std::shared_ptr<const CodeEditorKeyBindingMap> getCachedDefaultCodeEditorKeybindings() {
|
||||
struct Cache {
|
||||
Mutex mutex;
|
||||
Uint32 defaultModifier{ 0 };
|
||||
Uint32 secondaryModifier{ 0 };
|
||||
std::shared_ptr<const CodeEditorKeyBindingMap> bindings;
|
||||
};
|
||||
static Cache cache;
|
||||
|
||||
const Uint32 defaultModifier = KeyMod::getDefaultModifier();
|
||||
const Uint32 secondaryModifier = KeyMod::getDefaultSecondaryModifier();
|
||||
Lock lock( cache.mutex );
|
||||
if ( !cache.bindings || cache.defaultModifier != defaultModifier ||
|
||||
cache.secondaryModifier != secondaryModifier ) {
|
||||
cache.bindings =
|
||||
std::make_shared<const CodeEditorKeyBindingMap>( createDefaultCodeEditorKeybindings() );
|
||||
cache.defaultModifier = defaultModifier;
|
||||
cache.secondaryModifier = secondaryModifier;
|
||||
}
|
||||
return cache.bindings;
|
||||
}
|
||||
|
||||
const std::map<KeyBindings::Shortcut, std::string> UICodeEditor::getDefaultKeybindings() {
|
||||
return *getCachedDefaultCodeEditorKeybindings();
|
||||
}
|
||||
|
||||
const MouseBindings::ShortcutMap UICodeEditor::getDefaultMousebindings() {
|
||||
return { { { MouseAction::Down, EE_BUTTON_LMASK, KeyMod::getDefaultModifier() },
|
||||
"add-cursor-at-mouse-position" },
|
||||
@@ -2311,10 +2339,41 @@ void UICodeEditor::onDocumentTextChanged( const DocumentContentChange& change )
|
||||
mDocView.updateCache( change.range.start().line(), change.range.start().line(), 0 );
|
||||
|
||||
if ( !change.text.empty() && !mDocView.isWrapEnabled() ) {
|
||||
auto range = findLongestLineInRange( change.range );
|
||||
if ( range.second > mLongestLineWidth ) {
|
||||
mLongestLineIndex = range.first;
|
||||
mLongestLineWidth = range.second;
|
||||
static constexpr Int64 MAX_SYNCHRONOUS_LONGEST_LINE_SCAN = 64;
|
||||
static constexpr std::size_t MAX_SYNCHRONOUS_LONGEST_LINE_LENGTH = 4096;
|
||||
bool largeChange = change.text.size() > MAX_SYNCHRONOUS_LONGEST_LINE_LENGTH ||
|
||||
change.range.end().line() - change.range.start().line() + 1 >
|
||||
MAX_SYNCHRONOUS_LONGEST_LINE_SCAN;
|
||||
if ( !largeChange ) {
|
||||
for ( Int64 line = change.range.start().line(); line <= change.range.end().line();
|
||||
++line ) {
|
||||
if ( mDoc->getLineLength( line ) > MAX_SYNCHRONOUS_LONGEST_LINE_LENGTH ) {
|
||||
largeChange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !largeChange ) {
|
||||
Int64 insertedLines = 1;
|
||||
for ( auto chr : change.text ) {
|
||||
if ( chr == '\n' && ++insertedLines > MAX_SYNCHRONOUS_LONGEST_LINE_SCAN ) {
|
||||
largeChange = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( !largeChange ) {
|
||||
auto range = findLongestLineInRange( change.range );
|
||||
if ( range.second > mLongestLineWidth ) {
|
||||
mLongestLineIndex = range.first;
|
||||
mLongestLineWidth = range.second;
|
||||
}
|
||||
} else {
|
||||
// Unbounded synchronous measurement makes large pastes and document loads block on
|
||||
// too many lines or one extremely long line, including in editors that are currently
|
||||
// hidden. Reuse the existing debounced full-document update; visible editors will
|
||||
// measure once after the change.
|
||||
invalidateLongestLineWidth();
|
||||
}
|
||||
} else {
|
||||
invalidateLongestLineWidth();
|
||||
@@ -4795,6 +4854,7 @@ void UICodeEditor::drawLineEndings( const DocumentLineRange& lineRange, const Ve
|
||||
}
|
||||
|
||||
void UICodeEditor::registerCommands() {
|
||||
mUnlockedCmd.reserve( 8 );
|
||||
mDoc->setCommand( "move-to-previous-line", []( Client* client ) {
|
||||
static_cast<UICodeEditor*>( client )->moveToPreviousLine();
|
||||
} );
|
||||
@@ -4947,7 +5007,9 @@ Tools::UIDocFindReplace* UICodeEditor::getFindReplace() {
|
||||
}
|
||||
|
||||
void UICodeEditor::registerKeybindings() {
|
||||
mKeyBindings.addKeybinds( getDefaultKeybindings() );
|
||||
// Editors keep mutable bindings, but the immutable defaults only need to be constructed once
|
||||
// for each configured default-modifier pair.
|
||||
mKeyBindings.addKeybinds( *getCachedDefaultCodeEditorKeybindings() );
|
||||
mMouseBindings.addMousebinds( getDefaultMousebindings() );
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ static void compareImages( utest_state_s& utest_state, int* utest_result, EE::Wi
|
||||
int allowedNumDifferentPixels = 0 ) {
|
||||
auto saveType = Image::SaveType::WEBP;
|
||||
auto saveExt( Image::saveTypeToExtension( saveType ) );
|
||||
std::string expectedImagePath( "assets/" + imagesFolder + "/" + imageName + "." + saveExt );
|
||||
std::string expectedImagePath( Sys::getProcessPath() + "assets/" + imagesFolder + "/" +
|
||||
imageName + "." + saveExt );
|
||||
|
||||
Image::FormatConfiguration fconf;
|
||||
fconf.webpSaveLossless( true );
|
||||
|
||||
@@ -317,3 +317,42 @@ UTEST( GitHistory, ListsChangedFilesAndLoadsFirstParentDiff ) {
|
||||
EXPECT_NE( std::string::npos, diff.result.find( "-before" ) );
|
||||
EXPECT_NE( std::string::npos, diff.result.find( "+after" ) );
|
||||
}
|
||||
|
||||
UTEST( GitStatus, PreservesSuffixOfRenamedDirectoryNumstatPaths ) {
|
||||
const std::string gitPath = Sys::which( "git" );
|
||||
if ( gitPath.empty() )
|
||||
UTEST_SKIP( "Git is not installed" );
|
||||
GitTempDirectory temp;
|
||||
Git git( temp.path.string(), gitPath );
|
||||
std::string output;
|
||||
auto run = [&]( std::vector<std::string> args ) {
|
||||
output.clear();
|
||||
return git.git( args, temp.path.string(), output );
|
||||
};
|
||||
ASSERT_EQ( EXIT_SUCCESS, run( { "init", "-b", "main" } ) );
|
||||
ASSERT_EQ( EXIT_SUCCESS, run( { "config", "user.name", "Status Tester" } ) );
|
||||
ASSERT_EQ( EXIT_SUCCESS, run( { "config", "user.email", "status@example.invalid" } ) );
|
||||
ASSERT_TRUE( FileSystem::makeDir( ( temp.path / "bin/assets/fontrendering" ).string(), true ) );
|
||||
ASSERT_TRUE( FileSystem::fileWrite(
|
||||
( temp.path / "bin/assets/fontrendering/image.webp" ).string(), "image contents" ) );
|
||||
ASSERT_EQ( EXIT_SUCCESS, run( { "add", "bin/assets/fontrendering/image.webp" } ) );
|
||||
ASSERT_EQ( EXIT_SUCCESS, run( { "commit", "-m", "base" } ) );
|
||||
ASSERT_TRUE( FileSystem::makeDir( ( temp.path / "bin/unit_tests" ).string(), true ) );
|
||||
ASSERT_EQ( EXIT_SUCCESS, run( { "mv", "bin/assets", "bin/unit_tests/assets" } ) );
|
||||
|
||||
auto status = git.status( false, temp.path.string() );
|
||||
size_t staged = 0;
|
||||
size_t untracked = 0;
|
||||
for ( const auto& [_, files] : status.files ) {
|
||||
for ( const auto& file : files ) {
|
||||
if ( file.report.type == Git::GitStatusType::Staged ) {
|
||||
++staged;
|
||||
EXPECT_STREQ( "bin/unit_tests/assets/fontrendering/image.webp", file.file.c_str() );
|
||||
} else if ( file.report.type == Git::GitStatusType::Untracked ) {
|
||||
++untracked;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ( 1u, staged );
|
||||
EXPECT_EQ( 0u, untracked );
|
||||
}
|
||||
|
||||
@@ -148,6 +148,60 @@ UTEST( TextDocument, insertLargeMultilineBlock ) {
|
||||
EXPECT_STRINGEQ( "tail\n", doc.line( insertedLineCount + 2 ).getText() );
|
||||
}
|
||||
|
||||
UTEST( TextDocument, multilineInsertCursorEndsBeforeExistingSuffix ) {
|
||||
TextDocument doc;
|
||||
doc.insert( 0, { 0, 0 }, "prefix-suffix" );
|
||||
|
||||
TextPosition cursor =
|
||||
doc.insert( 0, { 0, 7 }, String::fromUtf8( std::string_view{ "alpha\nβeta\n" } ) );
|
||||
|
||||
EXPECT_EQ( 2, cursor.line() );
|
||||
EXPECT_EQ( 0, cursor.column() );
|
||||
EXPECT_STRINGEQ( "prefix-alpha\nβeta\nsuffix", doc.getText() );
|
||||
|
||||
cursor = doc.insert( 0, cursor, String::fromUtf8( std::string_view{ "γ\nδ" } ) );
|
||||
EXPECT_EQ( 3, cursor.line() );
|
||||
EXPECT_EQ( 1, cursor.column() );
|
||||
EXPECT_STRINGEQ( "prefix-alpha\nβeta\nγ\nδsuffix", doc.getText() );
|
||||
|
||||
TextDocument graphemeDoc;
|
||||
graphemeDoc.insert( 0, { 0, 0 },
|
||||
String::fromUtf8( std::string_view{ "prefix-\u0301suffix" } ) );
|
||||
cursor = graphemeDoc.insert( 0, { 0, 7 }, "line\nA" );
|
||||
EXPECT_EQ( 1, cursor.line() );
|
||||
EXPECT_EQ( 2, cursor.column() );
|
||||
EXPECT_STRINGEQ( "prefix-line\nA\u0301suffix", graphemeDoc.getText() );
|
||||
}
|
||||
|
||||
UTEST( TextDocument, lineHashRemainsStableAndTracksMutations ) {
|
||||
TextDocumentLine emptyLine( "", nullptr );
|
||||
EXPECT_EQ( String( "" ).getHash(), emptyLine.getHash() );
|
||||
|
||||
TextDocument doc;
|
||||
doc.insert( 0, { 0, 0 }, String::fromUtf8( std::string_view{ "alpha\nβeta" } ) );
|
||||
|
||||
String firstLine( "alpha\n" );
|
||||
String secondLine( String::fromUtf8( std::string_view{ "βeta\n" } ) );
|
||||
EXPECT_EQ( firstLine.getHash(), doc.getLineHash( 0 ) );
|
||||
EXPECT_EQ( secondLine.getHash(), doc.getLineHash( 1 ) );
|
||||
EXPECT_EQ( secondLine.getHash(), doc.getLineHash( 1 ) );
|
||||
|
||||
doc.insert( 0, { 1, 1 }, "!" );
|
||||
secondLine.insert( 1, "!" );
|
||||
EXPECT_EQ( secondLine.getHash(), doc.getLineHash( 1 ) );
|
||||
|
||||
auto lines = doc.getLines();
|
||||
ASSERT_EQ( size_t{ 2 }, lines.size() );
|
||||
EXPECT_EQ( doc.getLineHash( 0 ), lines[0].getHash() );
|
||||
EXPECT_EQ( doc.getLineHash( 1 ), lines[1].getHash() );
|
||||
TextDocumentLine assigned( "", nullptr );
|
||||
assigned = lines[0];
|
||||
EXPECT_EQ( firstLine.getHash(), assigned.getHash() );
|
||||
|
||||
TextDocumentLine moved( std::move( lines[1] ) );
|
||||
EXPECT_EQ( secondLine.getHash(), moved.getHash() );
|
||||
}
|
||||
|
||||
UTEST( TextDocument, insertEmptyTextDoesNothing ) {
|
||||
TextDocument doc;
|
||||
doc.insert( 0, { 0, 0 }, "content" );
|
||||
|
||||
@@ -16,6 +16,15 @@ using namespace EE::UI::Doc;
|
||||
using namespace EE::Scene;
|
||||
using namespace EE::System;
|
||||
|
||||
class TestableCodeEditor : public UICodeEditor {
|
||||
public:
|
||||
TestableCodeEditor() : UICodeEditor() {}
|
||||
|
||||
bool isLongestLineWidthDirtyForTest() const { return mLongestLineWidthDirty; }
|
||||
|
||||
void clearLongestLineWidthDirtyForTest() { mLongestLineWidthDirty = false; }
|
||||
};
|
||||
|
||||
UTEST( MainThreadLifetime, InvalidatedCallbacksDoNotRun ) {
|
||||
UIApplication app( WindowSettings{ 320, 240, "eepp - main thread lifetime test" } );
|
||||
int owner = 42;
|
||||
@@ -53,6 +62,67 @@ UTEST( MainThreadLifetime, DispatcherCanBeAttachedAfterConstruction ) {
|
||||
EXPECT_TRUE( called );
|
||||
}
|
||||
|
||||
UTEST( UICodeEditor, DefersLongestLineMeasurementForLargeChanges ) {
|
||||
UIApplication app( WindowSettings{ 320, 240, "eepp - deferred longest line test" } );
|
||||
auto* editor = eeNew( TestableCodeEditor, () );
|
||||
editor->setPixelsSize( 160, 80 );
|
||||
editor->setParent( app.getUI()->getRoot() );
|
||||
editor->setFindLongestLineWidthUpdateFrequency( Time::Zero );
|
||||
app.getUI()->flushDirtyStyleAndLayout();
|
||||
editor->setLineWrapMode( LineWrapMode::NoWrap );
|
||||
EXPECT_EQ( LineWrapMode::NoWrap, editor->getLineWrapMode() );
|
||||
editor->clearLongestLineWidthDirtyForTest();
|
||||
|
||||
String text;
|
||||
for ( size_t i = 0; i < 64; ++i )
|
||||
text += i == 32 ? String( 256, 'x' ) + "\n" : "short\n";
|
||||
editor->getDocument().textInput( text );
|
||||
|
||||
EXPECT_TRUE( editor->isLongestLineWidthDirtyForTest() );
|
||||
|
||||
editor->clearLongestLineWidthDirtyForTest();
|
||||
editor->getDocument().insert( 0, { 0, 0 }, "!" );
|
||||
EXPECT_FALSE( editor->isLongestLineWidthDirtyForTest() );
|
||||
|
||||
editor->getDocument().reset();
|
||||
editor->clearLongestLineWidthDirtyForTest();
|
||||
editor->getDocument().textInput( String( 4097, 'x' ) );
|
||||
EXPECT_TRUE( editor->isLongestLineWidthDirtyForTest() );
|
||||
|
||||
editor->clearLongestLineWidthDirtyForTest();
|
||||
editor->getDocument().insert( 0, { 0, 0 }, "!" );
|
||||
EXPECT_TRUE( editor->isLongestLineWidthDirtyForTest() );
|
||||
|
||||
eeDelete( editor );
|
||||
}
|
||||
|
||||
UTEST( UICodeEditor, DefaultKeybindingCacheTracksConfiguredModifiers ) {
|
||||
const Uint32 originalDefaultModifier = KeyMod::getDefaultModifier();
|
||||
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() )
|
||||
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() )
|
||||
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() )
|
||||
EXPECT_STREQ( "copy", copy->second.c_str() );
|
||||
}
|
||||
|
||||
static const std::string userCode = R"objcpp(#import "common.h"
|
||||
#import <cmath>
|
||||
#import <gdiplus.h>
|
||||
|
||||
@@ -16,11 +16,13 @@ using namespace EE::UI::Tools;
|
||||
UTEST( UIDiffView, LoadFromStringsAndVerifyDiffLines ) {
|
||||
UIApplication app( WindowSettings{ 800, 600, "eepp - unit tests" } );
|
||||
UIDiffView* diffView = UIDiffView::New();
|
||||
EXPECT_TRUE( diffView->findAllByType<UIImageViewer>( UI_TYPE_IMAGE_VIEWER ).empty() );
|
||||
|
||||
std::string oldText = "line 1\nline 2\nline 3\nline 4";
|
||||
std::string newText = "line 1\nline 2 changed\nline 3\nline 4 added\nline 5";
|
||||
|
||||
diffView->loadFromStrings( oldText, newText );
|
||||
EXPECT_TRUE( diffView->findAllByType<UIImageViewer>( UI_TYPE_IMAGE_VIEWER ).empty() );
|
||||
|
||||
const auto& lines = diffView->getDiffLines();
|
||||
|
||||
@@ -158,6 +160,7 @@ UTEST( UIDiffView, MultiFileViewerHandlesLargePatches ) {
|
||||
|
||||
auto* viewer = UIDiffView::NewMultiFileDiffViewer( patchText );
|
||||
EXPECT_EQ( fileCount, viewer->findAllByType<UIDiffView>( UI_TYPE_DIFF_VIEW ).size() );
|
||||
EXPECT_TRUE( viewer->findAllByType<UIImageViewer>( UI_TYPE_IMAGE_VIEWER ).empty() );
|
||||
EXPECT_FALSE( viewer->getUISceneNode()->isLoading() );
|
||||
|
||||
eeDelete( viewer );
|
||||
@@ -180,6 +183,7 @@ UTEST( UIDiffView, LoadFromFileImageDiffUsesImageViewers ) {
|
||||
EXPECT_TRUE( diffView->isImageDiff() );
|
||||
EXPECT_TRUE( diffView->getLeftImageViewer()->isVisible() );
|
||||
EXPECT_TRUE( diffView->getRightImageViewer()->isVisible() );
|
||||
EXPECT_EQ( size_t{ 2 }, diffView->findAllByType<UIImageViewer>( UI_TYPE_IMAGE_VIEWER ).size() );
|
||||
EXPECT_FALSE( diffView->getEditor()->isVisible() );
|
||||
EXPECT_FALSE( diffView->getLeftEditor()->isVisible() );
|
||||
EXPECT_FALSE( diffView->getRightEditor()->isVisible() );
|
||||
|
||||
@@ -29,6 +29,24 @@ UTEST( UISceneNode, CssPointerCursorUsesHandCursor ) {
|
||||
EXPECT_STREQ( Cursor::toName( Cursor::Arrow ), "arrow" );
|
||||
}
|
||||
|
||||
UTEST( Node, DescendantWorldBoundsRefreshAfterDirtyAncestorMoves ) {
|
||||
Node* parent = Node::New();
|
||||
Node* child = Node::New();
|
||||
Node* descendant = Node::New();
|
||||
child->setParent( parent );
|
||||
descendant->setParent( child );
|
||||
child->setPosition( 10.f, 0.f );
|
||||
descendant->setPosition( 5.f, 0.f );
|
||||
descendant->setSize( 10.f, 10.f );
|
||||
|
||||
EXPECT_EQ( 15.f, descendant->getWorldBounds().Left );
|
||||
child->setPosition( 20.f, 0.f );
|
||||
EXPECT_EQ( 25.f, descendant->getScreenRect().Left );
|
||||
EXPECT_EQ( 25.f, descendant->getWorldBounds().Left );
|
||||
|
||||
eeDelete( parent );
|
||||
}
|
||||
|
||||
static void init_test_scene_node( UISceneNode* sceneNode ) {
|
||||
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
|
||||
FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ).get();
|
||||
|
||||
@@ -1424,11 +1424,12 @@ Git::Status Git::status( bool recurseSubmodules, const std::string& projectDir )
|
||||
}
|
||||
|
||||
if ( isBinary || ( inserts || deletes ) ) {
|
||||
std::string rptrn( "(.*)%{.*%s->%s(.*)%}" );
|
||||
std::string rptrn( "(.*)%{.*%s->%s(.*)%}(.*)" );
|
||||
LuaPattern pattern( rptrn );
|
||||
if ( pattern.matches( file.data(), 0, matches, file.size() ) ) {
|
||||
file = file.substr( matches[1].start, matches[1].end - matches[1].start ) +
|
||||
file.substr( matches[2].start, matches[2].end - matches[2].start );
|
||||
file.substr( matches[2].start, matches[2].end - matches[2].start ) +
|
||||
file.substr( matches[3].start, matches[3].end - matches[3].start );
|
||||
}
|
||||
|
||||
auto filePath = subModulePath + file;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <eepp/graphics/image.hpp>
|
||||
#include <eepp/graphics/primitives.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/system/base64.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/system/luapattern.hpp>
|
||||
#include <eepp/system/scopedop.hpp>
|
||||
@@ -255,6 +256,12 @@ GitPlugin::~GitPlugin() {
|
||||
Sys::sleep( Milliseconds( 1.f ) );
|
||||
}
|
||||
|
||||
void GitPlugin::onSaveState( IniFile* state ) {
|
||||
std::string commitMessage;
|
||||
Base64::encode( mLastCommitMsg.toUtf8(), commitMessage );
|
||||
state->setValue( "git", "commit_message", commitMessage );
|
||||
}
|
||||
|
||||
void GitPlugin::runAsyncTask( std::function<void()> task ) {
|
||||
auto runningTasks = mRunningAsyncTasks;
|
||||
++*runningTasks;
|
||||
@@ -348,6 +355,12 @@ void GitPlugin::load( PluginManager* pluginManager ) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string commitMessage;
|
||||
Base64::decode(
|
||||
getPluginContext()->getConfig().iniState.getValue( "git", "commit_message", "" ),
|
||||
commitMessage );
|
||||
mLastCommitMsg = String::fromUtf8( commitMessage );
|
||||
|
||||
if ( mKeyBindings.empty() ) {
|
||||
mKeyBindings["git-blame"] = "alt+shift+b";
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ class GitPlugin : public PluginBase {
|
||||
|
||||
void registerSettings( SettingsPage& page ) override;
|
||||
|
||||
void onSaveState( IniFile* state ) override;
|
||||
|
||||
void onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) override;
|
||||
|
||||
FileSystemListenerOptions getFileSystemListenerOptions() const override;
|
||||
|
||||
Reference in New Issue
Block a user