editable - refactoring of tabs processing

This commit is contained in:
Vadim Lopatin 2015-02-09 11:23:58 +03:00
parent ed6d2df29a
commit 183571efa0
2 changed files with 113 additions and 21 deletions

View File

@ -402,20 +402,42 @@ alias TokenPropString = ubyte[];
/// interface for custom syntax highlight
interface SyntaxHighlighter {
/// returns editable content
@property EditableContent content();
/// set editable content
@property SyntaxHighlighter content(EditableContent content);
/// categorize characters in content by token types
void updateHighlight(dstring[] lines, TokenPropString[] props, int changeStartLine, int changeEndLine);
/// return true if toggle line comment is supported for file type
@property bool supportsToggleLineComment();
/// return true if can toggle line comments for specified text range
bool canToggleLineComment(EditableContent content, TextRange range);
bool canToggleLineComment(TextRange range);
/// toggle line comments for specified text range
void toggleLineComment(EditableContent content, TextRange range, Object source);
void toggleLineComment(TextRange range, Object source);
/// return true if toggle block comment is supported for file type
@property bool supportsToggleBlockComment();
/// return true if can toggle block comments for specified text range
bool canToggleBlockComment(EditableContent content, TextRange range);
bool canToggleBlockComment(TextRange range);
/// toggle block comments for specified text range
void toggleBlockComment(EditableContent content, TextRange range, Object source);
void toggleBlockComment(TextRange range, Object source);
}
/// measure line text (tabs, spaces, and nonspace positions)
struct TextLineMeasure {
/// line length
int len;
/// first non-space index in line
int firstNonSpace = -1;
/// first non-space position according to tab size
int firstNonSpaceX;
/// last non-space character index in line
int lastNonSpace = -1;
/// last non-space position based on tab size
int lastNonSpaceX;
/// true if line has zero length or consists of spaces and tabs only
@property bool empty() { return len == 0 || firstNonSpace < 0; }
}
/// editable plain text (singleline/multiline)
@ -442,6 +464,7 @@ class EditableContent {
@property EditableContent syntaxHighlighter(SyntaxHighlighter syntaxHighlighter) {
_syntaxHighlighter = syntaxHighlighter;
_syntaxHighlighter.content = this;
updateTokenProps(0, cast(int)_lines.length);
return this;
}
@ -461,6 +484,31 @@ class EditableContent {
_readOnly = readOnly;
}
protected int _tabSize = 4;
protected bool _useSpacesForTabs = true;
/// returns tab size (in number of spaces)
@property int tabSize() {
return _tabSize;
}
/// sets tab size (in number of spaces)
@property EditableContent tabSize(int newTabSize) {
if (newTabSize < 1)
newTabSize = 1;
else if (newTabSize > 16)
newTabSize = 16;
_tabSize = newTabSize;
return this;
}
/// when true, spaces will be inserted instead of tabs
@property bool useSpacesForTabs() {
return _useSpacesForTabs;
}
/// set new Tab key behavior flag: when true, spaces will be inserted instead of tabs
@property EditableContent useSpacesForTabs(bool useSpacesForTabs) {
_useSpacesForTabs = useSpacesForTabs;
return this;
}
/// listeners for edit operations
Signal!EditableContentListener contentChangeListeners;
@ -622,6 +670,52 @@ class EditableContent {
return TextRange(TextPosition(lineIndex, 0), lineIndex < _lines.length - 1 ? lineBegin(lineIndex + 1) : lineEnd(lineIndex));
}
/// measures line non-space start and end positions
TextLineMeasure measureLine(int lineIndex) {
TextLineMeasure res;
dstring s = _lines[lineIndex];
res.len = cast(int)s.length;
if (lineIndex < 0 || lineIndex >= _lines.length)
return res;
int x = 0;
for (int i = 0; i < s.length; i++) {
dchar ch = s[i];
if (ch == ' ') {
x++;
} else if (ch == '\t') {
x = (x + _tabSize) % _tabSize;
} else {
if (res.firstNonSpace < 0) {
res.firstNonSpace = i;
res.firstNonSpaceX = x;
}
res.lastNonSpace = i;
res.lastNonSpaceX = x;
x++;
}
}
return res;
}
/// return true if line with index lineIndex is empty (has length 0 or consists only of spaces and tabs)
bool lineIsEmpty(int lineIndex) {
if (lineIndex < 0 || lineIndex >= _lines.length)
return true;
dstring s = _lines[lineIndex];
foreach(ch; s)
if (ch != ' ' && ch != '\t')
return false;
return true;
}
/// corrent range to cover full lines
TextRange fullLinesRange(TextRange r) {
r.start.pos = 0;
if (r.end.pos > 0)
r.end = lineBegin(r.end.line + 1);
return r;
}
/// returns position before first non-space character of line, returns 0 position if no non-space chars
TextPosition firstNonSpace(int lineIndex) {
dstring s = line(lineIndex);

View File

@ -180,14 +180,12 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
protected Point _scrollPos;
protected bool _fixedFont;
protected int _spaceWidth;
protected int _tabSize = 4;
protected int _leftPaneWidth; // left pane - can be used to show line numbers, collapse controls, bookmarks, breakpoints, custom icons
protected int _minFontSize = -1; // disable zooming
protected int _maxFontSize = -1; // disable zooming
protected bool _wantTabs = true;
protected bool _useSpacesForTabs = false;
protected bool _showLineNumbers = false; // show line numbers in left pane
protected bool _showModificationMarks = false; // show modification marks in left pane
protected bool _showIcons = false; // show icons in left pane
@ -564,18 +562,18 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
/// when true, spaces will be inserted instead of tabs
@property bool useSpacesForTabs() {
return _useSpacesForTabs;
return _content.useSpacesForTabs;
}
/// set new Tab key behavior flag: when true, spaces will be inserted instead of tabs
@property EditWidgetBase useSpacesForTabs(bool useSpacesForTabs) {
_useSpacesForTabs = useSpacesForTabs;
_content.useSpacesForTabs = useSpacesForTabs;
return this;
}
/// returns tab size (in number of spaces)
@property int tabSize() {
return _tabSize;
return _content.tabSize;
}
/// sets tab size (in number of spaces)
@ -584,8 +582,8 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
newTabSize = 1;
else if (newTabSize > 16)
newTabSize = 16;
if (newTabSize != _tabSize) {
_tabSize = newTabSize;
if (newTabSize != tabSize) {
_content.tabSize = newTabSize;
requestLayout();
}
return this;
@ -807,7 +805,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
protected int calcLineWidth(dstring s) {
int w = 0;
if (_fixedFont) {
int tabw = _tabSize * _spaceWidth;
int tabw = tabSize * _spaceWidth;
// version optimized for fixed font
for (int i = 0; i < s.length; i++) {
if (s[i] == '\t') {
@ -910,7 +908,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
case EditorActions.ToggleBlockComment:
if (!_content.syntaxHighlighter || !_content.syntaxHighlighter.supportsToggleBlockComment)
a.state = ACTION_STATE_INVISIBLE;
else if (_content.syntaxHighlighter.canToggleBlockComment(_content, _selectionRange))
else if (_content.syntaxHighlighter.canToggleBlockComment(_selectionRange))
a.state = ACTION_STATE_ENABLED;
else
a.state = ACTION_STATE_DISABLE;
@ -918,7 +916,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
case EditorActions.ToggleLineComment:
if (!_content.syntaxHighlighter || !_content.syntaxHighlighter.supportsToggleLineComment)
a.state = ACTION_STATE_INVISIBLE;
else if (_content.syntaxHighlighter.canToggleLineComment(_content, _selectionRange))
else if (_content.syntaxHighlighter.canToggleLineComment(_selectionRange))
a.state = ACTION_STATE_ENABLED;
else
a.state = ACTION_STATE_DISABLE;
@ -1142,7 +1140,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
if (readOnly)
return true;
if (_selectionRange.empty) {
if (_useSpacesForTabs) {
if (useSpacesForTabs) {
// insert one or more spaces to
EditOperation op = new EditOperation(EditAction.Replace, TextRange(_caretPos, _caretPos), [spacesForTab(_caretPos.pos)]);
_content.performOperation(op, this);
@ -1157,7 +1155,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
return handleAction(new Action(EditorActions.Indent));
} else {
// insert tab
if (_useSpacesForTabs) {
if (useSpacesForTabs) {
// insert one or more spaces to
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [spacesForTab(_selectionRange.start.pos)]);
_content.performOperation(op, this);
@ -1286,7 +1284,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
return src[unindentPos .. $].dup;
} else {
// indent
if (_useSpacesForTabs) {
if (useSpacesForTabs) {
if (cursor > 0)
cursorPos.pos += tabSize;
return spacesForTab(0) ~ src;
@ -2039,12 +2037,12 @@ class EditBox : EditWidgetBase {
}
return true;
case EditorActions.ToggleBlockComment:
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleBlockComment && _content.syntaxHighlighter.canToggleBlockComment(_content, _selectionRange))
_content.syntaxHighlighter.toggleBlockComment(_content, _selectionRange, this);
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleBlockComment && _content.syntaxHighlighter.canToggleBlockComment(_selectionRange))
_content.syntaxHighlighter.toggleBlockComment(_selectionRange, this);
return true;
case EditorActions.ToggleLineComment:
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleLineComment && _content.syntaxHighlighter.canToggleLineComment(_content, _selectionRange))
_content.syntaxHighlighter.toggleLineComment(_content, _selectionRange, this);
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleLineComment && _content.syntaxHighlighter.canToggleLineComment(_selectionRange))
_content.syntaxHighlighter.toggleLineComment(_selectionRange, this);
return true;
case EditorActions.InsertLine:
{