whole words search/replace/highlight support in editors - close #424

This commit is contained in:
Vadim Lopatin 2017-09-08 12:09:33 +03:00
parent d3469713c6
commit 2a78f1d9d1
1 changed files with 72 additions and 19 deletions

View File

@ -49,6 +49,13 @@ interface EditableContentChangeListener {
void onEditableContentChanged(EditableContent source);
}
/// Flags used for search / replace / text highlight
enum TextSearchFlag {
CaseSensitive = 1,
WholeWords = 2,
SelectionOnly = 4,
}
/// Editor action codes
enum EditorActions : int {
None = 0,
@ -2736,15 +2743,16 @@ class EditBox : EditWidgetBase {
}
protected dstring _textToHighlight;
protected bool _textToHighlightCaseSensitive;
protected uint _textToHighlightOptions;
/// text pattern to highlight - e.g. for search
@property dstring textToHighlight() {
return _textToHighlight;
}
/// set text to highlight -- e.g. for search
void setTextToHighlight(dstring pattern, bool caseSensitive) {
void setTextToHighlight(dstring pattern, uint textToHighlightOptions) {
_textToHighlight = pattern;
_textToHighlightCaseSensitive = caseSensitive;
_textToHighlightOptions = textToHighlightOptions;
invalidate();
}
@ -2756,39 +2764,72 @@ class EditBox : EditWidgetBase {
return;
ptrdiff_t start = 0;
import std.string : indexOf, CaseSensitive, Yes, No;
bool caseSensitive = (_textToHighlightOptions & TextSearchFlag.CaseSensitive) != 0;
bool wholeWords = (_textToHighlightOptions & TextSearchFlag.WholeWords) != 0;
bool selectionOnly = (_textToHighlightOptions & TextSearchFlag.SelectionOnly) != 0;
for (;;) {
ptrdiff_t pos = lineText[start .. $].indexOf(_textToHighlight, _textToHighlightCaseSensitive ? Yes.caseSensitive : No.caseSensitive);
ptrdiff_t pos = lineText[start .. $].indexOf(_textToHighlight, caseSensitive ? Yes.caseSensitive : No.caseSensitive);
if (pos < 0)
break;
// found text to highlight
start += pos;
TextRange r = TextRange(TextPosition(lineIndex, cast(int)start), TextPosition(lineIndex, cast(int)(start + _textToHighlight.length)));
if (!wholeWords || isWholeWord(lineText, start, start + _textToHighlight.length)) {
TextRange r = TextRange(TextPosition(lineIndex, cast(int)start), TextPosition(lineIndex, cast(int)(start + _textToHighlight.length)));
uint color = r.isInsideOrNext(caretPos) ? _searchHighlightColorCurrent : _searchHighlightColorOther;
highlightLineRange(buf, lineRect, color, r);
}
start += _textToHighlight.length;
uint color = r.isInsideOrNext(caretPos) ? _searchHighlightColorCurrent : _searchHighlightColorOther;
highlightLineRange(buf, lineRect, color, r);
}
}
/// find all occurences of text pattern in content
TextRange[] findAll(dstring pattern, bool caseSensitive) {
static bool isWordChar(dchar ch) {
if (ch >= 'a' && ch <= 'z')
return false;
if (ch >= 'A' && ch <= 'Z')
return false;
if (ch == '_')
return false;
return true;
}
static bool isValidWordBound(dchar innerChar, dchar outerChar) {
return isWordChar(innerChar) || isWordChar(outerChar);
}
/// returns true if selected range of string is whole word
static bool isWholeWord(dstring lineText, size_t start, size_t end) {
if (start >= lineText.length || start >= end)
return false;
if (start > 0 && !isValidWordBound(lineText[start], lineText[start - 1]))
return false;
if (end > 0 && end < lineText.length && !isValidWordBound(lineText[end - 1], lineText[end]))
return false;
return true;
}
/// find all occurences of text pattern in content; options = bitset of TextSearchFlag
TextRange[] findAll(dstring pattern, uint options) {
TextRange[] res;
res.assumeSafeAppend();
if (!pattern.length)
return res;
import std.string : indexOf, CaseSensitive, Yes, No;
bool caseSensitive = (options & TextSearchFlag.CaseSensitive) != 0;
bool wholeWords = (options & TextSearchFlag.WholeWords) != 0;
bool selectionOnly = (options & TextSearchFlag.SelectionOnly) != 0;
for (int i = 0; i < _content.length; i++) {
dstring lineText = _content.line(i);
if (lineText.length < pattern.length)
continue;
ptrdiff_t start = 0;
for (;;) {
ptrdiff_t pos = lineText[start .. $].indexOf(_textToHighlight, _textToHighlightCaseSensitive ? Yes.caseSensitive : No.caseSensitive);
ptrdiff_t pos = lineText[start .. $].indexOf(pattern, caseSensitive ? Yes.caseSensitive : No.caseSensitive);
if (pos < 0)
break;
// found text to highlight
start += pos;
TextRange r = TextRange(TextPosition(i, cast(int)start), TextPosition(i, cast(int)(start + _textToHighlight.length)));
res ~= r;
if (!wholeWords || isWholeWord(lineText, start, start + pattern.length)) {
TextRange r = TextRange(TextPosition(i, cast(int)start), TextPosition(i, cast(int)(start + pattern.length)));
res ~= r;
}
start += _textToHighlight.length;
}
}
@ -2796,8 +2837,8 @@ class EditBox : EditWidgetBase {
}
/// find next occurence of text pattern in content, returns true if found
bool findNextPattern(ref TextPosition pos, dstring pattern, bool caseSensitive, int direction) {
TextRange[] all = findAll(pattern, caseSensitive);
bool findNextPattern(ref TextPosition pos, dstring pattern, uint searchOptions, int direction) {
TextRange[] all = findAll(pattern, searchOptions);
if (!all.length)
return false;
int currentIndex = -1;
@ -3424,6 +3465,8 @@ class FindPanel : HorizontalLayout {
_cbWholeWords = childById!ImageCheckButton("cbWholeWords");
_cbSelection = childById!CheckBox("cbSelection");
_cbCaseSensitive.checkChange = &onCaseSensitiveCheckChange;
_cbWholeWords.checkChange = &onCaseSensitiveCheckChange;
_cbSelection.checkChange = &onCaseSensitiveCheckChange;
if (!replace)
childById("replace").visibility = Visibility.Gone;
//_edFind = new EditLine("edFind"
@ -3492,15 +3535,25 @@ class FindPanel : HorizontalLayout {
}
}
uint makeSearchFlags() {
uint res = 0;
if (_cbCaseSensitive.checked)
res |= TextSearchFlag.CaseSensitive;
if (_cbWholeWords.checked)
res |= TextSearchFlag.WholeWords;
if (_cbSelection.checked)
res |= TextSearchFlag.SelectionOnly;
return res;
}
bool findNext(bool back) {
setDirection(back);
dstring currentText = _edFind.text;
Log.d("findNext text=", currentText, " back=", back);
if (!currentText.length)
return false;
_editor.setTextToHighlight(currentText, _cbCaseSensitive.checked);
_editor.setTextToHighlight(currentText, makeSearchFlags);
TextPosition pos = _editor.caretPos;
bool res = _editor.findNextPattern(pos, currentText, _cbCaseSensitive.checked, back ? -1 : 1);
bool res = _editor.findNextPattern(pos, currentText, makeSearchFlags, back ? -1 : 1);
if (res) {
_editor.selectionRange = TextRange(pos, TextPosition(pos.line, pos.pos + cast(int)currentText.length));
_editor.ensureCaretVisible();
@ -3515,9 +3568,9 @@ class FindPanel : HorizontalLayout {
Log.d("replaceOne text=", currentText, " back=", _backDirection, " newText=", newText);
if (!currentText.length)
return false;
_editor.setTextToHighlight(currentText, _cbCaseSensitive.checked);
_editor.setTextToHighlight(currentText, makeSearchFlags);
TextPosition pos = _editor.caretPos;
bool res = _editor.findNextPattern(pos, currentText, _cbCaseSensitive.checked, 0);
bool res = _editor.findNextPattern(pos, currentText, makeSearchFlags, 0);
if (res) {
_editor.selectionRange = TextRange(pos, TextPosition(pos.line, pos.pos + cast(int)currentText.length));
_editor.replaceSelectionText(newText);
@ -3554,7 +3607,7 @@ class FindPanel : HorizontalLayout {
void updateHighlight() {
dstring currentText = _edFind.text;
Log.d("onFindTextChange.currentText=", currentText);
_editor.setTextToHighlight(currentText, _cbCaseSensitive.checked);
_editor.setTextToHighlight(currentText, makeSearchFlags);
}
void onFindTextChange(EditableContent source) {