mirror of https://github.com/buggins/dlangide.git
merge Freakazo changes for DCD - pool request 29
This commit is contained in:
commit
2e4f9c7efc
|
@ -209,6 +209,14 @@
|
||||||
<File path="src\dlangide\builders\builder.d" />
|
<File path="src\dlangide\builders\builder.d" />
|
||||||
<File path="src\dlangide\builders\extprocess.d" />
|
<File path="src\dlangide\builders\extprocess.d" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
<Folder name="tools">
|
||||||
|
<Folder name="d">
|
||||||
|
<File path="src\dlangide\tools\d\dcdinterface.d" />
|
||||||
|
<File path="src\dlangide\tools\d\deditortool.d" />
|
||||||
|
<File path="src\dlangide\tools\d\simpledsyntaxhighlighter.d" />
|
||||||
|
</Folder>
|
||||||
|
<File path="src\dlangide\tools\editortool.d" />
|
||||||
|
</Folder>
|
||||||
<Folder name="ui">
|
<Folder name="ui">
|
||||||
<File path="src\dlangide\ui\commands.d" />
|
<File path="src\dlangide\ui\commands.d" />
|
||||||
<File path="src\dlangide\ui\dsourceedit.d" />
|
<File path="src\dlangide\ui\dsourceedit.d" />
|
||||||
|
|
|
@ -297,7 +297,7 @@ class ExternalProcess {
|
||||||
params ~= _program;
|
params ~= _program;
|
||||||
params ~= _args;
|
params ~= _args;
|
||||||
if (!_stderr)
|
if (!_stderr)
|
||||||
redirect = Redirect.stdout | Redirect.stderrToStdout; //Redirect.stdin |
|
redirect = Redirect.stdout | Redirect.stderrToStdout | Redirect.stdin;
|
||||||
else
|
else
|
||||||
redirect = Redirect.all;
|
redirect = Redirect.all;
|
||||||
Log.i("Trying to run program ", _program, " with args ", _args);
|
Log.i("Trying to run program ", _program, " with args ", _args);
|
||||||
|
@ -393,4 +393,14 @@ class ExternalProcess {
|
||||||
}
|
}
|
||||||
return _state;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -40,6 +40,9 @@ enum IDEActions : int {
|
||||||
ProjectFolderRemoveItem,
|
ProjectFolderRemoveItem,
|
||||||
ProjectFolderOpenItem,
|
ProjectFolderOpenItem,
|
||||||
ProjectFolderRenameItem,
|
ProjectFolderRenameItem,
|
||||||
|
GoToDefinition,
|
||||||
|
GetCompletionSuggestions,
|
||||||
|
InsertCompletion,
|
||||||
}
|
}
|
||||||
|
|
||||||
__gshared static this() {
|
__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_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_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_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);
|
|
@ -4,6 +4,7 @@ import dlangui.core.logger;
|
||||||
import dlangui.widgets.editors;
|
import dlangui.widgets.editors;
|
||||||
import dlangui.widgets.srcedit;
|
import dlangui.widgets.srcedit;
|
||||||
import dlangui.widgets.menu;
|
import dlangui.widgets.menu;
|
||||||
|
import dlangui.widgets.popup;
|
||||||
|
|
||||||
import ddc.lexer.textsource;
|
import ddc.lexer.textsource;
|
||||||
import ddc.lexer.exceptions;
|
import ddc.lexer.exceptions;
|
||||||
|
@ -12,6 +13,7 @@ import ddc.lexer.tokenizer;
|
||||||
import dlangide.workspace.workspace;
|
import dlangide.workspace.workspace;
|
||||||
import dlangide.workspace.project;
|
import dlangide.workspace.project;
|
||||||
import dlangide.ui.commands;
|
import dlangide.ui.commands;
|
||||||
|
import dlangide.tools.d.simpledsyntaxhighlighter;
|
||||||
|
|
||||||
import std.algorithm;
|
import std.algorithm;
|
||||||
|
|
||||||
|
@ -30,7 +32,7 @@ class DSourceEdit : SourceEdit {
|
||||||
setTokenHightlightColor(TokenCategory.Comment_Documentation, 0x206000);
|
setTokenHightlightColor(TokenCategory.Comment_Documentation, 0x206000);
|
||||||
//setTokenHightlightColor(TokenCategory.Identifier, 0x206000); // no colors
|
//setTokenHightlightColor(TokenCategory.Identifier, 0x206000); // no colors
|
||||||
MenuItem editPopupItem = new MenuItem(null);
|
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;
|
popupMenu = editPopupItem;
|
||||||
showIcons = true;
|
showIcons = true;
|
||||||
showFolding = true;
|
showFolding = true;
|
||||||
|
@ -79,6 +81,11 @@ class DSourceEdit : SourceEdit {
|
||||||
case IDEActions.FileSave:
|
case IDEActions.FileSave:
|
||||||
save();
|
save();
|
||||||
return true;
|
return true;
|
||||||
|
case IDEActions.InsertCompletion:
|
||||||
|
EditOperation edit = new EditOperation(EditAction.Replace, getCaretPosition, a.label);
|
||||||
|
_content.performOperation(edit, this);
|
||||||
|
setFocus();
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -86,6 +93,36 @@ class DSourceEdit : SourceEdit {
|
||||||
return super.handleAction(a);
|
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
|
/// change caret position and ensure it is visible
|
||||||
void setCaretPos(int line, int column)
|
void setCaretPos(int line, int column)
|
||||||
{
|
{
|
||||||
|
@ -94,636 +131,3 @@ class DSourceEdit : SourceEdit {
|
||||||
ensureCaretVisible();
|
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import dlangui.widgets.appframe;
|
||||||
import dlangui.widgets.docks;
|
import dlangui.widgets.docks;
|
||||||
import dlangui.widgets.toolbars;
|
import dlangui.widgets.toolbars;
|
||||||
import dlangui.widgets.combobox;
|
import dlangui.widgets.combobox;
|
||||||
|
import dlangui.widgets.popup;
|
||||||
import dlangui.dialogs.dialog;
|
import dlangui.dialogs.dialog;
|
||||||
import dlangui.dialogs.filedlg;
|
import dlangui.dialogs.filedlg;
|
||||||
import dlangui.core.stdaction;
|
import dlangui.core.stdaction;
|
||||||
|
@ -22,6 +23,7 @@ import dlangide.ui.homescreen;
|
||||||
import dlangide.workspace.workspace;
|
import dlangide.workspace.workspace;
|
||||||
import dlangide.workspace.project;
|
import dlangide.workspace.project;
|
||||||
import dlangide.builders.builder;
|
import dlangide.builders.builder;
|
||||||
|
import dlangide.tools.editorTool;
|
||||||
|
|
||||||
import std.conv;
|
import std.conv;
|
||||||
import std.utf;
|
import std.utf;
|
||||||
|
@ -60,6 +62,7 @@ class IDEFrame : AppFrame {
|
||||||
OutputPanel _logPanel;
|
OutputPanel _logPanel;
|
||||||
DockHost _dockHost;
|
DockHost _dockHost;
|
||||||
TabWidget _tabs;
|
TabWidget _tabs;
|
||||||
|
EditorTool _editorTool;
|
||||||
|
|
||||||
dstring frameWindowCaptionSuffix = "DLangIDE"d;
|
dstring frameWindowCaptionSuffix = "DLangIDE"d;
|
||||||
|
|
||||||
|
@ -72,6 +75,7 @@ class IDEFrame : AppFrame {
|
||||||
|
|
||||||
override protected void init() {
|
override protected void init() {
|
||||||
_appName = "dlangide";
|
_appName = "dlangide";
|
||||||
|
_editorTool = new DEditorTool(this);
|
||||||
super.init();
|
super.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,6 +337,9 @@ class IDEFrame : AppFrame {
|
||||||
|
|
||||||
editItem.add(ACTION_EDIT_PREFERENCES);
|
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"));
|
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);
|
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(fileItem);
|
||||||
mainMenuItems.add(editItem);
|
mainMenuItems.add(editItem);
|
||||||
mainMenuItems.add(projectItem);
|
mainMenuItems.add(projectItem);
|
||||||
|
mainMenuItems.add(navItem);
|
||||||
mainMenuItems.add(buildItem);
|
mainMenuItems.add(buildItem);
|
||||||
mainMenuItems.add(debugItem);
|
mainMenuItems.add(debugItem);
|
||||||
//mainMenuItems.add(viewItem);
|
//mainMenuItems.add(viewItem);
|
||||||
|
@ -538,6 +546,15 @@ class IDEFrame : AppFrame {
|
||||||
};
|
};
|
||||||
dlg.show();
|
dlg.show();
|
||||||
return true;
|
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:
|
default:
|
||||||
return super.handleAction(a);
|
return super.handleAction(a);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,10 @@ MENU_WINDOW_CLOSE_ALL_DOCUMENTS=Close All Documents
|
||||||
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
|
||||||
|
GO_TO_DEFINITION=Go To Definition
|
||||||
|
SHOW_COMPLETIONS=Get Autocompletions
|
||||||
|
MENU_NAVIGATE=NAVIGATE
|
||||||
|
|
||||||
|
|
||||||
TAB_LONG_LIST=Long list
|
TAB_LONG_LIST=Long list
|
||||||
TAB_BUTTONS=Buttons
|
TAB_BUTTONS=Buttons
|
||||||
|
|
Loading…
Reference in New Issue