From 13f265655088e7dff0b32e065aaa750007e50a98 Mon Sep 17 00:00:00 2001
From: Vadim Lopatin <coolreader.org@gmail.com>
Date: Thu, 28 Jan 2016 11:58:21 +0300
Subject: [PATCH] async DCD get code completions handling - close #93

---
 src/dlangide/tools/d/dcdinterface.d | 51 ++++++++++++++++++-----------
 src/dlangide/tools/d/deditortool.d  | 27 +++++++--------
 src/dlangide/tools/editortool.d     |  5 +--
 src/dlangide/ui/dsourceedit.d       |  1 +
 src/dlangide/ui/frame.d             |  6 ++--
 5 files changed, 53 insertions(+), 37 deletions(-)

diff --git a/src/dlangide/tools/d/dcdinterface.d b/src/dlangide/tools/d/dcdinterface.d
index 3f07022..ddf5688 100644
--- a/src/dlangide/tools/d/dcdinterface.d
+++ b/src/dlangide/tools/d/dcdinterface.d
@@ -19,7 +19,7 @@ enum DCDResult : int {
 
 alias DocCommentsResultSet = Tuple!(DCDResult, "result", string[], "docComments");
 alias FindDeclarationResultSet = Tuple!(DCDResult, "result", string, "fileName", ulong, "offset");
-alias ResultSet = Tuple!(DCDResult, "result", dstring[], "output");
+alias CompletionResultSet = Tuple!(DCDResult, "result", dstring[], "output");
 
 import server.autocomplete;
 import common.messages;
@@ -199,32 +199,43 @@ class DCDInterface : Thread {
         return task;
     }
 
-    ResultSet getCompletions(CustomEventTarget guiExecutor, in string[] importPaths, in string filename, in string content, int index) {
+    /// DCD get code completions task
+    class GetCompletionsTask : DCDTask {
 
-        debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
-        ResultSet result;
-        AutocompleteRequest request;
-        request.sourceCode = cast(ubyte[])content;
-        request.fileName = filename;
-        request.cursorPosition = index; 
+        protected void delegate(CompletionResultSet output) _callback;
+        protected CompletionResultSet result;
 
-        AutocompleteResponse response = complete(request, *getModuleCache(importPaths));
-        if(response.completions is null || response.completions.length == 0){
-            result.result = DCDResult.NO_RESULT;
-            return result;
+        this(CustomEventTarget guiExecutor, string[] importPaths, in string filename, in string content, int index, void delegate(CompletionResultSet output) callback) {
+            super(guiExecutor, importPaths, filename, content, index);
+            _callback = callback;
         }
 
-        result.result = DCDResult.SUCCESS;
-        result.output.length = response.completions.length;
-        int i=0;
-        foreach(s;response.completions){
-            result.output[i++]=to!dstring(s);            
-        }
-        debug(DCD) Log.d("DCD output:\n", response.completions);
+        override void performRequest() {
+            AutocompleteResponse response = complete(request, *getModuleCache(_importPaths));
+            if(response.completions is null || response.completions.length == 0){
+                result.result = DCDResult.NO_RESULT;
+                return;
+            }
 
-        return result;
+            result.result = DCDResult.SUCCESS;
+            result.output.length = response.completions.length;
+            int i=0;
+            foreach(s;response.completions){
+                result.output[i++]=to!dstring(s);            
+            }
+            debug(DCD) Log.d("DCD output:\n", response.completions);
+        }
+        override void postResults() {
+            _callback(result);
+        }
     }
 
+    DCDTask getCompletions(CustomEventTarget guiExecutor, string[] importPaths, string filename, string content, int index, void delegate(CompletionResultSet output) callback) {
 
+        debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
+        GetCompletionsTask task = new GetCompletionsTask(guiExecutor, importPaths, filename, content, index, callback);
+        _queue.put(task);
+        return task;
+    }
 
 }
diff --git a/src/dlangide/tools/d/deditortool.d b/src/dlangide/tools/d/deditortool.d
index d113232..da30bdb 100644
--- a/src/dlangide/tools/d/deditortool.d
+++ b/src/dlangide/tools/d/deditortool.d
@@ -24,6 +24,7 @@ class DEditorTool : EditorTool
     ~this() {
         cancelGoToDefinition();
         cancelGetDocComments();
+        cancelGetCompletions();
     }
 
     DCDTask _getDocCommentsTask;
@@ -44,22 +45,26 @@ class DEditorTool : EditorTool
     }
 
     override void cancelGetDocComments() {
-        // override it
         if (_getDocCommentsTask) {
             _getDocCommentsTask.cancel();
             _getDocCommentsTask = null;
         }
     }
 
-
     override void cancelGoToDefinition() {
-        // override it
         if (_goToDefinitionTask) {
             _goToDefinitionTask.cancel();
             _goToDefinitionTask = null;
         }
     }
 
+    override void cancelGetCompletions() {
+        if (_getCompletionsTask) {
+            _getCompletionsTask.cancel();
+            _getCompletionsTask = null;
+        }
+    }
+
     DCDTask _goToDefinitionTask;
     override void goToDefinition(DSourceEdit editor, TextPosition caretPosition) {
         cancelGoToDefinition();
@@ -101,20 +106,16 @@ class DEditorTool : EditorTool
 
     }
 
-    override dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition) {
+    DCDTask _getCompletionsTask;
+    override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[]) callback) {
         string[] importPaths = editor.importPaths();
 
         string content = toUTF8(editor.text);
         auto byteOffset = caretPositionToByteOffset(content, caretPosition);
-        ResultSet output = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset);
-        switch(output.result) {
-            //TODO: Show dialog
-            case DCDResult.FAIL:
-            case DCDResult.NO_RESULT:
-            case DCDResult.SUCCESS:
-            default:
-                return output.output;
-        }
+        _getCompletionsTask = _frame.dcdInterface.getCompletions(editor.window, importPaths, editor.filename, content, byteOffset, delegate(CompletionResultSet output) {
+             callback(output.output);
+            _getCompletionsTask = null;
+        });
     }
 
 private:
