1 module dlangide.tools.d.dcdinterface;
2 
3 import dlangui.core.logger;
4 import dlangui.core.files;
5 import dlangui.platforms.common.platform;
6 import ddebug.common.queue;
7 
8 import core.thread;
9 
10 import std.typecons;
11 import std.conv;
12 import std.string;
13 
14 enum DCDResult : int {
15     SUCCESS,
16     NO_RESULT,
17     FAIL,
18 }
19 
20 alias DocCommentsResultSet = Tuple!(DCDResult, "result", string[], "docComments");
21 alias FindDeclarationResultSet = Tuple!(DCDResult, "result", string, "fileName", ulong, "offset");
22 alias CompletionResultSet = Tuple!(DCDResult, "result", dstring[], "output", char[], "completionKinds");
23 
24 import server.autocomplete;
25 import common.messages;
26 
27 class DCDTask {
28     protected bool _cancelled;
29     protected CustomEventTarget _guiExecutor;
30     protected string[] _importPaths;
31     protected string _filename;
32     protected string _content;
33     protected int _index;
34     protected AutocompleteRequest request;
35     this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index) {
36         _guiExecutor = guiExecutor;
37         _importPaths = importPaths;
38         _filename = filename;
39         _content = content;
40         _index = index;
41     }
42     @property bool cancelled() { return _cancelled; }
43     void cancel() {
44         synchronized(this) {
45             _cancelled = true;
46         }
47     }
48     void createRequest() {
49         request.sourceCode = cast(ubyte[])_content;
50         request.fileName = _filename;
51         request.cursorPosition = _index; 
52     }
53     void performRequest() {
54         // override
55     }
56     void postResults() {
57         // override
58     }
59     void execute() {
60         if (_cancelled)
61             return;
62         createRequest();
63         performRequest();
64         synchronized(this) {
65             if (_cancelled)
66                 return;
67             _guiExecutor.executeInUiThread(&postResults);
68         }
69     }
70 }
71 
72 /// Async interface to DCD
73 class DCDInterface : Thread {
74 
75     import dsymbol.modulecache;
76     protected ModuleCache _moduleCache = ModuleCache(new ASTAllocator);
77     protected BlockingQueue!DCDTask _queue;
78 
79     this() {
80         super(&threadFunc);
81         _queue = new BlockingQueue!DCDTask();
82         name = "DCDthread";
83         start();
84     }
85 
86     ~this() {
87         _queue.close();
88         join();
89         destroy(_queue);
90         _queue = null;
91     }
92 
93     protected ModuleCache * getModuleCache(in string[] importPaths) {
94         // TODO: clear cache if import paths removed or changed
95         // hold several module cache instances - make cache of caches
96         _moduleCache.addImportPaths(importPaths);
97         return &_moduleCache;
98     }
99 
100     void threadFunc() {
101         Log.d("Starting DCD tasks thread");
102         while (!_queue.closed()) {
103             DCDTask task;
104             if (!_queue.get(task))
105                 break;
106             if (task && !task.cancelled) {
107                 Log.d("Execute DCD task");
108                 task.execute();
109                 Log.d("DCD task execution finished");
110             }
111         }
112         Log.d("Exiting DCD tasks thread");
113     }
114 
115     import dsymbol.modulecache;
116 
117     protected string dumpContext(string content, int pos) {
118         if (pos >= 0 && pos <= content.length) {
119             int start = pos;
120             int end = pos;
121             for (int i = 0; start > 0 && content[start - 1] != '\n' && i < 10; i++)
122                 start--;
123             for (int i = 0; end < content.length - 1 && content[end] != '\n' && i < 10; i++)
124                 end++;
125             return content[start .. pos] ~ "|" ~ content[pos .. end];
126         }
127         return "";
128     }
129 
130     /// DCD doc comments task
131     class DocCommentsTask : DCDTask {
132 
133         protected void delegate(DocCommentsResultSet output) _callback;
134         protected DocCommentsResultSet result;
135 
136         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(DocCommentsResultSet output) callback) {
137             super(guiExecutor, importPaths, filename, content, index);
138             _callback = callback;
139         }
140 
141         override void performRequest() {
142             AutocompleteResponse response = getDoc(request, *getModuleCache(_importPaths));
143 
144             result.docComments = response.docComments;
145             result.result = DCDResult.SUCCESS;
146 
147             debug(DCD) Log.d("DCD doc comments:\n", result.docComments);
148 
149             if (result.docComments is null) {
150                 result.result = DCDResult.NO_RESULT;
151             }
152         }
153         override void postResults() {
154             _callback(result);
155         }
156     }
157 
158     DCDTask getDocComments(CustomEventTarget guiExecutor, string[] importPaths, string filename, string content, int index, void delegate(DocCommentsResultSet output) callback) {
159         debug(DCD) Log.d("getDocComments: ", dumpContext(content, index));
160         DocCommentsTask task = new DocCommentsTask(guiExecutor, importPaths, filename, content, index, callback);
161         _queue.put(task);
162         return task;
163     }
164 
165     /// DCD go to definition task
166     class GoToDefinitionTask : DCDTask {
167 
168         protected void delegate(FindDeclarationResultSet output) _callback;
169         protected FindDeclarationResultSet result;
170 
171         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(FindDeclarationResultSet output) callback) {
172             super(guiExecutor, importPaths, filename, content, index);
173             _callback = callback;
174         }
175 
176         override void performRequest() {
177             AutocompleteResponse response = findDeclaration(request, *getModuleCache(_importPaths));
178 
179             result.fileName = response.symbolFilePath;
180             result.offset = response.symbolLocation;
181             result.result = DCDResult.SUCCESS;
182 
183             debug(DCD) Log.d("DCD fileName:\n", result.fileName);
184 
185             if (result.fileName is null) {
186                 result.result = DCDResult.NO_RESULT;
187             }
188         }
189         override void postResults() {
190             _callback(result);
191         }
192     }
193 
194     DCDTask goToDefinition(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(FindDeclarationResultSet res) callback) {
195 
196         debug(DCD) Log.d("DCD GoToDefinition task Context: ", dumpContext(content, index), " importPaths:", importPaths);
197         GoToDefinitionTask task = new GoToDefinitionTask(guiExecutor, importPaths, filename, content, index, callback);
198         _queue.put(task);
199         return task;
200     }
201 
202     /// DCD get code completions task
203     class GetCompletionsTask : DCDTask {
204 
205         protected void delegate(CompletionResultSet output) _callback;
206         protected CompletionResultSet result;
207 
208         this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(CompletionResultSet output) callback) {
209             super(guiExecutor, importPaths, filename, content, index);
210             _callback = callback;
211         }
212 
213         override void performRequest() {
214             AutocompleteResponse response = complete(request, *getModuleCache(_importPaths));
215             if(response.completions is null || response.completions.length == 0){
216                 result.result = DCDResult.NO_RESULT;
217                 return;
218             }
219 
220             result.result = DCDResult.SUCCESS;
221             result.output.length = response.completions.length;
222             result.completionKinds.length = response.completions.length;
223             int i=0;
224             foreach(s;response.completions) {
225                 char type = 0;
226                 if (i < response.completionKinds.length)
227                     type = response.completionKinds[i];
228                 result.completionKinds[i] = type;
229                 result.output[i++] = to!dstring(s);
230             }
231             debug(DCD) Log.d("DCD output:\n", response.completions);
232         }
233         override void postResults() {
234             _callback(result);
235         }
236     }
237 
238     DCDTask getCompletions(CustomEventTarget guiExecutor, string[] importPaths, string filename, string content, int index, void delegate(CompletionResultSet output) callback) {
239 
240         debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
241         GetCompletionsTask task = new GetCompletionsTask(guiExecutor, importPaths, filename, content, index, callback);
242         _queue.put(task);
243         return task;
244     }
245 
246 }