mirror of https://github.com/buggins/dlangui.git
tabs and indentations in editors
This commit is contained in:
parent
ca094497a1
commit
2473a3f604
|
@ -194,7 +194,7 @@ class Win32Font : Font {
|
||||||
lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
|
lf.lfCharSet = ANSI_CHARSET; //DEFAULT_CHARSET;
|
||||||
lf.lfFaceName[0..def.face.length] = def.face;
|
lf.lfFaceName[0..def.face.length] = def.face;
|
||||||
lf.lfFaceName[def.face.length] = 0;
|
lf.lfFaceName[def.face.length] = 0;
|
||||||
lf.lfHeight = -size;
|
lf.lfHeight = size; //-size;
|
||||||
lf.lfItalic = italic;
|
lf.lfItalic = italic;
|
||||||
lf.lfOutPrecision = OUT_OUTLINE_PRECIS; //OUT_TT_ONLY_PRECIS;
|
lf.lfOutPrecision = OUT_OUTLINE_PRECIS; //OUT_TT_ONLY_PRECIS;
|
||||||
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||||
|
|
|
@ -567,6 +567,12 @@ enum EditorActions {
|
||||||
Undo,
|
Undo,
|
||||||
/// Redo last undoed change
|
/// Redo last undoed change
|
||||||
Redo,
|
Redo,
|
||||||
|
|
||||||
|
/// Tab (e.g., Tab key to insert tab character or indent text)
|
||||||
|
Tab,
|
||||||
|
/// Tab (unindent text, or remove whitespace before cursor, usually Shift+Tab)
|
||||||
|
BackTab,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// base for all editor widgets
|
/// base for all editor widgets
|
||||||
|
@ -580,6 +586,10 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
protected int _spaceWidth;
|
protected int _spaceWidth;
|
||||||
protected int _tabSize = 4;
|
protected int _tabSize = 4;
|
||||||
|
|
||||||
|
protected bool _wantTabs = true;
|
||||||
|
protected bool _useSpacesForTabs = false;
|
||||||
|
|
||||||
|
|
||||||
this(string ID) {
|
this(string ID) {
|
||||||
super(ID);
|
super(ID);
|
||||||
focusable = true;
|
focusable = true;
|
||||||
|
@ -639,9 +649,51 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
new Action(EditorActions.Redo, KeyCode.KEY_Y, KeyFlag.Control),
|
new Action(EditorActions.Redo, KeyCode.KEY_Y, KeyFlag.Control),
|
||||||
new Action(EditorActions.Redo, KeyCode.KEY_Z, KeyFlag.Control|KeyFlag.Shift),
|
new Action(EditorActions.Redo, KeyCode.KEY_Z, KeyFlag.Control|KeyFlag.Shift),
|
||||||
|
|
||||||
|
new Action(EditorActions.Tab, KeyCode.TAB, 0),
|
||||||
|
new Action(EditorActions.BackTab, KeyCode.TAB, KeyFlag.Shift),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// when true, Tab / Shift+Tab presses are processed internally in widget (e.g. insert tab character) instead of focus change navigation.
|
||||||
|
@property bool wantTabs() {
|
||||||
|
return _wantTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets tab size (in number of spaces)
|
||||||
|
@property EditWidgetBase wantTabs(bool wantTabs) {
|
||||||
|
_wantTabs = wantTabs;
|
||||||
|
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 EditWidgetBase useSpacesForTabs(bool useSpacesForTabs) {
|
||||||
|
_useSpacesForTabs = useSpacesForTabs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns tab size (in number of spaces)
|
||||||
|
@property int tabSize() {
|
||||||
|
return _tabSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets tab size (in number of spaces)
|
||||||
|
@property EditWidgetBase tabSize(int newTabSize) {
|
||||||
|
if (newTabSize < 1)
|
||||||
|
newTabSize = 1;
|
||||||
|
else if (newTabSize > 16)
|
||||||
|
newTabSize = 16;
|
||||||
|
if (newTabSize != _tabSize) {
|
||||||
|
_tabSize = newTabSize;
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
protected void updateMaxLineWidth() {
|
protected void updateMaxLineWidth() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -775,6 +827,19 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// generate string of spaces, to reach next tab position
|
||||||
|
protected dstring spacesForTab(int currentPos) {
|
||||||
|
int newPos = (currentPos + tabSize + 1) / tabSize * tabSize;
|
||||||
|
return " "d[0..(newPos - currentPos)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns true if one or more lines selected fully
|
||||||
|
protected bool wholeLinesSelected() {
|
||||||
|
return _selectionRange.end.line > _selectionRange.start.line
|
||||||
|
&& _selectionRange.end.pos == 0
|
||||||
|
&& _selectionRange.start.pos == 0;
|
||||||
|
}
|
||||||
|
|
||||||
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];
|
||||||
|
@ -923,6 +988,52 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
{
|
{
|
||||||
_content.redo();
|
_content.redo();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
case EditorActions.Tab:
|
||||||
|
{
|
||||||
|
if (_selectionRange.empty) {
|
||||||
|
if (_useSpacesForTabs) {
|
||||||
|
// insert one or more spaces to
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, TextRange(_caretPos, _caretPos), [spacesForTab(_caretPos.pos)]);
|
||||||
|
_content.performOperation(op);
|
||||||
|
} else {
|
||||||
|
// just insert tab character
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, TextRange(_caretPos, _caretPos), ["\t"d]);
|
||||||
|
_content.performOperation(op);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wholeLinesSelected()) {
|
||||||
|
// indent range
|
||||||
|
indentRange(false);
|
||||||
|
} else {
|
||||||
|
// insert tab
|
||||||
|
if (_useSpacesForTabs) {
|
||||||
|
// insert one or more spaces to
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [spacesForTab(_selectionRange.start.pos)]);
|
||||||
|
_content.performOperation(op);
|
||||||
|
} else {
|
||||||
|
// just insert tab character
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, ["\t"d]);
|
||||||
|
_content.performOperation(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case EditorActions.BackTab:
|
||||||
|
{
|
||||||
|
if (_selectionRange.empty) {
|
||||||
|
// remove spaces before caret
|
||||||
|
} else {
|
||||||
|
if (wholeLinesSelected()) {
|
||||||
|
// unindent range
|
||||||
|
indentRange(true);
|
||||||
|
} else {
|
||||||
|
// remove space before selection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -930,6 +1041,67 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
return super.handleAction(a);
|
return super.handleAction(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// change line indent
|
||||||
|
protected dstring indentLine(dstring src, bool back) {
|
||||||
|
int firstNonSpace = -1;
|
||||||
|
int x = 0;
|
||||||
|
int unindentPos = -1;
|
||||||
|
for (int i = 0; i < src.length; i++) {
|
||||||
|
dchar ch = src[i];
|
||||||
|
if (ch == ' ') {
|
||||||
|
x++;
|
||||||
|
} else if (ch == '\t') {
|
||||||
|
x = (x + tabSize + 1) / tabSize * tabSize;
|
||||||
|
} else {
|
||||||
|
firstNonSpace = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (x <= tabSize)
|
||||||
|
unindentPos = i + 1;
|
||||||
|
}
|
||||||
|
if (firstNonSpace == -1) // only spaces or empty line -- do not change it
|
||||||
|
return src;
|
||||||
|
if (back) {
|
||||||
|
// unindent
|
||||||
|
if (unindentPos == -1)
|
||||||
|
return src; // no change
|
||||||
|
if (unindentPos == src.length)
|
||||||
|
return ""d;
|
||||||
|
return src[unindentPos .. $].dup;
|
||||||
|
} else {
|
||||||
|
// indent
|
||||||
|
if (_useSpacesForTabs) {
|
||||||
|
return spacesForTab(0) ~ src;
|
||||||
|
} else {
|
||||||
|
return "\t"d ~ src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return src;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// indent / unindent range
|
||||||
|
protected void indentRange(bool back) {
|
||||||
|
int lineCount = _selectionRange.end.line - _selectionRange.start.line;
|
||||||
|
dstring[] newContent = new dstring[lineCount + 1];
|
||||||
|
bool changed = false;
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
dstring srcline = _content.line(_selectionRange.start.line + i);
|
||||||
|
dstring dstline = indentLine(srcline, back);
|
||||||
|
newContent[i] = dstline;
|
||||||
|
if (dstline.length != srcline.length)
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
TextRange saveRange = _selectionRange;
|
||||||
|
TextPosition saveCursor = _caretPos;
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, newContent);
|
||||||
|
_content.performOperation(op);
|
||||||
|
_selectionRange = saveRange;
|
||||||
|
_caretPos = saveCursor;
|
||||||
|
ensureCaretVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// handle keys
|
/// handle keys
|
||||||
override bool onKeyEvent(KeyEvent event) {
|
override bool onKeyEvent(KeyEvent event) {
|
||||||
//
|
//
|
||||||
|
@ -941,7 +1113,7 @@ class EditWidgetBase : WidgetGroup, EditableContentListener {
|
||||||
} else if (event.action == KeyAction.Text && event.text.length) {
|
} else if (event.action == KeyAction.Text && event.text.length) {
|
||||||
Log.d("text entered: ", event.text);
|
Log.d("text entered: ", event.text);
|
||||||
dchar ch = event.text[0];
|
dchar ch = event.text[0];
|
||||||
if (ch >= 32 || ch == '\t') { // ignore Backspace and Return
|
if (ch >= 32) { // ignore Backspace and Return
|
||||||
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [event.text]);
|
EditOperation op = new EditOperation(EditAction.Replace, _selectionRange, [event.text]);
|
||||||
_content.performOperation(op);
|
_content.performOperation(op);
|
||||||
return true;
|
return true;
|
||||||
|
@ -987,6 +1159,7 @@ class EditLine : EditWidgetBase {
|
||||||
super(ID);
|
super(ID);
|
||||||
_content = new EditableContent(false);
|
_content = new EditableContent(false);
|
||||||
_content.contentChangeListeners = this;
|
_content.contentChangeListeners = this;
|
||||||
|
wantTabs = false;
|
||||||
styleId = "EDIT_LINE";
|
styleId = "EDIT_LINE";
|
||||||
text = initialContent;
|
text = initialContent;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue