TextDocument find improvements: lua pattern find and whole word filter.

This commit is contained in:
Martín Lucas Golini
2020-12-25 17:45:41 -03:00
parent dccd0617e5
commit ccb22f34aa
9 changed files with 195 additions and 76 deletions

View File

@@ -88,9 +88,19 @@ class EE_API String {
/** @return If the value passed is a letter */
static bool isLetter( const int& value );
/** @return If the value passed is a letter or a number */
static bool isAlphaNum( const int& value );
/** @return If the string is a representation of a hexa number */
static bool isHexNotation( const std::string& value, const std::string& withPrefix = "" );
/** @return If the needle substring, found starting at startPos is a whole-word. */
static bool isWholeWord( const std::string& haystack, const std::string& needle,
const Int64& startPos );
/** @return If the needle substring, found starting at startPos is a whole-word. */
static bool isWholeWord( const String& haystack, const String& needle, const Int64& startPos );
/** Split a String and hold it on a vector */
static std::vector<String> split( const String& str, const StringBaseType& delim = '\n',
const bool& pushEmptyString = false );

View File

@@ -31,6 +31,8 @@ class EE_API TextDocument {
enum class LineEnding { LF, CRLF };
enum class FindReplaceType { Normal, LuaPattern };
class EE_API Client {
public:
virtual ~Client();
@@ -247,17 +249,31 @@ class EE_API TextDocument {
void setCommand( const std::string& command, DocumentCommand func );
TextPosition find( String text, TextPosition from = { 0, 0 }, const bool& caseSensitive = true,
TextRange restrictRange = TextRange() );
TextRange find( String text, TextPosition from = { 0, 0 }, const bool& caseSensitive = true,
const bool& wholeWord = false,
const FindReplaceType& type = FindReplaceType::Normal,
TextRange restrictRange = TextRange() );
TextPosition findLast( String text, TextPosition from = { 0, 0 },
const bool& caseSensitive = true,
const bool& caseSensitive = true, const bool& wholeWord = false,
TextRange restrictRange = TextRange() );
std::vector<TextRange> findAll( const String& text, const bool& caseSensitive = true,
const bool& wholeWord = false,
const FindReplaceType& type = FindReplaceType::Normal,
TextRange restrictRange = TextRange() );
int replaceAll( const String& text, const String& replace, const bool& caseSensitive = true,
const bool& wholeWord = false,
const FindReplaceType& type = FindReplaceType::Normal,
TextRange restrictRange = TextRange() );
TextPosition replaceSelection( const String& replace );
TextPosition replace( String search, const String& replace, TextPosition from = { 0, 0 },
const bool& caseSensitive = true, TextRange restrictRange = TextRange() );
const bool& caseSensitive = true, const bool& wholeWord = false,
const FindReplaceType& type = FindReplaceType::Normal,
TextRange restrictRange = TextRange() );
String getIndentString();

View File

@@ -12,6 +12,7 @@ class EE_API TextRange {
mStart( start ), mEnd( end ) {}
bool isValid() const { return mStart.isValid() && mEnd.isValid(); }
void clear() {
mStart = {};
mEnd = {};
@@ -27,6 +28,8 @@ class EE_API TextRange {
TextRange normalized() const { return TextRange( normalizedStart(), normalizedEnd() ); }
TextRange reversed() { return TextRange( mEnd, mStart ); }
void setStart( const TextPosition& position ) { mStart = position; }
void setEnd( const TextPosition& position ) { mEnd = position; }

View File

@@ -145,6 +145,10 @@ bool String::isLetter( const int& value ) {
( value != 215 ) && ( value != 247 ) );
}
bool String::isAlphaNum( const int& value ) {
return isLetter( value ) || isNumber( value );
}
bool String::isHexNotation( const std::string& value, const std::string& withPrefix ) {
if ( !withPrefix.empty() && !String::startsWith( value, withPrefix ) ) {
return false;
@@ -1165,4 +1169,17 @@ String operator+( const String& left, const String& right ) {
return string;
}
bool String::isWholeWord( const std::string& haystack, const std::string& needle,
const Int64& startPos ) {
return ( 0 == startPos || !( std::isalnum( haystack[startPos - 1] ) ) ) &&
( startPos + needle.size() >= haystack.size() ||
!( std::isalnum( haystack[startPos + needle.size()] ) ) );
}
bool String::isWholeWord( const String& haystack, const String& needle, const Int64& startPos ) {
return ( 0 == startPos || !( isAlphaNum( haystack[startPos - 1] ) ) ) &&
( startPos + needle.size() >= haystack.size() ||
!( isAlphaNum( haystack[startPos + needle.size()] ) ) );
}
} // namespace EE

View File

@@ -1235,10 +1235,31 @@ void TextDocument::setCommand( const std::string& command, TextDocument::Documen
mCommands[command] = func;
}
TextPosition TextDocument::find( String text, TextPosition from, const bool& caseSensitive,
TextRange restrictRange ) {
static std::pair<size_t, size_t> findType( const String& str, const String& findStr,
const TextDocument::FindReplaceType& type ) {
switch ( type ) {
case TextDocument::FindReplaceType::LuaPattern: {
LuaPattern words( findStr );
int start, end = 0;
words.find( str, start, end );
if ( start < 0 )
return { String::StringType::npos, String::StringType::npos };
else
return { start, end };
}
case TextDocument::FindReplaceType::Normal:
default: {
size_t res = str.find( findStr );
return { res, String::InvalidPos == res ? res : res + findStr.size() };
}
}
}
TextRange TextDocument::find( 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 = endOfDoc();
@@ -1246,38 +1267,42 @@ TextPosition TextDocument::find( String text, TextPosition from, const bool& cas
restrictRange = sanitizeRange( restrictRange.normalized() );
to = restrictRange.end();
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<size_t, size_t> col;
if ( i == from.line() ) {
col = caseSensitive
? line( i ).getText().substr( from.column() ).find( text )
: String::toLower( line( i ).getText() ).substr( from.column() ).find( text );
if ( String::StringType::npos != col )
col += from.column();
? 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();
}
} else if ( i == to.line() && to != endOfDoc() ) {
col =
caseSensitive
? line( i ).getText().substr( 0, to.column() ).find( text )
: String::toLower( line( i ).getText() ).substr( 0, to.column() ).find( text );
col = caseSensitive
? findType( line( i ).getText().substr( 0, to.column() ), text, type )
: findType( String::toLower( line( i ).getText() ).substr( 0, to.column() ),
text, type );
} else {
col = caseSensitive ? line( i ).getText().find( text )
: String::toLower( line( i ).getText() ).find( text );
col = caseSensitive ? findType( line( i ).getText(), text, type )
: findType( String::toLower( line( i ).getText() ), text, type );
}
if ( String::StringType::npos != col ) {
return { (Int64)i, (Int64)col };
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 } };
}
}
return TextPosition();
return TextRange();
}
TextPosition TextDocument::findLast( String text, TextPosition from, const bool& caseSensitive,
TextRange restrictRange ) {
const bool& wholeWord, TextRange restrictRange ) {
if ( text.empty() )
return TextPosition();
from = sanitizePosition( from );
@@ -1309,13 +1334,55 @@ TextPosition TextDocument::findLast( String text, TextPosition from, const bool&
col = caseSensitive ? line( i ).getText().rfind( text )
: String::toLower( line( i ).getText() ).rfind( text );
}
if ( String::StringType::npos != col ) {
if ( String::StringType::npos != col &&
( !wholeWord || String::isWholeWord( line( i ).getText(), text, col ) ) ) {
return { (Int64)i, (Int64)col };
}
}
return TextPosition();
}
std::vector<TextRange> TextDocument::findAll( const String& text, const bool& caseSensitive,
const bool& wholeWord, const FindReplaceType& type,
TextRange restrictRange ) {
std::vector<TextRange> all;
TextRange found;
TextPosition from = startOfDoc();
if ( restrictRange.isValid() )
from = restrictRange.normalized().start();
do {
found = find( text, from, caseSensitive, wholeWord, type, restrictRange );
if ( found.isValid() ) {
from = found.end();
all.push_back( found );
}
} while ( found.isValid() );
return all;
}
int TextDocument::replaceAll( const String& text, const String& replace, const bool& caseSensitive,
const bool& wholeWord, const FindReplaceType& type,
TextRange restrictRange ) {
if ( text.empty() )
return 0;
int count = 0;
TextRange found;
TextPosition startedPosition = getSelection().start();
TextPosition from = startOfDoc();
if ( restrictRange.isValid() )
from = restrictRange.normalized().start();
do {
found = find( text, from, caseSensitive, wholeWord, type, restrictRange );
if ( found.isValid() ) {
setSelection( found );
from = replaceSelection( replace );
count++;
}
} while ( found.isValid() );
setSelection( startedPosition );
return count;
}
TextPosition TextDocument::replaceSelection( const String& replace ) {
if ( hasSelection() ) {
deleteTo( 0 );
@@ -1325,16 +1392,14 @@ TextPosition TextDocument::replaceSelection( const String& replace ) {
}
TextPosition TextDocument::replace( String search, const String& replace, TextPosition from,
const bool& caseSensitive, TextRange restrictRange ) {
TextPosition start( find( search, from, caseSensitive, restrictRange ) );
if ( start.isValid() ) {
TextPosition end = positionOffset( start, search.size() );
if ( end.isValid() ) {
setSelection( { start, end } );
deleteTo( 0 );
textInput( replace );
return end;
}
const bool& caseSensitive, const bool& wholeWord,
const FindReplaceType& type, TextRange restrictRange ) {
TextRange found( find( search, from, caseSensitive, wholeWord, type, restrictRange ) );
if ( found.isValid() ) {
setSelection( found );
deleteTo( 0 );
textInput( replace );
return found.end();
}
return TextPosition();
}

View File

@@ -271,7 +271,8 @@ bool App::findPrevText( SearchState& search ) {
from = from < range.start() ? range.start() : from;
}
TextPosition found = doc.findLast( search.text, from, search.caseSensitive, search.range );
TextPosition found =
doc.findLast( search.text, from, search.caseSensitive, search.wholeWord, search.range );
if ( found.isValid() ) {
doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } );
return true;
@@ -301,14 +302,16 @@ bool App::findNextText( SearchState& search ) {
from = from < range.start() ? range.start() : from;
}
TextPosition found = doc.find( search.text, from, search.caseSensitive, range );
TextRange found =
doc.find( search.text, from, search.caseSensitive, search.wholeWord, search.type, range );
if ( found.isValid() ) {
doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } );
doc.setSelection( found.reversed() );
return true;
} else {
found = doc.find( search.text, range.start(), search.caseSensitive, range );
found = doc.find( search.text, range.start(), search.caseSensitive, search.wholeWord,
search.type, range );
if ( found.isValid() ) {
doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } );
doc.setSelection( found.reversed() );
return true;
}
}
@@ -331,24 +334,12 @@ int App::replaceAll( SearchState& search, const String& replace ) {
search.text = mLastSearch;
if ( search.text.empty() )
return 0;
int count = 0;
search.editor->getDocument().setActiveClient( search.editor );
mLastSearch = search.text;
TextDocument& doc = search.editor->getDocument();
TextPosition found;
TextPosition startedPosition = doc.getSelection().start();
TextPosition from = doc.startOfDoc();
if ( search.range.isValid() )
from = search.range.normalized().start();
do {
found = doc.find( search.text, from, search.caseSensitive, search.range );
if ( found.isValid() ) {
doc.setSelection( { doc.positionOffset( found, search.text.size() ), found } );
from = doc.replaceSelection( replace );
count++;
}
} while ( found.isValid() );
int count = doc.replaceAll( search.text, replace, search.caseSensitive, search.wholeWord,
search.type, search.range );
doc.setSelection( startedPosition );
return count;
}
@@ -583,6 +574,22 @@ void App::initSearchBar() {
UITextInput* replaceInput = mSearchBarLayout->find<UITextInput>( "search_replace" );
UICheckBox* caseSensitiveChk = mSearchBarLayout->find<UICheckBox>( "case_sensitive" );
UICheckBox* wholeWordChk = mSearchBarLayout->find<UICheckBox>( "whole_word" );
UICheckBox* luaPatternChk = mSearchBarLayout->find<UICheckBox>( "lua_pattern" );
caseSensitiveChk->addEventListener(
Event::OnValueChange, [&, caseSensitiveChk]( const Event* ) {
mSearchState.caseSensitive = caseSensitiveChk->isChecked();
} );
wholeWordChk->addEventListener( Event::OnValueChange, [&, wholeWordChk]( const Event* ) {
mSearchState.wholeWord = wholeWordChk->isChecked();
} );
luaPatternChk->addEventListener( Event::OnValueChange, [&, luaPatternChk]( const Event* ) {
mSearchState.type = luaPatternChk->isChecked() ? TextDocument::FindReplaceType::LuaPattern
: TextDocument::FindReplaceType::Normal;
} );
findInput->addEventListener( Event::OnTextChanged, [&, findInput]( const Event* ) {
if ( mSearchState.editor && mEditorSplitter->editorExists( mSearchState.editor ) ) {
mSearchState.text = findInput->getText();
@@ -626,20 +633,20 @@ void App::initSearchBar() {
} );
mSearchBarLayout->addCommand( "change-case", [&, caseSensitiveChk] {
caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() );
mSearchState.caseSensitive = caseSensitiveChk->isChecked();
} );
mSearchBarLayout->addCommand( "change-whole-word", [&, wholeWordChk] {
wholeWordChk->setChecked( !wholeWordChk->isChecked() );
mSearchState.wholeWord = wholeWordChk->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" },
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" } } );
addReturnListener( findInput, "repeat-find" );
addReturnListener( replaceInput, "find-and-replace" );
addClickListener( mSearchBarLayout->find<UIPushButton>( "find_prev" ), "find-prev" );
@@ -2451,12 +2458,15 @@ void App::init( const std::string& file, const Float& pidelDensity ) {
<TextInput id="search_find" layout_width="match_parent" layout_height="18dp" padding="0" margin-bottom="2dp" />
<TextInput id="search_replace" layout_width="match_parent" layout_height="18dp" padding="0" />
</vbox>
<vbox layout_width="wrap_content" layout_height="wrap_content" margin-right="4dp">
<CheckBox id="case_sensitive" layout_width="wrap_content" layout_height="wrap_content" text="Case sensitive" selected="true" />
<CheckBox id="whole_word" layout_width="wrap_content" layout_height="wrap_content" text="Match Whole Word" selected="false" />
<CheckBox id="lua_pattern" layout_width="wrap_content" layout_height="wrap_content" text="Lua Pattern" selected="false" />
</vbox>
<vbox layout_width="wrap_content" layout_height="wrap_content">
<hbox layout_width="wrap_content" layout_height="wrap_content" margin-bottom="2dp">
<PushButton id="find_prev" layout_width="wrap_content" layout_height="18dp" text="Previous" margin-right="4dp" />
<PushButton id="find_next" layout_width="wrap_content" layout_height="18dp" text="Next" margin-right="4dp" />
<CheckBox id="case_sensitive" layout_width="wrap_content" layout_height="wrap_content" text="Case sensitive" selected="true" />
<CheckBox id="whole_word" layout_width="wrap_content" layout_height="wrap_content" text="Match Whole Word" selected="false" margin-left="8dp" visible="false" />
<RelativeLayout layout_width="0" layout_weight="1" layout_height="18dp">
<Widget id="searchbar_close" class="close_button" layout_width="wrap_content" layout_height="wrap_content" layout_gravity="center_vertical|right" margin-right="2dp" />
</RelativeLayout>

View File

@@ -87,6 +87,7 @@ struct SearchState {
TextRange range = TextRange();
bool caseSensitive{ false };
bool wholeWord{ false };
TextDocument::FindReplaceType type{ TextDocument::FindReplaceType::Normal };
void reset() {
editor = nullptr;
range = TextRange();

View File

@@ -28,7 +28,7 @@ LinterModule::LinterModule( const std::string& lintersPath, std::shared_ptr<Thre
LinterModule::~LinterModule() {
mClosing = true;
for ( auto editor : mEditors ) {
for ( const auto& editor : mEditors ) {
for ( auto listener : editor.second )
editor.first->removeEventListener( listener );
editor.first->unregisterModule( this );
@@ -294,8 +294,12 @@ void LinterModule::drawAfterLineText( UICodeEditor* editor, const Int64& index,
const String& text = doc->line( index ).getText();
size_t minCol = text.find_first_not_of( " \t\f\v\n\r", match.pos.column() );
if ( minCol == String::InvalidPos )
match.pos.column();
minCol = match.pos.column();
minCol = std::max( (Int64)minCol, match.pos.column() );
if ( minCol >= text.size() )
minCol = match.pos.column();
if ( minCol >= text.size() )
minCol = text.size() - 1;
std::string str( text.substr( minCol ).size() - 1, '~' );
String string( str );
@@ -312,7 +316,7 @@ bool LinterModule::onMouseMove( UICodeEditor* editor, const Vector2i& pos, const
auto it = mMatches.find( editor->getDocumentRef().get() );
if ( it != mMatches.end() ) {
Vector2f localPos( editor->convertToNodeSpace( pos.asFloat() ) );
for ( auto matchIt : it->second ) {
for ( const auto& matchIt : it->second ) {
auto& match = matchIt.second;
if ( match.box.contains( localPos ) ) {
editor->setTooltipText( match.text );

View File

@@ -33,13 +33,6 @@ static String textLine( const std::string& fileText, const size_t& fromPos, size
return fileText.substr( start, end - start );
}
static bool isWholeWord( const std::string& fileText, const std::string& text,
const Int64& searchRes ) {
return ( 0 == searchRes || !( std::isalnum( fileText[searchRes - 1] ) ) ) &&
( searchRes + text.size() >= fileText.size() ||
!( std::isalnum( fileText[searchRes + text.size()] ) ) );
}
static std::vector<ProjectSearch::ResultData::Result>
searchInFileHorspool( const std::string& file, const std::string& text, const bool& caseSensitive,
const bool& wholeWord, const String::BMH::OccTable& occ ) {
@@ -55,7 +48,7 @@ searchInFileHorspool( const std::string& file, const std::string& text, const bo
do {
searchRes = String::BMH::find( fileText, text, searchRes, occ );
if ( searchRes != -1 ) {
if ( wholeWord && !isWholeWord( fileText, text, searchRes ) ) {
if ( wholeWord && !String::isWholeWord( fileText, text, searchRes ) ) {
lSearchRes = searchRes;
searchRes += text.size();
continue;
@@ -75,7 +68,7 @@ searchInFileHorspool( const std::string& file, const std::string& text, const bo
do {
searchRes = String::BMH::find( fileText, text, searchRes, occ );
if ( searchRes != -1 ) {
if ( wholeWord && !isWholeWord( fileText, text, searchRes ) ) {
if ( wholeWord && !String::isWholeWord( fileText, text, searchRes ) ) {
lSearchRes = searchRes;
searchRes += text.size();
continue;