//===--- 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 "Features.inc" #include "ClangdLSPServer.h" #include "Path.h" #include "Trace.h" #include "Transport.h" #include "index/Serialization.h" #include "clang/Basic/Version.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include namespace clang { namespace clangd { // FIXME: remove this option when Dex is cheap enough. static llvm::cl::opt UseDex("use-dex-index", llvm::cl::desc("Use experimental Dex dynamic index."), llvm::cl::init(false), llvm::cl::Hidden); 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())); // FIXME: also support "plain" style where signatures are always omitted. enum CompletionStyleFlag { Detailed, Bundled }; static llvm::cl::opt CompletionStyle( "completion-style", llvm::cl::desc("Granularity of code completion suggestions"), llvm::cl::values( clEnumValN(Detailed, "detailed", "One completion item for each semantically distinct " "completion, with full type information."), clEnumValN(Bundled, "bundled", "Similar completion items (e.g. function overloads) are " "combined. Type information shown where possible.")), llvm::cl::init(Detailed)); // 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(CodeCompleteOptions().IncludeIneligibleResults), llvm::cl::Hidden); static llvm::cl::opt InputStyle( "input-style", llvm::cl::desc("Input JSON stream encoding"), llvm::cl::values( clEnumValN(JSONStreamStyle::Standard, "standard", "usual LSP protocol"), clEnumValN(JSONStreamStyle::Delimited, "delimited", "messages delimited by --- lines, with # comment support")), llvm::cl::init(JSONStreamStyle::Standard)); static llvm::cl::opt PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"), llvm::cl::init(false)); static llvm::cl::opt LogLevel( "log", llvm::cl::desc("Verbosity of log messages written to stderr"), llvm::cl::values(clEnumValN(Logger::Error, "error", "Error messages only"), clEnumValN(Logger::Info, "info", "High level execution tracing"), clEnumValN(Logger::Debug, "verbose", "Low level details")), llvm::cl::init(Logger::Info)); static llvm::cl::opt Test("lit-test", llvm::cl::desc("Abbreviation for -input-style=delimited -pretty " "-run-synchronously -enable-test-scheme. " "Intended to simplify lit tests."), llvm::cl::init(false), llvm::cl::Hidden); static llvm::cl::opt EnableTestScheme( "enable-test-uri-scheme", llvm::cl::desc("Enable 'test:' URI scheme. Only use in lit tests."), llvm::cl::init(false), llvm::cl::Hidden); enum PCHStorageFlag { Disk, Memory }; 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 LimitResults( "limit-results", llvm::cl::desc("Limit the number of results returned by clangd. " "0 means no limit."), llvm::cl::init(100)); 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 EnableIndex( "index", llvm::cl::desc( "Enable index-based features. By default, clangd maintains an index " "built from symbols in opened files. Global index support needs to " "enabled separatedly."), llvm::cl::init(true), llvm::cl::Hidden); static llvm::cl::opt AllScopesCompletion( "all-scopes-completion", llvm::cl::desc( "If set to true, code completion will include index symbols that are " "not defined in the scopes (e.g. " "namespaces) visible from the code completion point. Such completions " "can insert scope qualifiers."), llvm::cl::init(true)); static llvm::cl::opt ShowOrigins( "debug-origin", llvm::cl::desc("Show origins of completion items"), llvm::cl::init(CodeCompleteOptions().ShowOrigins), llvm::cl::Hidden); static llvm::cl::opt HeaderInsertionDecorators( "header-insertion-decorators", llvm::cl::desc("Prepend a circular dot or space before the completion " "label, depending on whether " "an include line will be inserted or not."), llvm::cl::init(true)); static llvm::cl::opt IndexFile( "index-file", llvm::cl::desc( "Index file to build the static index. The file must have been created " "by a compatible clangd-index.\n" "WARNING: This option is experimental only, and will be removed " "eventually. Don't rely on it."), llvm::cl::init(""), llvm::cl::Hidden); static llvm::cl::opt EnableBackgroundIndex( "background-index", llvm::cl::desc( "Index project code in the background and persist index on disk. " "Experimental"), llvm::cl::init(false), llvm::cl::Hidden); static llvm::cl::opt BackgroundIndexRebuildPeriod( "background-index-rebuild-period", llvm::cl::desc( "If set to non-zero, the background index rebuilds the symbol index " "periodically every X milliseconds; otherwise, the " "symbol index will be updated for each indexed file."), llvm::cl::init(5000), llvm::cl::Hidden); enum CompileArgsFrom { LSPCompileArgs, FilesystemCompileArgs }; static llvm::cl::opt CompileArgsFrom( "compile_args_from", llvm::cl::desc("The source of compile commands"), llvm::cl::values(clEnumValN(LSPCompileArgs, "lsp", "All compile commands come from LSP and " "'compile_commands.json' files are ignored"), clEnumValN(FilesystemCompileArgs, "filesystem", "All compile commands come from the " "'compile_commands.json' files")), llvm::cl::init(FilesystemCompileArgs), llvm::cl::Hidden); static llvm::cl::opt EnableFunctionArgSnippets( "function-arg-placeholders", llvm::cl::desc("When disabled, completions contain only parentheses for " "function calls. When enabled, completions also contain " "placeholders for method parameters."), llvm::cl::init(CodeCompleteOptions().EnableFunctionArgSnippets)); namespace { /// \brief Supports a test URI scheme with relaxed constraints for lit tests. /// The path in a test URI will be combined with a platform-specific fake /// directory to form an absolute path. For example, test:///a.cpp is resolved /// C:\clangd-test\a.cpp on Windows and /clangd-test/a.cpp on Unix. class TestScheme : public URIScheme { public: llvm::Expected getAbsolutePath(llvm::StringRef /*Authority*/, llvm::StringRef Body, llvm::StringRef /*HintPath*/) const override { using namespace llvm::sys; // Still require "/" in body to mimic file scheme, as we want lengths of an // equivalent URI in both schemes to be the same. if (!Body.startswith("/")) return llvm::make_error( "Expect URI body to be an absolute path starting with '/': " + Body, llvm::inconvertibleErrorCode()); Body = Body.ltrim('/'); llvm::SmallVector Path(Body.begin(), Body.end()); path::native(Path); fs::make_absolute(TestScheme::TestDir, Path); return std::string(Path.begin(), Path.end()); } llvm::Expected uriFromAbsolutePath(llvm::StringRef AbsolutePath) const override { llvm::StringRef Body = AbsolutePath; if (!Body.consume_front(TestScheme::TestDir)) { return llvm::make_error( "Path " + AbsolutePath + " doesn't start with root " + TestDir, llvm::inconvertibleErrorCode()); } return URI("test", /*Authority=*/"", llvm::sys::path::convert_to_slash(Body)); } private: const static char TestDir[]; }; #ifdef _WIN32 const char TestScheme::TestDir[] = "C:\\clangd-test"; #else const char TestScheme::TestDir[] = "/clangd-test"; #endif } // namespace } // namespace clangd } // namespace clang enum class ErrorResultCode : int { NoShutdownRequest = 1, CantRunAsXPCService = 2 }; int main(int argc, char *argv[]) { using namespace clang; using namespace clang::clangd; llvm::sys::PrintStackTraceOnErrorSignal(argv[0]); llvm::cl::SetVersionPrinter([](llvm::raw_ostream &OS) { OS << clang::getClangToolFullVersion("clangd") << "\n"; }); llvm::cl::ParseCommandLineOptions( argc, argv, "clangd is a language server that provides IDE-like features to editors. " "\n\nIt should be used via an editor plugin rather than invoked " "directly. " "For more information, see:" "\n\thttps://clang.llvm.org/extra/clangd.html" "\n\thttps://microsoft.github.io/language-server-protocol/"); if (Test) { RunSynchronously = true; InputStyle = JSONStreamStyle::Delimited; PrettyPrint = true; preventThreadStarvationInTests(); // Ensure background index makes progress. } if (Test || EnableTestScheme) { static URISchemeRegistry::Add X( "test", "Test scheme for clangd lit tests."); } if (!RunSynchronously && WorkerThreadsCount == 0) { llvm::errs() << "A number of worker threads cannot be 0. Did you mean to " "specify -run-synchronously?"; return 1; } if (RunSynchronously) { if (WorkerThreadsCount.getNumOccurrences()) llvm::errs() << "Ignoring -j because -run-synchronously is set.\n"; WorkerThreadsCount = 0; } // Validate command line arguments. llvm::Optional InputMirrorStream; if (!InputMirrorFile.empty()) { std::error_code EC; InputMirrorStream.emplace(InputMirrorFile, /*ref*/ EC, llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write); if (EC) { InputMirrorStream.reset(); llvm::errs() << "Error while opening an input mirror file: " << EC.message(); } else { InputMirrorStream->SetUnbuffered(); } } // Setup tracing facilities if CLANGD_TRACE is set. In practice enabling a // trace flag in your editor's config is annoying, launching with // `CLANGD_TRACE=trace.json vim` is easier. llvm::Optional TraceStream; std::unique_ptr Tracer; if (auto *TraceFile = getenv("CLANGD_TRACE")) { std::error_code EC; TraceStream.emplace(TraceFile, /*ref*/ EC, llvm::sys::fs::FA_Read | llvm::sys::fs::FA_Write); if (EC) { TraceStream.reset(); llvm::errs() << "Error while opening trace file " << TraceFile << ": " << EC.message(); } else { Tracer = trace::createJSONTracer(*TraceStream, PrettyPrint); } } llvm::Optional TracingSession; if (Tracer) TracingSession.emplace(*Tracer); // Use buffered stream to stderr (we still flush each log message). Unbuffered // stream can cause significant (non-deterministic) latency for the logger. llvm::errs().SetBuffered(); StreamLogger Logger(llvm::errs(), LogLevel); LoggingSession LoggingSession(Logger); // If --compile-commands-dir arg was invoked, check value and override default // path. llvm::Optional CompileCommandsDirPath; if (!CompileCommandsDir.empty()) { if (llvm::sys::fs::exists(CompileCommandsDir)) { // We support passing both relative and absolute paths to the // --compile-commands-dir argument, but we assume the path is absolute in // the rest of clangd so we make sure the path is absolute before // continuing. llvm::SmallString<128> Path(CompileCommandsDir); if (std::error_code EC = llvm::sys::fs::make_absolute(Path)) { llvm::errs() << "Error while converting the relative path specified by " "--compile-commands-dir to an absolute path: " << EC.message() << ". The argument will be ignored.\n"; } else { CompileCommandsDirPath = Path.str(); } } else { llvm::errs() << "Path specified by --compile-commands-dir does not exist. The " "argument will be ignored.\n"; } } ClangdServer::Options Opts; switch (PCHStorage) { case PCHStorageFlag::Memory: Opts.StorePreamblesInMemory = true; break; case PCHStorageFlag::Disk: Opts.StorePreamblesInMemory = false; break; } if (!ResourceDir.empty()) Opts.ResourceDir = ResourceDir; Opts.BuildDynamicSymbolIndex = EnableIndex; Opts.HeavyweightDynamicSymbolIndex = UseDex; Opts.BackgroundIndex = EnableBackgroundIndex; Opts.BackgroundIndexRebuildPeriodMs = BackgroundIndexRebuildPeriod; std::unique_ptr StaticIdx; std::future AsyncIndexLoad; // Block exit while loading the index. if (EnableIndex && !IndexFile.empty()) { // Load the index asynchronously. Meanwhile SwapIndex returns no results. SwapIndex *Placeholder; StaticIdx.reset(Placeholder = new SwapIndex(llvm::make_unique())); AsyncIndexLoad = runAsync([Placeholder] { if (auto Idx = loadIndex(IndexFile, /*UseDex=*/true)) Placeholder->reset(std::move(Idx)); }); if (RunSynchronously) AsyncIndexLoad.wait(); } Opts.StaticIndex = StaticIdx.get(); Opts.AsyncThreadsCount = WorkerThreadsCount; clangd::CodeCompleteOptions CCOpts; CCOpts.IncludeIneligibleResults = IncludeIneligibleResults; CCOpts.Limit = LimitResults; CCOpts.BundleOverloads = CompletionStyle != Detailed; CCOpts.ShowOrigins = ShowOrigins; if (!HeaderInsertionDecorators) { CCOpts.IncludeIndicator.Insert.clear(); CCOpts.IncludeIndicator.NoInsert.clear(); } CCOpts.SpeculativeIndexRequest = Opts.StaticIndex; CCOpts.EnableFunctionArgSnippets = EnableFunctionArgSnippets; CCOpts.AllScopes = AllScopesCompletion; // Initialize and run ClangdLSPServer. // Change stdin to binary to not lose \r\n on windows. llvm::sys::ChangeStdinToBinary(); std::unique_ptr TransportLayer; if (getenv("CLANGD_AS_XPC_SERVICE")) { #if CLANGD_BUILD_XPC TransportLayer = newXPCTransport(); #else llvm::errs() << "This clangd binary wasn't built with XPC support.\n"; return (int)ErrorResultCode::CantRunAsXPCService; #endif } else { TransportLayer = newJSONTransport( stdin, llvm::outs(), InputMirrorStream ? InputMirrorStream.getPointer() : nullptr, PrettyPrint, InputStyle); } ClangdLSPServer LSPServer( *TransportLayer, CCOpts, CompileCommandsDirPath, /*UseDirBasedCDB=*/CompileArgsFrom == FilesystemCompileArgs, Opts); llvm::set_thread_name("clangd.main"); return LSPServer.run() ? 0 : static_cast(ErrorResultCode::NoShutdownRequest); }