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:
Martín Lucas Golini
2026-08-30 01:05:24 -03:00
parent caba2d4462
commit 3afcb3f2d2
17 changed files with 439 additions and 89 deletions
@@ -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>