#include "log.hh" #include "pipeline.hh" #include "platform.h" #include "serializer.h" #include "serializers/json.h" #include "test.h" #include "working_files.h" using namespace ccls; #include #include #include using namespace llvm; using namespace llvm::cl; #include #include #include #include #include #include #include std::string g_init_options; namespace { opt opt_help("h", desc("Alias for -help")); opt opt_verbose("v", desc("verbosity"), init(0)); opt opt_test_index("test-index", desc("run index tests")); opt opt_test_unit("test-unit", desc("run unit tests")); opt opt_init("init", desc("extra initialization options")); opt opt_log_file("log-file", desc("log"), value_desc("filename")); opt opt_log_file_append("log-file-append", desc("log"), value_desc("filename")); list opt_extra(Positional, ZeroOrMore, desc("extra")); void CloseLog() { fclose(ccls::log::file); } } // namespace int main(int argc, char** argv) { TraceMe(); sys::PrintStackTraceOnErrorSignal(argv[0]); ParseCommandLineOptions(argc, argv, "C/C++/Objective-C language server\n\n" "See more on https://github.com/MaskRay/ccls/wiki"); if (opt_help) { PrintHelpMessage(); // Also emit doctest help if --test-unit is passed. if (!opt_test_unit) return 0; } pipeline::Init(); #ifdef _WIN32 // We need to write to stdout in binary mode because in Windows, writing // \n will implicitly write \r\n. Language server API will ignore a // \r\r\n split request. _setmode(_fileno(stdout), O_BINARY); _setmode(_fileno(stdin), O_BINARY); #endif IndexInit(); bool language_server = true; if (opt_log_file.size() || opt_log_file_append.size()) { ccls::log::file = opt_log_file.size() ? fopen(opt_log_file.c_str(), "wb") : fopen(opt_log_file_append.c_str(), "ab"); if (!ccls::log::file) { fprintf( stderr, "failed to open %s\n", (opt_log_file.size() ? opt_log_file : opt_log_file_append).c_str()); return 2; } setbuf(ccls::log::file, NULL); atexit(CloseLog); } if (opt_test_unit) { language_server = false; doctest::Context context; std::vector args{argv[0]}; if (opt_help) args.push_back("-h"); for (auto& arg : opt_extra) args.push_back(arg.c_str()); context.applyCommandLine(args.size(), args.data()); int res = context.run(); if (res != 0 || context.shouldExit()) return res; } if (opt_test_index) { language_server = false; if (!RunIndexTests("", sys::Process::StandardInIsUserInput())) return 1; } if (language_server) { if (!opt_init.empty()) { // We check syntax error here but override client-side // initializationOptions in messages/initialize.cc g_init_options = opt_init; rapidjson::Document reader; rapidjson::ParseResult ok = reader.Parse(g_init_options.c_str()); if (!ok) { fprintf(stderr, "Failed to parse --init as JSON: %s (%zd)\n", rapidjson::GetParseError_En(ok.Code()), ok.Offset()); return 1; } JsonReader json_reader{&reader}; try { Config config; Reflect(json_reader, config); } catch (std::invalid_argument& e) { fprintf(stderr, "Failed to parse --init %s, expected %s\n", static_cast(json_reader).GetPath().c_str(), e.what()); return 1; } } std::unordered_map request_times; // The thread that reads from stdin and dispatchs commands to the main thread. pipeline::LaunchStdin(&request_times); // The thread that writes responses from the main thread to stdout. pipeline::LaunchStdout(&request_times); // Main thread which also spawns indexer threads upon the "initialize" request. pipeline::MainLoop(); } return 0; }