mirror of https://github.com/buggins/dlangui.git
DML editor
This commit is contained in:
parent
5b67dbde81
commit
bd56d0084b
|
@ -367,6 +367,7 @@
|
||||||
<Folder name="dlangui">
|
<Folder name="dlangui">
|
||||||
<Folder name="core">
|
<Folder name="core">
|
||||||
<File path="src\dlangui\core\collections.d" />
|
<File path="src\dlangui\core\collections.d" />
|
||||||
|
<File path="src\dlangui\core\dmlhighlight.d" />
|
||||||
<File path="src\dlangui\core\editable.d" />
|
<File path="src\dlangui\core\editable.d" />
|
||||||
<File path="src\dlangui\core\events.d" />
|
<File path="src\dlangui\core\events.d" />
|
||||||
<File path="src\dlangui\core\files.d" />
|
<File path="src\dlangui\core\files.d" />
|
||||||
|
@ -377,6 +378,7 @@
|
||||||
<File path="src\dlangui\core\settings.d" />
|
<File path="src\dlangui\core\settings.d" />
|
||||||
<File path="src\dlangui\core\signals.d" />
|
<File path="src\dlangui\core\signals.d" />
|
||||||
<File path="src\dlangui\core\stdaction.d" />
|
<File path="src\dlangui\core\stdaction.d" />
|
||||||
|
<File path="src\dlangui\core\textsource.d" />
|
||||||
<File path="src\dlangui\core\types.d" />
|
<File path="src\dlangui\core\types.d" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder name="dialogs">
|
<Folder name="dialogs">
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
<debuglevel>0</debuglevel>
|
<debuglevel>0</debuglevel>
|
||||||
<debugids />
|
<debugids />
|
||||||
<versionlevel>0</versionlevel>
|
<versionlevel>0</versionlevel>
|
||||||
<versionids>Unicode USE_FREETYPE USE_OPENGL</versionids>
|
<versionids>EmbedStandardResources Unicode USE_FREETYPE USE_OPENGL</versionids>
|
||||||
<dump_source>0</dump_source>
|
<dump_source>0</dump_source>
|
||||||
<mapverbosity>0</mapverbosity>
|
<mapverbosity>0</mapverbosity>
|
||||||
<createImplib>0</createImplib>
|
<createImplib>0</createImplib>
|
||||||
|
|
|
@ -3,6 +3,8 @@ module dmledit;
|
||||||
import dlangui;
|
import dlangui;
|
||||||
import dlangui.dialogs.filedlg;
|
import dlangui.dialogs.filedlg;
|
||||||
import dlangui.dialogs.dialog;
|
import dlangui.dialogs.dialog;
|
||||||
|
import dlangui.core.dmlhighlight;
|
||||||
|
import std.array : replaceFirst;
|
||||||
|
|
||||||
mixin APP_ENTRY_POINT;
|
mixin APP_ENTRY_POINT;
|
||||||
|
|
||||||
|
@ -37,9 +39,29 @@ const Action ACTION_EDIT_UNINDENT = (new Action(EditorActions.Unindent, "MENU_ED
|
||||||
const Action ACTION_EDIT_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, "MENU_EDIT_TOGGLE_LINE_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control)).disableByDefault();
|
const Action ACTION_EDIT_TOGGLE_LINE_COMMENT = (new Action(EditorActions.ToggleLineComment, "MENU_EDIT_TOGGLE_LINE_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control)).disableByDefault();
|
||||||
const Action ACTION_EDIT_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, "MENU_EDIT_TOGGLE_BLOCK_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control|KeyFlag.Shift)).disableByDefault();
|
const Action ACTION_EDIT_TOGGLE_BLOCK_COMMENT = (new Action(EditorActions.ToggleBlockComment, "MENU_EDIT_TOGGLE_BLOCK_COMMENT"c, null, KeyCode.KEY_DIVIDE, KeyFlag.Control|KeyFlag.Shift)).disableByDefault();
|
||||||
const Action ACTION_EDIT_PREFERENCES = (new Action(IDEActions.EditPreferences, "MENU_EDIT_PREFERENCES"c, null)).disableByDefault();
|
const Action ACTION_EDIT_PREFERENCES = (new Action(IDEActions.EditPreferences, "MENU_EDIT_PREFERENCES"c, null)).disableByDefault();
|
||||||
const Action ACTION_DEBUG_START = new Action(IDEActions.DebugStart, "MENU_DEBUG_START_DEBUGGING"c, "debug-run"c, KeyCode.F5, 0);
|
const Action ACTION_DEBUG_START = new Action(IDEActions.DebugStart, "MENU_DEBUG_UPDATE_PREVIEW"c, "debug-run"c, KeyCode.F5, 0);
|
||||||
const Action ACTION_HELP_ABOUT = new Action(IDEActions.HelpAbout, "MENU_HELP_ABOUT"c);
|
const Action ACTION_HELP_ABOUT = new Action(IDEActions.HelpAbout, "MENU_HELP_ABOUT"c);
|
||||||
|
|
||||||
|
/// DIDE source file editor
|
||||||
|
class DMLSourceEdit : SourceEdit {
|
||||||
|
this(string ID) {
|
||||||
|
super(ID);
|
||||||
|
MenuItem editPopupItem = new MenuItem(null);
|
||||||
|
editPopupItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_DEBUG_START);
|
||||||
|
popupMenu = editPopupItem;
|
||||||
|
content.syntaxSupport = new DMLSyntaxSupport("");
|
||||||
|
setTokenHightlightColor(TokenCategory.Comment, 0x008000); // green
|
||||||
|
setTokenHightlightColor(TokenCategory.Keyword, 0x0000FF); // blue
|
||||||
|
setTokenHightlightColor(TokenCategory.String, 0xa31515); // brown
|
||||||
|
setTokenHightlightColor(TokenCategory.Error, 0xFF0000); // red
|
||||||
|
|
||||||
|
}
|
||||||
|
this() {
|
||||||
|
this("DMLEDIT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class EditFrame : AppFrame {
|
class EditFrame : AppFrame {
|
||||||
|
|
||||||
MenuItem mainMenuItems;
|
MenuItem mainMenuItems;
|
||||||
|
@ -47,7 +69,9 @@ class EditFrame : AppFrame {
|
||||||
override protected void init() {
|
override protected void init() {
|
||||||
_appName = "DMLEdit";
|
_appName = "DMLEdit";
|
||||||
super.init();
|
super.init();
|
||||||
|
updatePreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create main menu
|
/// create main menu
|
||||||
override protected MainMenu createMainMenu() {
|
override protected MainMenu createMainMenu() {
|
||||||
mainMenuItems = new MenuItem();
|
mainMenuItems = new MenuItem();
|
||||||
|
@ -58,7 +82,7 @@ class EditFrame : AppFrame {
|
||||||
MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
|
MenuItem editItem = new MenuItem(new Action(2, "MENU_EDIT"));
|
||||||
editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE,
|
editItem.add(ACTION_EDIT_COPY, ACTION_EDIT_PASTE,
|
||||||
ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO,
|
ACTION_EDIT_CUT, ACTION_EDIT_UNDO, ACTION_EDIT_REDO,
|
||||||
ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT);
|
ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT, ACTION_EDIT_TOGGLE_LINE_COMMENT, ACTION_EDIT_TOGGLE_BLOCK_COMMENT, ACTION_DEBUG_START);
|
||||||
|
|
||||||
editItem.add(ACTION_EDIT_PREFERENCES);
|
editItem.add(ACTION_EDIT_PREFERENCES);
|
||||||
mainMenuItems.add(editItem);
|
mainMenuItems.add(editItem);
|
||||||
|
@ -141,15 +165,25 @@ class EditFrame : AppFrame {
|
||||||
string source = toUTF8(dsource);
|
string source = toUTF8(dsource);
|
||||||
try {
|
try {
|
||||||
Widget w = parseML(source);
|
Widget w = parseML(source);
|
||||||
statusLine.setStatusText("No errors"d);
|
if (statusLine)
|
||||||
|
statusLine.setStatusText("No errors"d);
|
||||||
_preview.contentWidget = w;
|
_preview.contentWidget = w;
|
||||||
} catch (ParserException e) {
|
} catch (ParserException e) {
|
||||||
statusLine.setStatusText(toUTF32("ERROR: " ~ e.msg));
|
if (statusLine)
|
||||||
_editor.setCaretPos(e.line, e.pos);
|
statusLine.setStatusText(toUTF32("ERROR: " ~ e.msg));
|
||||||
|
_editor.setCaretPos(e.line + 1, e.pos);
|
||||||
|
string msg = "\n" ~ e.msg ~ "\n";
|
||||||
|
msg = replaceFirst(msg, " near `", "\nnear `");
|
||||||
|
TextWidget w = new MultilineTextWidget(null, toUTF32(msg));
|
||||||
|
w.padding = 10;
|
||||||
|
w.margins = 10;
|
||||||
|
w.maxLines = 10;
|
||||||
|
w.backgroundColor = 0xC0FF8080;
|
||||||
|
_preview.contentWidget = w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SourceEdit _editor;
|
protected DMLSourceEdit _editor;
|
||||||
protected ScrollWidget _preview;
|
protected ScrollWidget _preview;
|
||||||
/// create app body widget
|
/// create app body widget
|
||||||
override protected Widget createBody() {
|
override protected Widget createBody() {
|
||||||
|
@ -159,7 +193,7 @@ class EditFrame : AppFrame {
|
||||||
HorizontalLayout hlayout = new HorizontalLayout();
|
HorizontalLayout hlayout = new HorizontalLayout();
|
||||||
hlayout.layoutWidth = FILL_PARENT;
|
hlayout.layoutWidth = FILL_PARENT;
|
||||||
hlayout.layoutHeight = FILL_PARENT;
|
hlayout.layoutHeight = FILL_PARENT;
|
||||||
_editor = new SourceEdit();
|
_editor = new DMLSourceEdit();
|
||||||
hlayout.addChild(_editor);
|
hlayout.addChild(_editor);
|
||||||
_editor.text = q{
|
_editor.text = q{
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
|
@ -181,8 +215,8 @@ VerticalLayout {
|
||||||
}
|
}
|
||||||
CheckBox{ id: cb1; text: "Some checkbox" }
|
CheckBox{ id: cb1; text: "Some checkbox" }
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
RadioButton{ id: rb1; text: "Radio Button 1" }
|
RadioButton { id: rb1; text: "Radio Button 1" }
|
||||||
RadioButton{ id: rb1; text: "Radio Button 2" }
|
RadioButton { id: rb1; text: "Radio Button 2" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -212,25 +246,6 @@ extern (C) int UIAppMain(string[] args) {
|
||||||
|
|
||||||
// create some widget to show in window
|
// create some widget to show in window
|
||||||
window.mainWidget = new EditFrame();
|
window.mainWidget = new EditFrame();
|
||||||
/*
|
|
||||||
parseML(q{
|
|
||||||
VerticalLayout {
|
|
||||||
id: vlayout
|
|
||||||
margins: Rect { left: 5; right: 3; top: 2; bottom: 4 }
|
|
||||||
padding: Rect { 5, 4, 3, 2 } // same as Rect { left: 5; top: 4; right: 3; bottom: 2 }
|
|
||||||
TextWidget {
|
|
||||||
id: myLabel1
|
|
||||||
text: "Some text"; padding: 5
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
TextWidget {
|
|
||||||
id: myLabel2
|
|
||||||
text: SOME_TEXT_RESOURCE_ID; margins: 5
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
// show window
|
// show window
|
||||||
window.show();
|
window.show();
|
||||||
|
|
|
@ -29,6 +29,7 @@ MENU_WINDOW_PREFERENCES=&Preferences
|
||||||
MENU_HELP=&Help
|
MENU_HELP=&Help
|
||||||
MENU_HELP_VIEW_HELP=&View help
|
MENU_HELP_VIEW_HELP=&View help
|
||||||
MENU_HELP_ABOUT=&About
|
MENU_HELP_ABOUT=&About
|
||||||
|
MENU_DEBUG_UPDATE_PREVIEW=Update Preview
|
||||||
|
|
||||||
TAB_LONG_LIST=Long list
|
TAB_LONG_LIST=Long list
|
||||||
TAB_BUTTONS=Buttons
|
TAB_BUTTONS=Buttons
|
||||||
|
|
|
@ -0,0 +1,664 @@
|
||||||
|
module dlangui.core.dmlhighlight;
|
||||||
|
|
||||||
|
import dlangui.core.parser;
|
||||||
|
import dlangui.core.editable;
|
||||||
|
import dlangui.core.linestream;
|
||||||
|
import dlangui.core.textsource;
|
||||||
|
import dlangui.core.logger;
|
||||||
|
|
||||||
|
class DMLSyntaxSupport : SyntaxSupport {
|
||||||
|
|
||||||
|
EditableContent _content;
|
||||||
|
SourceFile _file;
|
||||||
|
this (string filename) {
|
||||||
|
_file = new SourceFile(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenPropString[] _props;
|
||||||
|
|
||||||
|
/// returns editable content
|
||||||
|
@property EditableContent content() { return _content; }
|
||||||
|
/// set editable content
|
||||||
|
@property SyntaxSupport content(EditableContent content) {
|
||||||
|
_content = content;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum BracketMatch {
|
||||||
|
CONTINUE,
|
||||||
|
FOUND,
|
||||||
|
ERROR
|
||||||
|
}
|
||||||
|
private static struct BracketStack {
|
||||||
|
dchar[] buf;
|
||||||
|
int pos;
|
||||||
|
bool reverse;
|
||||||
|
void init(bool reverse) {
|
||||||
|
this.reverse = reverse;
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
void push(dchar ch) {
|
||||||
|
if (buf.length <= pos)
|
||||||
|
buf.length = pos + 16;
|
||||||
|
buf[pos++] = ch;
|
||||||
|
}
|
||||||
|
dchar pop() {
|
||||||
|
if (pos <= 0)
|
||||||
|
return 0;
|
||||||
|
return buf[--pos];
|
||||||
|
}
|
||||||
|
BracketMatch process(dchar ch) {
|
||||||
|
if (reverse) {
|
||||||
|
if (isCloseBracket(ch)) {
|
||||||
|
push(ch);
|
||||||
|
return BracketMatch.CONTINUE;
|
||||||
|
} else {
|
||||||
|
if (pop() != pairedBracket(ch))
|
||||||
|
return BracketMatch.ERROR;
|
||||||
|
if (pos == 0)
|
||||||
|
return BracketMatch.FOUND;
|
||||||
|
return BracketMatch.CONTINUE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isOpenBracket(ch)) {
|
||||||
|
push(ch);
|
||||||
|
return BracketMatch.CONTINUE;
|
||||||
|
} else {
|
||||||
|
if (pop() != pairedBracket(ch))
|
||||||
|
return BracketMatch.ERROR;
|
||||||
|
if (pos == 0)
|
||||||
|
return BracketMatch.FOUND;
|
||||||
|
return BracketMatch.CONTINUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BracketStack _bracketStack;
|
||||||
|
static bool isBracket(dchar ch) {
|
||||||
|
return pairedBracket(ch) != 0;
|
||||||
|
}
|
||||||
|
static dchar pairedBracket(dchar ch) {
|
||||||
|
switch (ch) {
|
||||||
|
case '(':
|
||||||
|
return ')';
|
||||||
|
case ')':
|
||||||
|
return '(';
|
||||||
|
case '{':
|
||||||
|
return '}';
|
||||||
|
case '}':
|
||||||
|
return '{';
|
||||||
|
case '[':
|
||||||
|
return ']';
|
||||||
|
case ']':
|
||||||
|
return '[';
|
||||||
|
default:
|
||||||
|
return 0; // not a bracket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static bool isOpenBracket(dchar ch) {
|
||||||
|
switch (ch) {
|
||||||
|
case '(':
|
||||||
|
case '{':
|
||||||
|
case '[':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static bool isCloseBracket(dchar ch) {
|
||||||
|
switch (ch) {
|
||||||
|
case ')':
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected dchar nextBracket(int dir, ref TextPosition p) {
|
||||||
|
for (;;) {
|
||||||
|
TextPosition oldpos = p;
|
||||||
|
p = dir < 0 ? _content.prevCharPos(p) : _content.nextCharPos(p);
|
||||||
|
if (p == oldpos)
|
||||||
|
return 0;
|
||||||
|
auto prop = _content.tokenProp(p);
|
||||||
|
if (tokenCategory(prop) == TokenCategory.Op) {
|
||||||
|
dchar ch = _content[p];
|
||||||
|
if (isBracket(ch))
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns paired bracket {} () [] for char at position p, returns paired char position or p if not found or not bracket
|
||||||
|
override TextPosition findPairedBracket(TextPosition p) {
|
||||||
|
if (p.line < 0 || p.line >= content.length)
|
||||||
|
return p;
|
||||||
|
dstring s = content.line(p.line);
|
||||||
|
if (p.pos < 0 || p.pos >= s.length)
|
||||||
|
return p;
|
||||||
|
dchar ch = content[p];
|
||||||
|
dchar paired = pairedBracket(ch);
|
||||||
|
if (!paired)
|
||||||
|
return p;
|
||||||
|
TextPosition startPos = p;
|
||||||
|
int dir = isOpenBracket(ch) ? 1 : -1;
|
||||||
|
_bracketStack.init(dir < 0);
|
||||||
|
_bracketStack.process(ch);
|
||||||
|
for (;;) {
|
||||||
|
ch = nextBracket(dir, p);
|
||||||
|
if (!ch) // no more brackets
|
||||||
|
return startPos;
|
||||||
|
auto match = _bracketStack.process(ch);
|
||||||
|
if (match == BracketMatch.FOUND)
|
||||||
|
return p;
|
||||||
|
if (match == BracketMatch.ERROR)
|
||||||
|
return startPos;
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// return true if toggle line comment is supported for file type
|
||||||
|
override @property bool supportsToggleLineComment() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return true if can toggle line comments for specified text range
|
||||||
|
override bool canToggleLineComment(TextRange range) {
|
||||||
|
TextRange r = content.fullLinesRange(range);
|
||||||
|
if (isInsideBlockComment(r.start) || isInsideBlockComment(r.end))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool isLineComment(dstring s) {
|
||||||
|
for (int i = 0; i < cast(int)s.length - 1; i++) {
|
||||||
|
if (s[i] == '/' && s[i + 1] == '/')
|
||||||
|
return true;
|
||||||
|
else if (s[i] != ' ' && s[i] != '\t')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected dstring commentLine(dstring s, int commentX) {
|
||||||
|
dchar[] res;
|
||||||
|
int x = 0;
|
||||||
|
bool commented = false;
|
||||||
|
for (int i = 0; i < s.length; i++) {
|
||||||
|
dchar ch = s[i];
|
||||||
|
if (ch == '\t') {
|
||||||
|
int newX = (x + _content.tabSize) / _content.tabSize * _content.tabSize;
|
||||||
|
if (!commented && newX >= commentX) {
|
||||||
|
commented = true;
|
||||||
|
if (newX != commentX) {
|
||||||
|
// replace tab with space
|
||||||
|
for (; x <= commentX; x++)
|
||||||
|
res ~= ' ';
|
||||||
|
} else {
|
||||||
|
res ~= ch;
|
||||||
|
x = newX;
|
||||||
|
}
|
||||||
|
res ~= "//"d;
|
||||||
|
x += 2;
|
||||||
|
} else {
|
||||||
|
res ~= ch;
|
||||||
|
x = newX;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!commented && x == commentX) {
|
||||||
|
commented = true;
|
||||||
|
res ~= "//"d;
|
||||||
|
res ~= ch;
|
||||||
|
x += 3;
|
||||||
|
} else {
|
||||||
|
res ~= ch;
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!commented) {
|
||||||
|
for (; x < commentX; x++)
|
||||||
|
res ~= ' ';
|
||||||
|
res ~= "//"d;
|
||||||
|
}
|
||||||
|
return cast(dstring)res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// remove single line comment from beginning of line
|
||||||
|
protected dstring uncommentLine(dstring s) {
|
||||||
|
int p = -1;
|
||||||
|
for (int i = 0; i < cast(int)s.length - 1; i++) {
|
||||||
|
if (s[i] == '/' && s[i + 1] == '/') {
|
||||||
|
p = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (p < 0)
|
||||||
|
return s;
|
||||||
|
s = s[0..p] ~ s[p + 2 .. $];
|
||||||
|
for (int i = 0; i < s.length; i++) {
|
||||||
|
if (s[i] != ' ' && s[i] != '\t') {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// searches for neares token start before or equal to position
|
||||||
|
protected TextPosition tokenStart(TextPosition pos) {
|
||||||
|
TextPosition p = pos;
|
||||||
|
for (;;) {
|
||||||
|
TextPosition prevPos = content.prevCharPos(p);
|
||||||
|
if (p == prevPos)
|
||||||
|
return p; // begin of file
|
||||||
|
TokenProp prop = content.tokenProp(p);
|
||||||
|
TokenProp prevProp = content.tokenProp(prevPos);
|
||||||
|
if (prop && prop != prevProp)
|
||||||
|
return p;
|
||||||
|
p = prevPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct TokenWithRange {
|
||||||
|
Token token;
|
||||||
|
TextRange range;
|
||||||
|
@property string toString() {
|
||||||
|
return token.toString ~ range.toString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Token[] _tokens;
|
||||||
|
protected int _tokenIndex;
|
||||||
|
|
||||||
|
protected bool initTokenizer() {
|
||||||
|
_tokens = tokenizeML(content.lines);
|
||||||
|
_tokenIndex = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TokenWithRange nextToken() {
|
||||||
|
TokenWithRange res;
|
||||||
|
if (_tokenIndex < _tokens.length) {
|
||||||
|
res.range.start = TextPosition(_tokens[_tokenIndex].line, _tokens[_tokenIndex].pos);
|
||||||
|
if (_tokenIndex + 1 < _tokens.length)
|
||||||
|
res.range.end = TextPosition(_tokens[_tokenIndex + 1].line, _tokens[_tokenIndex + 1].pos);
|
||||||
|
else
|
||||||
|
res.range.end = content.endOfFile();
|
||||||
|
res.token = _tokens[_tokenIndex];
|
||||||
|
_tokenIndex++;
|
||||||
|
} else {
|
||||||
|
res.range.end = res.range.start = content.endOfFile();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TokenWithRange getPositionToken(TextPosition pos) {
|
||||||
|
initTokenizer();
|
||||||
|
for (;;) {
|
||||||
|
TokenWithRange tokenRange = nextToken();
|
||||||
|
//Log.d("read token: ", tokenRange);
|
||||||
|
if (tokenRange.token.type == TokenType.eof) {
|
||||||
|
//Log.d("end of file");
|
||||||
|
return tokenRange;
|
||||||
|
}
|
||||||
|
if (pos >= tokenRange.range.start && pos < tokenRange.range.end) {
|
||||||
|
//Log.d("found: ", pos, " in ", tokenRange);
|
||||||
|
return tokenRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TokenWithRange[] getRangeTokens(TextRange range) {
|
||||||
|
TokenWithRange[] res;
|
||||||
|
initTokenizer();
|
||||||
|
for (;;) {
|
||||||
|
TokenWithRange tokenRange = nextToken();
|
||||||
|
//Log.d("read token: ", tokenRange);
|
||||||
|
if (tokenRange.token.type == TokenType.eof) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (tokenRange.range.intersects(range)) {
|
||||||
|
//Log.d("found: ", pos, " in ", tokenRange);
|
||||||
|
res ~= tokenRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool isInsideBlockComment(TextPosition pos) {
|
||||||
|
TokenWithRange tokenRange = getPositionToken(pos);
|
||||||
|
if (tokenRange.token.type == TokenType.comment && tokenRange.token.isMultilineComment)
|
||||||
|
return pos > tokenRange.range.start && pos < tokenRange.range.end;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// toggle line comments for specified text range
|
||||||
|
override void toggleLineComment(TextRange range, Object source) {
|
||||||
|
TextRange r = content.fullLinesRange(range);
|
||||||
|
if (isInsideBlockComment(r.start) || isInsideBlockComment(r.end))
|
||||||
|
return;
|
||||||
|
int lineCount = r.end.line - r.start.line;
|
||||||
|
bool noEolAtEndOfRange = false;
|
||||||
|
if (lineCount == 0 || r.end.pos > 0) {
|
||||||
|
noEolAtEndOfRange = true;
|
||||||
|
lineCount++;
|
||||||
|
}
|
||||||
|
int minLeftX = -1;
|
||||||
|
bool hasComments = false;
|
||||||
|
bool hasNoComments = false;
|
||||||
|
bool hasNonEmpty = false;
|
||||||
|
dstring[] srctext;
|
||||||
|
dstring[] dsttext;
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
int lineIndex = r.start.line + i;
|
||||||
|
dstring s = content.line(lineIndex);
|
||||||
|
srctext ~= s;
|
||||||
|
TextLineMeasure m = content.measureLine(lineIndex);
|
||||||
|
if (!m.empty) {
|
||||||
|
if (minLeftX < 0 || minLeftX > m.firstNonSpaceX)
|
||||||
|
minLeftX = m.firstNonSpaceX;
|
||||||
|
hasNonEmpty = true;
|
||||||
|
if (isLineComment(s))
|
||||||
|
hasComments = true;
|
||||||
|
else
|
||||||
|
hasNoComments = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (minLeftX < 0)
|
||||||
|
minLeftX = 0;
|
||||||
|
if (hasNoComments || !hasComments) {
|
||||||
|
// comment
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
dsttext ~= commentLine(srctext[i], minLeftX);
|
||||||
|
}
|
||||||
|
if (!noEolAtEndOfRange)
|
||||||
|
dsttext ~= ""d;
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, r, dsttext);
|
||||||
|
_content.performOperation(op, source);
|
||||||
|
} else {
|
||||||
|
// uncomment
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
dsttext ~= uncommentLine(srctext[i]);
|
||||||
|
}
|
||||||
|
if (!noEolAtEndOfRange)
|
||||||
|
dsttext ~= ""d;
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, r, dsttext);
|
||||||
|
_content.performOperation(op, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return true if toggle block comment is supported for file type
|
||||||
|
override @property bool supportsToggleBlockComment() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/// return true if can toggle block comments for specified text range
|
||||||
|
override bool canToggleBlockComment(TextRange range) {
|
||||||
|
TokenWithRange startToken = getPositionToken(range.start);
|
||||||
|
TokenWithRange endToken = getPositionToken(range.end);
|
||||||
|
//Log.d("canToggleBlockComment: startToken=", startToken, " endToken=", endToken);
|
||||||
|
if (startToken.range == endToken.range && startToken.token.isMultilineComment) {
|
||||||
|
//Log.d("canToggleBlockComment: can uncomment");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (range.empty)
|
||||||
|
return false;
|
||||||
|
TokenWithRange[] tokens = getRangeTokens(range);
|
||||||
|
foreach(ref t; tokens) {
|
||||||
|
if (t.token.type == TokenType.comment) {
|
||||||
|
if (t.token.isMultilineComment) {
|
||||||
|
// disable until nested comments support is implemented
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
// single line comment
|
||||||
|
if (t.range.isInside(range.start) || t.range.isInside(range.end))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/// toggle block comments for specified text range
|
||||||
|
override void toggleBlockComment(TextRange srcrange, Object source) {
|
||||||
|
TokenWithRange startToken = getPositionToken(srcrange.start);
|
||||||
|
TokenWithRange endToken = getPositionToken(srcrange.end);
|
||||||
|
if (startToken.range == endToken.range && startToken.token.isMultilineComment) {
|
||||||
|
TextRange range = startToken.range;
|
||||||
|
dstring[] dsttext;
|
||||||
|
for (int i = range.start.line; i <= range.end.line; i++) {
|
||||||
|
dstring s = content.line(i);
|
||||||
|
int charsRemoved = 0;
|
||||||
|
int minp = 0;
|
||||||
|
if (i == range.start.line) {
|
||||||
|
int maxp = content.lineLength(range.start.line);
|
||||||
|
if (i == range.end.line)
|
||||||
|
maxp = range.end.pos - 2;
|
||||||
|
charsRemoved = 2;
|
||||||
|
for (int j = range.start.pos + charsRemoved; j < maxp; j++) {
|
||||||
|
if (s[j] != s[j - 1])
|
||||||
|
break;
|
||||||
|
charsRemoved++;
|
||||||
|
}
|
||||||
|
//Log.d("line before removing start of comment:", s);
|
||||||
|
s = s[range.start.pos + charsRemoved .. $];
|
||||||
|
//Log.d("line after removing start of comment:", s);
|
||||||
|
charsRemoved += range.start.pos;
|
||||||
|
}
|
||||||
|
if (i == range.end.line) {
|
||||||
|
int endp = range.end.pos;
|
||||||
|
if (charsRemoved > 0)
|
||||||
|
endp -= charsRemoved;
|
||||||
|
int endRemoved = 2;
|
||||||
|
for (int j = endp - endRemoved; j >= 0; j--) {
|
||||||
|
if (s[j] != s[j + 1])
|
||||||
|
break;
|
||||||
|
endRemoved++;
|
||||||
|
}
|
||||||
|
//Log.d("line before removing end of comment:", s);
|
||||||
|
s = s[0 .. endp - endRemoved];
|
||||||
|
//Log.d("line after removing end of comment:", s);
|
||||||
|
}
|
||||||
|
dsttext ~= s;
|
||||||
|
}
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, range, dsttext);
|
||||||
|
_content.performOperation(op, source);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (srcrange.empty)
|
||||||
|
return;
|
||||||
|
TokenWithRange[] tokens = getRangeTokens(srcrange);
|
||||||
|
foreach(ref t; tokens) {
|
||||||
|
if (t.token.type == TokenType.comment) {
|
||||||
|
if (t.token.isMultilineComment) {
|
||||||
|
// disable until nested comments support is implemented
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// single line comment
|
||||||
|
if (t.range.isInside(srcrange.start) || t.range.isInside(srcrange.end))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dstring[] dsttext;
|
||||||
|
for (int i = srcrange.start.line; i <= srcrange.end.line; i++) {
|
||||||
|
dstring s = content.line(i);
|
||||||
|
int charsAdded = 0;
|
||||||
|
if (i == srcrange.start.line) {
|
||||||
|
int p = srcrange.start.pos;
|
||||||
|
if (p < s.length) {
|
||||||
|
s = s[p .. $];
|
||||||
|
charsAdded = -p;
|
||||||
|
} else {
|
||||||
|
charsAdded = -(cast(int)s.length);
|
||||||
|
s = null;
|
||||||
|
}
|
||||||
|
s = "/*" ~ s;
|
||||||
|
charsAdded += 2;
|
||||||
|
}
|
||||||
|
if (i == srcrange.end.line) {
|
||||||
|
int p = srcrange.end.pos + charsAdded;
|
||||||
|
s = p > 0 ? s[0..p] : null;
|
||||||
|
s ~= "*/";
|
||||||
|
}
|
||||||
|
dsttext ~= s;
|
||||||
|
}
|
||||||
|
EditOperation op = new EditOperation(EditAction.Replace, srcrange, dsttext);
|
||||||
|
_content.performOperation(op, source);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// categorize characters in content by token types
|
||||||
|
void updateHighlight(dstring[] lines, TokenPropString[] props, int changeStartLine, int changeEndLine) {
|
||||||
|
|
||||||
|
initTokenizer();
|
||||||
|
_props = props;
|
||||||
|
changeStartLine = 0;
|
||||||
|
changeEndLine = cast(int)lines.length;
|
||||||
|
int tokenPos = 0;
|
||||||
|
int tokenLine = 0;
|
||||||
|
ubyte category = 0;
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
TokenWithRange token = nextToken();
|
||||||
|
if (token.token.type == TokenType.eof) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uint newPos = token.range.start.pos;
|
||||||
|
uint newLine = token.range.start.line;
|
||||||
|
|
||||||
|
// fill with category
|
||||||
|
for (int i = tokenLine; i <= newLine; i++) {
|
||||||
|
int start = i > tokenLine ? 0 : tokenPos;
|
||||||
|
int end = i < newLine ? cast(int)lines[i].length : newPos;
|
||||||
|
for (int j = start; j < end; j++) {
|
||||||
|
if (j < _props[i].length) {
|
||||||
|
_props[i][j] = category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle token - convert to category
|
||||||
|
switch(token.token.type) {
|
||||||
|
case TokenType.comment:
|
||||||
|
category = TokenCategory.Comment;
|
||||||
|
break;
|
||||||
|
case TokenType.ident:
|
||||||
|
category = TokenCategory.Identifier;
|
||||||
|
break;
|
||||||
|
case TokenType.str:
|
||||||
|
category = TokenCategory.String;
|
||||||
|
break;
|
||||||
|
case TokenType.integer:
|
||||||
|
category = TokenCategory.Integer;
|
||||||
|
break;
|
||||||
|
case TokenType.floating:
|
||||||
|
category = TokenCategory.Float;
|
||||||
|
break;
|
||||||
|
case TokenType.error:
|
||||||
|
category = TokenCategory.Error;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (token.token.type >= TokenType.colon)
|
||||||
|
category = TokenCategory.Op;
|
||||||
|
else
|
||||||
|
category = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokenPos = newPos;
|
||||||
|
tokenLine= newLine;
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("exception while trying to parse DML source", e);
|
||||||
|
}
|
||||||
|
_props = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// returns true if smart indent is supported
|
||||||
|
override bool supportsSmartIndents() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool _opInProgress;
|
||||||
|
protected void applyNewLineSmartIndent(EditOperation op, Object source) {
|
||||||
|
int line = op.newRange.end.line;
|
||||||
|
if (line == 0)
|
||||||
|
return; // not for first line
|
||||||
|
int prevLine = line - 1;
|
||||||
|
dstring lineText = _content.line(line);
|
||||||
|
TextLineMeasure lineMeasurement = _content.measureLine(line);
|
||||||
|
TextLineMeasure prevLineMeasurement = _content.measureLine(prevLine);
|
||||||
|
while (prevLineMeasurement.empty && prevLine > 0) {
|
||||||
|
prevLine--;
|
||||||
|
prevLineMeasurement = _content.measureLine(prevLine);
|
||||||
|
}
|
||||||
|
if (lineMeasurement.firstNonSpaceX >= 0 && lineMeasurement.firstNonSpaceX < prevLineMeasurement.firstNonSpaceX) {
|
||||||
|
dstring prevLineText = _content.line(prevLine);
|
||||||
|
TokenPropString prevLineTokenProps = _content.lineTokenProps(prevLine);
|
||||||
|
dchar lastOpChar = 0;
|
||||||
|
for (int j = prevLineMeasurement.lastNonSpace; j >= 0; j--) {
|
||||||
|
auto cat = j < prevLineTokenProps.length ? tokenCategory(prevLineTokenProps[j]) : 0;
|
||||||
|
if (cat == TokenCategory.Op) {
|
||||||
|
lastOpChar = prevLineText[j];
|
||||||
|
break;
|
||||||
|
} else if (cat != TokenCategory.Comment && cat != TokenCategory.WhiteSpace) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int spacex = prevLineMeasurement.firstNonSpaceX;
|
||||||
|
if (lastOpChar == '{')
|
||||||
|
spacex = _content.nextTab(spacex);
|
||||||
|
dstring txt = _content.fillSpace(spacex);
|
||||||
|
EditOperation op2 = new EditOperation(EditAction.Replace, TextRange(TextPosition(line, 0), TextPosition(line, lineMeasurement.firstNonSpace >= 0 ? lineMeasurement.firstNonSpace : 0)), [txt]);
|
||||||
|
_opInProgress = true;
|
||||||
|
_content.performOperation(op2, source);
|
||||||
|
_opInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyClosingCurlySmartIndent(EditOperation op, Object source) {
|
||||||
|
int line = op.newRange.end.line;
|
||||||
|
TextPosition p2 = findPairedBracket(op.newRange.start);
|
||||||
|
if (p2 == op.newRange.start || p2.line > op.newRange.start.line)
|
||||||
|
return;
|
||||||
|
int prevLine = p2.line;
|
||||||
|
TextLineMeasure lineMeasurement = _content.measureLine(line);
|
||||||
|
TextLineMeasure prevLineMeasurement = _content.measureLine(prevLine);
|
||||||
|
if (lineMeasurement.firstNonSpace != op.newRange.start.pos)
|
||||||
|
return; // not in beginning of line
|
||||||
|
if (lineMeasurement.firstNonSpaceX >= 0 && lineMeasurement.firstNonSpaceX != prevLineMeasurement.firstNonSpaceX) {
|
||||||
|
dstring prevLineText = _content.line(prevLine);
|
||||||
|
TokenPropString prevLineTokenProps = _content.lineTokenProps(prevLine);
|
||||||
|
int spacex = prevLineMeasurement.firstNonSpaceX;
|
||||||
|
if (spacex != lineMeasurement.firstNonSpaceX) {
|
||||||
|
dstring txt = _content.fillSpace(spacex);
|
||||||
|
txt = txt ~ "}";
|
||||||
|
EditOperation op2 = new EditOperation(EditAction.Replace, TextRange(TextPosition(line, 0), TextPosition(line, lineMeasurement.firstNonSpace >= 0 ? lineMeasurement.firstNonSpace + 1 : 0)), [txt]);
|
||||||
|
_opInProgress = true;
|
||||||
|
_content.performOperation(op2, source);
|
||||||
|
_opInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// apply smart indent, if supported
|
||||||
|
override void applySmartIndent(EditOperation op, Object source) {
|
||||||
|
if (_opInProgress)
|
||||||
|
return;
|
||||||
|
if (op.isInsertNewLine) {
|
||||||
|
// Enter key pressed - new line inserted or splitted
|
||||||
|
applyNewLineSmartIndent(op, source);
|
||||||
|
} else if (op.singleChar == '}') {
|
||||||
|
// } entered - probably need unindent
|
||||||
|
applyClosingCurlySmartIndent(op, source);
|
||||||
|
} else if (op.singleChar == '{') {
|
||||||
|
// { entered - probably need auto closing }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import dlangui.widgets.metadata;
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
import std.algorithm : equal, min, max;
|
import std.algorithm : equal, min, max;
|
||||||
import std.utf : toUTF32, toUTF8;
|
import std.utf : toUTF32, toUTF8;
|
||||||
|
import std.array : join;
|
||||||
|
|
||||||
class ParserException : Exception {
|
class ParserException : Exception {
|
||||||
protected string _msg;
|
protected string _msg;
|
||||||
|
@ -117,11 +118,23 @@ struct Token {
|
||||||
TokenType type;
|
TokenType type;
|
||||||
ushort line;
|
ushort line;
|
||||||
ushort pos;
|
ushort pos;
|
||||||
|
bool multiline;
|
||||||
string text;
|
string text;
|
||||||
union {
|
union {
|
||||||
int intvalue;
|
int intvalue;
|
||||||
double floatvalue;
|
double floatvalue;
|
||||||
}
|
}
|
||||||
|
public @property string toString() {
|
||||||
|
if (type == TokenType.integer)
|
||||||
|
return "" ~ to!string(line) ~ ":" ~ to!string(pos) ~ " " ~ to!string(type) ~ " " ~ to!string(intvalue);
|
||||||
|
else if (type == TokenType.floating)
|
||||||
|
return "" ~ to!string(line) ~ ":" ~ to!string(pos) ~ " " ~ to!string(type) ~ " " ~ to!string(floatvalue);
|
||||||
|
else
|
||||||
|
return "" ~ to!string(line) ~ ":" ~ to!string(pos) ~ " " ~ to!string(type) ~ " \"" ~ text ~ "\"";
|
||||||
|
}
|
||||||
|
@property bool isMultilineComment() {
|
||||||
|
return type == TokenType.comment && multiline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// simple tokenizer for DlangUI ML
|
/// simple tokenizer for DlangUI ML
|
||||||
|
@ -355,6 +368,7 @@ class Tokenizer {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_token.type = TokenType.comment;
|
_token.type = TokenType.comment;
|
||||||
|
_token.multiline = false;
|
||||||
return _token;
|
return _token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,6 +385,7 @@ class Tokenizer {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_token.type = TokenType.comment;
|
_token.type = TokenType.comment;
|
||||||
|
_token.multiline = true;
|
||||||
return _token;
|
return _token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,3 +815,22 @@ public Widget parseML(string code, string filename = "", Widget context = null)
|
||||||
scope(exit) destroy(parser);
|
scope(exit) destroy(parser);
|
||||||
return parser.parse();
|
return parser.parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// tokenize source into array of tokens (excluding EOF)
|
||||||
|
public Token[] tokenizeML(const(dstring[]) lines) {
|
||||||
|
string code = toUTF8(join(lines, "\n"));
|
||||||
|
return tokenizeML(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// tokenize source into array of tokens (excluding EOF)
|
||||||
|
public Token[] tokenizeML(string code) {
|
||||||
|
Token[] res;
|
||||||
|
auto tokenizer = new Tokenizer(code, "");
|
||||||
|
for (;;) {
|
||||||
|
auto token = tokenizer.nextToken();
|
||||||
|
if (token.type == TokenType.eof)
|
||||||
|
break;
|
||||||
|
res ~= token;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
module dlangui.core.textsource;
|
||||||
|
|
||||||
|
private import std.utf;
|
||||||
|
private import std.array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source file information.
|
||||||
|
* Even if contains only file name, it's better to use it instead of string - object reference size is twice less than array ref.
|
||||||
|
*/
|
||||||
|
class SourceFile {
|
||||||
|
protected string _filename;
|
||||||
|
@property string filename() { return _filename; }
|
||||||
|
public this(string filename) {
|
||||||
|
_filename = filename;
|
||||||
|
}
|
||||||
|
override @property string toString() {
|
||||||
|
return _filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// source lines for tokenizer
|
||||||
|
interface SourceLines {
|
||||||
|
/// source file
|
||||||
|
@property SourceFile file();
|
||||||
|
/// last read line
|
||||||
|
@property uint line();
|
||||||
|
/// source encoding
|
||||||
|
//@property EncodingType encoding() { return _encoding; }
|
||||||
|
/// error code
|
||||||
|
@property int errorCode();
|
||||||
|
/// error message
|
||||||
|
@property string errorMessage();
|
||||||
|
/// error line
|
||||||
|
@property int errorLine();
|
||||||
|
/// error position
|
||||||
|
@property int errorPos();
|
||||||
|
/// end of file reached
|
||||||
|
@property bool eof();
|
||||||
|
|
||||||
|
/// read line, return null if EOF reached or error occured
|
||||||
|
dchar[] readLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEXT_SOURCE_ERROR_EOF = 1;
|
||||||
|
|
||||||
|
/// Simple text source based on array
|
||||||
|
class ArraySourceLines : SourceLines {
|
||||||
|
protected SourceFile _file;
|
||||||
|
protected uint _line;
|
||||||
|
protected uint _firstLine;
|
||||||
|
protected dstring[] _lines;
|
||||||
|
static __gshared protected dchar[] _emptyLine = ""d.dup;
|
||||||
|
|
||||||
|
this() {
|
||||||
|
}
|
||||||
|
|
||||||
|
this(dstring[] lines, SourceFile file, uint firstLine = 0) {
|
||||||
|
init(lines, file, firstLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
this(string code, string filename) {
|
||||||
|
_lines = (toUTF32(code)).split("\n");
|
||||||
|
_file = new SourceFile(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
_lines = null;
|
||||||
|
_line = 0;
|
||||||
|
_firstLine = 0;
|
||||||
|
_file = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(dstring[] lines, SourceFile file, uint firstLine = 0) {
|
||||||
|
_lines = lines;
|
||||||
|
_firstLine = firstLine;
|
||||||
|
_line = 0;
|
||||||
|
_file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reset(int line) {
|
||||||
|
_line = line;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// end of file reached
|
||||||
|
override @property bool eof() {
|
||||||
|
return _line >= _lines.length;
|
||||||
|
}
|
||||||
|
/// source file
|
||||||
|
override @property SourceFile file() { return _file; }
|
||||||
|
/// last read line
|
||||||
|
override @property uint line() { return _line + _firstLine; }
|
||||||
|
/// source encoding
|
||||||
|
//@property EncodingType encoding() { return _encoding; }
|
||||||
|
/// error code
|
||||||
|
override @property int errorCode() { return 0; }
|
||||||
|
/// error message
|
||||||
|
override @property string errorMessage() { return ""; }
|
||||||
|
/// error line
|
||||||
|
override @property int errorLine() { return 0; }
|
||||||
|
/// error position
|
||||||
|
override @property int errorPos() { return 0; }
|
||||||
|
|
||||||
|
/// read line, return null if EOF reached or error occured
|
||||||
|
override dchar[] readLine() {
|
||||||
|
if (_line < _lines.length) {
|
||||||
|
if (_lines[_line])
|
||||||
|
return cast(dchar[])_lines[_line++];
|
||||||
|
_line++;
|
||||||
|
return _emptyLine;
|
||||||
|
}
|
||||||
|
return null; // EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -824,6 +824,7 @@ class ImageCache {
|
||||||
_map[filename] = item;
|
_map[filename] = item;
|
||||||
return item.get;
|
return item.get;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get and cache color transformed image
|
/// get and cache color transformed image
|
||||||
ref DrawBufRef get(string filename, ref ColorTransform transform) {
|
ref DrawBufRef get(string filename, ref ColorTransform transform) {
|
||||||
if (transform.empty)
|
if (transform.empty)
|
||||||
|
|
|
@ -1050,4 +1050,4 @@ class CanvasWidget : Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
import dlangui.widgets.metadata;
|
import dlangui.widgets.metadata;
|
||||||
mixin(registerWidgets!(Widget, TextWidget, Button, ImageWidget, ImageButton, ImageTextButton, RadioButton, CheckBox, ScrollBar)());
|
mixin(registerWidgets!(Widget, TextWidget, MultilineTextWidget, Button, ImageWidget, ImageButton, ImageTextButton, RadioButton, CheckBox, ScrollBar)());
|
||||||
|
|
|
@ -326,6 +326,12 @@ class Widget {
|
||||||
requestLayout();
|
requestLayout();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
/// set margins for widget with the same value for left, top, right, bottom - override one from style
|
||||||
|
@property Widget margins(int v) {
|
||||||
|
ownStyle.margins = Rect(v, v, v, v);
|
||||||
|
requestLayout();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
immutable static int FOCUS_RECT_PADDING = 2;
|
immutable static int FOCUS_RECT_PADDING = 2;
|
||||||
/// get padding (between background bounds and content of widget)
|
/// get padding (between background bounds and content of widget)
|
||||||
@property Rect padding() const {
|
@property Rect padding() const {
|
||||||
|
@ -355,6 +361,12 @@ class Widget {
|
||||||
requestLayout();
|
requestLayout();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
/// set padding for widget to the same value for left, top, right, bottom - override one from style
|
||||||
|
@property Widget padding(int v) {
|
||||||
|
ownStyle.padding = Rect(v, v, v, v);
|
||||||
|
requestLayout();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
/// returns background color
|
/// returns background color
|
||||||
@property uint backgroundColor() const { return stateStyle.backgroundColor; }
|
@property uint backgroundColor() const { return stateStyle.backgroundColor; }
|
||||||
/// set background color for widget - override one from style
|
/// set background color for widget - override one from style
|
||||||
|
|
Loading…
Reference in New Issue