//===--- ClangdMain.cpp - clangd server loop ------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "ClangdLSPServer.h" #include "JSONRPCDispatcher.h" #include "Path.h" #include "Trace.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include using namespace clang; using namespace clang::clangd; namespace { enum class PCHStorageFlag { Disk, Memory }; } static llvm::cl::opt CompileCommandsDir( "compile-commands-dir", llvm::cl::desc("Specify a path to look for compile_commands.json. If path " "is invalid, clangd will look in the current directory and " "parent paths of each source file.")); static llvm::cl::opt WorkerThreadsCount("j", llvm::cl::desc("Number of async workers used by clangd"), llvm::cl::init(getDefaultAsyncThreadsCount())); static llvm::cl::opt EnableSnippets( "enable-snippets", llvm::cl::desc( "Present snippet completions instead of plaintext completions. " "This also enables code pattern results." /* FIXME: should it? */), llvm::cl::init(clangd::CodeCompleteOptions().EnableSnippets)); // FIXME: Flags are the wrong mechanism for user preferences. // We should probably read a dotfile or similar. static llvm::cl::opt IncludeIneligibleResults( "include-ineligible-results", llvm::cl::desc( "Include ineligible completion results (e.g. private members)"), llvm::cl::init(clangd::CodeCompleteOptions().IncludeIneligibleResults), llvm::cl::Hidden); static llvm::cl::opt PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"), llvm::cl::init(false)); static llvm::cl::opt PCHStorage( "pch-storage", llvm::cl::desc("Storing PCHs in memory increases memory usages, but may " "improve performance"), llvm::cl::values( clEnumValN(PCHStorageFlag::Disk, "disk", "store PCHs on disk"), clEnumValN(PCHStorageFlag::Memory, "memory", "store PCHs in memory")), llvm::cl::init(PCHStorageFlag::Disk)); static llvm::cl::opt RunSynchronously( "run-synchronously", llvm::cl::desc("Parse on main thread. If set, -j is ignored"), llvm::cl::init(false), llvm::cl::Hidden); static llvm::cl::opt ResourceDir("resource-dir", llvm::cl::desc("Directory for system clang headers"), llvm::cl::init(""), llvm::cl::Hidden); static llvm::cl::opt InputMirrorFile( "input-mirror-file", llvm::cl::desc( "Mirror all LSP input to the specified file. Useful for debugging."), llvm::cl::init(""), llvm::cl::Hidden); static llvm::cl::opt TraceFile( "trace", llvm::cl::desc( "Trace internal events and timestamps in chrome://tracing JSON format"), llvm::cl::init(""), llvm::cl::Hidden); static llvm::cl::opt EnableIndexBasedCompletion( "enable-index-based-completion", llvm::cl::desc( "Enable index-based global code completion (experimental). Clangd will " "use index built from symbols in opened files"), llvm::cl::init(false), llvm::cl::Hidden); int main(int argc, char *argv[]) { llvm::cl::ParseCommandLineOptions(argc, argv, "clangd"); if (!RunSynchronously && WorkerThreadsCount == 0) { llvm::errs() << "A number of worker threads cannot be 0. Did you mean to " "specify -run-synchronously?"; return 1; } // Ignore -j option if -run-synchonously is used. // FIXME: a warning should be shown here. if (RunSynchronously) WorkerThreadsCount = 0; // Validate command line arguments. llvm::Optional InputMirrorStream; if (!InputMirrorFile.empty()) { std::error_code EC; InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC, llvm::sys::fs::F_RW); if (EC) { InputMirrorStream.reset(); llvm::errs() << "Error while opening an input mirror file: " << EC.message(); } } // Setup tracing facilities. llvm::Optional TraceStream; std::unique_ptr Tracer; if (!TraceFile.empty()) { std::error_code EC; TraceStream.emplace(TraceFile, /*ref*/ EC, llvm::sys::fs::F_RW); if (EC) { TraceFile.reset(); llvm::errs() << "Error while opening trace file: " << EC.message(); } else { Tracer = trace::createJSONTracer(*TraceStream, PrettyPrint); } } llvm::Optional TracingSession; if (Tracer) TracingSession.emplace(*Tracer); llvm::raw_ostream &Outs = llvm::outs(); llvm::raw_ostream &Logs = llvm::errs(); JSONOutput Out(Outs, Logs, InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, PrettyPrint); clangd::LoggingSession LoggingSession(Out); // If --compile-commands-dir arg was invoked, check value and override default // path. llvm::Optional CompileCommandsDirPath; if (CompileCommandsDir.empty()) { CompileCommandsDirPath = llvm::None; } else if (!llvm::sys::path::is_absolute(CompileCommandsDir) || !llvm::sys::fs::exists(CompileCommandsDir)) { llvm::errs() << "Path specified by --compile-commands-dir either does not " "exist or is not an absolute " "path. The argument will be ignored.\n"; CompileCommandsDirPath = llvm::None; } else { CompileCommandsDirPath = CompileCommandsDir; } bool StorePreamblesInMemory; switch (PCHStorage) { case PCHStorageFlag::Memory: StorePreamblesInMemory = true; break; case PCHStorageFlag::Disk: StorePreamblesInMemory = false; break; } llvm::Optional ResourceDirRef = None; if (!ResourceDir.empty()) ResourceDirRef = ResourceDir; // Change stdin to binary to not lose \r\n on windows. llvm::sys::ChangeStdinToBinary(); clangd::CodeCompleteOptions CCOpts; CCOpts.EnableSnippets = EnableSnippets; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; // Initialize and run ClangdLSPServer. ClangdLSPServer LSPServer(Out, WorkerThreadsCount, StorePreamblesInMemory, CCOpts, ResourceDirRef, CompileCommandsDirPath, EnableIndexBasedCompletion); constexpr int NoShutdownRequestErrorCode = 1; llvm::set_thread_name("clangd.main"); return LSPServer.run(std::cin) ? 0 : NoShutdownRequestErrorCode; }