mirror of https://github.com/buggins/dlangui.git
Text editor line marks support, bookmarks support
This commit is contained in:
parent
732685c99d
commit
5baf435713
|
|
@ -433,6 +433,10 @@ interface EditableContentListener {
|
||||||
void onContentChange(EditableContent content, EditOperation operation, ref TextRange rangeBefore, ref TextRange rangeAfter, Object source);
|
void onContentChange(EditableContent content, EditOperation operation, ref TextRange rangeBefore, ref TextRange rangeAfter, Object source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface EditableContentMarksChangeListener {
|
||||||
|
void onMarksChange(EditableContent content, LineIcon[] movedMarks, LineIcon[] removedMarks);
|
||||||
|
}
|
||||||
|
|
||||||
/// TokenCategory holder
|
/// TokenCategory holder
|
||||||
alias TokenProp = ubyte;
|
alias TokenProp = ubyte;
|
||||||
/// TokenCategory string
|
/// TokenCategory string
|
||||||
|
|
@ -583,6 +587,8 @@ class EditableContent {
|
||||||
|
|
||||||
/// listeners for edit operations
|
/// listeners for edit operations
|
||||||
Signal!EditableContentListener contentChanged;
|
Signal!EditableContentListener contentChanged;
|
||||||
|
/// listeners for mark changes after edit operation
|
||||||
|
Signal!EditableContentMarksChangeListener marksChanged;
|
||||||
|
|
||||||
protected bool _multiline;
|
protected bool _multiline;
|
||||||
/// returns true if miltyline content is supported
|
/// returns true if miltyline content is supported
|
||||||
|
|
@ -959,6 +965,12 @@ class EditableContent {
|
||||||
void handleContentChange(EditOperation op, ref TextRange rangeBefore, ref TextRange rangeAfter, Object source) {
|
void handleContentChange(EditOperation op, ref TextRange rangeBefore, ref TextRange rangeAfter, Object source) {
|
||||||
// update highlight if necessary
|
// update highlight if necessary
|
||||||
updateTokenProps(rangeAfter.start.line, rangeAfter.end.line + 1);
|
updateTokenProps(rangeAfter.start.line, rangeAfter.end.line + 1);
|
||||||
|
LineIcon[] moved;
|
||||||
|
LineIcon[] removed;
|
||||||
|
if (_lineIcons.updateLinePositions(rangeBefore, rangeAfter, moved, removed)) {
|
||||||
|
if (marksChanged.assigned)
|
||||||
|
marksChanged(this, moved, removed);
|
||||||
|
}
|
||||||
// call listeners
|
// call listeners
|
||||||
if (contentChanged.assigned)
|
if (contentChanged.assigned)
|
||||||
contentChanged(this, op, rangeBefore, rangeAfter, source);
|
contentChanged(this, op, rangeBefore, rangeAfter, source);
|
||||||
|
|
@ -1438,10 +1450,20 @@ enum LineIconType : int {
|
||||||
|
|
||||||
/// text editor line icon
|
/// text editor line icon
|
||||||
class LineIcon {
|
class LineIcon {
|
||||||
|
/// mark type
|
||||||
|
LineIconType type;
|
||||||
/// line number
|
/// line number
|
||||||
int line;
|
int line;
|
||||||
LineIconType type;
|
/// arbitrary parameter
|
||||||
Object param;
|
Object param;
|
||||||
|
/// empty
|
||||||
|
this() {
|
||||||
|
}
|
||||||
|
this(LineIconType type, int line, Object obj = null) {
|
||||||
|
this.type = type;
|
||||||
|
this.line = line;
|
||||||
|
this.param = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// text editor line icon list
|
/// text editor line icon list
|
||||||
|
|
@ -1516,12 +1538,16 @@ struct LineIcons {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
/// remove all icon marks of specified type
|
/// remove all icon marks of specified type, return true if any of items removed
|
||||||
void removeByType(LineIconType type) {
|
bool removeByType(LineIconType type) {
|
||||||
|
bool res = false;
|
||||||
for (int i = _len - 1; i >= 0; i--) {
|
for (int i = _len - 1; i >= 0; i--) {
|
||||||
if (_items[i].type == type)
|
if (_items[i].type == type) {
|
||||||
remove(i);
|
remove(i);
|
||||||
|
res = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
/// get array of icons of specified type
|
/// get array of icons of specified type
|
||||||
LineIcon[] findByType(LineIconType type) {
|
LineIcon[] findByType(LineIconType type) {
|
||||||
|
|
@ -1532,9 +1558,89 @@ struct LineIcons {
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
/// update mark position lines after text change
|
/// get array of icons of specified type
|
||||||
void updateLinePositions(TextRange rangeBefore, TextRange rangeAfter) {
|
LineIcon findByLineAndType(int line, LineIconType type) {
|
||||||
// TODO
|
for (int i = 0; i < _len; i++) {
|
||||||
|
if (_items[i].type == type && _items[i].line == line)
|
||||||
|
return _items[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
/// update mark position lines after text change, returns true if any of marks were moved or removed
|
||||||
|
bool updateLinePositions(TextRange rangeBefore, TextRange rangeAfter, ref LineIcon[] moved, ref LineIcon[] removed) {
|
||||||
|
moved = null;
|
||||||
|
removed = null;
|
||||||
|
bool res = false;
|
||||||
|
for (int i = _len - 1; i >= 0; i--) {
|
||||||
|
LineIcon item = _items[i];
|
||||||
|
if (rangeBefore.start.line > item.line && rangeAfter.start.line > item.line)
|
||||||
|
continue; // line is before ranges
|
||||||
|
else if (rangeBefore.start.line < item.line || rangeAfter.start.line < item.line) {
|
||||||
|
// line is fully after change
|
||||||
|
int deltaLines = rangeAfter.end.line - rangeBefore.end.line;
|
||||||
|
if (!deltaLines)
|
||||||
|
continue;
|
||||||
|
if (deltaLines < 0 && rangeBefore.end.line >= item.line && rangeAfter.end.line < item.line) {
|
||||||
|
// remove
|
||||||
|
removed ~= item;
|
||||||
|
_items.remove(i);
|
||||||
|
res = true;
|
||||||
|
} else {
|
||||||
|
// move
|
||||||
|
item.line += deltaLines;
|
||||||
|
moved ~= item;
|
||||||
|
res = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
LineIcon findNext(LineIconType type, int line, int direction) {
|
||||||
|
LineIcon firstBefore;
|
||||||
|
LineIcon firstAfter;
|
||||||
|
if (direction < 0) {
|
||||||
|
// backward
|
||||||
|
for (int i = _len - 1; i >= 0; i--) {
|
||||||
|
LineIcon item = _items[i];
|
||||||
|
if (item.type != type)
|
||||||
|
continue;
|
||||||
|
if (!firstBefore && item.line < line)
|
||||||
|
firstBefore = item;
|
||||||
|
else if (!firstAfter && item.line > line)
|
||||||
|
firstAfter = item;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// forward
|
||||||
|
for (int i = 0; i < _len; i++) {
|
||||||
|
LineIcon item = _items[i];
|
||||||
|
if (item.type != type)
|
||||||
|
continue;
|
||||||
|
if (!firstBefore && item.line < line)
|
||||||
|
firstBefore = item;
|
||||||
|
else if (!firstAfter && item.line > line)
|
||||||
|
firstAfter = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (firstAfter)
|
||||||
|
return firstAfter;
|
||||||
|
return firstBefore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property bool hasBookmarks() {
|
||||||
|
for (int i = 0; i < _len; i++) {
|
||||||
|
if (_items[i].type == LineIconType.bookmark)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleBookmark(int line) {
|
||||||
|
LineIcon existing = findByLineAndType(line, LineIconType.bookmark);
|
||||||
|
if (existing)
|
||||||
|
remove(existing);
|
||||||
|
else
|
||||||
|
add(new LineIcon(LineIconType.bookmark, line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,13 @@ enum EditorActions : int {
|
||||||
DeleteLine,
|
DeleteLine,
|
||||||
/// Insert line
|
/// Insert line
|
||||||
InsertLine,
|
InsertLine,
|
||||||
|
|
||||||
|
/// Toggle bookmark in current line
|
||||||
|
ToggleBookmark,
|
||||||
|
/// move cursor to next bookmark
|
||||||
|
GoToNextBookmark,
|
||||||
|
/// move cursor to previous bookmark
|
||||||
|
GoToPreviousBookmark,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -193,6 +200,9 @@ const Action ACTION_EDITOR_TOGGLE_REPLACE_MODE = (new Action(EditorActions.Toggl
|
||||||
const Action ACTION_EDITOR_SELECT_ALL = (new Action(EditorActions.SelectAll, KeyCode.KEY_A, KeyFlag.Control));
|
const Action ACTION_EDITOR_SELECT_ALL = (new Action(EditorActions.SelectAll, KeyCode.KEY_A, KeyFlag.Control));
|
||||||
const Action ACTION_EDITOR_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, KeyCode.KEY_DIVIDE, KeyFlag.Control));
|
const Action ACTION_EDITOR_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, KeyCode.KEY_DIVIDE, KeyFlag.Control));
|
||||||
const Action ACTION_EDITOR_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, KeyCode.KEY_DIVIDE, KeyFlag.Control | KeyFlag.Shift));
|
const Action ACTION_EDITOR_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, KeyCode.KEY_DIVIDE, KeyFlag.Control | KeyFlag.Shift));
|
||||||
|
const Action ACTION_EDITOR_TOGGLE_BOOKMARK = (new Action(EditorActions.ToggleBookmark, "ACTION_EDITOR_TOGGLE_BOOKMARK"c));
|
||||||
|
const Action ACTION_EDITOR_GOTO_NEXT_BOOKMARK = (new Action(EditorActions.GoToNextBookmark, "ACTION_EDITOR_GOTO_NEXT_BOOKMARK"c));
|
||||||
|
const Action ACTION_EDITOR_GOTO_PREVIOUS_BOOKMARK = (new Action(EditorActions.GoToPreviousBookmark, "ACTION_EDITOR_GOTO_PREVIOUS_BOOKMARK"c));
|
||||||
|
|
||||||
const Action[] STD_EDITOR_ACTIONS = [ACTION_EDITOR_INSERT_NEW_LINE, ACTION_EDITOR_PREPEND_NEW_LINE,
|
const Action[] STD_EDITOR_ACTIONS = [ACTION_EDITOR_INSERT_NEW_LINE, ACTION_EDITOR_PREPEND_NEW_LINE,
|
||||||
ACTION_EDITOR_APPEND_NEW_LINE, ACTION_EDITOR_DELETE_LINE, ACTION_EDITOR_TOGGLE_REPLACE_MODE,
|
ACTION_EDITOR_APPEND_NEW_LINE, ACTION_EDITOR_DELETE_LINE, ACTION_EDITOR_TOGGLE_REPLACE_MODE,
|
||||||
|
|
@ -449,6 +459,12 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
return enabled && _content.hasUndo;
|
return enabled && _content.hasUndo;
|
||||||
case EditorActions.Redo:
|
case EditorActions.Redo:
|
||||||
return enabled && _content.hasRedo;
|
return enabled && _content.hasRedo;
|
||||||
|
case EditorActions.ToggleBookmark:
|
||||||
|
return _content.multiline;
|
||||||
|
case EditorActions.GoToNextBookmark:
|
||||||
|
return _content.multiline && _content.lineIcons.hasBookmarks;
|
||||||
|
case EditorActions.GoToPreviousBookmark:
|
||||||
|
return _content.multiline && _content.lineIcons.hasBookmarks;
|
||||||
default:
|
default:
|
||||||
return super.isActionEnabled(action);
|
return super.isActionEnabled(action);
|
||||||
}
|
}
|
||||||
|
|
@ -1289,6 +1305,22 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
ensureCaretVisible();
|
ensureCaretVisible();
|
||||||
requestActionsUpdate();
|
requestActionsUpdate();
|
||||||
return true;
|
return true;
|
||||||
|
case EditorActions.ToggleBookmark:
|
||||||
|
if (_content.multiline) {
|
||||||
|
_content.lineIcons.toggleBookmark(_selectionRange.end.line);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case EditorActions.GoToNextBookmark:
|
||||||
|
case EditorActions.GoToPreviousBookmark:
|
||||||
|
if (_content.multiline) {
|
||||||
|
LineIcon mark = _content.lineIcons.findNext(LineIconType.bookmark, _selectionRange.end.line, a.id == EditorActions.GoToNextBookmark ? 1 : -1);
|
||||||
|
if (mark) {
|
||||||
|
setCaretPos(mark.line, 0, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,7 @@ CREATE_NEW_FOLDER=Create new folder
|
||||||
INPUT_NAME_FOR_FOLDER=Input folder name
|
INPUT_NAME_FOR_FOLDER=Input folder name
|
||||||
CREATE_FOLDER_ERROR_TITLE=Cannot create folder
|
CREATE_FOLDER_ERROR_TITLE=Cannot create folder
|
||||||
CREATE_FOLDER_ERROR_MESSAGE=Folder creation is failed
|
CREATE_FOLDER_ERROR_MESSAGE=Folder creation is failed
|
||||||
|
|
||||||
|
ACTION_EDITOR_TOGGLE_BOOKMARK=Toggle bookmark
|
||||||
|
ACTION_EDITOR_GOTO_NEXT_BOOKMARK=Go to next bookmark
|
||||||
|
ACTION_EDITOR_GOTO_PREVIOUS_BOOKMARK=Go to previous bookmark
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue