mirror of https://github.com/buggins/dlangui.git
editable - refactoring of tabs processing
This commit is contained in:
parent
ed6d2df29a
commit
183571efa0
|
|
@ -402,20 +402,42 @@ alias TokenPropString = ubyte[];
|
||||||
|
|
||||||
/// interface for custom syntax highlight
|
/// interface for custom syntax highlight
|
||||||
interface SyntaxHighlighter {
|
interface SyntaxHighlighter {
|
||||||
|
|
||||||
|
/// returns editable content
|
||||||
|
@property EditableContent content();
|
||||||
|
/// set editable content
|
||||||
|
@property SyntaxHighlighter content(EditableContent content);
|
||||||
|
|
||||||
/// categorize characters in content by token types
|
/// categorize characters in content by token types
|
||||||
void updateHighlight(dstring[] lines, TokenPropString[] props, int changeStartLine, int changeEndLine);
|
void updateHighlight(dstring[] lines, TokenPropString[] props, int changeStartLine, int changeEndLine);
|
||||||
/// return true if toggle line comment is supported for file type
|
/// return true if toggle line comment is supported for file type
|
||||||
@property bool supportsToggleLineComment();
|
@property bool supportsToggleLineComment();
|
||||||
/// return true if can toggle line comments for specified text range
|
/// 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
|
/// 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
|
/// return true if toggle block comment is supported for file type
|
||||||
@property bool supportsToggleBlockComment();
|
@property bool supportsToggleBlockComment();
|
||||||
/// return true if can toggle block comments for specified text range
|
/// 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
|
/// 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)
|
/// editable plain text (singleline/multiline)
|
||||||
|
|
@ -442,6 +464,7 @@ class EditableContent {
|
||||||
|
|
||||||
@property EditableContent syntaxHighlighter(SyntaxHighlighter syntaxHighlighter) {
|
@property EditableContent syntaxHighlighter(SyntaxHighlighter syntaxHighlighter) {
|
||||||
_syntaxHighlighter = syntaxHighlighter;
|
_syntaxHighlighter = syntaxHighlighter;
|
||||||
|
_syntaxHighlighter.content = this;
|
||||||
updateTokenProps(0, cast(int)_lines.length);
|
updateTokenProps(0, cast(int)_lines.length);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
@ -461,6 +484,31 @@ class EditableContent {
|
||||||
_readOnly = readOnly;
|
_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
|
/// listeners for edit operations
|
||||||
Signal!EditableContentListener contentChangeListeners;
|
Signal!EditableContentListener contentChangeListeners;
|
||||||
|
|
||||||
|
|
@ -622,6 +670,52 @@ class EditableContent {
|
||||||
return TextRange(TextPosition(lineIndex, 0), lineIndex < _lines.length - 1 ? lineBegin(lineIndex + 1) : lineEnd(lineIndex));
|
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
|
/// returns position before first non-space character of line, returns 0 position if no non-space chars
|
||||||
TextPosition firstNonSpace(int lineIndex) {
|
TextPosition firstNonSpace(int lineIndex) {
|
||||||
dstring s = line(lineIndex);
|
dstring s = line(lineIndex);
|
||||||
|
|
|
||||||
|
|
@ -180,14 +180,12 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
protected Point _scrollPos;
|
protected Point _scrollPos;
|
||||||
protected bool _fixedFont;
|
protected bool _fixedFont;
|
||||||
protected int _spaceWidth;
|
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 _leftPaneWidth; // left pane - can be used to show line numbers, collapse controls, bookmarks, breakpoints, custom icons
|
||||||
|
|
||||||
protected int _minFontSize = -1; // disable zooming
|
protected int _minFontSize = -1; // disable zooming
|
||||||
protected int _maxFontSize = -1; // disable zooming
|
protected int _maxFontSize = -1; // disable zooming
|
||||||
|
|
||||||
protected bool _wantTabs = true;
|
protected bool _wantTabs = true;
|
||||||
protected bool _useSpacesForTabs = false;
|
|
||||||
protected bool _showLineNumbers = false; // show line numbers in left pane
|
protected bool _showLineNumbers = false; // show line numbers in left pane
|
||||||
protected bool _showModificationMarks = false; // show modification marks in left pane
|
protected bool _showModificationMarks = false; // show modification marks in left pane
|
||||||
protected bool _showIcons = false; // show icons 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
|
/// when true, spaces will be inserted instead of tabs
|
||||||
@property bool useSpacesForTabs() {
|
@property bool useSpacesForTabs() {
|
||||||
return _useSpacesForTabs;
|
return _content.useSpacesForTabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// set new Tab key behavior flag: when true, spaces will be inserted instead of tabs
|
/// set new Tab key behavior flag: when true, spaces will be inserted instead of tabs
|
||||||
@property EditWidgetBase useSpacesForTabs(bool useSpacesForTabs) {
|
@property EditWidgetBase useSpacesForTabs(bool useSpacesForTabs) {
|
||||||
_useSpacesForTabs = useSpacesForTabs;
|
_content.useSpacesForTabs = useSpacesForTabs;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns tab size (in number of spaces)
|
/// returns tab size (in number of spaces)
|
||||||
@property int tabSize() {
|
@property int tabSize() {
|
||||||
return _tabSize;
|
return _content.tabSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// sets tab size (in number of spaces)
|
/// sets tab size (in number of spaces)
|
||||||
|
|
@ -584,8 +582,8 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
newTabSize = 1;
|
newTabSize = 1;
|
||||||
else if (newTabSize > 16)
|
else if (newTabSize > 16)
|
||||||
newTabSize = 16;
|
newTabSize = 16;
|
||||||
if (newTabSize != _tabSize) {
|
if (newTabSize != tabSize) {
|
||||||
_tabSize = newTabSize;
|
_content.tabSize = newTabSize;
|
||||||
requestLayout();
|
requestLayout();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -807,7 +805,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
protected int calcLineWidth(dstring s) {
|
protected int calcLineWidth(dstring s) {
|
||||||
int w = 0;
|
int w = 0;
|
||||||
if (_fixedFont) {
|
if (_fixedFont) {
|
||||||
int tabw = _tabSize * _spaceWidth;
|
int tabw = tabSize * _spaceWidth;
|
||||||
// version optimized for fixed font
|
// version optimized for fixed font
|
||||||
for (int i = 0; i < s.length; i++) {
|
for (int i = 0; i < s.length; i++) {
|
||||||
if (s[i] == '\t') {
|
if (s[i] == '\t') {
|
||||||
|
|
@ -910,7 +908,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
case EditorActions.ToggleBlockComment:
|
case EditorActions.ToggleBlockComment:
|
||||||
if (!_content.syntaxHighlighter || !_content.syntaxHighlighter.supportsToggleBlockComment)
|
if (!_content.syntaxHighlighter || !_content.syntaxHighlighter.supportsToggleBlockComment)
|
||||||
a.state = ACTION_STATE_INVISIBLE;
|
a.state = ACTION_STATE_INVISIBLE;
|
||||||
else if (_content.syntaxHighlighter.canToggleBlockComment(_content, _selectionRange))
|
else if (_content.syntaxHighlighter.canToggleBlockComment(_selectionRange))
|
||||||
a.state = ACTION_STATE_ENABLED;
|
a.state = ACTION_STATE_ENABLED;
|
||||||
else
|
else
|
||||||
a.state = ACTION_STATE_DISABLE;
|
a.state = ACTION_STATE_DISABLE;
|
||||||
|
|
@ -918,7 +916,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
case EditorActions.ToggleLineComment:
|
case EditorActions.ToggleLineComment:
|
||||||
if (!_content.syntaxHighlighter || !_content.syntaxHighlighter.supportsToggleLineComment)
|
if (!_content.syntaxHighlighter || !_content.syntaxHighlighter.supportsToggleLineComment)
|
||||||
a.state = ACTION_STATE_INVISIBLE;
|
a.state = ACTION_STATE_INVISIBLE;
|
||||||
else if (_content.syntaxHighlighter.canToggleLineComment(_content, _selectionRange))
|
else if (_content.syntaxHighlighter.canToggleLineComment(_selectionRange))
|
||||||
a.state = ACTION_STATE_ENABLED;
|
a.state = ACTION_STATE_ENABLED;
|
||||||
else
|
else
|
||||||
a.state = ACTION_STATE_DISABLE;
|
a.state = ACTION_STATE_DISABLE;
|
||||||
|
|
@ -1142,7 +1140,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
if (readOnly)
|
if (readOnly)
|
||||||
return true;
|
return true;
|
||||||
if (_selectionRange.empty) {
|
if (_selectionRange.empty) {
|
||||||
if (_useSpacesForTabs) {
|
if (useSpacesForTabs) {
|
||||||
// insert one or more spaces to
|
// insert one or more spaces to
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, TextRange(_caretPos, _caretPos), [spacesForTab(_caretPos.pos)]);
|
EditOperation op = new EditOperation(EditAction.Replace, TextRange(_caretPos, _caretPos), [spacesForTab(_caretPos.pos)]);
|
||||||
_content.performOperation(op, this);
|
_content.performOperation(op, this);
|
||||||
|
|
@ -1157,7 +1155,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
return handleAction(new Action(EditorActions.Indent));
|
return handleAction(new Action(EditorActions.Indent));
|
||||||
} else {
|
} else {
|
||||||
// insert tab
|
// insert tab
|
||||||
if (_useSpacesForTabs) {
|
if (useSpacesForTabs) {
|
||||||
// insert one or more spaces to
|
// insert one or more spaces to
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [spacesForTab(_selectionRange.start.pos)]);
|
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [spacesForTab(_selectionRange.start.pos)]);
|
||||||
_content.performOperation(op, this);
|
_content.performOperation(op, this);
|
||||||
|
|
@ -1286,7 +1284,7 @@ class EditWidgetBase : ScrollWidgetBase, EditableContentListener, MenuItemAction
|
||||||
return src[unindentPos .. $].dup;
|
return src[unindentPos .. $].dup;
|
||||||
} else {
|
} else {
|
||||||
// indent
|
// indent
|
||||||
if (_useSpacesForTabs) {
|
if (useSpacesForTabs) {
|
||||||
if (cursor > 0)
|
if (cursor > 0)
|
||||||
cursorPos.pos += tabSize;
|
cursorPos.pos += tabSize;
|
||||||
return spacesForTab(0) ~ src;
|
return spacesForTab(0) ~ src;
|
||||||
|
|
@ -2039,12 +2037,12 @@ class EditBox : EditWidgetBase {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case EditorActions.ToggleBlockComment:
|
case EditorActions.ToggleBlockComment:
|
||||||
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleBlockComment && _content.syntaxHighlighter.canToggleBlockComment(_content, _selectionRange))
|
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleBlockComment && _content.syntaxHighlighter.canToggleBlockComment(_selectionRange))
|
||||||
_content.syntaxHighlighter.toggleBlockComment(_content, _selectionRange, this);
|
_content.syntaxHighlighter.toggleBlockComment(_selectionRange, this);
|
||||||
return true;
|
return true;
|
||||||
case EditorActions.ToggleLineComment:
|
case EditorActions.ToggleLineComment:
|
||||||
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleLineComment && _content.syntaxHighlighter.canToggleLineComment(_content, _selectionRange))
|
if (_content.syntaxHighlighter && _content.syntaxHighlighter.supportsToggleLineComment && _content.syntaxHighlighter.canToggleLineComment(_selectionRange))
|
||||||
_content.syntaxHighlighter.toggleLineComment(_content, _selectionRange, this);
|
_content.syntaxHighlighter.toggleLineComment(_selectionRange, this);
|
||||||
return true;
|
return true;
|
||||||
case EditorActions.InsertLine:
|
case EditorActions.InsertLine:
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue