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 break; 121 default: 122 break; 123 } 124 _goToDefinitionTask = null; 125 }); 126 127 } 128 129 DCDTask _getCompletionsTask; 130 override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[] completions, string[] icons, CompletionTypes type) callback) { 131 cancelGetCompletions(); 132 string[] importPaths = editor.importPaths(_frame.settings); 133 134 string content = toUTF8(editor.text); 135 auto byteOffset = caretPositionToByteOffset(content, caretPosition); 136 _getCompletionsTask = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset, delegate(CompletionResultSet output) { 137 string[] icons; 138 dstring[] labels; 139 foreach(index, label; output.output) { 140 string iconId; 141 char ch = label.kind; 142 switch(ch) { 143 case 'c': // - class name 144 iconId = "symbol-class"; 145 break; 146 case 'i': // - interface name 147 iconId = "symbol-interface"; 148 break; 149 case 's': // - struct name 150 iconId = "symbol-struct"; 151 break; 152 case 'u': // - union name 153 iconId = "symbol-union"; 154 break; 155 case 'v': // - variable name 156 iconId = "symbol-var"; 157 break; 158 case 'm': // - member variable name 159 iconId = "symbol-membervar"; 160 break; 161 case 'k': // - keyword, built-in version, scope statement 162 iconId = "symbol-keyword"; 163 break; 164 case 'f': // - function or method 165 iconId = "symbol-function"; 166 break; 167 case 'g': // - enum name 168 iconId = "symbol-enum"; 169 break; 170 case 'e': // - enum member 171 iconId = "symbol-enum"; 172 break; 173 case 'P': // - package name 174 iconId = "symbol-package"; 175 break; 176 case 'M': // - module name 177 iconId = "symbol-module"; 178 break; 179 case 'a': // - array 180 iconId = "symbol-array"; 181 break; 182 case 'A': // - associative array 183 iconId = "symbol-array"; 184 break; 185 case 'l': // - alias name 186 iconId = "symbol-alias"; 187 break; 188 case 't': // - template name 189 iconId = "symbol-template"; 190 break; 191 case 'T': // - mixin template name 192 iconId = "symbol-mixintemplate"; 193 break; 194 default: 195 iconId = "symbol-other"; 196 break; 197 } 198 icons ~= iconId; 199 labels ~= label.name; 200 } 201 callback(labels, icons, output.type); 202 _getCompletionsTask = null; 203 }); 204 } 205 206 private: 207 208 } 209 210 /// convert caret position to byte offset in utf8 content 211 int caretPositionToByteOffset(string content, TextPosition caretPosition) { 212 auto line = 0; 213 auto pos = 0; 214 auto bytes = 0; 215 foreach(c; content) { 216 if(line == caretPosition.line) { 217 if(pos >= caretPosition.pos) 218 break; 219 if ((c & 0xC0) != 0x80) 220 pos++; 221 } else if (line > caretPosition.line) { 222 break; 223 } 224 bytes++; 225 if(c == '\n') { 226 line++; 227 pos = 0; 228 } 229 } 230 return bytes; 231 } 232 233 /// convert byte offset in utf8 content to caret position 234 TextPosition byteOffsetToCaret(string content, int byteOffset) { 235 int bytes = 0; 236 int line = 0; 237 int pos = 0; 238 TextPosition textPos; 239 foreach(c; content) { 240 if(bytes >= byteOffset) { 241 //We all good. 242 textPos.line = line; 243 textPos.pos = pos; 244 return textPos; 245 } 246 bytes++; 247 if(c == '\n') 248 { 249 line++; 250 pos = 0; 251 } 252 else { 253 if ((c & 0xC0) != 0x80) 254 pos++; 255 } 256 } 257 return textPos; 258 }