diff --git a/dlangide.visualdproj b/dlangide.visualdproj
index 2277468..08a26d3 100644
--- a/dlangide.visualdproj
+++ b/dlangide.visualdproj
@@ -209,6 +209,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/dlangide/builders/extprocess.d b/src/dlangide/builders/extprocess.d
index 021d5f9..69b64a1 100644
--- a/src/dlangide/builders/extprocess.d
+++ b/src/dlangide/builders/extprocess.d
@@ -297,7 +297,7 @@ class ExternalProcess {
params ~= _program;
params ~= _args;
if (!_stderr)
- redirect = Redirect.stdout | Redirect.stderrToStdout; //Redirect.stdin |
+ redirect = Redirect.stdout | Redirect.stderrToStdout | Redirect.stdin;
else
redirect = Redirect.all;
Log.i("Trying to run program ", _program, " with args ", _args);
@@ -393,4 +393,14 @@ class ExternalProcess {
}
return _state;
}
+
+ void write(dstring data) {
+ if(_state == ExternalProcessState.Error || _state == ExternalProcessState.None || _state == ExternalProcessState.Stopped) {
+ return;
+ }
+ else {
+ _pipes.stdin.write(data);
+ _pipes.stdin.close();
+ }
+ }
}
diff --git a/src/dlangide/tools/d/dcdinterface.d b/src/dlangide/tools/d/dcdinterface.d
new file mode 100644
index 0000000..f11b7d0
--- /dev/null
+++ b/src/dlangide/tools/d/dcdinterface.d
@@ -0,0 +1,128 @@
+module dlangide.tools.d.dcdinterface;
+
+import dlangui.core.logger;
+
+import dlangide.builders.extprocess;
+
+import std.typecons;
+import std.conv;
+import std.string;
+
+enum DCDResult : int {
+ DCD_NOT_RUNNING = 0,
+ SUCCESS,
+ NO_RESULT,
+ FAIL,
+}
+alias ResultSet = Tuple!(DCDResult, "result", dstring[], "output");
+
+//Interface to DCD
+//TODO: Check if server is running, start server if needed etc.
+class DCDInterface {
+ ExternalProcess dcdProcess;
+ ProtectedTextStorage stdoutTarget;
+ this() {
+ dcdProcess = new ExternalProcess();
+ stdoutTarget = new ProtectedTextStorage();
+ }
+
+ ResultSet goToDefinition(in dstring content, int index) {
+ ExternalProcess dcdProcess = new ExternalProcess();
+
+ ResultSet result;
+ if(dcdProcess.state != ExternalProcessState.None) {
+ result.result = DCDResult.FAIL;
+ return result;
+ }
+
+ char[][] arguments = ["-l".dup, "-c".dup];
+ arguments ~= [to!(char[])(index)];
+ ProtectedTextStorage stdoutTarget = new ProtectedTextStorage();
+
+ dcdProcess.run("dcd-client".dup, arguments, "/usr/bin".dup, stdoutTarget);
+ dcdProcess.write(content);
+ dcdProcess.wait();
+
+ dstring[] output = stdoutTarget.readText.splitLines();
+
+ if(dcdProcess.poll() == ExternalProcessState.Stopped) {
+ result.result = DCDResult.SUCCESS;
+ }
+ else {
+ result.result = DCDResult.FAIL;
+ return result;
+ }
+
+ if(output.length > 0) {
+ if(output[0].indexOf("Not Found".dup) == 0) {
+ result.result = DCDResult.NO_RESULT;
+ return result;
+ }
+ }
+
+ auto split = output[0].indexOf("\t");
+ if(split == -1) {
+ Log.d("DCD output format error.");
+ result.result = DCDResult.FAIL;
+ return result;
+ }
+
+ result.output ~= output[0][0 .. split];
+ result.output ~= output[0][split+1 .. $];
+ return result;
+ }
+
+ ResultSet getCompletions(in dstring content, int index) {
+ ExternalProcess dcdProcess = new ExternalProcess();
+
+ ResultSet result;
+ if(dcdProcess.state != ExternalProcessState.None) {
+ result.result = DCDResult.FAIL;
+ return result;
+ }
+
+ char[][] arguments = ["-c".dup];
+ arguments ~= [to!(char[])(index)];
+ ProtectedTextStorage stdoutTarget = new ProtectedTextStorage();
+
+ dcdProcess.run("dcd-client".dup, arguments, "/usr/bin".dup, stdoutTarget);
+ dcdProcess.write(content);
+ dcdProcess.wait();
+
+ dstring[] output = stdoutTarget.readText.splitLines();
+
+ if(dcdProcess.poll() == ExternalProcessState.Stopped) {
+ result.result = DCDResult.SUCCESS;
+ }
+ else {
+ result.result = DCDResult.FAIL;
+ return result;
+ }
+
+ if(output.length == 0) {
+ result.result = DCDResult.NO_RESULT;
+ return result;
+ }
+
+ enum State : int {None = 0, Identifiers, Calltips}
+ State state = State.None;
+ foreach(dstring outputLine ; output) {
+ if(outputLine == "identifiers") {
+ state = State.Identifiers;
+ }
+ else if(outputLine == "calltips") {
+ state = State.Calltips;
+ }
+ else {
+ auto split = outputLine.indexOf("\t");
+ if(split < 0) {
+ break;
+ }
+ if(state == State.Identifiers) {
+ result.output ~= outputLine[0 .. split];
+ }
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/dlangide/tools/d/deditortool.d b/src/dlangide/tools/d/deditortool.d
new file mode 100644
index 0000000..2a14d6d
--- /dev/null
+++ b/src/dlangide/tools/d/deditortool.d
@@ -0,0 +1,114 @@
+module dlangide.tools.d.deditorTool;
+
+import dlangide.tools.editorTool;
+import dlangide.tools.d.dcdinterface;
+import dlangide.ui.dsourceedit;
+import dlangui.widgets.editors;
+import dlangide.ui.frame;
+import std.stdio;
+import std.string;
+import dlangui.core.logger;
+
+import std.conv;
+
+class DEditorTool : EditorTool
+{
+
+
+ this(IDEFrame frame) {
+ _dcd = new DCDInterface();
+ super(frame);
+ }
+
+ override bool goToDefinition(DSourceEdit editor, TextPosition caretPosition) {
+
+ auto byteOffset = caretPositionToByteOffset(editor.text, caretPosition);
+ ResultSet output = _dcd.goToDefinition(editor.text, byteOffset);
+
+
+ switch(output.result) {
+ //TODO: Show dialog
+ case DCDResult.FAIL:
+ case DCDResult.DCD_NOT_RUNNING:
+ case DCDResult.NO_RESULT:
+ return false;
+ case DCDResult.SUCCESS:
+ auto target = to!int(output.output[1]);
+ if(output.output[0].indexOf("stdin".dup) != -1) {
+ Log.d("Declaration is in current file. Jumping to it.");
+ auto destPos = byteOffsetToCaret(editor.text, target);
+ editor.setCaretPos(destPos.line,destPos.pos);
+ }
+ else {
+ //Must open file first to get the content for finding the correct caret position.
+ _frame.openSourceFile(to!string(output.output[0]));
+ auto destPos = byteOffsetToCaret(_frame.currentEditor.text(), target);
+ _frame.currentEditor.setCaretPos(destPos.line,destPos.pos);
+ }
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ override dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition) {
+
+ auto byteOffset = caretPositionToByteOffset(editor.text, caretPosition);
+ ResultSet output = _dcd.getCompletions(editor.text, byteOffset);
+ switch(output.result) {
+ //TODO: Show dialog
+ case DCDResult.FAIL:
+ case DCDResult.DCD_NOT_RUNNING:
+ case DCDResult.NO_RESULT:
+ case DCDResult.SUCCESS:
+ default:
+ return output.output;
+ }
+ }
+
+private:
+ DCDInterface _dcd;
+
+ int caretPositionToByteOffset(dstring content, TextPosition caretPosition) {
+ auto line = 0;
+ auto pos = 0;
+ auto bytes = 0;
+ foreach(c; content) {
+ bytes++;
+ if(c == '\n') {
+ line++;
+ }
+ if(line == caretPosition.line) {
+ if(pos == caretPosition.pos)
+ break;
+ pos++;
+ }
+ }
+ return bytes;
+ }
+
+ TextPosition byteOffsetToCaret(dstring content, int byteOffset) {
+ int bytes = 0;
+ int line = 0;
+ int pos = 0;
+ TextPosition textPos;
+ foreach(c; content) {
+ if(bytes == byteOffset) {
+ //We all good.
+ textPos.line = line;
+ textPos.pos = pos;
+ return textPos;
+ }
+ bytes++;
+ if(c == '\n')
+ {
+ line++;
+ pos = 0;
+ }
+ else {
+ pos++;
+ }
+ }
+ return textPos;
+ }
+}
diff --git a/src/dlangide/tools/d/simpledsyntaxhighlighter.d b/src/dlangide/tools/d/simpledsyntaxhighlighter.d
new file mode 100644
index 0000000..635c76f
--- /dev/null
+++ b/src/dlangide/tools/d/simpledsyntaxhighlighter.d
@@ -0,0 +1,641 @@
+module dlangide.tools.d.simpledsyntaxhighlighter;
+
+import dlangui.core.logger;
+import dlangui.widgets.editors;
+import dlangui.widgets.srcedit;
+
+import ddc.lexer.textsource;
+import ddc.lexer.exceptions;
+import ddc.lexer.tokenizer;
+
+class SimpleDSyntaxHighlighter : SyntaxHighlighter {
+
+ EditableContent _content;
+ SourceFile _file;
+ ArraySourceLines _lines;
+ Tokenizer _tokenizer;
+ this (string filename) {
+ _file = new SourceFile(filename);
+ _lines = new ArraySourceLines();
+ _tokenizer = new Tokenizer(_lines);
+ _tokenizer.errorTolerant = true;
+ }
+
+ TokenPropString[] _props;
+
+ /// returns editable content
+ @property EditableContent content() { return _content; }
+ /// set editable content
+ @property SyntaxHighlighter 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 TextPosition _lastTokenStart;
+ protected Token _lastToken;
+ protected bool initTokenizer(TextPosition startPos) {
+ const dstring[] lines = content.lines;
+ _lines.init(cast(dstring[])(lines[startPos.line .. $]), _file, startPos.line);
+ _tokenizer.init(_lines, startPos.pos);
+ _lastTokenStart = startPos;
+ _lastToken = null;
+ nextToken();
+ return true;
+ }
+
+ protected TokenWithRange nextToken() {
+ TokenWithRange res;
+ if (_lastToken && _lastToken.type == TokenType.EOF) {
+ // end of file
+ res.range.start = _lastTokenStart;
+ res.range.end = content.endOfFile();
+ res.token = null;
+ return res;
+ }
+ res.range.start = _lastTokenStart;
+ res.token = _lastToken;
+ _lastToken = _tokenizer.nextToken();
+ if (_lastToken)
+ _lastToken = _lastToken.clone();
+ _lastTokenStart = _lastToken ? TextPosition(_lastToken.line - 1, _lastToken.pos - 1) : content.endOfFile();
+ res.range.end = _lastTokenStart;
+ return res;
+ }
+
+ protected TokenWithRange getPositionToken(TextPosition pos) {
+ //Log.d("getPositionToken for ", pos);
+ TextPosition start = tokenStart(pos);
+ //Log.d("token start found: ", start);
+ initTokenizer(start);
+ for (;;) {
+ TokenWithRange tokenRange = nextToken();
+ //Log.d("read token: ", tokenRange);
+ if (!tokenRange.token) {
+ //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;
+ //Log.d("getPositionToken for ", pos);
+ TextPosition start = tokenStart(range.start);
+ //Log.d("token start found: ", start);
+ initTokenizer(start);
+ for (;;) {
+ TokenWithRange tokenRange = nextToken();
+ //Log.d("read token: ", tokenRange);
+ if (!tokenRange.token) {
+ //Log.d("end of file");
+ 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 && 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.token && endToken.token && 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.token && endToken.token && 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) {
+ //Log.d("updateHighlight");
+ long ms0 = currentTimeMillis();
+ _props = props;
+ changeStartLine = 0;
+ changeEndLine = cast(int)lines.length;
+ _lines.init(lines[changeStartLine..$], _file, changeStartLine);
+ _tokenizer.init(_lines);
+ int tokenPos = 0;
+ int tokenLine = 0;
+ ubyte category = 0;
+ try {
+ for (;;) {
+ Token token = _tokenizer.nextToken();
+ if (token is null) {
+ //Log.d("Null token returned");
+ break;
+ }
+ uint newPos = token.pos - 1;
+ uint newLine = token.line - 1;
+
+ //Log.d("", tokenLine + 1, ":", tokenPos + 1, " \t", token.line, ":", token.pos, "\t", token.toString);
+ if (token.type == TokenType.EOF) {
+ //Log.d("EOF token");
+ }
+
+ // 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.type) {
+ case TokenType.COMMENT:
+ category = token.isDocumentationComment ? TokenCategory.Comment_Documentation : TokenCategory.Comment;
+ break;
+ case TokenType.KEYWORD:
+ category = TokenCategory.Keyword;
+ break;
+ case TokenType.IDENTIFIER:
+ category = TokenCategory.Identifier;
+ break;
+ case TokenType.STRING:
+ category = TokenCategory.String;
+ break;
+ case TokenType.CHARACTER:
+ category = TokenCategory.Character;
+ break;
+ case TokenType.INTEGER:
+ category = TokenCategory.Integer;
+ break;
+ case TokenType.FLOAT:
+ category = TokenCategory.Float;
+ break;
+ case TokenType.OP:
+ category = TokenCategory.Op;
+ break;
+ case TokenType.INVALID:
+ switch (token.invalidTokenType) {
+ case TokenType.IDENTIFIER:
+ category = TokenCategory.Error_InvalidIdentifier;
+ break;
+ case TokenType.STRING:
+ category = TokenCategory.Error_InvalidString;
+ break;
+ case TokenType.COMMENT:
+ category = TokenCategory.Error_InvalidComment;
+ break;
+ case TokenType.OP:
+ category = TokenCategory.Error_InvalidOp;
+ break;
+ case TokenType.FLOAT:
+ case TokenType.INTEGER:
+ category = TokenCategory.Error_InvalidNumber;
+ break;
+ default:
+ category = TokenCategory.Error;
+ break;
+ }
+ break;
+ default:
+ category = 0;
+ break;
+ }
+ tokenPos = newPos;
+ tokenLine= newLine;
+
+ if (token.type == TokenType.EOF) {
+ //Log.d("EOF token");
+ break;
+ }
+ }
+ } catch (Exception e) {
+ Log.e("exception while trying to parse D source", e);
+ }
+ _lines.close();
+ _props = null;
+ long elapsed = currentTimeMillis() - ms0;
+ if (elapsed > 20)
+ Log.d("updateHighlight took ", elapsed, "ms");
+ }
+}
+
diff --git a/src/dlangide/tools/editortool.d b/src/dlangide/tools/editortool.d
new file mode 100644
index 0000000..c9fa8db
--- /dev/null
+++ b/src/dlangide/tools/editortool.d
@@ -0,0 +1,23 @@
+module dlangide.tools.editorTool;
+
+
+
+import dlangui.widgets.editors;
+import dlangui.core.types;
+import dlangide.ui.frame;
+import dlangide.ui.dsourceedit;
+
+public import dlangide.tools.d.deditorTool;
+
+class EditorTool
+{
+ this(IDEFrame frame) {
+ _frame = frame;
+ }
+ //Since files might be unsaved, we must send all the text content.
+ abstract bool goToDefinition(DSourceEdit editor, TextPosition caretPosition);
+ abstract dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition);
+
+ protected IDEFrame _frame;
+
+}
diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d
index bbe2971..b0b855f 100644
--- a/src/dlangide/ui/commands.d
+++ b/src/dlangide/ui/commands.d
@@ -40,6 +40,9 @@ enum IDEActions : int {
ProjectFolderRemoveItem,
ProjectFolderOpenItem,
ProjectFolderRenameItem,
+ GoToDefinition,
+ GetCompletionSuggestions,
+ InsertCompletion,
}
__gshared static this() {
@@ -92,4 +95,5 @@ const Action ACTION_HELP_ABOUT = new Action(IDEActions.HelpAbout, "MENU_HELP_ABO
const Action ACTION_WINDOW_CLOSE_ALL_DOCUMENTS = new Action(IDEActions.WindowCloseAllDocuments, "MENU_WINDOW_CLOSE_ALL_DOCUMENTS"c);
const Action ACTION_CREATE_NEW_WORKSPACE = new Action(IDEActions.CreateNewWorkspace, "Create new workspace"d);
const Action ACTION_ADD_TO_CURRENT_WORKSPACE = new Action(IDEActions.AddToCurrentWorkspace, "Add to current workspace"d);
-
+const Action ACTION_GO_TO_DEFINITION = new Action(IDEActions.GoToDefinition, "GO_TO_DEFINITION"c, ""c, KeyCode.KEY_G, KeyFlag.Control);
+const Action ACTION_GET_COMPLETIONS = new Action(IDEActions.GetCompletionSuggestions, "SHOW_COMPLETIONS"c, ""c, KeyCode.KEY_G, KeyFlag.Control|KeyFlag.Shift);
\ No newline at end of file
diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d
index f01b33b..ce53896 100644
--- a/src/dlangide/ui/dsourceedit.d
+++ b/src/dlangide/ui/dsourceedit.d
@@ -4,6 +4,7 @@ import dlangui.core.logger;
import dlangui.widgets.editors;
import dlangui.widgets.srcedit;
import dlangui.widgets.menu;
+import dlangui.widgets.popup;
import ddc.lexer.textsource;
import ddc.lexer.exceptions;
@@ -12,6 +13,7 @@ import ddc.lexer.tokenizer;
import dlangide.workspace.workspace;
import dlangide.workspace.project;
import dlangide.ui.commands;
+import dlangide.tools.d.simpledsyntaxhighlighter;
import std.algorithm;
@@ -30,7 +32,7 @@ class DSourceEdit : SourceEdit {
setTokenHightlightColor(TokenCategory.Comment_Documentation, 0x206000);
//setTokenHightlightColor(TokenCategory.Identifier, 0x206000); // no colors
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);
+ 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_GET_COMPLETIONS);
popupMenu = editPopupItem;
showIcons = true;
showFolding = true;
@@ -79,6 +81,11 @@ class DSourceEdit : SourceEdit {
case IDEActions.FileSave:
save();
return true;
+ case IDEActions.InsertCompletion:
+ EditOperation edit = new EditOperation(EditAction.Replace, getCaretPosition, a.label);
+ _content.performOperation(edit, this);
+ setFocus();
+ return true;
default:
break;
}
@@ -86,6 +93,36 @@ class DSourceEdit : SourceEdit {
return super.handleAction(a);
}
+ void showCompletionPopup(dstring[] suggestions) {
+
+ if(suggestions.length == 0) {
+ return;
+ }
+
+ MenuItem completionPopupItems = new MenuItem(null);
+ //Add all the suggestions.
+ foreach(int i, dstring suggestion ; suggestions) {
+ auto action = new Action(IDEActions.InsertCompletion, suggestion);
+ completionPopupItems.add(action);
+ }
+ completionPopupItems.updateActionState(this);
+
+ PopupMenu popupMenu = new PopupMenu(completionPopupItems);
+ popupMenu.onMenuItemActionListener = this;
+ popupMenu.maxHeight(400);
+ popupMenu.selectItem(0);
+
+ PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, textPosToClient(_caretPos).left + left + _leftPaneWidth, textPosToClient(_caretPos).top + top + margins.top);
+ popup.setFocus();
+ popup.flags = PopupFlags.CloseOnClickOutside;
+
+ Log.d("Showing popup at ", textPosToClient(_caretPos).left, " ", textPosToClient(_caretPos).top);
+ }
+
+ TextPosition getCaretPosition() {
+ return _caretPos;
+ }
+
/// change caret position and ensure it is visible
void setCaretPos(int line, int column)
{
@@ -94,636 +131,3 @@ class DSourceEdit : SourceEdit {
ensureCaretVisible();
}
}
-
-
-
-class SimpleDSyntaxHighlighter : SyntaxHighlighter {
-
- EditableContent _content;
- SourceFile _file;
- ArraySourceLines _lines;
- Tokenizer _tokenizer;
- this (string filename) {
- _file = new SourceFile(filename);
- _lines = new ArraySourceLines();
- _tokenizer = new Tokenizer(_lines);
- _tokenizer.errorTolerant = true;
- }
-
- TokenPropString[] _props;
-
- /// returns editable content
- @property EditableContent content() { return _content; }
- /// set editable content
- @property SyntaxHighlighter 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 TextPosition _lastTokenStart;
- protected Token _lastToken;
- protected bool initTokenizer(TextPosition startPos) {
- const dstring[] lines = content.lines;
- _lines.init(cast(dstring[])(lines[startPos.line .. $]), _file, startPos.line);
- _tokenizer.init(_lines, startPos.pos);
- _lastTokenStart = startPos;
- _lastToken = null;
- nextToken();
- return true;
- }
-
- protected TokenWithRange nextToken() {
- TokenWithRange res;
- if (_lastToken && _lastToken.type == TokenType.EOF) {
- // end of file
- res.range.start = _lastTokenStart;
- res.range.end = content.endOfFile();
- res.token = null;
- return res;
- }
- res.range.start = _lastTokenStart;
- res.token = _lastToken;
- _lastToken = _tokenizer.nextToken();
- if (_lastToken)
- _lastToken = _lastToken.clone();
- _lastTokenStart = _lastToken ? TextPosition(_lastToken.line - 1, _lastToken.pos - 1) : content.endOfFile();
- res.range.end = _lastTokenStart;
- return res;
- }
-
- protected TokenWithRange getPositionToken(TextPosition pos) {
- //Log.d("getPositionToken for ", pos);
- TextPosition start = tokenStart(pos);
- //Log.d("token start found: ", start);
- initTokenizer(start);
- for (;;) {
- TokenWithRange tokenRange = nextToken();
- //Log.d("read token: ", tokenRange);
- if (!tokenRange.token) {
- //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;
- //Log.d("getPositionToken for ", pos);
- TextPosition start = tokenStart(range.start);
- //Log.d("token start found: ", start);
- initTokenizer(start);
- for (;;) {
- TokenWithRange tokenRange = nextToken();
- //Log.d("read token: ", tokenRange);
- if (!tokenRange.token) {
- //Log.d("end of file");
- 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 && 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.token && endToken.token && 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.token && endToken.token && 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) {
- //Log.d("updateHighlight");
- long ms0 = currentTimeMillis();
- _props = props;
- changeStartLine = 0;
- changeEndLine = cast(int)lines.length;
- _lines.init(lines[changeStartLine..$], _file, changeStartLine);
- _tokenizer.init(_lines);
- int tokenPos = 0;
- int tokenLine = 0;
- ubyte category = 0;
- try {
- for (;;) {
- Token token = _tokenizer.nextToken();
- if (token is null) {
- //Log.d("Null token returned");
- break;
- }
- uint newPos = token.pos - 1;
- uint newLine = token.line - 1;
-
- //Log.d("", tokenLine + 1, ":", tokenPos + 1, " \t", token.line, ":", token.pos, "\t", token.toString);
- if (token.type == TokenType.EOF) {
- //Log.d("EOF token");
- }
-
- // 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.type) {
- case TokenType.COMMENT:
- category = token.isDocumentationComment ? TokenCategory.Comment_Documentation : TokenCategory.Comment;
- break;
- case TokenType.KEYWORD:
- category = TokenCategory.Keyword;
- break;
- case TokenType.IDENTIFIER:
- category = TokenCategory.Identifier;
- break;
- case TokenType.STRING:
- category = TokenCategory.String;
- break;
- case TokenType.CHARACTER:
- category = TokenCategory.Character;
- break;
- case TokenType.INTEGER:
- category = TokenCategory.Integer;
- break;
- case TokenType.FLOAT:
- category = TokenCategory.Float;
- break;
- case TokenType.OP:
- category = TokenCategory.Op;
- break;
- case TokenType.INVALID:
- switch (token.invalidTokenType) {
- case TokenType.IDENTIFIER:
- category = TokenCategory.Error_InvalidIdentifier;
- break;
- case TokenType.STRING:
- category = TokenCategory.Error_InvalidString;
- break;
- case TokenType.COMMENT:
- category = TokenCategory.Error_InvalidComment;
- break;
- case TokenType.OP:
- category = TokenCategory.Error_InvalidOp;
- break;
- case TokenType.FLOAT:
- case TokenType.INTEGER:
- category = TokenCategory.Error_InvalidNumber;
- break;
- default:
- category = TokenCategory.Error;
- break;
- }
- break;
- default:
- category = 0;
- break;
- }
- tokenPos = newPos;
- tokenLine= newLine;
-
- if (token.type == TokenType.EOF) {
- //Log.d("EOF token");
- break;
- }
- }
- } catch (Exception e) {
- Log.e("exception while trying to parse D source", e);
- }
- _lines.close();
- _props = null;
- long elapsed = currentTimeMillis() - ms0;
- if (elapsed > 20)
- Log.d("updateHighlight took ", elapsed, "ms");
- }
-}
diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d
index 93c7280..af2d832 100644
--- a/src/dlangide/ui/frame.d
+++ b/src/dlangide/ui/frame.d
@@ -10,6 +10,7 @@ import dlangui.widgets.appframe;
import dlangui.widgets.docks;
import dlangui.widgets.toolbars;
import dlangui.widgets.combobox;
+import dlangui.widgets.popup;
import dlangui.dialogs.dialog;
import dlangui.dialogs.filedlg;
import dlangui.core.stdaction;
@@ -22,6 +23,7 @@ import dlangide.ui.homescreen;
import dlangide.workspace.workspace;
import dlangide.workspace.project;
import dlangide.builders.builder;
+import dlangide.tools.editorTool;
import std.conv;
import std.utf;
@@ -60,6 +62,7 @@ class IDEFrame : AppFrame {
OutputPanel _logPanel;
DockHost _dockHost;
TabWidget _tabs;
+ EditorTool _editorTool;
dstring frameWindowCaptionSuffix = "DLangIDE"d;
@@ -72,6 +75,7 @@ class IDEFrame : AppFrame {
override protected void init() {
_appName = "dlangide";
+ _editorTool = new DEditorTool(this);
super.init();
}
@@ -333,6 +337,9 @@ class IDEFrame : AppFrame {
editItem.add(ACTION_EDIT_PREFERENCES);
+ MenuItem navItem = new MenuItem(new Action(21, "MENU_NAVIGATE"));
+ navItem.add(ACTION_GO_TO_DEFINITION, ACTION_GET_COMPLETIONS);
+
MenuItem projectItem = new MenuItem(new Action(21, "MENU_PROJECT"));
projectItem.add(ACTION_PROJECT_SET_STARTUP, ACTION_PROJECT_REFRESH, ACTION_PROJECT_UPDATE_DEPENDENCIES, ACTION_PROJECT_SETTINGS);
@@ -354,6 +361,7 @@ class IDEFrame : AppFrame {
mainMenuItems.add(fileItem);
mainMenuItems.add(editItem);
mainMenuItems.add(projectItem);
+ mainMenuItems.add(navItem);
mainMenuItems.add(buildItem);
mainMenuItems.add(debugItem);
//mainMenuItems.add(viewItem);
@@ -538,6 +546,15 @@ class IDEFrame : AppFrame {
};
dlg.show();
return true;
+ case IDEActions.GoToDefinition:
+ Log.d("Trying to go to definition.");
+ _editorTool.goToDefinition(currentEditor(), currentEditor.getCaretPosition());
+ return true;
+ case IDEActions.GetCompletionSuggestions:
+ Log.d("Getting auto completion suggestions.");
+ auto results = _editorTool.getCompletions(currentEditor, currentEditor.getCaretPosition);
+ currentEditor.showCompletionPopup(results);
+ return true;
default:
return super.handleAction(a);
}
diff --git a/views/res/i18n/en.ini b/views/res/i18n/en.ini
index a069f2f..1939fe4 100644
--- a/views/res/i18n/en.ini
+++ b/views/res/i18n/en.ini
@@ -55,6 +55,10 @@ MENU_WINDOW_CLOSE_ALL_DOCUMENTS=Close All Documents
MENU_HELP=&HELP
MENU_HELP_VIEW_HELP=&View help
MENU_HELP_ABOUT=&About
+GO_TO_DEFINITION=Go To Definition
+SHOW_COMPLETIONS=Get Autocompletions
+MENU_NAVIGATE=NAVIGATE
+
TAB_LONG_LIST=Long list
TAB_BUTTONS=Buttons