From d766508c99fe412626fa0ed7472bcf105131d4c8 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Thu, 12 Feb 2015 04:53:41 +1100 Subject: [PATCH 01/15] Initial implementation of "Go to definition". Uses DCD to get location. See issue #5 --- src/dlangide/tools/EditorTool.d | 22 ++++++ src/dlangide/tools/d/DCDInterface.d | 22 ++++++ src/dlangide/tools/d/DEditorTool.d | 114 ++++++++++++++++++++++++++++ src/dlangide/ui/commands.d | 2 + src/dlangide/ui/dsourceedit.d | 7 +- src/dlangide/ui/frame.d | 9 +++ views/res/i18n/en.ini | 1 + 7 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/dlangide/tools/EditorTool.d create mode 100644 src/dlangide/tools/d/DCDInterface.d create mode 100644 src/dlangide/tools/d/DEditorTool.d diff --git a/src/dlangide/tools/EditorTool.d b/src/dlangide/tools/EditorTool.d new file mode 100644 index 0000000..a04299f --- /dev/null +++ b/src/dlangide/tools/EditorTool.d @@ -0,0 +1,22 @@ +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.editorTool; + +class EditorTool +{ + this(IDEFrame frame) { + _frame = frame; + } + //Since files might be unsaved, we must send all the text content. + abstract bool goToDefinition(DSourceEdit content, TextPosition caretPosition); + + protected IDEFrame _frame; + +} \ No newline at end of file diff --git a/src/dlangide/tools/d/DCDInterface.d b/src/dlangide/tools/d/DCDInterface.d new file mode 100644 index 0000000..ed7df02 --- /dev/null +++ b/src/dlangide/tools/d/DCDInterface.d @@ -0,0 +1,22 @@ +module dlangide.tools.d.DCDInterface; + +import dlangide.builders.extprocess; + +//Interface to DCD +//TODO: Check if server is running, start server if needed etc. +class DCDInterface { + ExternalProcess dcdProcess; + this() { + dcdProcess = new ExternalProcess(); + } + bool execute(char[][] arguments ,ref dstring output) { + ProtectedTextStorage stdoutTarget = new ProtectedTextStorage(); + ExternalProcess dcdProcess = new ExternalProcess(); + //TODO: Working Directory, where is that? + dcdProcess.run("dcd-client".dup, arguments, "/usr/bin".dup, stdoutTarget); + while(dcdProcess.poll() == ExternalProcessState.Running){ } + output = stdoutTarget.readText(); + return true; + } + +} \ No newline at end of file diff --git a/src/dlangide/tools/d/DEditorTool.d b/src/dlangide/tools/d/DEditorTool.d new file mode 100644 index 0000000..7ff6db8 --- /dev/null +++ b/src/dlangide/tools/d/DEditorTool.d @@ -0,0 +1,114 @@ +module dlangide.tools.d.editorTool; + +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 content = editor.text(); + auto byteOffset = caretPositionToByteOffset(content, caretPosition); + + char[][] arguments = ["-l".dup, "-c".dup]; + arguments ~= [to!(char[])(byteOffset)]; + arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; + + dstring output; + _dcd.execute(arguments, output); + + string[] outputLines = to!string(output).splitLines(); + Log.d("DCD:", outputLines); + + foreach(string outputLine ; outputLines) { + if(outputLine.indexOf("Not Found".dup) == -1) { + auto split = outputLine.indexOf("\t"); + if(split == -1) { + Log.d("DCD output format error."); + break; + } + if(indexOf(outputLine[0 .. split],"stdin".dup) != -1) { + Log.d("Declaration is in current file. Can jump to it."); + auto target = to!int(outputLine[split+1 .. $]); + auto destPos = byteOffsetToCaret(content, target); + editor.setCaretPos(destPos.line,destPos.pos); + } + else { + auto filename = outputLine[0 .. split]; + if(_frame !is null) { + writeln("Well I'm trying"); + _frame.openSourceFile(filename); + auto target = to!int(outputLine[split+1 .. $]); + auto destPos = byteOffsetToCaret(_frame.currentEditor.text(), target); + + _frame.currentEditor.setCaretPos(destPos.line,destPos.pos); + writeln("Well I tried"); + } + } + } + } + return true; + } + +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; + } +} \ No newline at end of file diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d index 9a92f09..b011599 100644 --- a/src/dlangide/ui/commands.d +++ b/src/dlangide/ui/commands.d @@ -40,6 +40,7 @@ enum IDEActions : int { ProjectFolderRemoveItem, ProjectFolderOpenItem, ProjectFolderRenameItem, + GoToDefinition, } const Action ACTION_PROJECT_FOLDER_ADD_ITEM = new Action(IDEActions.ProjectFolderAddItem, "MENU_PROJECT_FOLDER_ADD_ITEM"c); @@ -85,3 +86,4 @@ 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, "edit-cut"c, KeyCode.KEY_G, KeyFlag.Control); \ No newline at end of file diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index 2393dfd..5d4cc6c 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -86,6 +86,11 @@ class DSourceEdit : SourceEdit { return super.handleAction(a); } + TextPosition getCaretPosition() { + return _caretPos; + } + + /// change caret position and ensure it is visible void setCaretPos(int line, int column) { @@ -245,7 +250,7 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter { for (;;) { ch = nextBracket(dir, p); if (!ch) // no more brackets - return startPos; + break; auto match = _bracketStack.process(ch); if (match == BracketMatch.FOUND) return p; diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d index 93be601..d12a5ad 100644 --- a/src/dlangide/ui/frame.d +++ b/src/dlangide/ui/frame.d @@ -22,6 +22,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 +61,7 @@ class IDEFrame : AppFrame { OutputPanel _logPanel; DockHost _dockHost; TabWidget _tabs; + EditorTool _editorTool; dstring frameWindowCaptionSuffix = "DLangIDE"d; @@ -69,6 +71,7 @@ class IDEFrame : AppFrame { } override protected void init() { + _editorTool = new DEditorTool(this); super.init(); } @@ -378,6 +381,8 @@ class IDEFrame : AppFrame { tb.addControl(cbBuildConfiguration); tb.addButtons(ACTION_PROJECT_BUILD); + tb.addButtons(ACTION_GO_TO_DEFINITION); + tb = res.getOrAddToolbar("Edit"); tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT); @@ -507,6 +512,10 @@ class IDEFrame : AppFrame { }; dlg.show(); return true; + case IDEActions.GoToDefinition: + Log.i("Trying to go to definition"); + _editorTool.goToDefinition(currentEditor(), currentEditor.getCaretPosition()); + return true; default: return super.handleAction(a); } diff --git a/views/res/i18n/en.ini b/views/res/i18n/en.ini index 91529fd..cf89974 100644 --- a/views/res/i18n/en.ini +++ b/views/res/i18n/en.ini @@ -54,6 +54,7 @@ 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 TAB_LONG_LIST=Long list TAB_BUTTONS=Buttons From e09844343e8c1f0bbb5c02f94c006b2cfc4f50e7 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Thu, 12 Feb 2015 06:06:23 +1100 Subject: [PATCH 02/15] Implementated getting completion suggestions. See issue #20 Suggestions is not displayed yet. --- src/dlangide/tools/EditorTool.d | 3 ++- src/dlangide/tools/d/DEditorTool.d | 34 ++++++++++++++++++++++++++++++ src/dlangide/ui/commands.d | 4 +++- src/dlangide/ui/frame.d | 7 +++++- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/dlangide/tools/EditorTool.d b/src/dlangide/tools/EditorTool.d index a04299f..ee84c8f 100644 --- a/src/dlangide/tools/EditorTool.d +++ b/src/dlangide/tools/EditorTool.d @@ -15,7 +15,8 @@ class EditorTool _frame = frame; } //Since files might be unsaved, we must send all the text content. - abstract bool goToDefinition(DSourceEdit content, TextPosition caretPosition); + abstract bool goToDefinition(DSourceEdit editor, TextPosition caretPosition); + abstract dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition); protected IDEFrame _frame; diff --git a/src/dlangide/tools/d/DEditorTool.d b/src/dlangide/tools/d/DEditorTool.d index 7ff6db8..6d3e731 100644 --- a/src/dlangide/tools/d/DEditorTool.d +++ b/src/dlangide/tools/d/DEditorTool.d @@ -66,6 +66,40 @@ class DEditorTool : EditorTool return true; } + override dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition) { + auto content = editor.text(); + auto byteOffset = caretPositionToByteOffset(content, caretPosition); + + char[][] arguments = ["-c".dup]; + arguments ~= [to!(char[])(byteOffset)]; + arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; + + dstring output; + _dcd.execute(arguments, output); + + char[] state = "".dup; + dstring[] suggestions; + foreach(dstring outputLine ; output.splitLines()) { + if(outputLine == "identifiers") { + state = "identifiers".dup; + } + else if(outputLine == "calltips") { + state = "calltips".dup; + } + else { + auto split = outputLine.indexOf("\t"); + if(split < 0) { + break; + } + if(state == "identifiers") { + suggestions ~= outputLine[0 .. split]; + } + } + } + return suggestions; + } + + private: DCDInterface _dcd; diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d index b011599..0fc3ec2 100644 --- a/src/dlangide/ui/commands.d +++ b/src/dlangide/ui/commands.d @@ -41,6 +41,7 @@ enum IDEActions : int { ProjectFolderOpenItem, ProjectFolderRenameItem, GoToDefinition, + GetCompletionSuggestions, } const Action ACTION_PROJECT_FOLDER_ADD_ITEM = new Action(IDEActions.ProjectFolderAddItem, "MENU_PROJECT_FOLDER_ADD_ITEM"c); @@ -86,4 +87,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, "edit-cut"c, KeyCode.KEY_G, KeyFlag.Control); \ No newline at end of file +const Action ACTION_GO_TO_DEFINITION = new Action(IDEActions.GoToDefinition, "GO_TO_DEFINITION"c, "edit-cut"c, KeyCode.KEY_G, KeyFlag.Control); +const Action ACTION_GET_COMPLETIONS = new Action(IDEActions.GetCompletionSuggestions, "GO_TO_DEFINITION"c, "edit-cut"c, KeyCode.KEY_G, KeyFlag.Shift); \ No newline at end of file diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d index d12a5ad..f4399f2 100644 --- a/src/dlangide/ui/frame.d +++ b/src/dlangide/ui/frame.d @@ -382,6 +382,7 @@ class IDEFrame : AppFrame { tb.addButtons(ACTION_PROJECT_BUILD); tb.addButtons(ACTION_GO_TO_DEFINITION); + tb.addButtons(ACTION_GET_COMPLETIONS); tb = res.getOrAddToolbar("Edit"); tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR, @@ -513,9 +514,13 @@ class IDEFrame : AppFrame { dlg.show(); return true; case IDEActions.GoToDefinition: - Log.i("Trying to go to definition"); + 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); + return true; default: return super.handleAction(a); } From 56ac7b9fac952c7d3e1e1ca6791598dff526ade5 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Thu, 12 Feb 2015 10:18:51 +1100 Subject: [PATCH 03/15] Added popup to show completion suggestions. issue #20 --- src/dlangide/ui/dsourceedit.d | 19 ++++++++++++++++++- src/dlangide/ui/frame.d | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index 5d4cc6c..fc08684 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; @@ -86,11 +87,27 @@ class DSourceEdit : SourceEdit { return super.handleAction(a); } + + + void showCompletionPopup(dstring[] suggestions) { + MenuItem completionPopupItem = new MenuItem(null); + //Create popup menu + foreach(int i, dstring suggestion ; suggestions) { + auto action = new Action(i+1, suggestion); + completionPopupItem.add(action); + } + completionPopupItem.updateActionState(this); + PopupMenu popupMenu = new PopupMenu(completionPopupItem); + popupMenu.onMenuItemActionListener = this; + PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, textPosToClient(_caretPos).left + left + _leftPaneWidth, textPosToClient(_caretPos).top + top + margins.top); + 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) { diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d index f4399f2..9324d25 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; @@ -520,6 +521,7 @@ class IDEFrame : AppFrame { 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); From 0b01ca2b86906651429654b1085e31c4341e60d9 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Thu, 12 Feb 2015 10:41:26 +1100 Subject: [PATCH 04/15] Add navigation menu item and remove toolbar items. --- src/dlangide/tools/d/DEditorTool.d | 2 -- src/dlangide/ui/commands.d | 4 ++-- src/dlangide/ui/frame.d | 7 ++++--- views/res/i18n/en.ini | 1 + 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dlangide/tools/d/DEditorTool.d b/src/dlangide/tools/d/DEditorTool.d index 6d3e731..62c8271 100644 --- a/src/dlangide/tools/d/DEditorTool.d +++ b/src/dlangide/tools/d/DEditorTool.d @@ -52,13 +52,11 @@ class DEditorTool : EditorTool else { auto filename = outputLine[0 .. split]; if(_frame !is null) { - writeln("Well I'm trying"); _frame.openSourceFile(filename); auto target = to!int(outputLine[split+1 .. $]); auto destPos = byteOffsetToCaret(_frame.currentEditor.text(), target); _frame.currentEditor.setCaretPos(destPos.line,destPos.pos); - writeln("Well I tried"); } } } diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d index 0fc3ec2..7ae97be 100644 --- a/src/dlangide/ui/commands.d +++ b/src/dlangide/ui/commands.d @@ -87,5 +87,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, "edit-cut"c, KeyCode.KEY_G, KeyFlag.Control); -const Action ACTION_GET_COMPLETIONS = new Action(IDEActions.GetCompletionSuggestions, "GO_TO_DEFINITION"c, "edit-cut"c, KeyCode.KEY_G, KeyFlag.Shift); \ No newline at end of file +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, "GO_TO_DEFINITION"c, ""c, KeyCode.KEY_G, KeyFlag.Control|KeyFlag.Shift); \ No newline at end of file diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d index 9324d25..3520398 100644 --- a/src/dlangide/ui/frame.d +++ b/src/dlangide/ui/frame.d @@ -331,6 +331,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); @@ -352,6 +355,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); @@ -382,9 +386,6 @@ class IDEFrame : AppFrame { tb.addControl(cbBuildConfiguration); tb.addButtons(ACTION_PROJECT_BUILD); - tb.addButtons(ACTION_GO_TO_DEFINITION); - tb.addButtons(ACTION_GET_COMPLETIONS); - tb = res.getOrAddToolbar("Edit"); tb.addButtons(ACTION_EDIT_COPY, ACTION_EDIT_PASTE, ACTION_EDIT_CUT, ACTION_SEPARATOR, ACTION_EDIT_UNDO, ACTION_EDIT_REDO, ACTION_EDIT_INDENT, ACTION_EDIT_UNINDENT); diff --git a/views/res/i18n/en.ini b/views/res/i18n/en.ini index cf89974..37ebfbd 100644 --- a/views/res/i18n/en.ini +++ b/views/res/i18n/en.ini @@ -55,6 +55,7 @@ MENU_HELP=&HELP MENU_HELP_VIEW_HELP=&View help MENU_HELP_ABOUT=&About GO_TO_DEFINITION=Go To Definition +MENU_NAVIGATE=NAVIGATE TAB_LONG_LIST=Long list TAB_BUTTONS=Buttons From ec9b0a21476e7f39a1bd35f70ad5ad0e1232d6e0 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sat, 14 Feb 2015 09:02:46 +1100 Subject: [PATCH 05/15] Insert completion suggestion. issue #20 --- src/dlangide/tools/d/DCDInterface.d | 3 +++ src/dlangide/ui/commands.d | 1 + src/dlangide/ui/dsourceedit.d | 6 +++++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/dlangide/tools/d/DCDInterface.d b/src/dlangide/tools/d/DCDInterface.d index ed7df02..5fb7fc6 100644 --- a/src/dlangide/tools/d/DCDInterface.d +++ b/src/dlangide/tools/d/DCDInterface.d @@ -13,8 +13,11 @@ class DCDInterface { ProtectedTextStorage stdoutTarget = new ProtectedTextStorage(); ExternalProcess dcdProcess = new ExternalProcess(); //TODO: Working Directory, where is that? + //TODO: Inform user when dcd-client is not available. dcdProcess.run("dcd-client".dup, arguments, "/usr/bin".dup, stdoutTarget); + while(dcdProcess.poll() == ExternalProcessState.Running){ } + output = stdoutTarget.readText(); return true; } diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d index 7ae97be..4a305e7 100644 --- a/src/dlangide/ui/commands.d +++ b/src/dlangide/ui/commands.d @@ -42,6 +42,7 @@ enum IDEActions : int { ProjectFolderRenameItem, GoToDefinition, GetCompletionSuggestions, + InsertCompletion, } const Action ACTION_PROJECT_FOLDER_ADD_ITEM = new Action(IDEActions.ProjectFolderAddItem, "MENU_PROJECT_FOLDER_ADD_ITEM"c); diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index fc08684..985c520 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -80,6 +80,10 @@ 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); + return true; default: break; } @@ -93,7 +97,7 @@ class DSourceEdit : SourceEdit { MenuItem completionPopupItem = new MenuItem(null); //Create popup menu foreach(int i, dstring suggestion ; suggestions) { - auto action = new Action(i+1, suggestion); + auto action = new Action(IDEActions.InsertCompletion, suggestion); completionPopupItem.add(action); } completionPopupItem.updateActionState(this); From 6b5e3077539dd82efa5058f2e5700c891c9c36f9 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sat, 14 Feb 2015 09:35:59 +1100 Subject: [PATCH 06/15] Send the editor content instead of the file name to dcd --- src/dlangide/builders/extprocess.d | 12 +++++++++++- src/dlangide/tools/d/DCDInterface.d | 3 ++- src/dlangide/tools/d/DEditorTool.d | 8 ++++---- 3 files changed, 17 insertions(+), 6 deletions(-) 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 index 5fb7fc6..19705ce 100644 --- a/src/dlangide/tools/d/DCDInterface.d +++ b/src/dlangide/tools/d/DCDInterface.d @@ -9,12 +9,13 @@ class DCDInterface { this() { dcdProcess = new ExternalProcess(); } - bool execute(char[][] arguments ,ref dstring output) { + bool execute(char[][] arguments ,ref dstring output, dstring input) { ProtectedTextStorage stdoutTarget = new ProtectedTextStorage(); ExternalProcess dcdProcess = new ExternalProcess(); //TODO: Working Directory, where is that? //TODO: Inform user when dcd-client is not available. dcdProcess.run("dcd-client".dup, arguments, "/usr/bin".dup, stdoutTarget); + dcdProcess.write(input); while(dcdProcess.poll() == ExternalProcessState.Running){ } diff --git a/src/dlangide/tools/d/DEditorTool.d b/src/dlangide/tools/d/DEditorTool.d index 62c8271..7e8c5d6 100644 --- a/src/dlangide/tools/d/DEditorTool.d +++ b/src/dlangide/tools/d/DEditorTool.d @@ -28,10 +28,10 @@ class DEditorTool : EditorTool char[][] arguments = ["-l".dup, "-c".dup]; arguments ~= [to!(char[])(byteOffset)]; - arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; + //arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; dstring output; - _dcd.execute(arguments, output); + _dcd.execute(arguments, output, content); string[] outputLines = to!string(output).splitLines(); Log.d("DCD:", outputLines); @@ -70,10 +70,10 @@ class DEditorTool : EditorTool char[][] arguments = ["-c".dup]; arguments ~= [to!(char[])(byteOffset)]; - arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; + //arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; dstring output; - _dcd.execute(arguments, output); + _dcd.execute(arguments, output, content); char[] state = "".dup; dstring[] suggestions; From 317749bee85a930c620650d7170a66dfaa127b1b Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 06:38:35 +1100 Subject: [PATCH 07/15] revert change in findPairedBracket, set max height to completion popup menu. --- src/dlangide/ui/dsourceedit.d | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index b8bb680..5c1e011 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -103,6 +103,7 @@ class DSourceEdit : SourceEdit { completionPopupItem.updateActionState(this); PopupMenu popupMenu = new PopupMenu(completionPopupItem); popupMenu.onMenuItemActionListener = this; + popupMenu.maxHeight(400); PopupWidget popup = window.showPopup(popupMenu, this, PopupAlign.Point | PopupAlign.Right, textPosToClient(_caretPos).left + left + _leftPaneWidth, textPosToClient(_caretPos).top + top + margins.top); popup.flags = PopupFlags.CloseOnClickOutside; Log.d("Showing popup at ", textPosToClient(_caretPos).left, " ", textPosToClient(_caretPos).top); @@ -271,7 +272,7 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter { for (;;) { ch = nextBracket(dir, p); if (!ch) // no more brackets - break; + return p; auto match = _bracketStack.process(ch); if (match == BracketMatch.FOUND) return p; From 2b438dd6530667ef9c7d702f5c57b6ee98f0f984 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 07:06:25 +1100 Subject: [PATCH 08/15] Set focus on completion popup to allow for arrow key selection of completion item Also set focus back to editor after completion popup --- src/dlangide/ui/dsourceedit.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index 5c1e011..0102c45 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -83,6 +83,7 @@ class DSourceEdit : SourceEdit { case IDEActions.InsertCompletion: EditOperation edit = new EditOperation(EditAction.Replace, getCaretPosition, a.label); _content.performOperation(edit, this); + setFocus(); return true; default: break; @@ -104,7 +105,9 @@ class DSourceEdit : SourceEdit { PopupMenu popupMenu = new PopupMenu(completionPopupItem); 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); } From 0ebdc67c9694e4330545fe9e84cb1d133cc36d26 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 07:27:56 +1100 Subject: [PATCH 09/15] Add autocomplete to editor popup and added Show Completions translation --- src/dlangide/ui/commands.d | 2 +- src/dlangide/ui/dsourceedit.d | 20 ++++++++++++++------ views/res/i18n/en.ini | 2 ++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/dlangide/ui/commands.d b/src/dlangide/ui/commands.d index f28670b..b0b855f 100644 --- a/src/dlangide/ui/commands.d +++ b/src/dlangide/ui/commands.d @@ -96,4 +96,4 @@ const Action ACTION_WINDOW_CLOSE_ALL_DOCUMENTS = new Action(IDEActions.WindowClo 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, "GO_TO_DEFINITION"c, ""c, KeyCode.KEY_G, KeyFlag.Control|KeyFlag.Shift); \ No newline at end of file +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 0102c45..c922182 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -31,7 +31,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; @@ -95,20 +95,28 @@ class DSourceEdit : SourceEdit { void showCompletionPopup(dstring[] suggestions) { - MenuItem completionPopupItem = new MenuItem(null); - //Create popup menu + + 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); - completionPopupItem.add(action); + completionPopupItems.add(action); } - completionPopupItem.updateActionState(this); - PopupMenu popupMenu = new PopupMenu(completionPopupItem); + 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); } diff --git a/views/res/i18n/en.ini b/views/res/i18n/en.ini index cb2933e..1939fe4 100644 --- a/views/res/i18n/en.ini +++ b/views/res/i18n/en.ini @@ -56,8 +56,10 @@ 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 TAB_ANIMATION=Animation From 736517018f595a419d686c904f3b547322941c82 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 07:42:52 +1100 Subject: [PATCH 10/15] Moved SimpleDSyntaxHighlighter class out of dsourceedit --- .../tools/d/SimpleDSyntaxHighlighter.d | 640 ++++++++++++++++++ src/dlangide/ui/dsourceedit.d | 634 +---------------- 2 files changed, 641 insertions(+), 633 deletions(-) create mode 100644 src/dlangide/tools/d/SimpleDSyntaxHighlighter.d diff --git a/src/dlangide/tools/d/SimpleDSyntaxHighlighter.d b/src/dlangide/tools/d/SimpleDSyntaxHighlighter.d new file mode 100644 index 0000000..9b367e2 --- /dev/null +++ b/src/dlangide/tools/d/SimpleDSyntaxHighlighter.d @@ -0,0 +1,640 @@ +module dlangide.tools.d.syntaxHighlighter; + +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 p; + 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 ~= "//"; + x += 2; + } else { + res ~= ch; + x = newX; + } + } else { + if (!commented && x == commentX) { + commented = true; + res ~= "//"; + res ~= ch; + x += 3; + } else { + res ~= ch; + x++; + } + } + } + if (!commented) { + for (; x < commentX; x++) + res ~= ' '; + res ~= "//"; + } + 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"); + } +} \ No newline at end of file diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index c922182..0e9ebe7 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -13,6 +13,7 @@ import ddc.lexer.tokenizer; import dlangide.workspace.workspace; import dlangide.workspace.project; import dlangide.ui.commands; +import dlangide.tools.d.syntaxHighlighter; import std.algorithm; @@ -132,636 +133,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 p; - 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 ~= "//"; - x += 2; - } else { - res ~= ch; - x = newX; - } - } else { - if (!commented && x == commentX) { - commented = true; - res ~= "//"; - res ~= ch; - x += 3; - } else { - res ~= ch; - x++; - } - } - } - if (!commented) { - for (; x < commentX; x++) - res ~= ' '; - res ~= "//"; - } - 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"); - } -} From 747cb61fee189dda5b4e5c4a96bef12869e9ba58 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 17:02:53 +1100 Subject: [PATCH 11/15] Cleaned up Go To Definition implemention --- src/dlangide/tools/d/DCDInterface.d | 67 +++++++++++++++++++++++++++-- src/dlangide/tools/d/DEditorTool.d | 61 +++++++++++--------------- 2 files changed, 88 insertions(+), 40 deletions(-) diff --git a/src/dlangide/tools/d/DCDInterface.d b/src/dlangide/tools/d/DCDInterface.d index 19705ce..5fffb4c 100644 --- a/src/dlangide/tools/d/DCDInterface.d +++ b/src/dlangide/tools/d/DCDInterface.d @@ -1,23 +1,84 @@ 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(); } - bool execute(char[][] arguments ,ref dstring output, dstring input) { - ProtectedTextStorage 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; + } + + bool execute(char[][] arguments ,ref dstring output, dstring input) { //TODO: Working Directory, where is that? //TODO: Inform user when dcd-client is not available. dcdProcess.run("dcd-client".dup, arguments, "/usr/bin".dup, stdoutTarget); dcdProcess.write(input); - while(dcdProcess.poll() == ExternalProcessState.Running){ } + while(dcdProcess.poll() == ExternalProcessState.Running){ } output = stdoutTarget.readText(); return true; diff --git a/src/dlangide/tools/d/DEditorTool.d b/src/dlangide/tools/d/DEditorTool.d index 7e8c5d6..86e9bc2 100644 --- a/src/dlangide/tools/d/DEditorTool.d +++ b/src/dlangide/tools/d/DEditorTool.d @@ -22,46 +22,33 @@ class DEditorTool : EditorTool override bool goToDefinition(DSourceEdit editor, TextPosition caretPosition) { - - auto content = editor.text(); - auto byteOffset = caretPositionToByteOffset(content, caretPosition); + auto byteOffset = caretPositionToByteOffset(editor.text, caretPosition); + ResultSet output = _dcd.goToDefinition(editor.text, byteOffset); - char[][] arguments = ["-l".dup, "-c".dup]; - arguments ~= [to!(char[])(byteOffset)]; - //arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; - dstring output; - _dcd.execute(arguments, output, content); - - string[] outputLines = to!string(output).splitLines(); - Log.d("DCD:", outputLines); - - foreach(string outputLine ; outputLines) { - if(outputLine.indexOf("Not Found".dup) == -1) { - auto split = outputLine.indexOf("\t"); - if(split == -1) { - Log.d("DCD output format error."); - break; + 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); } - if(indexOf(outputLine[0 .. split],"stdin".dup) != -1) { - Log.d("Declaration is in current file. Can jump to it."); - auto target = to!int(outputLine[split+1 .. $]); - auto destPos = byteOffsetToCaret(content, target); - editor.setCaretPos(destPos.line,destPos.pos); - } - else { - auto filename = outputLine[0 .. split]; - if(_frame !is null) { - _frame.openSourceFile(filename); - auto target = to!int(outputLine[split+1 .. $]); - auto destPos = byteOffsetToCaret(_frame.currentEditor.text(), target); - - _frame.currentEditor.setCaretPos(destPos.line,destPos.pos); - } - } - } - } - return true; + return true; + default: + return false; + } } override dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition) { From 158d1f14427bd358705e556f69656a3cb1a6c593 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 17:27:16 +1100 Subject: [PATCH 12/15] Cleaned up Show completions --- src/dlangide/tools/d/DCDInterface.d | 54 +++++++++++++++++++++++++++++ src/dlangide/tools/d/DEditorTool.d | 39 ++++++--------------- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/dlangide/tools/d/DCDInterface.d b/src/dlangide/tools/d/DCDInterface.d index 5fffb4c..441a927 100644 --- a/src/dlangide/tools/d/DCDInterface.d +++ b/src/dlangide/tools/d/DCDInterface.d @@ -72,6 +72,60 @@ class DCDInterface { 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; + } + bool execute(char[][] arguments ,ref dstring output, dstring input) { //TODO: Working Directory, where is that? //TODO: Inform user when dcd-client is not available. diff --git a/src/dlangide/tools/d/DEditorTool.d b/src/dlangide/tools/d/DEditorTool.d index 86e9bc2..f866b4c 100644 --- a/src/dlangide/tools/d/DEditorTool.d +++ b/src/dlangide/tools/d/DEditorTool.d @@ -52,39 +52,20 @@ class DEditorTool : EditorTool } override dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition) { - auto content = editor.text(); - auto byteOffset = caretPositionToByteOffset(content, caretPosition); - char[][] arguments = ["-c".dup]; - arguments ~= [to!(char[])(byteOffset)]; - //arguments ~= [to!(char[])(editor.projectSourceFile.filename())]; - - dstring output; - _dcd.execute(arguments, output, content); - - char[] state = "".dup; - dstring[] suggestions; - foreach(dstring outputLine ; output.splitLines()) { - if(outputLine == "identifiers") { - state = "identifiers".dup; - } - else if(outputLine == "calltips") { - state = "calltips".dup; - } - else { - auto split = outputLine.indexOf("\t"); - if(split < 0) { - break; - } - if(state == "identifiers") { - suggestions ~= outputLine[0 .. split]; - } - } + 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; } - return suggestions; } - private: DCDInterface _dcd; From 7d59862c2bfeb5fa0699d8c65715be9930ef6b4b Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 17:29:28 +1100 Subject: [PATCH 13/15] Removed unused function --- src/dlangide/tools/d/DCDInterface.d | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/dlangide/tools/d/DCDInterface.d b/src/dlangide/tools/d/DCDInterface.d index 441a927..cfc0c2a 100644 --- a/src/dlangide/tools/d/DCDInterface.d +++ b/src/dlangide/tools/d/DCDInterface.d @@ -125,17 +125,4 @@ class DCDInterface { } return result; } - - bool execute(char[][] arguments ,ref dstring output, dstring input) { - //TODO: Working Directory, where is that? - //TODO: Inform user when dcd-client is not available. - dcdProcess.run("dcd-client".dup, arguments, "/usr/bin".dup, stdoutTarget); - dcdProcess.write(input); - - while(dcdProcess.poll() == ExternalProcessState.Running){ } - - output = stdoutTarget.readText(); - return true; - } - } \ No newline at end of file From bea3c5efb3476f0d4b05d4c8129710676da6f164 Mon Sep 17 00:00:00 2001 From: Hans-Albert Maritz Date: Sun, 15 Feb 2015 20:16:46 +1100 Subject: [PATCH 14/15] renamed files to fit style guidelines --- src/dlangide/tools/d/{DCDInterface.d => dcdinterface.d} | 4 ++-- src/dlangide/tools/d/{DEditorTool.d => deditortool.d} | 6 +++--- ...impleDSyntaxHighlighter.d => simpledsyntaxhighlighter.d} | 4 ++-- src/dlangide/tools/{EditorTool.d => editortool.d} | 4 ++-- src/dlangide/ui/dsourceedit.d | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) rename src/dlangide/tools/d/{DCDInterface.d => dcdinterface.d} (98%) rename src/dlangide/tools/d/{DEditorTool.d => deditortool.d} (97%) rename src/dlangide/tools/d/{SimpleDSyntaxHighlighter.d => simpledsyntaxhighlighter.d} (99%) rename src/dlangide/tools/{EditorTool.d => editortool.d} (91%) diff --git a/src/dlangide/tools/d/DCDInterface.d b/src/dlangide/tools/d/dcdinterface.d similarity index 98% rename from src/dlangide/tools/d/DCDInterface.d rename to src/dlangide/tools/d/dcdinterface.d index cfc0c2a..f11b7d0 100644 --- a/src/dlangide/tools/d/DCDInterface.d +++ b/src/dlangide/tools/d/dcdinterface.d @@ -1,4 +1,4 @@ -module dlangide.tools.d.DCDInterface; +module dlangide.tools.d.dcdinterface; import dlangui.core.logger; @@ -125,4 +125,4 @@ class DCDInterface { } return result; } -} \ No newline at end of file +} diff --git a/src/dlangide/tools/d/DEditorTool.d b/src/dlangide/tools/d/deditortool.d similarity index 97% rename from src/dlangide/tools/d/DEditorTool.d rename to src/dlangide/tools/d/deditortool.d index f866b4c..2a14d6d 100644 --- a/src/dlangide/tools/d/DEditorTool.d +++ b/src/dlangide/tools/d/deditortool.d @@ -1,7 +1,7 @@ -module dlangide.tools.d.editorTool; +module dlangide.tools.d.deditorTool; import dlangide.tools.editorTool; -import dlangide.tools.d.DCDInterface; +import dlangide.tools.d.dcdinterface; import dlangide.ui.dsourceedit; import dlangui.widgets.editors; import dlangide.ui.frame; @@ -111,4 +111,4 @@ private: } return textPos; } -} \ No newline at end of file +} diff --git a/src/dlangide/tools/d/SimpleDSyntaxHighlighter.d b/src/dlangide/tools/d/simpledsyntaxhighlighter.d similarity index 99% rename from src/dlangide/tools/d/SimpleDSyntaxHighlighter.d rename to src/dlangide/tools/d/simpledsyntaxhighlighter.d index 9b367e2..9ede506 100644 --- a/src/dlangide/tools/d/SimpleDSyntaxHighlighter.d +++ b/src/dlangide/tools/d/simpledsyntaxhighlighter.d @@ -1,4 +1,4 @@ -module dlangide.tools.d.syntaxHighlighter; +module dlangide.tools.d.simpledsyntaxhighlighter; import dlangui.core.logger; import dlangui.widgets.editors; @@ -637,4 +637,4 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter { if (elapsed > 20) Log.d("updateHighlight took ", elapsed, "ms"); } -} \ No newline at end of file +} diff --git a/src/dlangide/tools/EditorTool.d b/src/dlangide/tools/editortool.d similarity index 91% rename from src/dlangide/tools/EditorTool.d rename to src/dlangide/tools/editortool.d index ee84c8f..c9fa8db 100644 --- a/src/dlangide/tools/EditorTool.d +++ b/src/dlangide/tools/editortool.d @@ -7,7 +7,7 @@ import dlangui.core.types; import dlangide.ui.frame; import dlangide.ui.dsourceedit; -public import dlangide.tools.d.editorTool; +public import dlangide.tools.d.deditorTool; class EditorTool { @@ -20,4 +20,4 @@ class EditorTool protected IDEFrame _frame; -} \ No newline at end of file +} diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d index 0e9ebe7..bc75905 100644 --- a/src/dlangide/ui/dsourceedit.d +++ b/src/dlangide/ui/dsourceedit.d @@ -13,7 +13,7 @@ import ddc.lexer.tokenizer; import dlangide.workspace.workspace; import dlangide.workspace.project; import dlangide.ui.commands; -import dlangide.tools.d.syntaxHighlighter; +import dlangide.tools.d.simpledsyntaxhighlighter; import std.algorithm; From d7339d344a127f95338b3549a6a2f3721e03ae6d Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Tue, 17 Feb 2015 09:28:43 +0300 Subject: [PATCH 15/15] merge latest changes for d syntax highlighter --- .../tools/d/simpledsyntaxhighlighter.d | 374 +++++++++++++++++- 1 file changed, 372 insertions(+), 2 deletions(-) diff --git a/src/dlangide/tools/d/simpledsyntaxhighlighter.d b/src/dlangide/tools/d/simpledsyntaxhighlighter.d index 837ef3a..635c76f 100644 --- a/src/dlangide/tools/d/simpledsyntaxhighlighter.d +++ b/src/dlangide/tools/d/simpledsyntaxhighlighter.d @@ -33,8 +33,8 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter { private enum BracketMatch { CONTINUE, - FOUND, - ERROR + FOUND, + ERROR } private static struct BracketStack { dchar[] buf; @@ -268,4 +268,374 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter { 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"); + } } +