mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-05-28 17:16:29 +03:00
Fixes and sanity checks for issue SpartanJ/ecode#836. Added several tests that triggered the crashes.
This commit is contained in:
@@ -33,7 +33,6 @@
|
||||
} \
|
||||
\
|
||||
T* T::existsSingleton() { \
|
||||
Lock l( ms_mutex ); \
|
||||
return ms_singleton; \
|
||||
} \
|
||||
\
|
||||
@@ -62,7 +61,6 @@ template <typename T> class Singleton {
|
||||
public:
|
||||
/** Get the singleton pointer (without instance verification) */
|
||||
static T* existsSingleton() {
|
||||
Lock l( ms_mutex );
|
||||
return ms_singleton;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,10 @@ class EE_API DocumentView {
|
||||
|
||||
bool usesTabStops() const { return mConfig.tabStops; }
|
||||
|
||||
const std::vector<Int64> getDocLineToVisibleIndex() const { return mDocLineToVisibleIndex; }
|
||||
|
||||
const std::vector<Float> getVisibleLinesOffset() const { return mVisibleLinesOffset; }
|
||||
|
||||
protected:
|
||||
std::shared_ptr<TextDocument> mDoc;
|
||||
FontStyleConfig mFontStyle;
|
||||
|
||||
@@ -345,9 +345,16 @@ Float DocumentView::getWhiteSpaceWidth() const {
|
||||
}
|
||||
|
||||
void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) {
|
||||
if ( 0 == mMaxWidth || isOneToOne() )
|
||||
if ( 0 == mMaxWidth || isOneToOne() || !mDoc )
|
||||
return;
|
||||
|
||||
// Safety check: ensure fromLine and toLine are within bounds of the old state
|
||||
if ( fromLine < 0 || fromLine >= (Int64)mVisibleLinesOffset.size() ||
|
||||
toLine < 0 || toLine >= (Int64)mVisibleLinesOffset.size() || fromLine > toLine ) {
|
||||
invalidateCache();
|
||||
return;
|
||||
}
|
||||
|
||||
// Unfold ANY modification over a folded range
|
||||
if ( numLines < 0 ) {
|
||||
auto foldedRegions = intersectsFoldedRegions( { { fromLine, 0 }, { toLine, 0 } } );
|
||||
@@ -362,6 +369,15 @@ void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) {
|
||||
Int64 oldIdxFrom = static_cast<Int64>( toVisibleIndex( fromLine, false ) );
|
||||
Int64 oldIdxTo = static_cast<Int64>( toVisibleIndex( toLine, true ) );
|
||||
|
||||
if ( oldIdxFrom == static_cast<Int64>( VisibleIndex::invalid ) ||
|
||||
oldIdxTo == static_cast<Int64>( VisibleIndex::invalid ) ||
|
||||
oldIdxFrom > oldIdxTo ||
|
||||
oldIdxFrom >= (Int64)mVisibleLines.size() ||
|
||||
oldIdxTo >= (Int64)mVisibleLines.size() ) {
|
||||
invalidateCache();
|
||||
return;
|
||||
}
|
||||
|
||||
auto visibleLinesCount = mVisibleLines.size();
|
||||
|
||||
// Remove old visible lines
|
||||
@@ -383,6 +399,10 @@ void DocumentView::updateCache( Int64 fromLine, Int64 toLine, Int64 numLines ) {
|
||||
// Recompute line breaks
|
||||
auto netLines = toLine + numLines;
|
||||
auto idxOffset = oldIdxFrom;
|
||||
|
||||
if ( netLines >= (Int64)mDocLineToVisibleIndex.size() )
|
||||
mDocLineToVisibleIndex.resize( netLines + 1, static_cast<Int64>( VisibleIndex::invalid ) );
|
||||
|
||||
for ( auto i = fromLine; i <= netLines; i++ ) {
|
||||
if ( isFolded( i ) ) {
|
||||
mVisibleLinesOffset.insert(
|
||||
@@ -423,18 +443,25 @@ void DocumentView::recomputeDocLineToVisibleIndex( Int64 fromVisibleIndex, bool
|
||||
Int64 visibleLinesCount = mVisibleLines.size();
|
||||
if ( ensureDocSize )
|
||||
mDocLineToVisibleIndex.resize( mDoc->linesCount() );
|
||||
|
||||
if ( fromVisibleIndex < 0 || fromVisibleIndex >= visibleLinesCount )
|
||||
return;
|
||||
|
||||
Int64 previousLineIdx = mVisibleLines[fromVisibleIndex].line();
|
||||
for ( Int64 visibleIdx = fromVisibleIndex; visibleIdx < visibleLinesCount; visibleIdx++ ) {
|
||||
const auto& visibleLine = mVisibleLines[visibleIdx];
|
||||
if ( visibleLine.column() == 0 ) {
|
||||
// Non-contiguous lines means hidden lines
|
||||
if ( visibleLine.line() - previousLineIdx > 1 ) {
|
||||
for ( Int64 i = previousLineIdx + 1; i < visibleLine.line(); i++ )
|
||||
mDocLineToVisibleIndex[i] = static_cast<Int64>( VisibleIndex::invalid );
|
||||
for ( Int64 i = previousLineIdx + 1; i < visibleLine.line(); i++ ) {
|
||||
if ( i < (Int64)mDocLineToVisibleIndex.size() )
|
||||
mDocLineToVisibleIndex[i] = static_cast<Int64>( VisibleIndex::invalid );
|
||||
}
|
||||
}
|
||||
mDocLineToVisibleIndex[visibleLine.line()] =
|
||||
isFolded( visibleLine.line(), true ) ? static_cast<Int64>( VisibleIndex::invalid )
|
||||
: visibleIdx;
|
||||
if ( visibleLine.line() < (Int64)mDocLineToVisibleIndex.size() )
|
||||
mDocLineToVisibleIndex[visibleLine.line()] =
|
||||
isFolded( visibleLine.line(), true ) ? static_cast<Int64>( VisibleIndex::invalid )
|
||||
: visibleIdx;
|
||||
previousLineIdx = visibleLine.line();
|
||||
}
|
||||
}
|
||||
@@ -448,7 +475,8 @@ void DocumentView::foldRegion( Int64 foldDocIdx ) {
|
||||
auto foldRegion = mDoc->getFoldRangeService().find( foldDocIdx );
|
||||
if ( !foldRegion )
|
||||
return;
|
||||
if ( isOneToOne() && mDocLineToVisibleIndex.empty() )
|
||||
if ( isOneToOne() || mDocLineToVisibleIndex.empty() || mVisibleLines.empty() ||
|
||||
mVisibleLinesOffset.empty() )
|
||||
invalidateCache();
|
||||
Int64 toDocIdx = foldRegion->end().line();
|
||||
changeVisibility( foldDocIdx + 1, toDocIdx, false );
|
||||
@@ -467,6 +495,8 @@ void DocumentView::unfoldRegion( Int64 foldDocIdx, bool verifyConsistency, bool
|
||||
auto foldRegion = mDoc->getFoldRangeService().find( foldDocIdx );
|
||||
if ( !foldRegion )
|
||||
return;
|
||||
if ( mDocLineToVisibleIndex.empty() || mVisibleLines.empty() || mVisibleLinesOffset.empty() )
|
||||
invalidateCache();
|
||||
Int64 toDocIdx = foldRegion->end().line();
|
||||
removeFoldedRegion( *foldRegion );
|
||||
changeVisibility( foldDocIdx + 1, toDocIdx, true, recomputeOffset,
|
||||
@@ -559,7 +589,7 @@ void DocumentView::changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visi
|
||||
auto idxOffset = oldIdxFrom;
|
||||
for ( auto i = fromDocIdx; i <= toDocIdx; i++ ) {
|
||||
if ( isFolded( i, true ) ) {
|
||||
if ( recomputeOffset ) {
|
||||
if ( recomputeOffset && i < (Int64)mVisibleLinesOffset.size() ) {
|
||||
mVisibleLinesOffset[i] = LineWrap::computeOffsets(
|
||||
mDoc->line( i ).getText().view(), mFontStyle, mConfig.tabWidth,
|
||||
eemax( mMaxWidth - mWhiteSpaceWidth, mWhiteSpaceWidth ) );
|
||||
@@ -571,11 +601,13 @@ void DocumentView::changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visi
|
||||
mConfig.keepIndentation, mConfig.tabWidth,
|
||||
mWhiteSpaceWidth, mConfig.tabStops )
|
||||
: LineWrapInfo{ { 0 }, 0 };
|
||||
if ( recomputeOffset )
|
||||
if ( recomputeOffset && i < (Int64)mVisibleLinesOffset.size() )
|
||||
mVisibleLinesOffset[i] = lb.paddingStart;
|
||||
for ( const auto& col : lb.wraps ) {
|
||||
mVisibleLines.insert( mVisibleLines.begin() + idxOffset, { i, col } );
|
||||
idxOffset++;
|
||||
if ( idxOffset >= 0 && idxOffset <= (Int64)mVisibleLines.size() ) {
|
||||
mVisibleLines.insert( mVisibleLines.begin() + idxOffset, { i, col } );
|
||||
idxOffset++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,17 +617,22 @@ void DocumentView::changeVisibility( Int64 fromDocIdx, Int64 toDocIdx, bool visi
|
||||
auto oldIdxToVI = toVisibleIndex( toDocIdx, true );
|
||||
Int64 oldIdxFrom = static_cast<Int64>( oldIdxFromVI );
|
||||
Int64 oldIdxTo = static_cast<Int64>( oldIdxToVI );
|
||||
if ( VisibleIndex::invalid == oldIdxFromVI || VisibleIndex::invalid == oldIdxToVI )
|
||||
if ( VisibleIndex::invalid == oldIdxFromVI || VisibleIndex::invalid == oldIdxToVI ||
|
||||
oldIdxFrom < 0 || oldIdxTo < 0 ||
|
||||
oldIdxFrom > oldIdxTo || oldIdxFrom >= (Int64)mVisibleLines.size() ||
|
||||
oldIdxTo >= (Int64)mVisibleLines.size() )
|
||||
return;
|
||||
mVisibleLines.erase( mVisibleLines.begin() + oldIdxFrom,
|
||||
mVisibleLines.begin() + oldIdxTo + 1 );
|
||||
for ( Int64 idx = fromDocIdx; idx <= toDocIdx; idx++ ) {
|
||||
mDocLineToVisibleIndex[idx] = static_cast<Int64>( VisibleIndex::invalid );
|
||||
if ( idx < (Int64)mDocLineToVisibleIndex.size() )
|
||||
mDocLineToVisibleIndex[idx] = static_cast<Int64>( VisibleIndex::invalid );
|
||||
}
|
||||
Int64 linesCount = mDoc->linesCount();
|
||||
Int64 idxOffset = oldIdxTo - oldIdxFrom + 1;
|
||||
for ( Int64 idx = toDocIdx + 1; idx < linesCount; idx++ ) {
|
||||
if ( mDocLineToVisibleIndex[idx] != static_cast<Int64>( VisibleIndex::invalid ) )
|
||||
if ( idx < (Int64)mDocLineToVisibleIndex.size() &&
|
||||
mDocLineToVisibleIndex[idx] != static_cast<Int64>( VisibleIndex::invalid ) )
|
||||
mDocLineToVisibleIndex[idx] -= idxOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,18 +279,54 @@ bool FoldRangeService::isFoldingRegionInLine( Int64 docIdx ) {
|
||||
}
|
||||
|
||||
void FoldRangeService::shiftFoldingRegions( Int64 fromLine, Int64 numLines ) {
|
||||
// TODO: Optimize this
|
||||
Lock l( mMutex );
|
||||
FoldingRegions foldingRegions;
|
||||
for ( auto& foldingRegion : mFoldingRegions ) {
|
||||
if ( foldingRegion.second.start().line() > fromLine ) {
|
||||
foldingRegion.second.start().setLine( foldingRegion.second.start().line() + numLines );
|
||||
foldingRegion.second.end().setLine( foldingRegion.second.end().line() + numLines );
|
||||
foldingRegions[foldingRegion.second.start().line()] = foldingRegion.second;
|
||||
|
||||
if ( numLines < 0 ) {
|
||||
Int64 removedLines = -numLines;
|
||||
Int64 toLine = fromLine + removedLines;
|
||||
|
||||
for ( auto& foldingRegion : mFoldingRegions ) {
|
||||
auto& range = foldingRegion.second;
|
||||
|
||||
if ( range.start().line() >= toLine ) {
|
||||
range.start().setLine( range.start().line() + numLines );
|
||||
range.end().setLine( range.end().line() + numLines );
|
||||
foldingRegions[range.start().line()] = range;
|
||||
} else if ( range.start().line() <= fromLine ) {
|
||||
if ( range.end().line() >= toLine ) {
|
||||
range.end().setLine( range.end().line() + numLines );
|
||||
foldingRegions[range.start().line()] = range;
|
||||
} else if ( range.end().line() > fromLine ) {
|
||||
range.end().setLine( fromLine );
|
||||
if ( range.start().line() < range.end().line() )
|
||||
foldingRegions[range.start().line()] = range;
|
||||
} else {
|
||||
foldingRegions[range.start().line()] = range;
|
||||
}
|
||||
} else {
|
||||
if ( range.end().line() >= toLine ) {
|
||||
range.start().setLine( fromLine );
|
||||
range.end().setLine( range.end().line() + numLines );
|
||||
if ( range.start().line() < range.end().line() )
|
||||
foldingRegions[range.start().line()] = range;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ( auto& foldingRegion : mFoldingRegions ) {
|
||||
auto& range = foldingRegion.second;
|
||||
if ( range.start().line() >= fromLine ) {
|
||||
range.start().setLine( range.start().line() + numLines );
|
||||
range.end().setLine( range.end().line() + numLines );
|
||||
} else if ( range.end().line() >= fromLine ) {
|
||||
range.end().setLine( range.end().line() + numLines );
|
||||
}
|
||||
foldingRegions[range.start().line()] = range;
|
||||
}
|
||||
foldingRegions[foldingRegion.second.start().line()] = foldingRegion.second;
|
||||
}
|
||||
mFoldingRegions = foldingRegions;
|
||||
|
||||
mFoldingRegions = std::move( foldingRegions );
|
||||
}
|
||||
|
||||
void FoldRangeService::setFoldingRegions( std::vector<TextRange> regions ) {
|
||||
|
||||
@@ -232,6 +232,7 @@ SyntaxDefinition& addObjectiveC() {
|
||||
|
||||
sd.setFoldRangeType( FoldRangeType::Braces ).setFoldBraces( { { '{', '}' } } );
|
||||
sd.setBlockComment( { "/*", "*/" } );
|
||||
sd.addAlternativeName( "objc" );
|
||||
return sd;
|
||||
}
|
||||
|
||||
|
||||
@@ -235,6 +235,8 @@ SyntaxDefinition& addObjectiveCPP() {
|
||||
|
||||
sd.setFoldRangeType( FoldRangeType::Braces ).setFoldBraces( { { '{', '}' } } );
|
||||
sd.setBlockComment( { "/*", "*/" } );
|
||||
sd.addAlternativeName( "objective-cpp" );
|
||||
sd.addAlternativeName( "objcpp" );
|
||||
return sd;
|
||||
}
|
||||
|
||||
|
||||
@@ -544,17 +544,18 @@ static void preDefinitionLangsChunk2( SyntaxDefinitionManager* sdm ) {
|
||||
sdm->addPreDefinition(
|
||||
{ "Objeck", []() -> SyntaxDefinition& { return addObjeck(); }, { "%.obs$" } } );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"Objective-C",
|
||||
[]() -> SyntaxDefinition& { return addObjectiveC(); },
|
||||
{ "%.m$" },
|
||||
} );
|
||||
sdm->addPreDefinition( { "Objective-C",
|
||||
[]() -> SyntaxDefinition& { return addObjectiveC(); },
|
||||
{ "%.m$" },
|
||||
{},
|
||||
{ "objc" } } );
|
||||
|
||||
sdm->addPreDefinition( { "Objective-C++",
|
||||
[]() -> SyntaxDefinition& { return addObjectiveCPP(); },
|
||||
{ "%.mm$" },
|
||||
{},
|
||||
"objective-cpp" } );
|
||||
"objective-cpp",
|
||||
{ "objc++", "objcpp" } } );
|
||||
|
||||
sdm->addPreDefinition( {
|
||||
"OCaml",
|
||||
|
||||
311
src/tests/unit_tests/uicodeeditor_test.cpp
Normal file
311
src/tests/unit_tests/uicodeeditor_test.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "utest.h"
|
||||
#include <eepp/scene/node.hpp>
|
||||
#include <eepp/scene/scenemanager.hpp>
|
||||
#include <eepp/system/filesystem.hpp>
|
||||
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
|
||||
#include <eepp/ui/uiapplication.hpp>
|
||||
#include <eepp/ui/uicodeeditor.hpp>
|
||||
|
||||
using namespace EE;
|
||||
using namespace EE::UI;
|
||||
using namespace EE::UI::Doc;
|
||||
using namespace EE::Scene;
|
||||
|
||||
static const std::string userCode = R"objcpp(#import "common.h"
|
||||
#import <cmath>
|
||||
#import <gdiplus.h>
|
||||
#import <iostream>
|
||||
#import <vector>
|
||||
#import <windows.h>
|
||||
|
||||
@interface test () {
|
||||
struct DrawSineWave {
|
||||
struct AppState {
|
||||
int width = 800;
|
||||
int height = 600;
|
||||
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
|
||||
ULONG_PTR gdiplusToken;
|
||||
};
|
||||
static void Draw(HDC hdc, int width, int height) {
|
||||
|
||||
if (width <= 0 || height <= 0)
|
||||
return;
|
||||
|
||||
using namespace Gdiplus;
|
||||
|
||||
Graphics graphics(hdc);
|
||||
graphics.SetSmoothingMode(SmoothingModeAntiAlias);
|
||||
|
||||
// Background
|
||||
SolidBrush bgBrush(Color(255, 255, 255, 255));
|
||||
graphics.FillRectangle(&bgBrush, 0, 0, width, height);
|
||||
|
||||
// Axis
|
||||
Pen axisPen(Color(200, 200, 200), 1.0f);
|
||||
graphics.DrawLine(&axisPen, 0, height / 2, width, height / 2);
|
||||
|
||||
// Sine wave parameters
|
||||
double amplitude = (height - 20) / 2.0;
|
||||
double midY = height / 2.0;
|
||||
double period = width;
|
||||
double twoPi = 6.283185307179586;
|
||||
|
||||
// Build points
|
||||
std::vector<PointF> pts;
|
||||
double step = 0.25; // smaller step for smoother curve
|
||||
for (double x = 0; x < width; x += step) {
|
||||
double t = x / period;
|
||||
double y = midY - amplitude * std::sin(twoPi * t);
|
||||
pts.push_back(PointF(static_cast<REAL>(x), static_cast<REAL>(y)));
|
||||
}
|
||||
|
||||
// Draw sine wave
|
||||
Pen sinePen(Color(0, 120, 215), 2.0f);
|
||||
if (!pts.empty()) {
|
||||
graphics.DrawLines(&sinePen, pts.data(), static_cast<INT>(pts.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
OF_APPLICATION_DELEGATE(test)
|
||||
|
||||
@implementation test
|
||||
- (void)applicationDidFinishLaunching:(OFNotification *)notification {
|
||||
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
||||
DrawSineWave::Setup(hInstance, SW_SHOW);
|
||||
[OFApplication terminate];
|
||||
}
|
||||
@end
|
||||
)objcpp";
|
||||
|
||||
#define VERIFY_CONSISTENCY( editor ) \
|
||||
{ \
|
||||
const DocumentView& view = editor->getDocumentView(); \
|
||||
TextDocument& doc = editor->getDocument(); \
|
||||
if ( !view.isOneToOne() ) { \
|
||||
EXPECT_EQ( (size_t)doc.linesCount(), view.getDocLineToVisibleIndex().size() ); \
|
||||
EXPECT_EQ( (size_t)doc.linesCount(), view.getVisibleLinesOffset().size() ); \
|
||||
size_t expectedVisibleCount = 0; \
|
||||
for ( Int64 i = 0; i < (Int64)doc.linesCount(); i++ ) { \
|
||||
if ( view.isLineVisible( i ) ) { \
|
||||
EXPECT_NE( (Int64)VisibleIndex::invalid, (Int64)view.toVisibleIndex( i ) ); \
|
||||
Int64 startIdx = (Int64)view.toVisibleIndex( i ); \
|
||||
Int64 endIdx = (Int64)view.toVisibleIndex( i, true ); \
|
||||
expectedVisibleCount += ( endIdx - startIdx + 1 ); \
|
||||
} else { \
|
||||
EXPECT_EQ( (Int64)VisibleIndex::invalid, (Int64)view.toVisibleIndex( i ) ); \
|
||||
} \
|
||||
} \
|
||||
EXPECT_EQ( expectedVisibleCount, view.getVisibleLinesCount() ); \
|
||||
} \
|
||||
}
|
||||
|
||||
UTEST( UICodeEditor, DocumentViewStressTest ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - Stress Test", WindowStyle::Default,
|
||||
WindowBackend::Default, 32 ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ) );
|
||||
|
||||
auto* editor = UICodeEditor::New();
|
||||
editor->setParent( (Node*)app.getUI() );
|
||||
editor->setPixelsSize( 800, 600 );
|
||||
editor->getDocument().setSyntaxDefinition(
|
||||
SyntaxDefinitionManager::instance()->getByLanguageName( "C++" ) );
|
||||
|
||||
auto resetEditor = [&]() {
|
||||
editor->getDocument().selectAll();
|
||||
editor->getDocument().deleteSelection();
|
||||
editor->getDocument().textInput( userCode );
|
||||
editor->getDocument().getFoldRangeService().findRegionsNative();
|
||||
editor->unfoldAll();
|
||||
};
|
||||
|
||||
// --- SCENARIO 1: Folding Only (No Wrap) ---
|
||||
editor->setLineWrapMode( LineWrapMode::NoWrap );
|
||||
resetEditor();
|
||||
|
||||
// Fold everything
|
||||
editor->foldAll();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
|
||||
// Delete while folded (should unfold affected)
|
||||
editor->getDocument().setSelection( { { 2, 0 }, { 10, 0 } } );
|
||||
editor->getDocument().deleteSelection();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
editor->getDocument().undo();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
|
||||
// Insert text in the middle of a folded region
|
||||
editor->foldAll();
|
||||
editor->getDocument().setSelection( { { 5, 5 }, { 5, 5 } } );
|
||||
editor->getDocument().textInput( "STRESS_TEST" ); // Should unfold line 5
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
|
||||
// --- SCENARIO 2: Wrapping Only (No Folds) ---
|
||||
resetEditor();
|
||||
editor->setLineWrapMode( LineWrapMode::Letter );
|
||||
editor->setPixelsSize( 100, 600 ); // Force lots of wraps
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
|
||||
// Multi-line delete with wraps
|
||||
editor->getDocument().setSelection( { { 1, 5 }, { 4, 2 } } );
|
||||
editor->getDocument().deleteSelection();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
editor->getDocument().undo();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
|
||||
// --- SCENARIO 3: Folding + Wrapping ---
|
||||
resetEditor();
|
||||
editor->setLineWrapMode( LineWrapMode::Letter );
|
||||
editor->setPixelsSize( 100, 600 );
|
||||
editor->foldAll();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
|
||||
// Delete range straddling multiple folded regions with wraps
|
||||
// userCode has folds starting at lines: 1, 2, 3, 7
|
||||
editor->getDocument().setSelection( { { 0, 0 }, { 12, 0 } } );
|
||||
editor->getDocument().deleteSelection();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
editor->getDocument().undo();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
|
||||
// Random heavy operations
|
||||
resetEditor();
|
||||
editor->setLineWrapMode( LineWrapMode::Word );
|
||||
editor->setPixelsSize( 200, 600 );
|
||||
|
||||
for ( int i = 0; i < 5; i++ ) {
|
||||
editor->foldAll();
|
||||
editor->getDocument().setSelection( { { i * 2, 0 }, { i * 2 + 1, 5 } } );
|
||||
editor->getDocument().textInput( "RANDOM_INSERTION\nMORE_LINES\n" );
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
editor->unfoldAll();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
}
|
||||
|
||||
// Final check: Delete everything
|
||||
editor->getDocument().selectAll();
|
||||
editor->getDocument().deleteSelection();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
editor->getDocument().undo();
|
||||
VERIFY_CONSISTENCY( editor );
|
||||
}
|
||||
|
||||
UTEST( UICodeEditor, FoldingCrashReproduction ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - Reproduce Crash", WindowStyle::Default,
|
||||
WindowBackend::Default, 32 ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ) );
|
||||
|
||||
auto* editor = UICodeEditor::New();
|
||||
editor->setParent( (Node*)app.getUI() );
|
||||
editor->setPixelsSize( 800, 600 );
|
||||
|
||||
editor->getDocument().setSyntaxDefinition(
|
||||
SyntaxDefinitionManager::instance()->getByLanguageName( "C++" ) );
|
||||
editor->getDocument().textInput( userCode );
|
||||
|
||||
// Wait for folding regions to be updated
|
||||
editor->getDocument().getFoldRangeService().findRegionsNative();
|
||||
|
||||
// Try to reproduce the sequence that crashed:
|
||||
// 1. Fold regions
|
||||
editor->foldAll();
|
||||
|
||||
// 2. Select everything and delete
|
||||
editor->getDocument().selectAll();
|
||||
editor->getDocument().deleteSelection();
|
||||
|
||||
// 3. Undo
|
||||
editor->getDocument().undo();
|
||||
|
||||
// 4. Unfold all
|
||||
editor->unfoldAll();
|
||||
|
||||
// Another sequence: select range straddling folded region and delete
|
||||
editor->getDocument().textInput( userCode );
|
||||
editor->getDocument().getFoldRangeService().findRegionsNative();
|
||||
auto regions = editor->getDocument().getFoldRangeService().getFoldingRegions();
|
||||
|
||||
if ( !regions.empty() ) {
|
||||
auto firstRegionLine = regions.begin()->first;
|
||||
auto firstRegionRange = regions.begin()->second;
|
||||
|
||||
editor->fold( firstRegionLine );
|
||||
|
||||
// Select from before the folded region to after
|
||||
TextRange sel( { firstRegionLine, 0 }, { firstRegionRange.end().line() + 1, 0 } );
|
||||
editor->getDocument().setSelection( sel );
|
||||
editor->getDocument().deleteSelection();
|
||||
editor->getDocument().undo();
|
||||
}
|
||||
}
|
||||
|
||||
UTEST( UICodeEditor, ReproduceFoldingCrash ) {
|
||||
UIApplication app(
|
||||
WindowSettings( 800, 600, "eepp - Reproduce Crash", WindowStyle::Default,
|
||||
WindowBackend::Default, 32 ),
|
||||
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash() ) );
|
||||
|
||||
auto* editor = UICodeEditor::New();
|
||||
editor->setParent( (Node*)app.getUI() );
|
||||
editor->setPixelsSize( 800, 600 );
|
||||
|
||||
auto languages = SyntaxDefinitionManager::instance()->getLanguageNames();
|
||||
editor->getDocument().setSyntaxDefinition(
|
||||
SyntaxDefinitionManager::instance()->getByLanguageName( "C++" ) );
|
||||
editor->getDocument().textInput( userCode );
|
||||
|
||||
// Wait for folding regions to be updated
|
||||
editor->getDocument().getFoldRangeService().findRegionsNative();
|
||||
|
||||
auto regions = editor->getDocument().getFoldRangeService().getFoldingRegions();
|
||||
|
||||
// Brute force: Try all combinations of folded regions
|
||||
size_t numRegions = regions.size();
|
||||
if ( numRegions > 8 )
|
||||
numRegions = 8; // Limit for speed
|
||||
|
||||
for ( size_t i = 0; i < ( (size_t)1 << numRegions ); ++i ) {
|
||||
editor->getDocument().resetUndoRedo();
|
||||
editor->getDocument().resetSelection();
|
||||
editor->unfoldAll();
|
||||
|
||||
size_t idx = 0;
|
||||
for ( auto const& [line, range] : regions ) {
|
||||
if ( ( i >> idx ) & 1 ) {
|
||||
editor->fold( line );
|
||||
}
|
||||
if ( ++idx >= numRegions )
|
||||
break;
|
||||
}
|
||||
|
||||
// Try various selections and deletions
|
||||
idx = 0;
|
||||
for ( auto const& [line, range] : regions ) {
|
||||
// Selection from before the folded region to after
|
||||
TextRange sel( { line, 0 }, { range.end().line() + 1, 0 } );
|
||||
editor->getDocument().setSelection( sel );
|
||||
editor->getDocument().deleteSelection();
|
||||
editor->getDocument().undo();
|
||||
|
||||
// Selection starting inside the folded region
|
||||
if ( range.end().line() > line ) {
|
||||
TextRange sel2( { line + 1, 0 }, { range.end().line() + 1, 0 } );
|
||||
editor->getDocument().setSelection( sel2 );
|
||||
editor->getDocument().deleteSelection();
|
||||
editor->getDocument().undo();
|
||||
}
|
||||
|
||||
if ( ++idx >= numRegions )
|
||||
break;
|
||||
}
|
||||
|
||||
// Also try foldAll then select everything and delete
|
||||
editor->foldAll();
|
||||
editor->getDocument().selectAll();
|
||||
editor->getDocument().deleteSelection();
|
||||
editor->getDocument().undo();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user