1 module dlangide.tools.d.dcdinterface;
2 
3 import dlangui.core.logger;
4 import dlangui.core.files;
5 
6 import dlangide.builders.extprocess;
7 
8 import std.typecons;
9 import std.conv;
10 import std..string;
11 
12 const DCD_SERVER_PORT_FOR_DLANGIDE = 9167;
13 const DCD_DEFAULT_PORT = 9166;
14 
15 enum DCDResult : int {
16 	DCD_NOT_RUNNING = 0,
17 	SUCCESS,
18 	NO_RESULT,
19 	FAIL,
20 }
21 alias ResultSet = Tuple!(DCDResult, "result", dstring[], "output");
22 
23 
24 //Interface to DCD
25 //TODO: Check if server is running, start server if needed etc.
26 class DCDInterface {
27 
28     private int _port;
29     //ExternalProcess dcdProcess;
30     //ProtectedTextStorage stdoutTarget;
31 	this(int port = DCD_SERVER_PORT_FOR_DLANGIDE) {
32         _port = port;
33         //dcdProcess = new ExternalProcess();
34         //stdoutTarget = new ProtectedTextStorage();
35 	}
36 
37     protected string dumpContext(string content, int pos) {
38         if (pos >= 0 && pos <= content.length) {
39             int start = pos;
40             int end = pos;
41             for (int i = 0; start > 0 && content[start - 1] != '\n' && i < 10; i++)
42                 start--;
43             for (int i = 0; end < content.length - 1 && content[end] != '\n' && i < 10; i++)
44                 end++;
45             return content[start .. pos] ~ "|" ~ content[pos .. end];
46         }
47         return "";
48     }
49 
50     protected dstring[] invokeDcd(string[] arguments, string content, out bool success) {
51         success = false;
52 		ExternalProcess dcdProcess = new ExternalProcess();
53 
54 		ProtectedTextStorage stdoutTarget = new ProtectedTextStorage();
55 
56 		version(Windows) {
57 			string dcd_client_name = "dcd-client.exe";
58 			string dcd_client_dir = null;
59 		} else {
60 			string dcd_client_name = "dcd-client";
61 			string dcd_client_dir = "/usr/bin";
62 		}
63 		dcdProcess.run(dcd_client_name, arguments, dcd_client_dir, stdoutTarget);
64         
65 		dcdProcess.write(content);
66 		dcdProcess.wait();
67 
68 		dstring[] output =  stdoutTarget.readText.splitLines();
69 
70 		if(dcdProcess.poll() == ExternalProcessState.Stopped) {
71 			success = true;
72 		}
73         return output;
74     }
75 
76 	ResultSet goToDefinition(in string[] importPaths, in string content, int index) {
77 		ResultSet result;
78         
79         debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
80 
81 		string[] arguments = ["-l", "-c"];
82 		arguments ~= [to!string(index)];
83 
84         foreach(p; importPaths) {
85             arguments ~= "-I" ~ p;
86         }
87         if (_port != DCD_DEFAULT_PORT)
88             arguments ~= "-p" ~ to!string(_port);
89 
90         bool success = false;
91 		dstring[] output =  invokeDcd(arguments, content, success);
92 
93 		if (success) {
94 			result.result = DCDResult.SUCCESS;
95 		} else {
96 			result.result = DCDResult.FAIL;
97 			return result;
98 		}
99 
100         debug(DCD) Log.d("DCD output:\n", output);
101 
102 		if(output.length > 0) {
103             dstring firstLine = output[0];
104 			if(firstLine.startsWith("Not Found") || firstLine.startsWith("Not found")) {
105 				result.result = DCDResult.NO_RESULT;
106 				return result;
107 			}
108             auto split = firstLine.indexOf("\t");
109             if(split == -1) {
110                 Log.d("DCD output format error.");
111                 result.result = DCDResult.FAIL;
112                 return result;
113             }
114 
115             result.output ~= output[0][0 .. split];
116             result.output ~= output[0][split+1 .. $];
117 		} else {
118             result.result = DCDResult.NO_RESULT;
119             //result.result = DCDResult.FAIL;
120         }
121 
122 		return result;
123 	}
124 
125 	ResultSet getCompletions(in string[] importPaths, in string content, int index) {
126 
127         debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
128 
129 		ResultSet result;
130 
131 		string[] arguments = ["-c"];
132 		arguments ~= [to!string(index)];
133 
134         foreach(p; importPaths) {
135             arguments ~= "-I" ~ p;
136         }
137         if (_port != DCD_DEFAULT_PORT)
138             arguments ~= "-p" ~ to!string(_port);
139 
140         bool success = false;
141 		dstring[] output =  invokeDcd(arguments, content, success);
142 
143 		if (success) {
144 			result.result = DCDResult.SUCCESS;
145 		} else {
146 			result.result = DCDResult.FAIL;
147 			return result;
148 		}
149         debug(DCD) Log.d("DCD output:\n", output);
150 
151 		if (output.length == 0) {
152 			result.result = DCDResult.NO_RESULT;
153 			return result;
154 		}
155 
156 		enum State : int {None = 0, Identifiers, Calltips}
157 		State state = State.None;
158 		foreach(dstring outputLine ; output) {
159 			if(outputLine == "identifiers") {
160 				state = State.Identifiers;
161 			}
162 			else if(outputLine == "calltips") {
163 				state = State.Calltips;
164 			}
165 			else {
166 				auto split = outputLine.indexOf("\t");
167 				if(split < 0) {
168 					break;
169 				}
170 				if(state == State.Identifiers) {
171 					result.output ~= outputLine[0 .. split];
172 				}
173 			}
174 		}
175 		return result;
176 	}
177 }