diff --git a/src/dlangide/tools/editortool.d b/src/dlangide/tools/editortool.d
index a7b9c7a..2d958f3 100644
--- a/src/dlangide/tools/editortool.d
+++ b/src/dlangide/tools/editortool.d
@@ -17,10 +17,11 @@ class EditorTool
     //Since files might be unsaved, we must send all the text content.
     abstract void goToDefinition(DSourceEdit editor, TextPosition caretPosition);
     abstract void getDocComments(DSourceEdit editor, TextPosition caretPosition, void delegate(string[]) callback);
-    abstract dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition);
+    abstract void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[]) callback);
 
     void cancelGoToDefinition() {}
     void cancelGetDocComments() {}
+    void cancelGetCompletions() {}
 
     protected IDEFrame _frame;
     
@@ -36,7 +37,7 @@ class DefaultEditorTool : EditorTool
         assert(0); //Go To Definition should not be called for normal files.
     }
     
-    override dstring[] getCompletions(DSourceEdit editor, TextPosition caretPosition) {
+    override void getCompletions(DSourceEdit editor, TextPosition caretPosition, void delegate(dstring[]) callback) {
         assert(0);
     }
 
diff --git a/src/dlangide/ui/dsourceedit.d b/src/dlangide/ui/dsourceedit.d
index bcf0f7d..d8525d8 100644
--- a/src/dlangide/ui/dsourceedit.d
+++ b/src/dlangide/ui/dsourceedit.d
@@ -530,6 +530,7 @@ class DSourceEdit : SourceEdit, EditableContentMarksChangeListener {
         popup.flags = PopupFlags.CloseOnClickOutside;
 
         Log.d("Showing popup at ", textPosToClient(_caretPos).left, " ", textPosToClient(_caretPos).top);
+        window.update();
     }
 
 }
diff --git a/src/dlangide/ui/frame.d b/src/dlangide/ui/frame.d
index a01b867..0361e45 100644
--- a/src/dlangide/ui/frame.d
+++ b/src/dlangide/ui/frame.d
@@ -895,8 +895,10 @@ class IDEFrame : AppFrame, ProgramExecutionStatusListener, BreakpointListChangeL
                     return true;
                 case IDEActions.GetCompletionSuggestions:
                     Log.d("Getting auto completion suggestions.");
-                    auto results = currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos);
-                    currentEditor.showCompletionPopup(results);
+                    currentEditor.editorTool.getCompletions(currentEditor, currentEditor.caretPos, delegate(dstring[] results) {
+                        if (currentEditor)
+                            currentEditor.showCompletionPopup(results);
+                    });
                     return true;
                 case IDEActions.EditPreferences:
                     showPreferences();