From 5baf43571352e95fb1c7e5c169e086e250718cb2 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Mon, 14 Dec 2015 16:25:50 +0300 Subject: [PATCH] Text editor line marks support, bookmarks support --- src/dlangui/core/editable.d | 120 ++++++++++++++++++++++++++++++++-- src/dlangui/widgets/editors.d | 32 +++++++++ views/res/i18n/std_en.ini | 4 ++ 3 files changed, 149 insertions(+), 7 deletions(-) diff --git a/src/dlangui/core/editable.d b/src/dlangui/core/editable.d index 8838faec..c511f8ed 100644 --- a/src/dlangui/core/editable.d +++ b/src/dlangui/core/editable.d @@ -433,6 +433,10 @@ interface EditableContentListener { 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 alias TokenProp = ubyte; /// TokenCategory string @@ -583,6 +587,8 @@ class EditableContent { /// listeners for edit operations Signal!EditableContentListener contentChanged; + /// listeners for mark changes after edit operation + Signal!EditableContentMarksChangeListener marksChanged; protected bool _multiline; /// 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) { // update highlight if necessary 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 if (contentChanged.assigned) contentChanged(this, op, rangeBefore, rangeAfter, source); @@ -1438,10 +1450,20 @@ enum LineIconType : int { /// text editor line icon class LineIcon { + /// mark type + LineIconType type; /// line number int line; - LineIconType type; + /// arbitrary parameter 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 @@ -1516,12 +1538,16 @@ struct LineIcons { } return null; } - /// remove all icon marks of specified type - void removeByType(LineIconType type) { + /// remove all icon marks of specified type, return true if any of items removed + bool removeByType(LineIconType type) { + bool res = false; for (int i = _len - 1; i >= 0; i--) { - if (_items[i].type == type) + if (_items[i].type == type) { remove(i); + res = true; + } } + return res; } /// get array of icons of specified type LineIcon[] findByType(LineIconType type) { @@ -1532,9 +1558,89 @@ struct LineIcons { } return res; } - /// update mark position lines after text change - void updateLinePositions(TextRange rangeBefore, TextRange rangeAfter) { - // TODO + /// get array of icons of specified type + LineIcon findByLineAndType(int line, LineIconType type) { + 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)); } } diff --git a/src/dlangui/widgets/editors.d b/src/dlangui/widgets/editors.d index 456715d7..7d8ee52c 100644 --- a/src/dlangui/widgets/editors.d +++ b/src/dlangui/widgets/editors.d @@ -177,6 +177,13 @@ enum EditorActions : int { DeleteLine, /// Insert line 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_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_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, 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; case EditorActions.Redo: 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: return super.isActionEnabled(action); } @@ -1289,6 +1305,22 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction ensureCaretVisible(); requestActionsUpdate(); 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: break; } diff --git a/views/res/i18n/std_en.ini b/views/res/i18n/std_en.ini index 1e216bd8..f9e97414 100644 --- a/views/res/i18n/std_en.ini +++ b/views/res/i18n/std_en.ini @@ -20,3 +20,7 @@ CREATE_NEW_FOLDER=Create new folder INPUT_NAME_FOR_FOLDER=Input folder name CREATE_FOLDER_ERROR_TITLE=Cannot create folder 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