1 module dlangide.tools.d.deditorTool; 2 3 import dlangide.tools.editorTool; 4 import dlangide.tools.d.dcdinterface; 5 import dlangide.ui.dsourceedit; 6 import dlangui.widgets.editors; 7 import dlangide.ui.frame; 8 import std.stdio; 9 import std.string; 10 import std.utf; 11 import dlangui.core.logger; 12 13 import std.conv; 14 15 // TODO: async operation in background thread 16 // TODO: effective caretPositionToByteOffset/byteOffsetToCaret impl 17 18 class DEditorTool : EditorTool 19 { 20 this(IDEFrame frame) { 21 super(frame); 22 } 23 24 ~this() { 25 cancelGoToDefinition(); 26 cancelGetDocComments(); 27 cancelGetCompletions(); 28 } 29 30 static bool isIdentChar(char ch) { 31 return ch == '_' || (ch >= 'a' && ch <='z') || (ch >= 'A' && ch <='Z') || ((ch & 0x80) != 0); 32 } 33 static bool isAtWord(string content, size_t byteOffset) { 34 if (byteOffset >= content.length) 35 return false; 36 if (isIdentChar(content[byteOffset])) 37 return true; 38 if (byteOffset > 0 && isIdentChar(content[byteOffset - 1])) 39 return true; 40 if (byteOffset + 1 < content.length && isIdentChar(content[byteOffset + 1])) 41 return true; 42 return false; 43 } 44 45 DCDTask _getDocCommentsTask; 46 override void getDocComments(DSourceEdit editor, TextPosition caretPosition, void delegate(string[]) callback) { 47 cancelGetDocComments(); 48 string[] importPaths = editor.importPaths(); 49 string content = toUTF8(editor.text); 50 auto byteOffset = caretPositionToByteOffset(content, caretPosition); 51 if (!isAtWord(content, byteOffset)) 52 return; 53 _getDocCommentsTask = _frame.dcdInterface.getDocComments(editor.window, importPaths, editor.filename, content, byteOffset, delegate(DocCommentsResultSet output) { 54 if(output.result == DCDResult.SUCCESS) { 55 auto doc = output.docComments; 56 Log.d("Doc comments: ", doc); 57 if (doc.length) 58 callback(doc); 59 _getDocCommentsTask = null; 60 } 61 }); 62 } 63 64 override void cancelGetDocComments() { 65 if (_getDocCommentsTask) { 66 _getDocCommentsTask.cancel(); 67 _getDocCommentsTask = null; 68 } 69 } 70 71 override void cancelGoToDefinition() { 72 if (_goToDefinitionTask) { 73 _goToDefinitionTask.cancel(); 74 _goToDefinitionTask = null; 75 } 76 } 77 78 override void cancelGetCompletions() { 79 if (_getCompletionsTask) { 80 _getCompletionsTask.cancel(); 81 _getCompletionsTask = null; 82 } 83 } 84 85 DCDTask _goToDefinitionTask; 86 override void goToDefinition(DSourceEdit editor, TextPosition caretPosition) { 87 cancelGoToDefinition(); 88 string[] importPaths = editor.importPaths(); 89 string content = toUTF8(editor.text); 90 auto byteOffset = caretPositionToByteOffset(content, caretPosition); 91 92 93 _goToDefinitionTask = _frame.dcdInterface.goToDefinition(editor.window, importPaths, editor.filename, content, byteOffset, delegate(FindDeclarationResultSet output) { 94 // handle result 95 switch(output.result) { 96 //TODO: Show dialog 97 case DCDResult.FAIL: 98 case DCDResult.NO_RESULT: 99 editor.setFocus(); 100 break; 101 case DCDResult.SUCCESS: 102 auto fileName = output.fileName; 103 if(fileName.indexOf("stdin") == 0) { 104 Log.d("Declaration is in current file. Jumping to it."); 105 } else { 106 //Must open file first to get the content for finding the correct caret position. 107 if (!_frame.openSourceFile(to!string(fileName))) 108 break; 109 if (_frame.currentEditor.parent) 110 _frame.currentEditor.parent.layout(_frame.currentEditor.parent.pos); 111 content = toUTF8(_frame.currentEditor.text); 112 } 113 auto target = to!int(output.offset); 114 auto destPos = byteOffsetToCaret(content, target); 115 _frame.currentEditor.setCaretPos(destPos.line,destPos.pos, true, true); 116 _frame.currentEditor.setFocus(); 117 break; 118 default: 119 break; 120 } 121 _goToDefinitionTask = null; 122 }); 123 124 } 125 126 DCDTask _getCompletionsTask; 127 override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[] completions, string[] icons) callback) { 128 string[] importPaths = editor.importPaths(); 129 130 string content = toUTF8(editor.text); 131 auto byteOffset = caretPositionToByteOffset(content, caretPosition); 132 _getCompletionsTask = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset, delegate(CompletionResultSet output) { 133 string[] icons; 134 dstring[] labels; 135 foreach(index, label; output.output) { 136 string iconId; 137 char ch = index < output.completionKinds.length ? output.completionKinds[index] : 0; 138 switch(ch) { 139 case 'c': // - class name 140 iconId = "symbol-class"; 141 break; 142 case 'i': // - interface name 143 iconId = "symbol-interface"; 144 break; 145 case 's': // - struct name 146 iconId = "symbol-struct"; 147 break; 148 case 'u': // - union name 149 iconId = "symbol-union"; 150 break; 151 case 'v': // - variable name 152 iconId = "symbol-var"; 153 break; 154 case 'm': // - member variable name 155 iconId = "symbol-membervar"; 156 break; 157 case 'k': // - keyword, built-in version, scope statement 158 iconId = "symbol-keyword"; 159 break; 160 case 'f': // - function or method 161 iconId = "symbol-function"; 162 break; 163 case 'g': // - enum name 164 iconId = "symbol-enum"; 165 break; 166 case 'e': // - enum member 167 iconId = "symbol-enum"; 168 break; 169 case 'P': // - package name 170 iconId = "symbol-package"; 171 break; 172 case 'M': // - module name 173 iconId = "symbol-module"; 174 break; 175 case 'a': // - array 176 iconId = "symbol-array"; 177 break; 178 case 'A': // - associative array 179 iconId = "symbol-array"; 180 break; 181 case 'l': // - alias name 182 iconId = "symbol-alias"; 183 break; 184 case 't': // - template name 185 iconId = "symbol-template"; 186 break; 187 case 'T': // - mixin template name 188 iconId = "symbol-mixintemplate"; 189 break; 190 default: 191 break; 192 } 193 if (!iconId) 194 iconId = "symbol-other"; 195 icons ~= iconId; 196 labels ~= label; 197 } 198 callback(labels, icons); 199 _getCompletionsTask = null; 200 }); 201 } 202 203 private: 204 205 } 206 207 /// convert caret position to byte offset in utf8 content 208 int caretPositionToByteOffset(string content, TextPosition caretPosition) { 209 auto line = 0; 210 auto pos = 0; 211 auto bytes = 0; 212 foreach(c; content) { 213 if(line == caretPosition.line) { 214 if(pos == caretPosition.pos) 215 break; 216 pos++; 217 } else if (line > caretPosition.line) { 218 break; 219 } 220 bytes++; 221 if(c == '\n') { 222 line++; 223 pos = 0; 224 } 225 } 226 return bytes; 227 } 228 229 /// convert byte offset in utf8 content to caret position 230 TextPosition byteOffsetToCaret(string content, int byteOffset) { 231 int bytes = 0; 232 int line = 0; 233 int pos = 0; 234 TextPosition textPos; 235 foreach(c; content) { 236 if(bytes == byteOffset) { 237 //We all good. 238 textPos.line = line; 239 textPos.pos = pos; 240 return textPos; 241 } 242 bytes++; 243 if(c == '\n') 244 { 245 line++; 246 pos = 0; 247 } 248 else { 249 pos++; 250 } 251 } 252 return textPos; 253 }