mirror of https://github.com/buggins/dlangui.git
Editors: navigation by words - Ctrl/Left, Ctrl/Right
This commit is contained in:
parent
aa4e1411b2
commit
fb55d64849
|
@ -477,7 +477,10 @@ class EditableContent {
|
||||||
return ch >= '0' && ch <= '9';
|
return ch >= '0' && ch <= '9';
|
||||||
}
|
}
|
||||||
static bool isAlpha(dchar ch) pure nothrow {
|
static bool isAlpha(dchar ch) pure nothrow {
|
||||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch == '_');
|
return isLowerAlpha(ch) || isUpperAlpha(ch);
|
||||||
|
}
|
||||||
|
static bool isAlNum(dchar ch) pure nothrow {
|
||||||
|
return isDigit(ch) || isAlpha(ch);
|
||||||
}
|
}
|
||||||
static bool isLowerAlpha(dchar ch) pure nothrow {
|
static bool isLowerAlpha(dchar ch) pure nothrow {
|
||||||
return (ch >= 'a' && ch <= 'z') || (ch == '_');
|
return (ch >= 'a' && ch <= 'z') || (ch == '_');
|
||||||
|
@ -485,6 +488,37 @@ class EditableContent {
|
||||||
static bool isUpperAlpha(dchar ch) pure nothrow {
|
static bool isUpperAlpha(dchar ch) pure nothrow {
|
||||||
return (ch >= 'A' && ch <= 'Z');
|
return (ch >= 'A' && ch <= 'Z');
|
||||||
}
|
}
|
||||||
|
static bool isPunct(dchar ch) pure nothrow {
|
||||||
|
switch(ch) {
|
||||||
|
case '.':
|
||||||
|
case ',':
|
||||||
|
case ';':
|
||||||
|
case '?':
|
||||||
|
case '!':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static bool isBracket(dchar ch) pure nothrow {
|
||||||
|
switch(ch) {
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static bool isWordBound(dchar thischar, dchar nextchar) {
|
||||||
|
return (isAlNum(thischar) && !isAlNum(nextchar))
|
||||||
|
|| (isPunct(thischar) && !isPunct(nextchar))
|
||||||
|
|| (isBracket(thischar) && !isBracket(nextchar))
|
||||||
|
|| (thischar != ' ' && nextchar == ' ');
|
||||||
|
}
|
||||||
|
|
||||||
/// change text position to nearest word bound (direction < 0 - back, > 0 - forward)
|
/// change text position to nearest word bound (direction < 0 - back, > 0 - forward)
|
||||||
TextPosition moveByWord(TextPosition p, int direction, bool camelCasePartsAsWords) {
|
TextPosition moveByWord(TextPosition p, int direction, bool camelCasePartsAsWords) {
|
||||||
|
@ -502,6 +536,26 @@ class EditableContent {
|
||||||
// to beginning of line
|
// to beginning of line
|
||||||
p.pos = 0;
|
p.pos = 0;
|
||||||
} else {
|
} else {
|
||||||
|
dstring txt = line(p.line);
|
||||||
|
int found = -1;
|
||||||
|
for (int i = p.pos - 1; i > 0; i--) {
|
||||||
|
// check if position i + 1 is after word end
|
||||||
|
dchar thischar = i >= 0 && i < linelen ? txt[i] : ' ';
|
||||||
|
if (thischar == '\t')
|
||||||
|
thischar = ' ';
|
||||||
|
dchar nextchar = i - 1 >= 0 && i - 1 < linelen ? txt[i - 1] : ' ';
|
||||||
|
if (nextchar == '\t')
|
||||||
|
nextchar = ' ';
|
||||||
|
if (isWordBound(thischar, nextchar)
|
||||||
|
|| (camelCasePartsAsWords && isUpperAlpha(thischar) && isLowerAlpha(nextchar))) {
|
||||||
|
found = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found >= 0)
|
||||||
|
p.pos = found;
|
||||||
|
else
|
||||||
|
p.pos = 0;
|
||||||
}
|
}
|
||||||
} else if (direction > 0) {
|
} else if (direction > 0) {
|
||||||
// forward
|
// forward
|
||||||
|
@ -520,15 +574,11 @@ class EditableContent {
|
||||||
dchar thischar = txt[i];
|
dchar thischar = txt[i];
|
||||||
if (thischar == '\t')
|
if (thischar == '\t')
|
||||||
thischar = ' ';
|
thischar = ' ';
|
||||||
dchar nextchar = i < linelen ? txt[i + 1] : ' ';
|
dchar nextchar = i < linelen - 1 ? txt[i + 1] : ' ';
|
||||||
if (nextchar == '\t')
|
if (nextchar == '\t')
|
||||||
nextchar = ' ';
|
nextchar = ' ';
|
||||||
bool thisalpha = isAlpha(thischar);
|
if (isWordBound(thischar, nextchar)
|
||||||
bool nextalpha = isAlpha(nextchar);
|
|| (camelCasePartsAsWords && isLowerAlpha(thischar) && isUpperAlpha(nextchar))) {
|
||||||
if ((thisalpha && !nextalpha)
|
|
||||||
|| (camelCasePartsAsWords && isLowerAlpha(thischar) && isUpperAlpha(nextchar))
|
|
||||||
|| (thischar != ' ' && nextchar == ' ')
|
|
||||||
) {
|
|
||||||
found = i + 1;
|
found = i + 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1104,6 +1154,29 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
&& _selectionRange.start.pos == 0;
|
&& _selectionRange.start.pos == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected bool _camelCasePartsAsWords = true;
|
||||||
|
|
||||||
|
protected bool removeSelectionTextIfSelected() {
|
||||||
|
if (_selectionRange.empty)
|
||||||
|
return false;
|
||||||
|
// clear selection
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [""d]);
|
||||||
|
_content.performOperation(op, this);
|
||||||
|
ensureCaretVisible();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool removeRangeText(TextRange range) {
|
||||||
|
if (range.empty)
|
||||||
|
return false;
|
||||||
|
_selectionRange = range;
|
||||||
|
_caretPos = _selectionRange.end;
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, range, [""d]);
|
||||||
|
_content.performOperation(op, this);
|
||||||
|
ensureCaretVisible();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
override protected bool handleAction(Action a) {
|
override protected bool handleAction(Action a) {
|
||||||
TextPosition oldCaretPos = _caretPos;
|
TextPosition oldCaretPos = _caretPos;
|
||||||
dstring currentLine = _content[_caretPos.line];
|
dstring currentLine = _content[_caretPos.line];
|
||||||
|
@ -1135,6 +1208,28 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
ensureCaretVisible();
|
ensureCaretVisible();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
case EditorActions.WordLeft:
|
||||||
|
case EditorActions.SelectWordLeft:
|
||||||
|
{
|
||||||
|
TextPosition newpos = _content.moveByWord(_caretPos, -1, _camelCasePartsAsWords);
|
||||||
|
if (newpos != _caretPos) {
|
||||||
|
_caretPos = newpos;
|
||||||
|
updateSelectionAfterCursorMovement(oldCaretPos, a.id == EditorActions.SelectWordLeft);
|
||||||
|
ensureCaretVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case EditorActions.WordRight:
|
||||||
|
case EditorActions.SelectWordRight:
|
||||||
|
{
|
||||||
|
TextPosition newpos = _content.moveByWord(_caretPos, 1, _camelCasePartsAsWords);
|
||||||
|
if (newpos != _caretPos) {
|
||||||
|
_caretPos = newpos;
|
||||||
|
updateSelectionAfterCursorMovement(oldCaretPos, a.id == EditorActions.SelectWordRight);
|
||||||
|
ensureCaretVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
case EditorActions.DocumentBegin:
|
case EditorActions.DocumentBegin:
|
||||||
case EditorActions.SelectDocumentBegin:
|
case EditorActions.SelectDocumentBegin:
|
||||||
if (_caretPos.pos > 0 || _caretPos.line > 0) {
|
if (_caretPos.pos > 0 || _caretPos.line > 0) {
|
||||||
|
@ -1169,55 +1264,63 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
updateSelectionAfterCursorMovement(oldCaretPos, (a.id & 1) != 0);
|
updateSelectionAfterCursorMovement(oldCaretPos, (a.id & 1) != 0);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
case EditorActions.DelPrevWord:
|
||||||
|
if (readOnly)
|
||||||
|
return true;
|
||||||
|
correctCaretPos();
|
||||||
|
if (removeSelectionTextIfSelected()) // clear selection
|
||||||
|
return true;
|
||||||
|
TextPosition newpos = _content.moveByWord(_caretPos, -1, _camelCasePartsAsWords);
|
||||||
|
if (newpos != _caretPos) {
|
||||||
|
removeRangeText(TextRange(newpos, _caretPos));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case EditorActions.DelNextWord:
|
||||||
|
if (readOnly)
|
||||||
|
return true;
|
||||||
|
correctCaretPos();
|
||||||
|
if (removeSelectionTextIfSelected()) // clear selection
|
||||||
|
return true;
|
||||||
|
TextPosition newpos = _content.moveByWord(_caretPos, 1, _camelCasePartsAsWords);
|
||||||
|
if (newpos != _caretPos) {
|
||||||
|
removeRangeText(TextRange(_caretPos, newpos));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
case EditorActions.DelPrevChar:
|
case EditorActions.DelPrevChar:
|
||||||
if (readOnly)
|
if (readOnly)
|
||||||
return true;
|
return true;
|
||||||
if (!_selectionRange.empty) {
|
|
||||||
// clear selection
|
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [""d]);
|
|
||||||
_content.performOperation(op, this);
|
|
||||||
ensureCaretVisible();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
correctCaretPos();
|
correctCaretPos();
|
||||||
|
if (removeSelectionTextIfSelected()) // clear selection
|
||||||
|
return true;
|
||||||
if (_caretPos.pos > 0) {
|
if (_caretPos.pos > 0) {
|
||||||
// delete prev char in current line
|
// delete prev char in current line
|
||||||
TextRange range = TextRange(_caretPos, _caretPos);
|
TextRange range = TextRange(_caretPos, _caretPos);
|
||||||
range.start.pos--;
|
range.start.pos--;
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, range, [""d]);
|
removeRangeText(range);
|
||||||
_content.performOperation(op, this);
|
|
||||||
} else if (_caretPos.line > 0) {
|
} else if (_caretPos.line > 0) {
|
||||||
// merge with previous line
|
// merge with previous line
|
||||||
TextRange range = TextRange(_caretPos, _caretPos);
|
TextRange range = TextRange(_caretPos, _caretPos);
|
||||||
range.start.line--;
|
range.start = _content.lineEnd(range.start.line - 1);
|
||||||
dstring prevLine = _content[range.start.line];
|
removeRangeText(range);
|
||||||
range.start.pos = cast(int)prevLine.length;
|
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, range, [""d]);
|
|
||||||
_content.performOperation(op, this);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case EditorActions.DelNextChar:
|
case EditorActions.DelNextChar:
|
||||||
if (readOnly)
|
if (readOnly)
|
||||||
return true;
|
return true;
|
||||||
if (!_selectionRange.empty) {
|
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [""d]);
|
|
||||||
_content.performOperation(op, this);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
correctCaretPos();
|
correctCaretPos();
|
||||||
|
if (removeSelectionTextIfSelected()) // clear selection
|
||||||
|
return true;
|
||||||
if (_caretPos.pos < currentLine.length) {
|
if (_caretPos.pos < currentLine.length) {
|
||||||
// delete char in current line
|
// delete char in current line
|
||||||
TextRange range = TextRange(_caretPos, _caretPos);
|
TextRange range = TextRange(_caretPos, _caretPos);
|
||||||
range.end.pos++;
|
range.end.pos++;
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, range, [""d]);
|
removeRangeText(range);
|
||||||
_content.performOperation(op, this);
|
|
||||||
} else if (_caretPos.line < _content.length - 1) {
|
} else if (_caretPos.line < _content.length - 1) {
|
||||||
// merge with next line
|
// merge with next line
|
||||||
TextRange range = TextRange(_caretPos, _caretPos);
|
TextRange range = TextRange(_caretPos, _caretPos);
|
||||||
range.end.line++;
|
range.end.line++;
|
||||||
range.end.pos = 0;
|
range.end.pos = 0;
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, range, [""d]);
|
removeRangeText(range);
|
||||||
_content.performOperation(op, this);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case EditorActions.Copy:
|
case EditorActions.Copy:
|
||||||
|
@ -1604,10 +1707,6 @@ class EditLine : EditWidgetBase {
|
||||||
break;
|
break;
|
||||||
case EditorActions.Down:
|
case EditorActions.Down:
|
||||||
break;
|
break;
|
||||||
case EditorActions.WordLeft:
|
|
||||||
break;
|
|
||||||
case EditorActions.WordRight:
|
|
||||||
break;
|
|
||||||
case EditorActions.PageUp:
|
case EditorActions.PageUp:
|
||||||
break;
|
break;
|
||||||
case EditorActions.PageDown:
|
case EditorActions.PageDown:
|
||||||
|
@ -1933,12 +2032,6 @@ class EditBox : EditWidgetBase, OnScrollHandler {
|
||||||
ensureCaretVisible();
|
ensureCaretVisible();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case EditorActions.WordLeft:
|
|
||||||
case EditorActions.SelectWordLeft:
|
|
||||||
break;
|
|
||||||
case EditorActions.WordRight:
|
|
||||||
case EditorActions.SelectWordRight:
|
|
||||||
break;
|
|
||||||
case EditorActions.PageBegin:
|
case EditorActions.PageBegin:
|
||||||
case EditorActions.SelectPageBegin:
|
case EditorActions.SelectPageBegin:
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue