From 82ec47b5b7313cc00463d88e8302ab4d50f632fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 20 Mar 2024 20:55:48 -0300 Subject: [PATCH] ecode: Added the possibility to search and replace in the current document by replacing captures from Lua Pattern, for example, searching for: "function (%w+)%(%)" and replacing for "fn $1()" will replace the function declaration from "function (functionName)()" to "fn (functionName)()". So now this features is available for local and global search and replace. --- include/eepp/ui/doc/textdocument.hpp | 32 +- include/eepp/ui/tools/uidocfindreplace.hpp | 2 + src/eepp/system/luapattern.cpp | 24 +- src/eepp/ui/doc/textdocument.cpp | 345 ++++++++++++++------- src/eepp/ui/tools/uidocfindreplace.cpp | 29 +- src/tools/ecode/docsearchcontroller.cpp | 22 +- 6 files changed, 308 insertions(+), 146 deletions(-) diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 383a01a50..244a95765 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -43,6 +43,12 @@ class EE_API TextDocument { enum class LoadStatus { Loaded, Interrupted, Failed }; + struct SearchResult { + TextRange result{}; + std::vector captures{}; + bool isValid() const { return result.isValid(); } + }; + enum class MatchDirection { Forward, Backward }; class EE_API Client { @@ -345,13 +351,14 @@ class EE_API TextDocument { bool removeCommand( const std::string& command ); - TextRange find( const String& text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, - TextRange restrictRange = TextRange() ); + SearchResult find( const String& text, TextPosition from = { 0, 0 }, bool caseSensitive = true, + bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, + TextRange restrictRange = TextRange() ); - TextRange findLast( const String& text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, - TextRange restrictRange = TextRange() ); + SearchResult findLast( const String& text, TextPosition from = { 0, 0 }, + bool caseSensitive = true, bool wholeWord = false, + FindReplaceType type = FindReplaceType::Normal, + TextRange restrictRange = TextRange() ); TextRanges findAll( const String& text, bool caseSensitive = true, bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, @@ -690,13 +697,14 @@ class EE_API TextDocument { LoadStatus loadFromStream( IOStream& file, std::string path, bool callReset ); - TextRange findText( String text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, - TextRange restrictRange = TextRange() ); + SearchResult findText( String text, TextPosition from = { 0, 0 }, bool caseSensitive = true, + bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, + TextRange restrictRange = TextRange() ); - TextRange findTextLast( String text, TextPosition from = { 0, 0 }, bool caseSensitive = true, - bool wholeWord = false, FindReplaceType type = FindReplaceType::Normal, - TextRange restrictRange = TextRange() ); + SearchResult findTextLast( String text, TextPosition from = { 0, 0 }, bool caseSensitive = true, + bool wholeWord = false, + FindReplaceType type = FindReplaceType::Normal, + TextRange restrictRange = TextRange() ); }; struct TextSearchParams { diff --git a/include/eepp/ui/tools/uidocfindreplace.hpp b/include/eepp/ui/tools/uidocfindreplace.hpp index 5e2713fd2..477b5dd1d 100644 --- a/include/eepp/ui/tools/uidocfindreplace.hpp +++ b/include/eepp/ui/tools/uidocfindreplace.hpp @@ -71,6 +71,8 @@ class EE_API UIDocFindReplace : public UILinearLayout, public WidgetCommandExecu virtual Uint32 onKeyDown( const KeyEvent& event ); void refreshHighlight( UICodeEditor* editor ); + + bool replaceSelection( TextSearchParams& search, const String& replacement ); }; }}} // namespace EE::UI::Tools diff --git a/src/eepp/system/luapattern.cpp b/src/eepp/system/luapattern.cpp index 349338e0b..a85d5786b 100644 --- a/src/eepp/system/luapattern.cpp +++ b/src/eepp/system/luapattern.cpp @@ -63,16 +63,24 @@ LuaPattern::LuaPattern( const std::string_view& pattern ) : mPattern( pattern ), bool LuaPattern::matches( const char* stringSearch, int stringStartOffset, LuaPattern::Range* matchList, size_t stringLength ) const { - LuaPattern::Range matchesBuffer[MAX_DEFAULT_MATCHES]; - if ( matchList == nullptr ) - matchList = matchesBuffer; if ( stringLength == 0 ) stringLength = strlen( stringSearch ); - try { - mMatchNum = lua_str_match( stringSearch, stringStartOffset, stringLength, mPattern.data(), - (LuaMatch*)matchList ); - } catch ( const std::string& patternError ) { - mMatchNum = 0; + + if ( matchList == nullptr ) { + LuaPattern::Range matchesBuffer[MAX_DEFAULT_MATCHES]; + try { + mMatchNum = lua_str_match( stringSearch, stringStartOffset, stringLength, + mPattern.data(), (LuaMatch*)matchesBuffer ); + } catch ( const std::string& patternError ) { + mMatchNum = 0; + } + } else { + try { + mMatchNum = lua_str_match( stringSearch, stringStartOffset, stringLength, + mPattern.data(), (LuaMatch*)matchList ); + } catch ( const std::string& patternError ) { + mMatchNum = 0; + } } return mMatchNum == 0 ? false : true; } diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 5cea882f6..c0a01c517 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -1898,15 +1898,15 @@ void TextDocument::selectWord( bool withMulticursor ) { previousWordBoundary( getSelection().start(), false ) } ); } else if ( withMulticursor ) { String text( getSelectedText() ); - TextRange res( find( text, getBottomMostCursor().normalized().end() ) ); - if ( res.isValid() && !mSelection.exists( res.reversed() ) ) { - addSelection( res.reversed() ); + auto res( find( text, getBottomMostCursor().normalized().end() ) ); + if ( res.isValid() && !mSelection.exists( res.result.reversed() ) ) { + addSelection( res.result.reversed() ); } else { res = findLast( text, getTopMostCursor().normalized().start(), true, false, FindReplaceType::Normal, { startOfDoc(), getTopMostCursor().normalized().start() } ); - if ( res.isValid() && !mSelection.exists( res ) ) { - addSelection( res ); + if ( res.isValid() && !mSelection.exists( res.result ) ) { + addSelection( res.result ); } } } @@ -2331,17 +2331,35 @@ bool TextDocument::removeCommand( const std::string& command ) { return mCommands.erase( command ) > 0 || mRefCommands.erase( command ) > 0; } -static std::pair findType( const String& str, const String& findStr, - const TextDocument::FindReplaceType& type ) { +static constexpr auto MAX_CAPTURES = 12; + +struct FindTypeResult { + size_t start{ String::StringType::npos }; + size_t end{ String::StringType::npos }; + std::vector captures{}; +}; + +static FindTypeResult findType( const String& str, const String& findStr, + const TextDocument::FindReplaceType& type ) { switch ( type ) { case TextDocument::FindReplaceType::LuaPattern: { LuaPatternStorage words( findStr.toUtf8() ); - int start, end = 0; - words.find( str, start, end ); - if ( start < 0 ) + LuaPattern::Range matches[MAX_CAPTURES]; + if ( words.matches( str, matches ) ) { + FindTypeResult result{ static_cast( matches[0].start ), + static_cast( matches[0].end ) }; + if ( words.getNumMatches() > 1 ) { + std::vector captures; + captures.reserve( words.getNumMatches() - 1 ); + for ( size_t i = 1; i < words.getNumMatches(); i++ ) { + result.captures.emplace_back( + LuaPattern::Range{ matches[i].start, matches[i].end } ); + } + } + return result; + } else { return { String::StringType::npos, String::StringType::npos }; - else - return { start, end }; + } } case TextDocument::FindReplaceType::Normal: default: { @@ -2351,18 +2369,28 @@ static std::pair findType( const String& str, const String& find } } -static std::pair findLastType( const String& str, const String& findStr, - const TextDocument::FindReplaceType& type ) { +static FindTypeResult findLastType( const String& str, const String& findStr, + const TextDocument::FindReplaceType& type ) { switch ( type ) { case TextDocument::FindReplaceType::LuaPattern: { // TODO: Implement findLastType for Lua patterns LuaPatternStorage words( findStr.toUtf8() ); - int start, end = 0; - words.find( str, start, end ); - if ( start < 0 ) + LuaPattern::Range matches[MAX_CAPTURES]; + if ( words.matches( str, matches ) ) { + FindTypeResult result{ static_cast( matches[0].start ), + static_cast( matches[0].end ) }; + if ( words.getNumMatches() > 1 ) { + std::vector captures; + captures.reserve( words.getNumMatches() - 1 ); + for ( size_t i = 1; i < words.getNumMatches(); i++ ) { + result.captures.emplace_back( + LuaPattern::Range{ matches[i].start, matches[i].end } ); + } + } + return result; + } else { return { String::StringType::npos, String::StringType::npos }; - else - return { end, start }; + } } case TextDocument::FindReplaceType::Normal: default: { @@ -2372,10 +2400,27 @@ static std::pair findLastType( const String& str, const String& } } -TextRange TextDocument::findText( String text, TextPosition from, bool caseSensitive, - bool wholeWord, FindReplaceType type, TextRange restrictRange ) { +TextDocument::SearchResult toSearchResult( TextDocument* doc, const Int64 line, + const FindTypeResult& res ) { + TextDocument::SearchResult ret; + TextRange pos( + { { line, static_cast( res.start ) }, { line, static_cast( res.end ) } } ); + if ( pos.end().column() == (Int64)doc->line( pos.end().line() ).size() ) + pos.setEnd( doc->positionOffset( pos.end(), 1 ) ); + ret.result = std::move( pos ); + ret.captures.reserve( res.captures.size() ); + for ( const auto& capture : res.captures ) { + ret.captures.push_back( + TextRange( TextPosition( line, capture.start ), TextPosition( line, capture.end ) ) ); + } + return ret; +} + +TextDocument::SearchResult TextDocument::findText( String text, TextPosition from, + bool caseSensitive, bool wholeWord, + FindReplaceType type, TextRange restrictRange ) { if ( text.empty() ) - return TextRange(); + return TextDocument::SearchResult{}; from = sanitizePosition( from ); TextPosition to = endOfDoc(); @@ -2383,7 +2428,7 @@ TextRange TextDocument::findText( String text, TextPosition from, bool caseSensi restrictRange = sanitizeRange( restrictRange.normalized() ); to = restrictRange.end(); if ( from < restrictRange.start() || from > restrictRange.end() ) - return TextRange(); + return TextDocument::SearchResult{}; } if ( type == FindReplaceType::LuaPattern && caseSensitive == false ) @@ -2394,15 +2439,15 @@ TextRange TextDocument::findText( String text, TextPosition from, bool caseSensi text.toLower(); for ( Int64 i = from.line(); i <= to.line(); i++ ) { - std::pair col; + FindTypeResult col; if ( i == from.line() ) { col = caseSensitive ? findType( line( i ).getText().substr( from.column() ), text, type ) : findType( String::toLower( line( i ).getText() ).substr( from.column() ), text, type ); - if ( String::StringType::npos != col.first ) { - col.first += from.column(); - col.second += from.column(); + if ( String::StringType::npos != col.start ) { + col.start += from.column(); + col.end += from.column(); } } else if ( i == to.line() && to != endOfDoc() ) { col = caseSensitive @@ -2413,22 +2458,20 @@ TextRange TextDocument::findText( String text, TextPosition from, bool caseSensi col = caseSensitive ? findType( line( i ).getText(), text, type ) : findType( String::toLower( line( i ).getText() ), text, type ); } - if ( String::StringType::npos != col.first && - ( !wholeWord || String::isWholeWord( line( i ).getText(), text, col.first ) ) ) { - TextRange pos( { { (Int64)i, (Int64)col.first }, { (Int64)i, (Int64)col.second } } ); - if ( pos.end().column() == (Int64)mLines[pos.end().line()].size() ) - pos.setEnd( positionOffset( pos.end(), 1 ) ); - return pos; + if ( String::StringType::npos != col.start && + ( !wholeWord || String::isWholeWord( line( i ).getText(), text, col.start ) ) ) { + return toSearchResult( this, i, col ); } } - return TextRange(); + return TextDocument::SearchResult{}; } -TextRange TextDocument::findTextLast( String text, TextPosition from, bool caseSensitive, - bool wholeWord, FindReplaceType type, - TextRange restrictRange ) { +TextDocument::SearchResult TextDocument::findTextLast( String text, TextPosition from, + bool caseSensitive, bool wholeWord, + FindReplaceType type, + TextRange restrictRange ) { if ( text.empty() ) - return TextRange(); + return TextDocument::SearchResult{}; from = sanitizePosition( from ); TextPosition to = startOfDoc(); @@ -2436,7 +2479,7 @@ TextRange TextDocument::findTextLast( String text, TextPosition from, bool caseS restrictRange = sanitizeRange( restrictRange.normalized() ); to = restrictRange.start(); if ( from < restrictRange.start() || from > restrictRange.end() ) - return TextRange(); + return TextDocument::SearchResult{}; } if ( type == FindReplaceType::LuaPattern && caseSensitive == false ) @@ -2447,44 +2490,42 @@ TextRange TextDocument::findTextLast( String text, TextPosition from, bool caseS text.toLower(); for ( Int64 i = from.line(); i >= to.line(); i-- ) { - std::pair col; + FindTypeResult res; if ( i == from.line() ) { - col = caseSensitive + res = caseSensitive ? findLastType( line( i ).getText().substr( 0, from.column() ), text, type ) : findLastType( String::toLower( line( i ).getText().substr( 0, from.column() ) ), text, type ); } else if ( i == to.line() ) { - col = caseSensitive + res = caseSensitive ? findLastType( line( i ).getText().substr( to.column() ), text, type ) : findLastType( String::toLower( line( i ).getText().substr( to.column() ) ), text, type ); - if ( String::StringType::npos != col.first ) { - col.first += to.column(); - col.second += to.column(); + if ( String::StringType::npos != res.start ) { + res.start += to.column(); + res.end += to.column(); } } else { - col = caseSensitive + res = caseSensitive ? findLastType( line( i ).getText(), text, type ) : findLastType( String::toLower( line( i ).getText() ), text, type ); } - if ( String::StringType::npos != col.first && - ( !wholeWord || String::isWholeWord( line( i ).getText(), text, col.first ) ) ) { - TextRange pos( { { (Int64)i, (Int64)col.second }, { (Int64)i, (Int64)col.first } } ); - if ( pos.start().column() == (Int64)mLines[pos.start().line()].size() ) - pos.setStart( positionOffset( pos.start(), 1 ) ); - return pos; + if ( String::StringType::npos != res.start && + ( !wholeWord || String::isWholeWord( line( i ).getText(), text, res.start ) ) ) { + return toSearchResult( this, i, res ); } } - return TextRange(); + return TextDocument::SearchResult{}; } -TextRange TextDocument::find( const String& text, TextPosition from, bool caseSensitive, - bool wholeWord, FindReplaceType type, TextRange restrictRange ) { +TextDocument::SearchResult TextDocument::find( const String& text, TextPosition from, + bool caseSensitive, bool wholeWord, + FindReplaceType type, TextRange restrictRange ) { std::vector textLines = text.split( '\n', true, true ); if ( textLines.empty() || textLines.size() > mLines.size() ) - return TextRange(); + return {}; from = sanitizePosition( from ); @@ -2493,30 +2534,30 @@ TextRange TextDocument::find( const String& text, TextPosition from, bool caseSe restrictRange = sanitizeRange( restrictRange.normalized() ); to = restrictRange.end(); if ( from < restrictRange.start() || from >= restrictRange.end() ) - return TextRange(); + return {}; } if ( from == to ) - return TextRange(); + return {}; if ( textLines.size() == 1 ) return findText( text, from, caseSensitive, wholeWord, type, restrictRange ); - TextRange range = findText( textLines[0], from, caseSensitive, false, type, restrictRange ); + auto range = findText( textLines[0], from, caseSensitive, false, type, restrictRange ); if ( !range.isValid() ) - return TextRange(); + return {}; - TextPosition initPos( range.end().line(), 0 ); + TextPosition initPos( range.result.end().line(), 0 ); for ( size_t i = 1; i < textLines.size() - 1; i++ ) { if ( initPos < from || initPos > to ) - return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); String currentLine( mLines[initPos.line()].getText() ); if ( TextPosition( initPos.line(), (Int64)currentLine.size() - 1 ) > to ) - return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); if ( !caseSensitive ) { currentLine.toLower(); @@ -2527,44 +2568,46 @@ TextRange TextDocument::find( const String& text, TextPosition from, bool caseSe initPos = TextPosition( initPos.line() + 1, 0 ); if ( initPos >= restrictRange.end() ) - return TextRange(); + return {}; } else { - return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); } } if ( initPos < from || initPos > to ) - return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); const String& lastLine = mLines[initPos.line()].getText(); const String& curSearch = textLines[textLines.size() - 1]; if ( TextPosition( initPos.line(), (Int64)curSearch.size() - 1 ) > to ) - return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); if ( lastLine.size() < curSearch.size() ) - return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); if ( ( caseSensitive && String::startsWith( lastLine, curSearch ) ) || ( !caseSensitive && String::startsWith( String( lastLine ).toLower(), String( curSearch ).toLower() ) ) ) { - TextRange foundRange( range.start(), TextPosition( initPos.line(), curSearch.size() ) ); + TextRange foundRange( range.result.start(), + TextPosition( initPos.line(), curSearch.size() ) ); if ( foundRange.end().column() == (Int64)mLines[foundRange.end().line()].size() ) foundRange.setEnd( positionOffset( foundRange.end(), 1 ) ); - return foundRange; + return range; } else { - return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); } - return TextRange(); + return {}; } -TextRange TextDocument::findLast( const String& text, TextPosition from, bool caseSensitive, - bool wholeWord, FindReplaceType type, TextRange restrictRange ) { +TextDocument::SearchResult TextDocument::findLast( const String& text, TextPosition from, + bool caseSensitive, bool wholeWord, + FindReplaceType type, TextRange restrictRange ) { std::vector textLines = text.split( '\n', true, true ); if ( textLines.empty() || textLines.size() > mLines.size() ) - return TextRange(); + return {}; from = sanitizePosition( from ); @@ -2573,30 +2616,32 @@ TextRange TextDocument::findLast( const String& text, TextPosition from, bool ca restrictRange = sanitizeRange( restrictRange.normalized() ); to = restrictRange.start(); if ( from < restrictRange.start() || from > restrictRange.end() ) - return TextRange(); + return {}; } if ( from == to ) - return TextRange(); + return {}; if ( textLines.size() == 1 ) return findTextLast( text, from, caseSensitive, wholeWord, type, restrictRange ); - TextRange range = findTextLast( textLines[0], from, caseSensitive, false, type, restrictRange ); + auto range = findTextLast( textLines[0], from, caseSensitive, false, type, restrictRange ); if ( !range.isValid() ) - return TextRange(); + return {}; - TextPosition initPos( range.end().line(), 0 ); + TextPosition initPos( range.result.end().line(), 0 ); for ( size_t i = 1; i < textLines.size() - 1; i++ ) { if ( initPos < from || initPos > to ) - return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return findLast( text, range.result.end(), caseSensitive, wholeWord, type, + restrictRange ); String currentLine( mLines[initPos.line()].getText() ); if ( TextPosition( initPos.line(), (Int64)currentLine.size() - 1 ) > to ) - return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return findLast( text, range.result.end(), caseSensitive, wholeWord, type, + restrictRange ); if ( !caseSensitive ) { currentLine.toLower(); @@ -2606,32 +2651,30 @@ TextRange TextDocument::findLast( const String& text, TextPosition from, bool ca if ( currentLine == textLines[i] ) { initPos = TextPosition( i + 1, 0 ); } else { - return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return findLast( text, range.result.end(), caseSensitive, wholeWord, type, + restrictRange ); } } if ( initPos < from || initPos > to ) - return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return findLast( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); const String& lastLine = mLines[initPos.line()].getText(); const String& curSearch = textLines[textLines.size() - 1]; if ( TextPosition( initPos.line(), (Int64)curSearch.size() - 1 ) > to ) - return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return findLast( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); if ( lastLine.size() < curSearch.size() ) - return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + return findLast( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange ); if ( ( caseSensitive && String::startsWith( lastLine, curSearch ) ) || ( !caseSensitive && String::startsWith( String( lastLine ).toLower(), String( curSearch ).toLower() ) ) ) { - TextRange foundRange( range.start(), TextPosition( initPos.line(), curSearch.size() ) ); - if ( foundRange.end().column() == (Int64)mLines[foundRange.end().line()].size() ) - foundRange.setEnd( positionOffset( foundRange.end(), 1 ) ); - return foundRange; + return range; } - return TextRange(); + return {}; } void TextDocument::stopActiveFindAll() { @@ -2652,7 +2695,7 @@ TextRanges TextDocument::findAll( const String& text, bool caseSensitive, bool w FindReplaceType type, TextRange restrictRange, size_t maxResults ) { TextRanges all; - TextRange found; + TextDocument::SearchResult found; TextPosition from = startOfDoc(); auto stopFlagUP = std::make_unique( false ); bool* stopFlag = stopFlagUP.get(); @@ -2666,10 +2709,10 @@ TextRanges TextDocument::findAll( const String& text, bool caseSensitive, bool w do { found = find( text, from, caseSensitive, wholeWord, type, restrictRange ); if ( found.isValid() ) { - if ( !all.empty() && all.back() == found ) + if ( !all.empty() && all.back() == found.result ) break; - from = found.end(); - all.push_back( found ); + from = found.result.end(); + all.push_back( found.result ); if ( ( maxResults != 0 && all.size() >= maxResults ) || *stopFlag ) break; } @@ -2693,19 +2736,53 @@ int TextDocument::replaceAll( const String& text, const String& replace, const b if ( !wasRunningTransaction ) setRunningTransaction( true ); int count = 0; - TextRange found; + TextDocument::SearchResult found; TextPosition startedPosition = getSelection().start(); TextPosition from = startOfDoc(); if ( restrictRange.isValid() ) from = restrictRange.normalized().start(); + + size_t numCaptures = 0; + LuaPattern::Range matchList[MAX_CAPTURES]; + + if ( type == FindReplaceType::LuaPattern ) { + std::string replaceUtf8( replace.toUtf8() ); + LuaPattern ptrn( "$%d+"sv ); + while ( numCaptures < MAX_CAPTURES && + ptrn.matches( replaceUtf8, &matchList[numCaptures], + numCaptures > 0 ? matchList[numCaptures - 1].end : 0 ) ) { + numCaptures++; + } + } + do { found = find( text, from, caseSensitive, wholeWord, type, restrictRange ); if ( found.isValid() ) { - setSelection( found ); - from = replaceSelection( replace ); - count++; + if ( numCaptures && numCaptures == found.captures.size() ) { + String finalReplace( replace ); + std::string l( line( found.captures[0].start().line() ).toUtf8() ); + for ( size_t i = 0; i < numCaptures; i++ ) { + String matchSubStr( replace.substr( + matchList[i].start, matchList[i].end - matchList[i].start ) ); // $1 $2 ... + std::string matchNum( matchSubStr.substr( 1 ) ); // 1 2 ... + int num; + if ( String::fromString( num, matchNum ) && num > 0 && + num - 1 < static_cast( found.captures.size() ) ) { + auto start = found.captures[num - 1].start().column(); + auto end = found.captures[num - 1].end().column(); + finalReplace.replaceAll( + matchSubStr, String::fromUtf8( l.substr( start, end - start ) ) ); + } + } + setSelection( found.result ); + from = replaceSelection( finalReplace ); + } else { + setSelection( found.result ); + from = replaceSelection( replace ); + count++; + } } - } while ( found.isValid() && endOfDoc() != found.end() ); + } while ( found.isValid() && endOfDoc() != found.result.end() ); if ( !wasRunningTransaction ) setRunningTransaction( false ); setSelection( startedPosition ); @@ -2748,12 +2825,49 @@ void TextDocument::selectAllMatches() { TextPosition TextDocument::replace( String search, const String& replace, TextPosition from, const bool& caseSensitive, const bool& wholeWord, FindReplaceType type, TextRange restrictRange ) { - TextRange found( findText( search, from, caseSensitive, wholeWord, type, restrictRange ) ); + auto found( findText( search, from, caseSensitive, wholeWord, type, restrictRange ) ); + size_t numCaptures = 0; + LuaPattern::Range matchList[MAX_CAPTURES]; + + if ( type == FindReplaceType::LuaPattern ) { + std::string replaceUtf8( replace.toUtf8() ); + LuaPattern ptrn( "$%d+"sv ); + while ( numCaptures < MAX_CAPTURES && + ptrn.matches( replaceUtf8, &matchList[numCaptures], + numCaptures > 0 ? matchList[numCaptures - 1].end : 0 ) ) { + numCaptures++; + } + } + if ( found.isValid() ) { - setSelection( found ); - deleteTo( 0, 0 ); - setSelection( 0, insert( 0, getSelectionIndex( 0 ).start(), replace ) ); - return found.end(); + if ( numCaptures && numCaptures == found.captures.size() ) { + String finalReplace( replace ); + std::string l( line( found.captures[0].start().line() ).toUtf8() ); + for ( size_t i = 0; i < numCaptures; i++ ) { + String matchSubStr( replace.substr( + matchList[i].start, matchList[i].end - matchList[i].start ) ); // $1 $2 ... + std::string matchNum( matchSubStr.substr( 1 ) ); // 1 2 ... + int num; + if ( String::fromString( num, matchNum ) && num > 0 && + num - 1 < static_cast( found.captures.size() ) ) { + auto start = + restrictRange.start().column() + found.captures[num - 1].start().column(); + auto end = + restrictRange.start().column() + found.captures[num - 1].end().column(); + if ( start < static_cast( l.size() ) && + end < static_cast( l.size() ) ) { + finalReplace.replaceAll( + matchSubStr, String::fromUtf8( l.substr( start, end - start ) ) ); + } + } + } + setSelection( found.result ); + replaceSelection( finalReplace ); + } else { + setSelection( found.result ); + replaceSelection( replace ); + } + return found.result.end(); } return TextPosition(); } @@ -2859,7 +2973,8 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op do { foundOpen = find( openBracket, start, true, false, TextDocument::FindReplaceType::Normal, - { start, foundClose.start() } ); + { start, foundClose.result.start() } ) + .result; if ( foundOpen.isValid() ) { TextPosition closePosition = @@ -2879,14 +2994,15 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op } else { foundOpen = find( openBracket, start, true, false, TextDocument::FindReplaceType::Normal, - { start, foundClose.start() } ); + { start, foundClose.result.start() } ) + .result; } if ( foundOpen.isValid() ) { start = foundOpen.end(); changeDepth( highlighter, depth, start, 1 ); } else { - start = foundClose.end(); + start = foundClose.result.end(); changeDepth( highlighter, depth, start, -1 ); } } while ( foundOpen.isValid() ); @@ -2899,7 +3015,7 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op } } while ( depth > 0 ); - return foundClose; + return foundClose.result; } else { { TextPosition end( positionOffset( start, -closeBracket.size() ) ); @@ -2915,7 +3031,7 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op TextRange foundOpen; if ( matchingXMLTags ) { do { - foundOpen = findLast( openBracket, start ); + foundOpen = findLast( openBracket, start ).result; if ( foundOpen.isValid() ) { TextPosition closePosition = getMatchingBracket( foundOpen.normalized().start(), openBracket[0], '>', @@ -2932,7 +3048,7 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op } } while ( foundOpen.isValid() ); } else { - foundOpen = findLast( openBracket, start ); + foundOpen = findLast( openBracket, start ).result; } if ( !foundOpen.isValid() ) return {}; // Not found, exit @@ -2946,7 +3062,8 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op do { foundClose = findLast( closeBracket, start, true, false, - TextDocument::FindReplaceType::Normal, { start, foundOpen.start() } ); + TextDocument::FindReplaceType::Normal, { start, foundOpen.start() } ) + .result; if ( foundClose.isValid() ) { start = foundClose.end(); changeDepth( highlighter, depth, start, 1 ); @@ -2960,7 +3077,7 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op // Find the next open bracket from the last open bracket if ( matchingXMLTags ) { do { - foundOpen = findLast( openBracket, start ); + foundOpen = findLast( openBracket, start ).result; if ( foundOpen.isValid() ) { TextPosition closePosition = getMatchingBracket( foundOpen.normalized().start(), openBracket[0], @@ -2977,7 +3094,7 @@ TextRange TextDocument::getMatchingBracket( TextPosition start, const String& op } } while ( foundOpen.isValid() ); } else { - foundOpen = findLast( openBracket, start ); + foundOpen = findLast( openBracket, start ).result; } if ( !foundOpen.isValid() ) diff --git a/src/eepp/ui/tools/uidocfindreplace.cpp b/src/eepp/ui/tools/uidocfindreplace.cpp index f4cde74bd..265b0c501 100644 --- a/src/eepp/ui/tools/uidocfindreplace.cpp +++ b/src/eepp/ui/tools/uidocfindreplace.cpp @@ -221,7 +221,7 @@ UIDocFindReplace::UIDocFindReplace( UIWidget* parent, const std::shared_ptrgetText() ); } ); setCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); setCommand( "replace-selection", - [this] { mDoc->replaceSelection( mReplaceInput->getText() ); } ); + [this] { replaceSelection( mSearchState, mReplaceInput->getText() ); } ); setCommand( "change-case", [this, editor] { mCaseSensitive->setSelected( !mCaseSensitive->isSelected() ); refreshHighlight( editor ); @@ -415,14 +415,16 @@ bool UIDocFindReplace::findPrevText( TextSearchParams& search ) { txt.unescape(); TextRange found = mDoc->findLast( txt, from, search.caseSensitive, search.wholeWord, - search.type, search.range ); + search.type, search.range ) + .result; if ( found.isValid() ) { mDoc->setSelection( found ); mFindInput->removeClass( "error" ); return true; } else { found = mDoc->findLast( txt, range.end(), search.caseSensitive, search.wholeWord, - search.type, range ); + search.type, range ) + .result; if ( found.isValid() ) { mDoc->setSelection( found ); mFindInput->removeClass( "error" ); @@ -451,14 +453,15 @@ bool UIDocFindReplace::findNextText( TextSearchParams& search ) { txt.unescape(); TextRange found = - mDoc->find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ); + mDoc->find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ).result; if ( found.isValid() ) { mDoc->setSelection( found.reversed() ); mFindInput->removeClass( "error" ); return true; } else { found = mDoc->find( txt, range.start(), search.caseSensitive, search.wholeWord, search.type, - range ); + range ) + .result; if ( found.isValid() ) { mDoc->setSelection( found.reversed() ); mFindInput->removeClass( "error" ); @@ -511,7 +514,7 @@ bool UIDocFindReplace::findAndReplace( TextSearchParams& search, const String& r } if ( mDoc->hasSelection() && mDoc->getSelectedText() == txt ) { - mDoc->replaceSelection( repl ); + replaceSelection( search, repl ); return true; } else { return findNextText( search ); @@ -535,4 +538,18 @@ void UIDocFindReplace::refreshHighlight( UICodeEditor* editor ) { } }; +bool UIDocFindReplace::replaceSelection( TextSearchParams& search, const String& replacement ) { + UICodeEditor* editor = + getParent()->isType( UI_TYPE_CODEEDITOR ) ? getParent()->asType() : nullptr; + + if ( !editor || !editor->getDocument().hasSelection() ) + return false; + editor->getDocument().setActiveClient( editor ); + editor->getDocument().replace( search.text, replacement, + editor->getDocument().getSelection().normalized().start(), + search.caseSensitive, search.wholeWord, search.type, + editor->getDocument().getSelection().normalized() ); + return true; +} + }}} // namespace EE::UI::Tools diff --git a/src/tools/ecode/docsearchcontroller.cpp b/src/tools/ecode/docsearchcontroller.cpp index 6956e1ed9..fcc8b4cde 100644 --- a/src/tools/ecode/docsearchcontroller.cpp +++ b/src/tools/ecode/docsearchcontroller.cpp @@ -229,7 +229,8 @@ void DocSearchController::findPrevText( SearchState& search ) { txt.unescape(); TextRange found = doc.findLast( txt, from, search.caseSensitive, search.wholeWord, - search.type, search.range ); + search.type, search.range ) + .result; if ( found.isValid() ) { doc.setSelection( found ); mFindInput->runOnMainThread( [this, search] { @@ -239,7 +240,8 @@ void DocSearchController::findPrevText( SearchState& search ) { return; } else { found = doc.findLast( txt, range.end(), search.caseSensitive, search.wholeWord, - search.type, range ); + search.type, range ) + .result; if ( found.isValid() ) { doc.setSelection( found ); mFindInput->runOnMainThread( [this, search] { @@ -284,7 +286,8 @@ void DocSearchController::findNextText( SearchState& search, bool resetSelection txt.unescape(); TextRange found = - doc.find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ); + doc.find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ) + .result; if ( found.isValid() ) { doc.setSelection( found.reversed() ); @@ -295,7 +298,8 @@ void DocSearchController::findNextText( SearchState& search, bool resetSelection return; } else { found = doc.find( txt, range.start(), search.caseSensitive, search.wholeWord, - search.type, range ); + search.type, range ) + .result; if ( found.isValid() ) { doc.setSelection( found.reversed() ); mFindInput->runOnMainThread( [this, search] { @@ -316,7 +320,10 @@ bool DocSearchController::replaceSelection( SearchState& search, const String& r !search.editor->getDocument().hasSelection() ) return false; search.editor->getDocument().setActiveClient( search.editor ); - search.editor->getDocument().replaceSelection( replacement ); + search.editor->getDocument().replace( + search.text, replacement, search.editor->getDocument().getSelection().normalized().start(), + search.caseSensitive, search.wholeWord, search.type, + search.editor->getDocument().getSelection().normalized() ); return true; } @@ -379,7 +386,10 @@ void DocSearchController::findAndReplace( SearchState& search, const String& rep repl.unescape(); } - if ( doc.hasSelection() && doc.getSelectedText() == txt ) { + if ( doc.hasSelection() && + ( doc.getSelectedText() == txt || + ( search.type == TextDocument::FindReplaceType::LuaPattern && + LuaPattern::matches( doc.getAllSelectedText().toUtf8(), txt.toUtf8() ) ) ) ) { replaceSelection( search, repl ); } else { findNextText( search );