1 /**
2  * This file is part of DCD, a development tool for the D programming language.
3  * Copyright (C) 2014 Brian Schott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 module common.messages;
20 
21 import std.socket;
22 import msgpack;
23 import core.time : dur;
24 
25 /**
26  * The type of completion list being returned
27  */
28 enum CompletionType : string
29 {
30 	/**
31 	 * The completion list contains a listing of identifier/kind pairs.
32 	 */
33 	identifiers = "identifiers",
34 
35 	/**
36 	 * The auto-completion list consists of a listing of functions and their
37 	 * parameters.
38 	 */
39 	calltips = "calltips",
40 
41 	/**
42 	 * The response contains the location of a symbol declaration.
43 	 */
44 	location = "location",
45 
46 	/**
47 	 * The response contains documentation comments for the symbol.
48 	 */
49 	ddoc = "ddoc",
50 }
51 
52 /**
53  * Request kind
54  */
55 enum RequestKind : ushort
56 {
57 	// dfmt off
58 	uninitialized =  0b00000000_00000000,
59 	/// Autocompletion
60 	autocomplete =   0b00000000_00000001,
61 	/// Clear the completion cache
62 	clearCache =     0b00000000_00000010,
63 	/// Add import directory to server
64 	addImport =      0b00000000_00000100,
65 	/// Shut down the server
66 	shutdown =       0b00000000_00001000,
67 	/// Get declaration location of given symbol
68 	symbolLocation = 0b00000000_00010000,
69 	/// Get the doc comments for the symbol
70 	doc =            0b00000000_00100000,
71 	/// Query server status
72 	query =	         0b00000000_01000000,
73 	/// Search for symbol
74 	search =         0b00000000_10000000,
75 	/// List import directories
76 	listImports =    0b00000001_00000000,
77 	/// local symbol usage
78 	localUse =     	 0b00000010_00000000,
79 	// dfmt on
80 }
81 
82 /**
83  * Autocompletion request message
84  */
85 struct AutocompleteRequest
86 {
87 	/**
88 	 * File name used for error reporting
89 	 */
90 	string fileName;
91 
92 	/**
93 	 * Command coming from the client
94 	 */
95 	RequestKind kind;
96 
97 	/**
98 	 * Paths to be searched for import files
99 	 */
100 	string[] importPaths;
101 
102 	/**
103 	 * The source code to auto complete
104 	 */
105 	ubyte[] sourceCode;
106 
107 	/**
108 	 * The cursor position
109 	 */
110 	size_t cursorPosition;
111 
112 	/**
113 	 * Name of symbol searched for
114 	 */
115 	string searchName;
116 }
117 
118 /**
119  * Autocompletion response message
120  */
121 struct AutocompleteResponse
122 {
123 	/**
124 	 * The autocompletion type. (Parameters or identifier)
125 	 */
126 	string completionType;
127 
128 	/**
129 	 * The path to the file that contains the symbol.
130 	 */
131 	string symbolFilePath;
132 
133 	/**
134 	 * The byte offset at which the symbol is located.
135 	 */
136 	size_t symbolLocation;
137 
138 	/**
139 	 * The documentation comment
140 	 */
141 	string[] docComments;
142 
143 	/**
144 	 * The completions
145 	 */
146 	string[] completions;
147 
148 	/**
149 	 * The kinds of the items in the completions array. Will be empty if the
150 	 * completion type is a function argument list.
151 	 */
152 	char[] completionKinds;
153 
154 	/**
155 	 * Symbol locations for symbol searches.
156 	 */
157 	size_t[] locations;
158 
159 	/**
160 	 * Import paths that are registered by the server.
161 	 */
162 	string[] importPaths;
163 
164 	/**
165 	 * Symbol identifier
166 	 */
167 	ulong symbolIdentifier;
168 }
169 
170 /**
171  * Returns: true on success
172  */
173 bool sendRequest(Socket socket, AutocompleteRequest request)
174 {
175 	ubyte[] message = msgpack.pack(request);
176 	ubyte[] messageBuffer = new ubyte[message.length + message.length.sizeof];
177 	auto messageLength = message.length;
178 	messageBuffer[0 .. size_t.sizeof] = (cast(ubyte*) &messageLength)[0 .. size_t.sizeof];
179 	messageBuffer[size_t.sizeof .. $] = message[];
180 	return socket.send(messageBuffer) == messageBuffer.length;
181 }
182 
183 /**
184  * Gets the response from the server
185  */
186 AutocompleteResponse getResponse(Socket socket)
187 {
188 	ubyte[1024 * 16] buffer;
189 	auto bytesReceived = socket.receive(buffer);
190 	if (bytesReceived == Socket.ERROR)
191 		throw new Exception("Incorrect number of bytes received");
192 	if (bytesReceived == 0)
193 		throw new Exception("Server closed the connection, 0 bytes received");
194 	AutocompleteResponse response;
195 	msgpack.unpack(buffer[0..bytesReceived], response);
196 	return response;
197 }
198 
199 /**
200  * Returns: true if a server instance is running
201  * Params:
202  *     useTCP = `true` to check a TCP port, `false` for UNIX domain socket
203  *     socketFile = the file name for the UNIX domain socket
204  *     port = the TCP port
205  */
206 bool serverIsRunning(bool useTCP, string socketFile, ushort port)
207 {
208 	scope (failure)
209 		return false;
210 	AutocompleteRequest request;
211 	request.kind = RequestKind.query;
212 	Socket socket;
213 	scope (exit)
214 	{
215 		socket.shutdown(SocketShutdown.BOTH);
216 		socket.close();
217 	}
218 	version(Windows) useTCP = true;
219 	if (useTCP)
220 	{
221 		socket = new TcpSocket(AddressFamily.INET);
222 		socket.connect(new InternetAddress("localhost", port));
223 	}
224 	else
225 	{
226 		version(Windows) {} else
227 		{
228 			socket = new Socket(AddressFamily.UNIX, SocketType.STREAM);
229 			socket.connect(new UnixAddress(socketFile));
230 		}
231 	}
232 	socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(5));
233 	socket.blocking = true;
234 	if (sendRequest(socket, request))
235 		return getResponse(socket).completionType == "ack";
236 	else
237 		return false;
238 }