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(_frame.settings); 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 Log.d("Cancelling getDocComments()"); 67 _getDocCommentsTask.cancel(); 68 _getDocCommentsTask = null; 69 } 70 } 71 72 override void cancelGoToDefinition() { 73 if (_goToDefinitionTask) { 74 Log.d("Cancelling goToDefinition()"); 75 _goToDefinitionTask.cancel(); 76 _goToDefinitionTask = null; 77 } 78 } 79 80 override void cancelGetCompletions() { 81 if (_getCompletionsTask) { 82 Log.d("Cancelling getCompletions()"); 83 _getCompletionsTask.cancel(); 84 _getCompletionsTask = null; 85 } 86 } 87 88 DCDTask _goToDefinitionTask; 89 override void goToDefinition(DSourceEdit editor, TextPosition caretPosition) { 90 cancelGoToDefinition(); 91 string[] importPaths = editor.importPaths(_frame.settings); 92 string content = toUTF8(editor.text); 93 auto byteOffset = caretPositionToByteOffset(content, caretPosition); 94 95 96 _goToDefinitionTask = _frame.dcdInterface.goToDefinition(editor.window, importPaths, editor.filename, content, byteOffset, delegate(FindDeclarationResultSet output) { 97 // handle result 98 switch(output.result) { 99 //TODO: Show dialog 100 case DCDResult.FAIL: 101 case DCDResult.NO_RESULT: 102 editor.setFocus(); 103 break; 104 case DCDResult.SUCCESS: 105 auto fileName = output.fileName; 106 if(fileName.indexOf("stdin") == 0) { 107 Log.d("Declaration is in current file. Jumping to it."); 108 } else { 109 //Must open file first to get the content for finding the correct caret position. 110 if (!_frame.openSourceFile(to!string(fileName))) 111 break; 112 if (_frame.currentEditor.parent) 113 _frame.currentEditor.parent.layout(_frame.currentEditor.parent.pos); 114 content = toUTF8(_frame.currentEditor.text); 115 } 116 auto target = to!int(output.offset); 117 auto destPos = byteOffsetToCaret(content, target); 118 _frame.currentEditor.setCaretPos(destPos.line,destPos.pos, true, true); 119 _frame.currentEditor.setFocus(); 120 _frame.caretHistory.pushNewPosition(); 121 break; 122 default: 123 break; 124 } 125 _goToDefinitionTask = null; 126 }); 127 128 } 129 130 DCDTask _getCompletionsTask; 131 override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[] completions, string[] icons, CompletionTypes type) callback) { 132 cancelGetCompletions(); 133 string[] importPaths = editor.importPaths(_frame.settings); 134 135 string content = toUTF8(editor.text); 136 auto byteOffset = caretPositionToByteOffset(content, caretPosition); 137 _getCompletionsTask = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset, delegate(CompletionResultSet output) { 138 string[] icons; 139 dstring[] labels; 140 foreach(index, label; output.output) { 141 string iconId; 142 char ch = label.kind; 143 switch(ch) { 144 case 'c': // - class name 145 iconId = "symbol-class"; 146 break; 147 case 'i': // - interface name 148 iconId = "symbol-interface"; 149 break; 150 case 's': // - struct name 151 iconId = "symbol-struct"; 152 break; 153 case 'u': // - union name 154 iconId = "symbol-union"; 155 break; 156 case 'v': // - variable name 157 iconId = "symbol-var"; 158 break; 159 case 'm': // - member variable name 160 iconId = "symbol-membervar"; 161 break; 162 case 'k': // - keyword, built-in version, scope statement 163 iconId = "symbol-keyword"; 164 break; 165 case 'f': // - function or method 166 iconId = "symbol-function"; 167 break; 168 case 'g': // - enum name 169 iconId = "symbol-enum"; 170 break; 171 case 'e': // - enum member 172 iconId = "symbol-enum"; 173 break; 174 case 'P': // - package name 175 iconId = "symbol-package"; 176 break; 177 case 'M': // - module name 178 iconId = "symbol-module"; 179 break; 180 case 'a': // - array 181 iconId = "symbol-array"; 182 break; 183 case 'A': // - associative array 184 iconId = "symbol-array"; 185 break; 186 case 'l': // - alias name 187 iconId = "symbol-alias"; 188 break; 189 case 't': // - template name 190 iconId = "symbol-template"; 191 break; 192 case 'T': // - mixin template name 193 iconId = "symbol-mixintemplate"; 194 break; 195 default: 196 iconId = "symbol-other"; 197 break; 198 } 199 icons ~= iconId; 200 labels ~= label.name; 201 } 202 callback(labels, icons, output.type); 203 _getCompletionsTask = null; 204 }); 205 } 206 207 private: 208 209 } 210 211 /// convert caret position to byte offset in utf8 content 212 int caretPositionToByteOffset(string content, TextPosition caretPosition) { 213 auto line = 0; 214 auto pos = 0; 215 auto bytes = 0; 216 foreach(c; content) { 217 if(line == caretPosition.line) { 218 if(pos >= caretPosition.pos) 219 break; 220 if ((c & 0xC0) != 0x80) 221 pos++; 222 } else if (line > caretPosition.line) { 223 break; 224 } 225 bytes++; 226 if(c == '\n') { 227 line++; 228 pos = 0; 229 } 230 } 231 return bytes; 232 } 233 234 /// convert byte offset in utf8 content to caret position 235 TextPosition byteOffsetToCaret(string content, int byteOffset) { 236 int bytes = 0; 237 int line = 0; 238 int pos = 0; 239 TextPosition textPos; 240 foreach(c; content) { 241 if(bytes >= byteOffset) { 242 //We all good. 243 textPos.line = line; 244 textPos.pos = pos; 245 return textPos; 246 } 247 bytes++; 248 if(c == '\n') 249 { 250 line++; 251 pos = 0; 252 } 253 else { 254 if ((c & 0xC0) != 0x80) 255 pos++; 256 } 257 } 258 return textPos; 259 }