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 
24 /**
25  * The type of completion list being returned
26  */
27 enum CompletionType : string
28 {
29 	/**
30 	 * The completion list contains a listing of identifier/kind pairs.
31 	 */
32 	identifiers = "identifiers",
33 
34 	/**
35 	 * The auto-completion list consists of a listing of functions and their
36 	 * parameters.
37 	 */
38 	calltips = "calltips",
39 
40 	/**
41 	 * The response contains the location of a symbol declaration.
42 	 */
43 	location = "location",
44 
45 	/**
46 	 * The response contains documentation comments for the symbol.
47 	 */
48 	ddoc = "ddoc",
49 }
50 
51 /**
52  * Request kind
53  */
54 enum RequestKind : ushort
55 {
56 	// dfmt off
57 
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 
78 	// dfmt on
79 }
80 
81 /**
82  * Autocompletion request message
83  */
84 struct AutocompleteRequest
85 {
86 	/**
87 	 * File name used for error reporting
88 	 */
89 	string fileName;
90 
91 	/**
92 	 * Command coming from the client
93 	 */
94 	RequestKind kind;
95 
96 	/**
97 	 * Paths to be searched for import files
98 	 */
99 	string[] importPaths;
100 
101 	/**
102 	 * The source code to auto complete
103 	 */
104 	ubyte[] sourceCode;
105 
106 	/**
107 	 * The cursor position
108 	 */
109 	size_t cursorPosition;
110 
111 	/**
112 	 * Name of symbol searched for
113 	 */
114 	string searchName;
115 }
116 
117 /**
118  * Autocompletion response message
119  */
120 struct AutocompleteResponse
121 {
122 	/**
123 	 * The autocompletion type. (Parameters or identifier)
124 	 */
125 	string completionType;
126 
127 	/**
128 	 * The path to the file that contains the symbol.
129 	 */
130 	string symbolFilePath;
131 
132 	/**
133 	 * The byte offset at which the symbol is located.
134 	 */
135 	size_t symbolLocation;
136 
137 	/**
138 	 * The documentation comment
139 	 */
140 	string[] docComments;
141 
142 	/**
143 	 * The completions
144 	 */
145 	string[] completions;
146 
147 	/**
148 	 * The kinds of the items in the completions array. Will be empty if the
149 	 * completion type is a function argument list.
150 	 */
151 	char[] completionKinds;
152 
153 	/**
154 	 * Symbol locations for symbol searches.
155 	 */
156 	size_t[] locations;
157 
158 	/**
159 	 * Import paths that are registered by the server.
160 	 */
161 	string[] importPaths;
162 }
163 
164 /**
165  * Returns: true on success
166  */
167 bool sendRequest(Socket socket, AutocompleteRequest request)
168 {
169 	ubyte[] message = msgpack.pack(request);
170 	ubyte[] messageBuffer = new ubyte[message.length + message.length.sizeof];
171 	auto messageLength = message.length;
172 	messageBuffer[0 .. size_t.sizeof] = (cast(ubyte*) &messageLength)[0 .. size_t.sizeof];
173 	messageBuffer[size_t.sizeof .. $] = message[];
174 	return socket.send(messageBuffer) == messageBuffer.length;
175 }
176 
177 /**
178  * Gets the response from the server
179  */
180 AutocompleteResponse getResponse(Socket socket)
181 {
182 	ubyte[1024 * 16] buffer;
183 	auto bytesReceived = socket.receive(buffer);
184 	if (bytesReceived == Socket.ERROR)
185 		throw new Exception("Incorrect number of bytes received");
186 	if (bytesReceived == 0)
187 		throw new Exception("Server closed the connection, 0 bytes received");
188 	AutocompleteResponse response;
189 	msgpack.unpack(buffer[0..bytesReceived], response);
190 	return response;
191 }
192 
193 /**
194  * Returns: true if a server instance is running
195  * Params:
196  *     useTCP = `true` to check a TCP port, `false` for UNIX domain socket
197  *     socketFile = the file name for the UNIX domain socket
198  *     port = the TCP port
199  */
200 bool serverIsRunning(bool useTCP, string socketFile, ushort port)
201 {
202 	scope (failure)
203 		return false;
204 	AutocompleteRequest request;
205 	request.kind = RequestKind.query;
206 	Socket socket;
207 	scope (exit)
208 	{
209 		socket.shutdown(SocketShutdown.BOTH);
210 		socket.close();
211 	}
212 	if (useTCP)
213 	{
214 		socket = new TcpSocket(AddressFamily.INET);
215 		socket.connect(new InternetAddress("localhost", port));
216 	}
217 	else
218 	{
219 		socket = new Socket(AddressFamily.UNIX, SocketType.STREAM);
220 		socket.connect(new UnixAddress(socketFile));
221 	}
222 	socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(5));
223 	socket.blocking = true;
224 	if (sendRequest(socket, request))
225 		return getResponse(socket).completionType == "ack";
226 	else
227 		return false;
228 }