From c102bf580a1e874de00599b6337e7ae8ff2d7e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 7 Mar 2022 01:53:17 -0300 Subject: [PATCH] Added multiline support for TextDocument::find and TextDocument::findLast. --- include/eepp/core/string.hpp | 9 +- include/eepp/ui/doc/textdocument.hpp | 17 +- src/eepp/core/string.cpp | 64 +++--- src/eepp/system/filesystem.cpp | 23 +- src/eepp/ui/doc/textdocument.cpp | 216 ++++++++++++++++--- src/eepp/ui/keyboardshortcut.cpp | 5 +- src/eepp/ui/uitextinput.cpp | 4 +- src/tools/codeeditor/codeeditor.cpp | 9 +- src/tools/codeeditor/docsearchcontroller.cpp | 105 +++++++-- src/tools/codeeditor/docsearchcontroller.hpp | 1 + 10 files changed, 362 insertions(+), 91 deletions(-) diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index aae0245de..93880570e 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -103,11 +103,13 @@ class EE_API String { /** Split a String and hold it on a vector */ static std::vector split( const String& str, const StringBaseType& delim = '\n', - const bool& pushEmptyString = false ); + const bool& pushEmptyString = false, + const bool& keepDelim = false ); /** Split a string and hold it on a vector */ static std::vector split( const std::string& str, const Int8& delim = '\n', - const bool& pushEmptyString = false ); + const bool& pushEmptyString = false, + const bool& keepDelim = false ); /** Split a string and hold it on a vector. This function is meant to be used for code * splitting, detects functions, arrays, braces and quotes for the splitting. */ @@ -777,7 +779,8 @@ class EE_API String { StringBaseType lastChar() const; std::vector split( const StringBaseType& delim = '\n', - const bool& pushEmptyString = false ) const; + const bool& pushEmptyString = false, + const bool& keepDelim = false ) const; std::pair fuzzyMatch( const String& pattern ); diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 25f4b7810..f1ce98879 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -258,9 +258,10 @@ class EE_API TextDocument { const FindReplaceType& type = FindReplaceType::Normal, TextRange restrictRange = TextRange() ); - TextPosition findLast( String text, TextPosition from = { 0, 0 }, - const bool& caseSensitive = true, const bool& wholeWord = false, - TextRange restrictRange = TextRange() ); + TextRange findLast( String text, TextPosition from = { 0, 0 }, const bool& caseSensitive = true, + const bool& wholeWord = false, + const FindReplaceType& type = FindReplaceType::Normal, + TextRange restrictRange = TextRange() ); std::vector findAll( const String& text, const bool& caseSensitive = true, const bool& wholeWord = false, @@ -437,6 +438,16 @@ class EE_API TextDocument { void guessIndentType(); bool loadFromStream( IOStream& file, std::string path, bool callReset ); + + TextRange findText( String text, TextPosition from = { 0, 0 }, const bool& caseSensitive = true, + const bool& wholeWord = false, + const FindReplaceType& type = FindReplaceType::Normal, + TextRange restrictRange = TextRange() ); + + TextRange findTextLast( String text, TextPosition from = { 0, 0 }, + const bool& caseSensitive = true, const bool& wholeWord = false, + const FindReplaceType& type = FindReplaceType::Normal, + TextRange restrictRange = TextRange() ); }; }}} // namespace EE::UI::Doc diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 2958d4117..fdfc263c5 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -159,7 +159,7 @@ bool String::isHexNotation( const std::string& value, const std::string& withPre } std::vector String::split( const String& str, const StringBaseType& delim, - const bool& pushEmptyString ) { + const bool& pushEmptyString, const bool& keepDelim ) { std::vector cont; std::size_t current, previous = 0; current = str.find( delim ); @@ -173,9 +173,48 @@ std::vector String::split( const String& str, const StringBaseType& deli String substr( str.substr( previous, current - previous ) ); if ( pushEmptyString || !substr.empty() ) cont.emplace_back( std::move( substr ) ); + if ( keepDelim ) { + for ( size_t i = 0; i < cont.size(); i++ ) { + if ( i != cont.size() - 1 ) + cont[i].push_back( delim ); + if ( cont[cont.size() - 1].empty() ) + cont.pop_back(); + } + } return cont; } +std::vector String::split( const std::string& str, const Int8& delim, + const bool& pushEmptyString, const bool& keepDelim ) { + std::vector cont; + std::size_t current, previous = 0; + current = str.find( delim ); + while ( current != std::string::npos ) { + std::string substr( str.substr( previous, current - previous ) ); + if ( pushEmptyString || !substr.empty() ) + cont.emplace_back( std::move( substr ) ); + previous = current + 1; + current = str.find( delim, previous ); + } + std::string substr( str.substr( previous, current - previous ) ); + if ( pushEmptyString || !substr.empty() ) + cont.emplace_back( std::move( substr ) ); + if ( keepDelim ) { + for ( size_t i = 0; i < cont.size(); i++ ) { + if ( i != cont.size() - 1 ) + cont[i].push_back( delim ); + if ( cont[cont.size() - 1].empty() ) + cont.pop_back(); + } + } + return cont; +} + +std::vector String::split( const StringBaseType& delim, const bool& pushEmptyString, + const bool& keepDelim ) const { + return String::split( *this, delim, pushEmptyString, keepDelim ); +} + std::vector String::split( const std::string& str, const std::string& delims, const std::string& delimsPreserve, const std::string& quote ) { @@ -235,24 +274,6 @@ std::vector String::split( const std::string& str, const std::strin return tokens; } -std::vector String::split( const std::string& str, const Int8& delim, - const bool& pushEmptyString ) { - std::vector cont; - std::size_t current, previous = 0; - current = str.find( delim ); - while ( current != std::string::npos ) { - std::string substr( str.substr( previous, current - previous ) ); - if ( pushEmptyString || !substr.empty() ) - cont.emplace_back( std::move( substr ) ); - previous = current + 1; - current = str.find( delim, previous ); - } - std::string substr( str.substr( previous, current - previous ) ); - if ( pushEmptyString || !substr.empty() ) - cont.emplace_back( std::move( substr ) ); - return cont; -} - std::string String::join( const std::vector& strArray, const Int8& joinchar, const bool& appendLastJoinChar ) { size_t s = strArray.size(); @@ -380,11 +401,6 @@ String::StringBaseType String::lastChar() const { : mString[mString.size() - 1]; } -std::vector String::split( const StringBaseType& delim, - const bool& pushEmptyString ) const { - return String::split( *this, delim, pushEmptyString ); -} - // Lite (https://github.com/rxi/lite) fuzzy match implementation template constexpr int tFuzzyMatch( const T* str, const T* ptn ) { int score = 0; diff --git a/src/eepp/system/filesystem.cpp b/src/eepp/system/filesystem.cpp index 9bfde0177..1e0f87847 100644 --- a/src/eepp/system/filesystem.cpp +++ b/src/eepp/system/filesystem.cpp @@ -211,10 +211,11 @@ Uint32 FileSystem::fileGetModificationDate( const std::string& filepath ) { bool FileSystem::fileCanWrite( const std::string& filepath ) { #if EE_PLATFORM == EE_PLATFORM_WIN #if UNICODE - return 0 == ( GetFileAttributes( String::fromUtf8( filepath ).toWideString().c_str() ) & - FILE_ATTRIBUTE_READONLY ); + auto attrs = GetFileAttributes( String::fromUtf8( filepath ).toWideString().c_str() ); + return attrs != INVALID_FILE_ATTRIBUTES && 0 == ( attrs & FILE_ATTRIBUTE_READONLY ); #else - return 0 == ( GetFileAttributes( (LPCTSTR)filepath.c_str() ) & FILE_ATTRIBUTE_READONLY ); + auto attrs = GetFileAttributes( (LPCTSTR)filepath.c_str() ); + return attrs != INVALID_FILE_ATTRIBUTES && 0 == ( attrs & FILE_ATTRIBUTE_READONLY ); #endif #else struct stat st; @@ -235,10 +236,11 @@ bool FileSystem::fileCanWrite( const std::string& filepath ) { bool FileSystem::fileIsHidden( const std::string& filepath ) { #if EE_PLATFORM == EE_PLATFORM_WIN #if UNICODE - return 0 != ( GetFileAttributes( String::fromUtf8( filepath ).toWideString().c_str() ) & - FILE_ATTRIBUTE_HIDDEN ); + auto attrs = GetFileAttributes( String::fromUtf8( filepath ).toWideString().c_str() ); + return attrs != INVALID_FILE_ATTRIBUTES && 0 != ( attrs & FILE_ATTRIBUTE_HIDDEN ); #else - return 0 != ( GetFileAttributes( (LPCTSTR)filepath.c_str() ) & FILE_ATTRIBUTE_HIDDEN ); + auto attrs = GetFileAttributes( (LPCTSTR)filepath.c_str() ); + return attrs != INVALID_FILE_ATTRIBUTES && 0 != ( attrs & FILE_ATTRIBUTE_HIDDEN ); #endif #else std::string filename( fileNameFromPath( filepath ) ); @@ -296,10 +298,11 @@ bool FileSystem::isDirectory( const std::string& path ) { return ( stat( path.c_str(), &st ) == 0 ) && S_ISDIR( st.st_mode ); #else #if UNICODE - return 0 != ( GetFileAttributes( String::fromUtf8( path ).toWideString().c_str() ) & - FILE_ATTRIBUTE_DIRECTORY ); + auto attrs = GetFileAttributes( String::fromUtf8( path ).toWideString().c_str() ); + return attrs != INVALID_FILE_ATTRIBUTES && 0 != ( attrs & FILE_ATTRIBUTE_DIRECTORY ); #else - return 0 != ( GetFileAttributes( (LPCTSTR)path.c_str() ) & FILE_ATTRIBUTE_DIRECTORY ); + auto attrs = GetFileAttributes( (LPCTSTR)path.c_str() ); + return attrs != INVALID_FILE_ATTRIBUTES && 0 != ( attrs & FILE_ATTRIBUTE_DIRECTORY ); #endif #endif } @@ -473,7 +476,7 @@ std::vector FileSystem::filesInfoGetInPath( std::string path, bool lin dirAddSlashAtEnd( path ); std::vector fileInfo; auto files = filesGetInPath( path, sortByName, foldersFirst, ignoreHidden ); - for ( const auto &file : files ) + for ( const auto& file : files ) fileInfo.emplace_back( FileInfo( path + file, linkInfo ) ); return fileInfo; } diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 2655c57b8..c2bec7308 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -1285,9 +1285,30 @@ static std::pair findType( const String& str, const String& find } } -TextRange TextDocument::find( String text, TextPosition from, const bool& caseSensitive, - const bool& wholeWord, const FindReplaceType& type, - TextRange restrictRange ) { +static std::pair findLastType( const String& str, const String& findStr, + const TextDocument::FindReplaceType& type ) { + switch ( type ) { + case TextDocument::FindReplaceType::LuaPattern: { + // TODO: Implement findLastType for Lua patterns + LuaPattern words( findStr ); + int start, end = 0; + words.find( str, start, end ); + if ( start < 0 ) + return { String::StringType::npos, String::StringType::npos }; + else + return { end, start }; + } + case TextDocument::FindReplaceType::Normal: + default: { + size_t res = str.rfind( findStr ); + return { res, String::InvalidPos == res ? res : res + findStr.size() }; + } + } +} + +TextRange TextDocument::findText( String text, TextPosition from, const bool& caseSensitive, + const bool& wholeWord, const FindReplaceType& type, + TextRange restrictRange ) { if ( text.empty() ) return TextRange(); from = sanitizePosition( from ); @@ -1325,16 +1346,20 @@ TextRange TextDocument::find( String text, TextPosition from, const bool& caseSe } if ( String::StringType::npos != col.first && ( !wholeWord || String::isWholeWord( line( i ).getText(), text, col.first ) ) ) { - return { { (Int64)i, (Int64)col.first }, { (Int64)i, (Int64)col.second } }; + 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; } } return TextRange(); } -TextPosition TextDocument::findLast( String text, TextPosition from, const bool& caseSensitive, - const bool& wholeWord, TextRange restrictRange ) { +TextRange TextDocument::findTextLast( String text, TextPosition from, const bool& caseSensitive, + const bool& wholeWord, const FindReplaceType& type, + TextRange restrictRange ) { if ( text.empty() ) - return TextPosition(); + return TextRange(); from = sanitizePosition( from ); TextPosition to = startOfDoc(); @@ -1342,34 +1367,175 @@ TextPosition TextDocument::findLast( String text, TextPosition from, const bool& restrictRange = sanitizeRange( restrictRange.normalized() ); to = restrictRange.start(); if ( from < restrictRange.start() || from > restrictRange.end() ) - return TextPosition(); + return TextRange(); } if ( !caseSensitive ) text.toLower(); + for ( Int64 i = from.line(); i >= to.line(); i-- ) { - size_t col; + std::pair col; if ( i == from.line() ) { - col = caseSensitive ? line( i ).getText().substr( 0, from.column() ).rfind( text ) - : String::toLower( line( i ).getText() ) - .substr( 0, from.column() ) - .rfind( text ); + col = caseSensitive + ? findLastType( line( i ).getText().substr( 0, from.column() ), text, type ) + : findLastType( String::toLower( line( i ).getText() ), text, type ); } else if ( i == to.line() ) { col = caseSensitive - ? line( i ).getText().substr( to.column() ).rfind( text ) - : String::toLower( line( i ).getText() ).substr( to.column() ).rfind( text ); - if ( String::StringType::npos != col ) - col += to.column(); + ? 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(); + } } else { - col = caseSensitive ? line( i ).getText().rfind( text ) - : String::toLower( line( i ).getText() ).rfind( text ); + col = caseSensitive + ? findLastType( line( i ).getText(), text, type ) + : findLastType( String::toLower( line( i ).getText() ), text, type ); } - if ( String::StringType::npos != col && - ( !wholeWord || String::isWholeWord( line( i ).getText(), text, col ) ) ) { - return { (Int64)i, (Int64)col }; + 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; } } - return TextPosition(); + return TextRange(); +} + +TextRange TextDocument::find( String text, TextPosition from, const bool& caseSensitive, + const bool& wholeWord, const FindReplaceType& type, + TextRange restrictRange ) { + std::vector textLines = text.split( '\n', true, true ); + + if ( !textLines.empty() ) { + from = sanitizePosition( from ); + + TextPosition to = endOfDoc(); + if ( restrictRange.isValid() ) { + restrictRange = sanitizeRange( restrictRange.normalized() ); + to = restrictRange.end(); + if ( from < restrictRange.start() || from > restrictRange.end() ) + return TextRange(); + } + + if ( textLines.size() == 1 ) + return findText( text, from, caseSensitive, wholeWord, type, restrictRange ); + + TextRange range = findText( textLines[0], from, caseSensitive, false, type, restrictRange ); + + if ( range.isValid() ) { + TextPosition initPos( range.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 ); + + String currentLine( mLines[initPos.line()].getText() ); + + if ( TextPosition( initPos.line(), (Int64)currentLine.size() - 1 ) > to ) + return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + + if ( caseSensitive ) { + currentLine.toLower(); + textLines[i].toLower(); + } + + if ( currentLine == textLines[i] ) { + initPos = TextPosition( initPos.line(), 0 ); + } else { + return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + } + } + + if ( initPos < from || initPos > to ) + return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + + String lastLine( mLines[initPos.line()].getText() ); + String curSearch( textLines[textLines.size() - 1] ); + + if ( TextPosition( initPos.line(), (Int64)curSearch.size() - 1 ) > to ) + return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + + if ( lastLine.size() < curSearch.size() ) + return find( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + + if ( String::startsWith( lastLine, curSearch ) ) + return TextRange( range.start(), TextPosition( initPos.line(), curSearch.size() ) ); + } + } + + return TextRange(); +} + +TextRange TextDocument::findLast( String text, TextPosition from, const bool& caseSensitive, + const bool& wholeWord, const FindReplaceType& type, + TextRange restrictRange ) { + std::vector textLines = text.split( '\n', true, true ); + + if ( !textLines.empty() ) { + from = sanitizePosition( from ); + + TextPosition to = startOfDoc(); + if ( restrictRange.isValid() ) { + restrictRange = sanitizeRange( restrictRange.normalized() ); + to = restrictRange.start(); + if ( from < restrictRange.start() || from > restrictRange.end() ) + return TextRange(); + } + + if ( textLines.size() == 1 ) + return findTextLast( text, from, caseSensitive, wholeWord, type, restrictRange ); + + TextRange range = + findTextLast( textLines[0], from, caseSensitive, false, type, restrictRange ); + + if ( range.isValid() ) { + TextPosition initPos( range.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 ); + + String currentLine( mLines[initPos.line()].getText() ); + + if ( TextPosition( initPos.line(), (Int64)currentLine.size() - 1 ) > to ) + return findLast( text, range.end(), caseSensitive, wholeWord, type, + restrictRange ); + + if ( caseSensitive ) { + currentLine.toLower(); + textLines[i].toLower(); + } + + if ( currentLine == textLines[i] ) { + initPos = TextPosition( initPos.line(), 0 ); + } else { + return findLast( text, range.end(), caseSensitive, wholeWord, type, + restrictRange ); + } + } + + if ( initPos < from || initPos > to ) + return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + + String lastLine( mLines[initPos.line()].getText() ); + String curSearch( textLines[textLines.size() - 1] ); + + if ( TextPosition( initPos.line(), (Int64)curSearch.size() - 1 ) > to ) + return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + + if ( lastLine.size() < curSearch.size() ) + return findLast( text, range.end(), caseSensitive, wholeWord, type, restrictRange ); + + if ( String::startsWith( lastLine, curSearch ) ) + return TextRange( range.start(), TextPosition( initPos.line(), curSearch.size() ) ); + } + } + + return TextRange(); } std::vector TextDocument::findAll( const String& text, const bool& caseSensitive, @@ -1408,7 +1574,7 @@ int TextDocument::replaceAll( const String& text, const String& replace, const b from = replaceSelection( replace ); count++; } - } while ( found.isValid() ); + } while ( found.isValid() && endOfDoc() != found.end() ); setSelection( startedPosition ); return count; } @@ -1424,7 +1590,7 @@ TextPosition TextDocument::replaceSelection( const String& replace ) { TextPosition TextDocument::replace( String search, const String& replace, TextPosition from, const bool& caseSensitive, const bool& wholeWord, const FindReplaceType& type, TextRange restrictRange ) { - TextRange found( find( search, from, caseSensitive, wholeWord, type, restrictRange ) ); + TextRange found( findText( search, from, caseSensitive, wholeWord, type, restrictRange ) ); if ( found.isValid() ) { setSelection( found ); deleteTo( 0 ); diff --git a/src/eepp/ui/keyboardshortcut.cpp b/src/eepp/ui/keyboardshortcut.cpp index 28553c514..e25001956 100644 --- a/src/eepp/ui/keyboardshortcut.cpp +++ b/src/eepp/ui/keyboardshortcut.cpp @@ -5,7 +5,7 @@ using namespace EE::Window; namespace EE { namespace UI { -static std::map MOD_MAP = { +std::map MOD_MAP = { {"lshift", KEYMOD_SHIFT}, {"rshift", KEYMOD_SHIFT}, {"left shift", KEYMOD_SHIFT}, {"right shift", KEYMOD_SHIFT}, {"shift", KEYMOD_SHIFT}, {"lctrl", KEYMOD_CTRL}, {"rctrl", KEYMOD_CTRL}, {"left ctrl", KEYMOD_CTRL}, {"right ctrl", KEYMOD_CTRL}, @@ -73,7 +73,6 @@ void KeyBindings::replaceKeybind( const KeyBindings::Shortcut& keys, const std:: if ( it != mShortcuts.end() ) { mShortcuts.erase( it ); erased = true; - break; } } while ( erased ); mShortcuts[sanitizeShortcut( keys )] = command; @@ -83,8 +82,6 @@ KeyBindings::Shortcut KeyBindings::getShortcutFromString( const std::string& key Shortcut shortcut; Uint32 mod = 0; auto keysSplit = String::split( keys, '+' ); - if ( keys.find( "keypad +" ) != std::string::npos ) - mod = 0; if ( keysSplit.size() == 1 && getKeyMod( keysSplit[0] ) && keys.find( "++" ) ) keysSplit.emplace_back( "+" ); if ( keysSplit.size() == 2 && getKeyMod( keysSplit[0] ) && diff --git a/src/eepp/ui/uitextinput.cpp b/src/eepp/ui/uitextinput.cpp index 081a8b7d6..e089948fe 100644 --- a/src/eepp/ui/uitextinput.cpp +++ b/src/eepp/ui/uitextinput.cpp @@ -200,8 +200,8 @@ void UITextInput::alignFix() { switch ( Font::getVerticalAlign( getFlags() ) ) { case UI_VALIGN_CENTER: mRealAlignOffset.y = - ( Float )( ( Int32 )( ( mSize.y - mPaddingPx.Top - mPaddingPx.Bottom ) / 2 - - mTextCache->getLineSpacing() / 2 ) ) - + (Float)( (Int32)( ( mSize.y - mPaddingPx.Top - mPaddingPx.Bottom ) / 2 - + mTextCache->getLineSpacing() / 2 ) ) - 1; break; case UI_VALIGN_BOTTOM: diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 727ea97a1..37d568758 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -1126,6 +1126,7 @@ std::map App::getDefaultKeybindings() { std::map App::getLocalKeybindings() { return { { { KEY_RETURN, KEYMOD_LALT }, "fullscreen-toggle" }, { { KEY_F3, KEYMOD_NONE }, "repeat-find" }, + { { KEY_F3, KEYMOD_SHIFT }, "find-prev" }, { { KEY_F12, KEYMOD_NONE }, "console-toggle" }, { { KEY_F, KEYMOD_CTRL }, "find-replace" }, { { KEY_Q, KEYMOD_CTRL }, "close-app" }, @@ -1336,6 +1337,9 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { doc.setCommand( "repeat-find", [&] { mDocSearchController->findNextText( mDocSearchController->getSearchState() ); } ); + doc.setCommand( "find-prev", [&] { + mDocSearchController->findPrevText( mDocSearchController->getSearchState() ); + } ); doc.setCommand( "close-folder", [&] { closeFolder(); } ); doc.setCommand( "close-app", [&] { closeApp(); } ); doc.setCommand( "fullscreen-toggle", [&]() { @@ -2239,9 +2243,12 @@ void App::init( const std::string& file, const Float& pidelDensity ) { - + + + + diff --git a/src/tools/codeeditor/docsearchcontroller.cpp b/src/tools/codeeditor/docsearchcontroller.cpp index 689c3bcf1..4c3f42e3e 100644 --- a/src/tools/codeeditor/docsearchcontroller.cpp +++ b/src/tools/codeeditor/docsearchcontroller.cpp @@ -1,6 +1,26 @@ #include "docsearchcontroller.hpp" #include "codeeditor.hpp" +static void replaceAllEscapedSequences( String& target, const String& that, const String& with ) { + std::string::size_type pos = 0; + while ( ( pos = target.find( that, pos ) ) != String::InvalidPos ) { + if ( pos > 0 && target[pos - 1] == '\\' ) { + target.erase( pos, 1 ); + } else { + target.erase( pos, that.length() ); + target.insert( pos, with ); + } + pos += with.length(); + } +} + +static void escapeSequences( String& txt ) { + replaceAllEscapedSequences( txt, "\\n", String( '\n' ) ); + replaceAllEscapedSequences( txt, "\\t", String( '\t' ) ); + replaceAllEscapedSequences( txt, "\\r", String( '\r' ) ); + replaceAllEscapedSequences( txt, "\\\\", String( '\\' ) ); +} + DocSearchController::DocSearchController( UICodeEditorSplitter* editorSplitter, App* app ) : mEditorSplitter( editorSplitter ), mApp( app ) {} @@ -22,6 +42,7 @@ void DocSearchController::initSearchBar( UISearchBar* searchBar ) { UITextInput* findInput = mSearchBarLayout->find( "search_find" ); UITextInput* replaceInput = mSearchBarLayout->find( "search_replace" ); UICheckBox* caseSensitiveChk = mSearchBarLayout->find( "case_sensitive" ); + UICheckBox* escapeSequenceChk = mSearchBarLayout->find( "escape_sequence" ); UICheckBox* wholeWordChk = mSearchBarLayout->find( "whole_word" ); UICheckBox* luaPatternChk = mSearchBarLayout->find( "lua_pattern" ); @@ -30,6 +51,11 @@ void DocSearchController::initSearchBar( UISearchBar* searchBar ) { mSearchState.caseSensitive = caseSensitiveChk->isChecked(); } ); + escapeSequenceChk->addEventListener( + Event::OnValueChange, [&, escapeSequenceChk]( const Event* ) { + mSearchState.escapeSequences = escapeSequenceChk->isChecked(); + } ); + wholeWordChk->addEventListener( Event::OnValueChange, [&, wholeWordChk]( const Event* ) { mSearchState.wholeWord = wholeWordChk->isChecked(); } ); @@ -88,16 +114,22 @@ void DocSearchController::initSearchBar( UISearchBar* searchBar ) { mSearchBarLayout->addCommand( "change-whole-word", [&, wholeWordChk] { wholeWordChk->setChecked( !wholeWordChk->isChecked() ); } ); + mSearchBarLayout->addCommand( "change-escape-sequence", [&, escapeSequenceChk] { + escapeSequenceChk->setChecked( !escapeSequenceChk->isChecked() ); + } ); mSearchBarLayout->addCommand( "toggle-lua-pattern", [&, luaPatternChk] { luaPatternChk->setChecked( !luaPatternChk->isChecked() ); } ); - mSearchBarLayout->getKeyBindings().addKeybindsString( { { "f3", "repeat-find" }, - { "ctrl+g", "repeat-find" }, - { "escape", "close-searchbar" }, - { "ctrl+r", "replace-all" }, - { "ctrl+s", "change-case" }, - { "ctrl+w", "change-whole-word" }, - { "ctrl+l", "toggle-lua-pattern" } } ); + mSearchBarLayout->getKeyBindings().addKeybindsString( + { { mApp->getKeybind( "repeat-find" ), "repeat-find" }, + { mApp->getKeybind( "find-prev" ), "find-prev" }, + { "ctrl+g", "repeat-find" }, + { "escape", "close-searchbar" }, + { "ctrl+r", "replace-all" }, + { "ctrl+s", "change-case" }, + { "ctrl+w", "change-whole-word" }, + { "ctrl+l", "toggle-lua-pattern" }, + { "ctrl+e", "change-escape-sequence" } } ); addReturnListener( findInput, "repeat-find" ); addReturnListener( replaceInput, "find-and-replace" ); addClickListener( mSearchBarLayout->find( "find_prev" ), "find-prev" ); @@ -123,6 +155,8 @@ void DocSearchController::showFindView() { mSearchState.caseSensitive = mSearchBarLayout->find( "case_sensitive" )->isChecked(); mSearchState.wholeWord = mSearchBarLayout->find( "whole_word" )->isChecked(); + mSearchState.escapeSequences = + mSearchBarLayout->find( "escape_sequence" )->isChecked(); mSearchBarLayout->setEnabled( true )->setVisible( true ); UITextInput* findInput = mSearchBarLayout->find( "search_find" ); @@ -156,6 +190,7 @@ bool DocSearchController::findPrevText( SearchState& search ) { if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) return false; + UITextInput* findInput = mSearchBarLayout->find( "search_find" ); search.editor->getDocument().setActiveClient( search.editor ); mLastSearch = search.text; TextDocument& doc = search.editor->getDocument(); @@ -166,18 +201,26 @@ bool DocSearchController::findPrevText( SearchState& search ) { from = from < range.start() ? range.start() : from; } - TextPosition found = - doc.findLast( search.text, from, search.caseSensitive, search.wholeWord, search.range ); + String txt( search.text ); + if ( search.escapeSequences ) + escapeSequences( txt ); + + TextRange found = doc.findLast( txt, from, search.caseSensitive, search.wholeWord, + search.type, search.range ); if ( found.isValid() ) { - doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } ); + doc.setSelection( found ); + findInput->removeClass( "error" ); return true; } else { - found = doc.findLast( search.text, range.end() ); + found = doc.findLast( txt, range.end(), search.caseSensitive, search.wholeWord, + search.type, range ); if ( found.isValid() ) { - doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } ); + doc.setSelection( found ); + findInput->removeClass( "error" ); return true; } } + findInput->addClass( "error" ); return false; } @@ -187,6 +230,7 @@ bool DocSearchController::findNextText( SearchState& search ) { if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) return false; + UITextInput* findInput = mSearchBarLayout->find( "search_find" ); search.editor->getDocument().setActiveClient( search.editor ); mLastSearch = search.text; TextDocument& doc = search.editor->getDocument(); @@ -197,19 +241,26 @@ bool DocSearchController::findNextText( SearchState& search ) { from = from < range.start() ? range.start() : from; } + String txt( search.text ); + if ( search.escapeSequences ) + escapeSequences( txt ); + TextRange found = - doc.find( search.text, from, search.caseSensitive, search.wholeWord, search.type, range ); + doc.find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ); if ( found.isValid() ) { doc.setSelection( found.reversed() ); + findInput->removeClass( "error" ); return true; } else { - found = doc.find( search.text, range.start(), search.caseSensitive, search.wholeWord, - search.type, range ); + found = doc.find( txt, range.start(), search.caseSensitive, search.wholeWord, + search.type, range ); if ( found.isValid() ) { doc.setSelection( found.reversed() ); + findInput->removeClass( "error" ); return true; } } + findInput->addClass( "error" ); return false; } @@ -233,8 +284,16 @@ int DocSearchController::replaceAll( SearchState& search, const String& replace mLastSearch = search.text; TextDocument& doc = search.editor->getDocument(); TextPosition startedPosition = doc.getSelection().start(); - int count = doc.replaceAll( search.text, replace, search.caseSensitive, search.wholeWord, - search.type, search.range ); + + String txt( search.text ); + String repl( replace ); + if ( search.escapeSequences ) { + escapeSequences( txt ); + escapeSequences( repl ); + } + + int count = doc.replaceAll( txt, repl, search.caseSensitive, search.wholeWord, search.type, + search.range ); doc.setSelection( startedPosition ); return count; } @@ -249,8 +308,16 @@ bool DocSearchController::findAndReplace( SearchState& search, const String& rep search.editor->getDocument().setActiveClient( search.editor ); mLastSearch = search.text; TextDocument& doc = search.editor->getDocument(); - if ( doc.hasSelection() && doc.getSelectedText() == search.text ) { - return replaceSelection( search, replace ); + + String txt( search.text ); + String repl( replace ); + if ( search.escapeSequences ) { + escapeSequences( txt ); + escapeSequences( repl ); + } + + if ( doc.hasSelection() && doc.getSelectedText() == txt ) { + return replaceSelection( search, repl ); } else { return findNextText( search ); } diff --git a/src/tools/codeeditor/docsearchcontroller.hpp b/src/tools/codeeditor/docsearchcontroller.hpp index 6e7a9bad7..8fecc0885 100644 --- a/src/tools/codeeditor/docsearchcontroller.hpp +++ b/src/tools/codeeditor/docsearchcontroller.hpp @@ -9,6 +9,7 @@ struct SearchState { TextRange range = TextRange(); bool caseSensitive{ false }; bool wholeWord{ false }; + bool escapeSequences{ false }; TextDocument::FindReplaceType type{ TextDocument::FindReplaceType::Normal }; void reset() { editor = nullptr;