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 client.client; 20 21 import std.socket; 22 import std.stdio; 23 import std.getopt; 24 import std.array; 25 import std.process; 26 import std.algorithm; 27 import std.path; 28 import std.file; 29 import std.conv; 30 import std.string; 31 import std.experimental.logger; 32 33 import common.messages; 34 import common.dcd_version; 35 import common.socket; 36 37 int main(string[] args) 38 { 39 sharedLog.fatalHandler = () {}; 40 41 size_t cursorPos = size_t.max; 42 string[] importPaths; 43 ushort port; 44 bool help; 45 bool shutdown; 46 bool clearCache; 47 bool symbolLocation; 48 bool doc; 49 bool query; 50 bool printVersion; 51 bool listImports; 52 bool getIdentifier; 53 bool localUse; 54 string search; 55 version(Windows) 56 { 57 bool useTCP = true; 58 string socketFile; 59 } 60 else 61 { 62 bool useTCP = false; 63 string socketFile = generateSocketName(); 64 } 65 66 try 67 { 68 getopt(args, "cursorPos|c", &cursorPos, "I", &importPaths, 69 "port|p", &port, "help|h", &help, "shutdown", &shutdown, 70 "clearCache", &clearCache, "symbolLocation|l", &symbolLocation, 71 "doc|d", &doc, "query|status|q", &query, "search|s", &search, 72 "version", &printVersion, "listImports", &listImports, 73 "tcp", &useTCP, "socketFile", &socketFile, 74 "getIdentifier", &getIdentifier, 75 "localUsage", &localUse, // TODO:remove this line in Nov. 2017 76 "localUse|u", &localUse); 77 } 78 catch (ConvException e) 79 { 80 fatal(e.msg); 81 printHelp(args[0]); 82 return 1; 83 } 84 85 AutocompleteRequest request; 86 87 if (help) 88 { 89 printHelp(args[0]); 90 return 0; 91 } 92 93 if (printVersion) 94 { 95 version (Windows) 96 writeln(DCD_VERSION); 97 else version(built_with_dub) 98 writeln(DCD_VERSION); 99 else 100 write(DCD_VERSION, " ", GIT_HASH); 101 return 0; 102 } 103 104 version (Windows) if (socketFile !is null) 105 { 106 fatal("UNIX domain sockets not supported on Windows"); 107 return 1; 108 } 109 110 // If the user specified a port number, assume that they wanted a TCP 111 // connection. Otherwise set the port number to the default and let the 112 // useTCP flag deterimen what to do later. 113 if (port != 0) 114 useTCP = true; 115 else 116 port = DEFAULT_PORT_NUMBER; 117 118 if (useTCP) 119 socketFile = null; 120 121 if (query) 122 { 123 if (serverIsRunning(useTCP, socketFile, port)) 124 { 125 writeln("Server is running"); 126 return 0; 127 } 128 else 129 { 130 writeln("Server is not running"); 131 return 1; 132 } 133 } 134 else if (shutdown || clearCache) 135 { 136 if (shutdown) 137 request.kind = RequestKind.shutdown; 138 else if (clearCache) 139 request.kind = RequestKind.clearCache; 140 Socket socket = createSocket(socketFile, port); 141 scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } 142 return sendRequest(socket, request) ? 0 : 1; 143 } 144 else if (importPaths.length > 0) 145 { 146 request.kind |= RequestKind.addImport; 147 request.importPaths = importPaths.map!(a => absolutePath(a)).array; 148 if (cursorPos == size_t.max) 149 { 150 Socket socket = createSocket(socketFile, port); 151 scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } 152 if (!sendRequest(socket, request)) 153 return 1; 154 return 0; 155 } 156 } 157 else if (listImports) 158 { 159 request.kind |= RequestKind.listImports; 160 Socket socket = createSocket(socketFile, port); 161 scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } 162 sendRequest(socket, request); 163 AutocompleteResponse response = getResponse(socket); 164 printImportList(response); 165 return 0; 166 } 167 else if (search == null && cursorPos == size_t.max) 168 { 169 // cursor position is a required argument 170 printHelp(args[0]); 171 return 1; 172 } 173 174 // Read in the source 175 immutable bool usingStdin = args.length <= 1; 176 string fileName = usingStdin ? "stdin" : args[1]; 177 if (!usingStdin && !exists(args[1])) 178 { 179 stderr.writefln("%s does not exist", args[1]); 180 return 1; 181 } 182 ubyte[] sourceCode; 183 if (usingStdin) 184 { 185 ubyte[4096] buf; 186 while (true) 187 { 188 auto b = stdin.rawRead(buf); 189 if (b.length == 0) 190 break; 191 sourceCode ~= b; 192 } 193 } 194 else 195 { 196 if (!exists(args[1])) 197 { 198 stderr.writeln("Could not find ", args[1]); 199 return 1; 200 } 201 File f = File(args[1]); 202 sourceCode = uninitializedArray!(ubyte[])(to!size_t(f.size)); 203 f.rawRead(sourceCode); 204 } 205 206 request.fileName = fileName; 207 request.importPaths = importPaths; 208 request.sourceCode = sourceCode; 209 request.cursorPosition = cursorPos; 210 request.searchName = search; 211 212 if (symbolLocation | getIdentifier) 213 request.kind |= RequestKind.symbolLocation; 214 else if (doc) 215 request.kind |= RequestKind.doc; 216 else if (search) 217 request.kind |= RequestKind.search; 218 else if(localUse) 219 request.kind |= RequestKind.localUse; 220 else 221 request.kind |= RequestKind.autocomplete; 222 223 // Send message to server 224 Socket socket = createSocket(socketFile, port); 225 scope (exit) { socket.shutdown(SocketShutdown.BOTH); socket.close(); } 226 if (!sendRequest(socket, request)) 227 return 1; 228 229 AutocompleteResponse response = getResponse(socket); 230 231 if (symbolLocation) 232 printLocationResponse(response); 233 else if (getIdentifier) 234 printIdentifierResponse(response); 235 else if (doc) 236 printDocResponse(response); 237 else if (search !is null) 238 printSearchResponse(response); 239 else if (localUse) 240 printLocalUse(response); 241 else 242 printCompletionResponse(response); 243 244 return 0; 245 } 246 247 private: 248 249 void printHelp(string programName) 250 { 251 writefln( 252 ` 253 Usage: %1$s [Options] [FILENAME] 254 255 A file name is optional. If it is given, autocomplete information will be 256 given for the file specified. If it is missing, input will be read from 257 stdin instead. 258 259 Source code is assumed to be UTF-8 encoded and must not exceed 4 megabytes. 260 261 Options: 262 --help | -h 263 Displays this help message 264 265 --cursorPos | -c position 266 Provides auto-completion at the given cursor position. The cursor 267 position is measured in bytes from the beginning of the source code. 268 269 --clearCache 270 Instructs the server to clear out its autocompletion cache. 271 272 --shutdown 273 Instructs the server to shut down. 274 275 --symbolLocation | -l 276 Get the file name and position that the symbol at the cursor location 277 was defined. 278 279 --doc | -d 280 Gets documentation comments associated with the symbol at the cursor 281 location. 282 283 --search | -s symbolName 284 Searches for symbolName in both stdin / the given file name as well as 285 others files cached by the server. 286 287 --localUse | -u 288 Searches for all the uses of the symbol at the cursor location 289 in the given filename (or stdin). 290 291 --query | -q | --status 292 Query the server statis. Returns 0 if the server is running. Returns 293 1 if the server could not be contacted. 294 295 -I PATH 296 Instructs the server to add PATH to its list of paths searched for 297 imported modules. 298 299 --version 300 Prints the version number and then exits. 301 302 --port PORTNUMBER | -p PORTNUMBER 303 Uses PORTNUMBER to communicate with the server instead of the default 304 port 9166. Only used on Windows or when the --tcp option is set. 305 306 --tcp 307 Send requests on a TCP socket instead of a UNIX domain socket. This 308 switch has no effect on Windows. 309 310 --socketFile FILENAME 311 Use the given FILENAME as the path to the UNIX domain socket. Using 312 this switch is an error on Windows.`, programName); 313 } 314 315 Socket createSocket(string socketFile, ushort port) 316 { 317 import core.time : dur; 318 319 Socket socket; 320 if (socketFile is null) 321 { 322 socket = new TcpSocket(AddressFamily.INET); 323 socket.connect(new InternetAddress("localhost", port)); 324 } 325 else 326 { 327 version(Windows) 328 { 329 // should never be called with non-null socketFile on Windows 330 assert(false); 331 } 332 else 333 { 334 socket = new Socket(AddressFamily.UNIX, SocketType.STREAM); 335 socket.connect(new UnixAddress(socketFile)); 336 } 337 } 338 socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(5)); 339 socket.blocking = true; 340 return socket; 341 } 342 343 void printDocResponse(ref const AutocompleteResponse response) 344 { 345 import std.algorithm : each; 346 response.docComments.each!(writeln); 347 } 348 349 void printIdentifierResponse(ref const AutocompleteResponse response) 350 { 351 if (response.completions.length == 0) 352 return; 353 write(response.completions[0]); 354 write("\t"); 355 writeln(response.symbolIdentifier); 356 } 357 358 void printLocationResponse(ref const AutocompleteResponse response) 359 { 360 if (response.symbolFilePath is null) 361 writeln("Not found"); 362 else 363 writefln("%s\t%d", response.symbolFilePath, response.symbolLocation); 364 } 365 366 void printCompletionResponse(ref const AutocompleteResponse response) 367 { 368 if (response.completions.length > 0) 369 { 370 writeln(response.completionType); 371 auto app = appender!(string[])(); 372 if (response.completionType == CompletionType.identifiers) 373 { 374 for (size_t i = 0; i < response.completions.length; i++) 375 app.put(format("%s\t%s", response.completions[i], response.completionKinds[i])); 376 } 377 else 378 { 379 foreach (completion; response.completions) 380 { 381 app.put(completion); 382 } 383 } 384 // Deduplicate overloaded methods 385 foreach (line; app.data.sort().uniq) 386 writeln(line); 387 } 388 } 389 390 void printSearchResponse(const AutocompleteResponse response) 391 { 392 foreach(i; 0 .. response.completions.length) 393 { 394 writefln("%s\t%s\t%s", response.completions[i], response.completionKinds[i], 395 response.locations[i]); 396 } 397 } 398 399 void printLocalUse(const AutocompleteResponse response) 400 { 401 if (response.symbolFilePath.length) 402 { 403 writeln(response.symbolFilePath, '\t', response.symbolLocation); 404 foreach(loc; response.locations) 405 writeln(loc); 406 } 407 else write("00000"); 408 } 409 410 void printImportList(const AutocompleteResponse response) 411 { 412 import std.algorithm.iteration : each; 413 414 response.importPaths.each!(a => writeln(a)); 415 }