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