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 filename, in string content, int index) {
77 		ResultSet result;
78 
79         version(USE_LIBDPARSE) {
80             import dlangide.tools.d.dparser;
81             DParsingService.instance.addImportPaths(importPaths);
82             DParsedModule m = DParsingService.instance.findDeclaration(cast(ubyte[])content, filename, index);
83         }
84         
85         debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
86 
87 		string[] arguments = ["-l", "-c"];
88 		arguments ~= [to!string(index)];
89 
90         foreach(p; importPaths) {
91             arguments ~= "-I" ~ p;
92         }
93         if (_port != DCD_DEFAULT_PORT)
94             arguments ~= "-p" ~ to!string(_port);
95 
96         bool success = false;
97 		dstring[] output =  invokeDcd(arguments, content, success);
98 
99 		if (success) {
100 			result.result = DCDResult.SUCCESS;
101 		} else {
102 			result.result = DCDResult.FAIL;
103 			return result;
104 		}
105 
106         debug(DCD) Log.d("DCD output:\n", output);
107 
108 		if(output.length > 0) {
109             dstring firstLine = output[0];
110 			if(firstLine.startsWith("Not Found") || firstLine.startsWith("Not found")) {
111 				result.result = DCDResult.NO_RESULT;
112 				return result;
113 			}
114             auto split = firstLine.indexOf("\t");
115             if(split == -1) {
116                 Log.d("DCD output format error.");
117                 result.result = DCDResult.FAIL;
118                 return result;
119             }
120 
121             result.output ~= output[0][0 .. split];
122             result.output ~= output[0][split+1 .. $];
123 		} else {
124             result.result = DCDResult.NO_RESULT;
125             //result.result = DCDResult.FAIL;
126         }
127 
128 		return result;
129 	}
130 
131 	ResultSet getCompletions(in string[] importPaths, in string filename, in string content, int index) {
132 
133         debug(DCD) Log.d("DCD Context: ", dumpContext(content, index));
134 
135 		ResultSet result;
136 
137 		string[] arguments = ["-c"];
138 		arguments ~= [to!string(index)];
139 
140         foreach(p; importPaths) {
141             arguments ~= "-I" ~ p;
142         }
143         if (_port != DCD_DEFAULT_PORT)
144             arguments ~= "-p" ~ to!string(_port);
145 
146         bool success = false;
147 		dstring[] output =  invokeDcd(arguments, content, success);
148 
149 		if (success) {
150 			result.result = DCDResult.SUCCESS;
151 		} else {
152 			result.result = DCDResult.FAIL;
153 			return result;
154 		}
155         debug(DCD) Log.d("DCD output:\n", output);
156 
157 		if (output.length == 0) {
158 			result.result = DCDResult.NO_RESULT;
159 			return result;
160 		}
161 
162 		enum State : int {None = 0, Identifiers, Calltips}
163 		State state = State.None;
164 		foreach(dstring outputLine ; output) {
165 			if(outputLine == "identifiers") {
166 				state = State.Identifiers;
167 			}
168 			else if(outputLine == "calltips") {
169 				state = State.Calltips;
170 			}
171 			else {
172 				auto split = outputLine.indexOf("\t");
173 				if(split < 0) {
174 					break;
175 				}
176 				if(state == State.Identifiers) {
177 					result.output ~= outputLine[0 .. split];
178 				}
179 			}
180 		}
181 		return result;
182 	}
183 }