diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index 93880570e..0263d0a19 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -69,6 +69,12 @@ class EE_API String { return 0; } + /** Escape string sequence */ + static String escape( const String& str ); + + /** Unescape string sequence */ + static String unescape( const String& str ); + /** @return string hash */ static String::HashType hash( const std::string& str ); @@ -776,6 +782,10 @@ class EE_API String { String& toUpper(); + String& escape(); + + String& unescape(); + StringBaseType lastChar() const; std::vector split( const StringBaseType& delim = '\n', diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index ec6a3b25b..b7e3c569f 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -112,6 +112,154 @@ Int64 String::BMH::find( const std::string& haystack, const std::string& needle, return find( haystack, needle, haystackOffset, occ ); } +String String::escape( const String& str ) { + String output; + for ( size_t i = 0; i < str.size(); i++ ) { + switch ( str[i] ) { + case '\r': + output += "\\r"; + break; + case '\t': + output += "\\t"; + break; + case '\n': + output += "\\n"; + break; + case '\a': + output += "\\a"; + break; + case '\b': + output += "\\b"; + break; + case '\f': + output += "\\f"; + break; + case '\v': + output += "\\v"; + break; + default: { + output += str[i]; + break; + } + } + } + return output; +} + +String String::unescape( const String& str ) { + String output; + for ( size_t i = 0; i < str.size(); i++ ) { + if ( i > 0 && str[i - 1] == '\\' ) { + switch ( str[i] ) { + case '\\': + output.push_back( '\\' ); + break; + case 'r': + output.push_back( '\r' ); + break; + case 't': + output.push_back( '\t' ); + break; + case 'n': + output.push_back( '\n' ); + break; + case '\'': + output.push_back( '\'' ); + break; + case '"': + output.push_back( '"' ); + break; + case '?': + output.push_back( '\?' ); + break; + case 'a': + output.push_back( '\a' ); + break; + case 'b': + output.push_back( '\b' ); + break; + case 'f': + output.push_back( '\f' ); + break; + case 'v': + output.push_back( '\v' ); + break; + case 'x': { + size_t len = 2; + if ( i + len < str.size() ) { + std::string buffer; + for ( i = i + 1; i < str.size(); i++ ) + if ( std::isdigit( str[i] ) || ( str[i] >= 'a' && str[i] <= 'f' ) || + ( str[i] >= 'A' && str[i] <= 'F' ) ) { + buffer.push_back( str[i] ); + } else { + break; + } + if ( buffer.size() >= len ) { + Uint32 value; + if ( String::fromString( value, buffer, std::hex ) ) + output.push_back( value ); + } + } + break; + } + case 'u': + case 'U': { + size_t len = str[i] == 'u' ? 4 : 8; + if ( i + len < str.size() ) { + size_t to = i + len; + std::string buffer; + for ( i = i + 1; i <= to; i++ ) { + if ( std::isdigit( str[i] ) || ( str[i] >= 'a' && str[i] <= 'f' ) || + ( str[i] >= 'A' && str[i] <= 'F' ) ) { + buffer.push_back( str[i] ); + } + } + if ( buffer.size() == len ) { + Uint32 value; + if ( String::fromString( value, buffer, std::hex ) ) + output.push_back( value ); + } + } + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': { + size_t to = eemin( (size_t)i + 3, str.size() ); + std::string buffer; + for ( ; i < to; i++ ) { + if ( ( str[i] >= '0' && str[i] <= '8' ) ) { + buffer.push_back( str[i] ); + } else { + break; + } + } + if ( buffer.size() <= 3 ) { + Uint32 value; + if ( String::fromString( value, buffer, std::oct ) ) + output.push_back( value ); + } + break; + } + default: { + // Undefined behavior + break; + } + } + } else if ( str[i] != '\\' ) { + output.push_back( str[i] ); + } + } + return output; +} + String::HashType String::hash( const std::string& str ) { return String::hash( str.c_str() ); } @@ -400,6 +548,16 @@ String& String::toUpper() { return *this; } +String& String::escape() { + this->assign( String::escape( *this ) ); + return *this; +} + +String& String::unescape() { + this->assign( String::unescape( *this ) ); + return *this; +} + String::StringBaseType String::lastChar() const { return mString.empty() ? std::numeric_limits::max() : mString[mString.size() - 1]; diff --git a/src/tools/codeeditor/docsearchcontroller.cpp b/src/tools/codeeditor/docsearchcontroller.cpp index 8ec524389..baf6c3ecd 100644 --- a/src/tools/codeeditor/docsearchcontroller.cpp +++ b/src/tools/codeeditor/docsearchcontroller.cpp @@ -1,26 +1,6 @@ #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 unescapeSequences( 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 ) {} @@ -165,8 +145,14 @@ void DocSearchController::showFindView() { const TextDocument& doc = editor->getDocument(); - if ( doc.getSelection().hasSelection() && doc.getSelection().inSameLine() ) { + if ( doc.getSelection().hasSelection() ) { String text = doc.getSelectedText(); + if ( !doc.getSelection().inSameLine() ) { + text.escape(); + UICheckBox* escapeSequenceChk = mSearchBarLayout->find( "escape_sequence" ); + if ( !escapeSequenceChk->isChecked() ) + escapeSequenceChk->setChecked( true ); + } if ( !text.empty() ) { findInput->setText( text ); findInput->getDocument().selectAll(); @@ -203,17 +189,17 @@ bool DocSearchController::findPrevText( SearchState& search ) { String txt( search.text ); if ( search.escapeSequences ) - unescapeSequences( txt ); + txt.unescape(); - TextRange found = doc.findLast( txt, from, search.caseSensitive, search.wholeWord, - search.type, search.range ); + TextRange found = doc.findLast( txt, from, search.caseSensitive, search.wholeWord, search.type, + search.range ); if ( found.isValid() ) { doc.setSelection( found ); findInput->removeClass( "error" ); return true; } else { - found = doc.findLast( txt, range.end(), search.caseSensitive, search.wholeWord, - search.type, range ); + found = doc.findLast( txt, range.end(), search.caseSensitive, search.wholeWord, search.type, + range ); if ( found.isValid() ) { doc.setSelection( found ); findInput->removeClass( "error" ); @@ -243,7 +229,7 @@ bool DocSearchController::findNextText( SearchState& search ) { String txt( search.text ); if ( search.escapeSequences ) - unescapeSequences( txt ); + txt.unescape(); TextRange found = doc.find( txt, from, search.caseSensitive, search.wholeWord, search.type, range ); @@ -252,8 +238,8 @@ bool DocSearchController::findNextText( SearchState& search ) { findInput->removeClass( "error" ); return true; } else { - found = doc.find( txt, 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" ); @@ -288,8 +274,8 @@ int DocSearchController::replaceAll( SearchState& search, const String& replace String txt( search.text ); String repl( replace ); if ( search.escapeSequences ) { - unescapeSequences( txt ); - unescapeSequences( repl ); + txt.unescape(); + repl.unescape(); } int count = doc.replaceAll( txt, repl, search.caseSensitive, search.wholeWord, search.type, @@ -312,8 +298,8 @@ bool DocSearchController::findAndReplace( SearchState& search, const String& rep String txt( search.text ); String repl( replace ); if ( search.escapeSequences ) { - unescapeSequences( txt ); - unescapeSequences( repl ); + txt.unescape(); + repl.unescape(); } if ( doc.hasSelection() && doc.getSelectedText() == txt ) {