ccls/src/command_line.cc

531 lines
20 KiB
C++
Raw Normal View History

2017-03-25 20:15:00 +00:00
// TODO: cleanup includes
#include "cache_manager.h"
#include "clang_complete.h"
2017-12-29 16:29:47 +00:00
#include "code_complete_cache.h"
2017-04-08 22:54:36 +00:00
#include "file_consumer.h"
2017-12-29 16:29:47 +00:00
#include "import_manager.h"
#include "import_pipeline.h"
#include "include_complete.h"
2017-02-25 23:59:09 +00:00
#include "indexer.h"
2017-03-05 02:16:23 +00:00
#include "language_server_api.h"
2017-06-15 05:32:23 +00:00
#include "lex_utils.h"
#include "lru_cache.h"
2017-09-22 01:14:57 +00:00
#include "match.h"
#include "message_handler.h"
2017-04-09 02:24:32 +00:00
#include "options.h"
2017-03-25 20:27:28 +00:00
#include "platform.h"
2017-09-22 01:14:57 +00:00
#include "project.h"
#include "query.h"
#include "query_utils.h"
2017-12-24 00:25:18 +00:00
#include "queue_manager.h"
2017-12-29 16:29:47 +00:00
#include "semantic_highlight_symbol_cache.h"
2017-07-30 04:24:02 +00:00
#include "serializer.h"
#include "serializers/json.h"
2017-03-10 07:06:01 +00:00
#include "test.h"
2017-03-25 20:15:00 +00:00
#include "threaded_queue.h"
2017-09-22 01:14:57 +00:00
#include "timer.h"
#include "timestamp_manager.h"
2017-09-13 03:35:27 +00:00
#include "work_thread.h"
#include "working_files.h"
2017-03-05 02:16:23 +00:00
2017-03-25 20:27:28 +00:00
#include <doctest/doctest.h>
#include <rapidjson/error/en.h>
2018-01-11 02:43:01 +00:00
#include <loguru.hpp>
2017-03-25 20:27:28 +00:00
#include <stdio.h>
#include <stdlib.h>
#include <functional>
2017-03-25 20:27:28 +00:00
#include <iostream>
2017-05-21 07:37:53 +00:00
#include <iterator>
2017-09-22 01:14:57 +00:00
#include <string>
2017-03-25 20:27:28 +00:00
#include <thread>
2017-09-22 01:14:57 +00:00
#include <unordered_map>
2017-03-25 20:27:28 +00:00
#include <vector>
2017-02-25 23:59:09 +00:00
// TODO: provide a feature like 'https://github.com/goldsborough/clang-expand',
// ie, a fully linear view of a function with inline function calls expanded.
// We can probably use vscode decorators to achieve it.
2017-09-22 01:14:57 +00:00
// TODO: implement ThreadPool type which monitors CPU usage / number of work
// items per second completed and scales up/down number of running threads.
std::string g_init_options;
bool g_debug;
namespace {
2017-04-09 02:24:32 +00:00
std::vector<std::string> kEmptyArgs;
// If true stdout will be printed to stderr.
bool g_log_stdin_stdout_to_stderr = false;
2017-09-22 01:14:57 +00:00
// This function returns true if e2e timing should be displayed for the given
// IpcId.
bool ShouldDisplayIpcTiming(IpcId id) {
switch (id) {
2017-09-22 01:14:57 +00:00
case IpcId::TextDocumentPublishDiagnostics:
case IpcId::CqueryPublishInactiveRegions:
case IpcId::Unknown:
2017-09-22 01:14:57 +00:00
return false;
default:
return true;
}
}
REGISTER_IPC_MESSAGE(Ipc_CancelRequest);
2018-01-29 23:43:22 +00:00
void PrintHelp() {
std::cout << R"help(cquery is a low-latency C/C++/Objective-C language server.
Mode:
--clang-sanity-check
Run a simple index test. Verifies basic clang functionality.
Needs to be executed from the cquery root checkout directory.
--test-unit Run unit tests.
--test-index <opt_filter_path>
Run index tests. opt_filter_path can be used to specify which
test to run (ie, "foo" will run all tests which contain "foo"
in the path). If not provided all tests are run.
(default if no other mode is specified)
Run as a language server over stdin and stdout
Other command line options:
--debug Disable libclang crash recovery so that in case of libclang or
indexer callback issue, the process will crash and we can
get a stack trace.
--init <initializationOptions>
Override client provided initialization options
https://github.com/cquery-project/cquery/wiki/Initialization-options
--log-stdin-stdout-to-stderr
Print stdin (requests) and stdout (responses) to stderr
--log-file <path> Logging file for diagnostics
--log-all-to-stderr Write all log messages to STDERR.
2018-01-29 23:43:22 +00:00
--wait-for-input Wait for an '[Enter]' before exiting
--help Print this help information.
--ci Prevents tests from prompting the user for input. Used for
continuous integration so it can fail faster instead of timing
out.
See more on https://github.com/cquery-project/cquery/wiki
)help";
}
} // namespace
2017-04-23 20:02:41 +00:00
2017-09-22 01:32:55 +00:00
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// QUERYDB MAIN ////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
2017-09-22 01:14:57 +00:00
bool QueryDbMainLoop(Config* config,
QueryDatabase* db,
MultiQueueWaiter* waiter,
Project* project,
2017-12-29 16:29:47 +00:00
FileConsumerSharedState* file_consumer_shared,
2017-09-22 01:14:57 +00:00
ImportManager* import_manager,
ImportPipelineStatus* status,
2017-09-22 01:14:57 +00:00
TimestampManager* timestamp_manager,
SemanticHighlightSymbolCache* semantic_cache,
2017-09-22 01:14:57 +00:00
WorkingFiles* working_files,
ClangCompleteManager* clang_complete,
IncludeComplete* include_complete,
CodeCompleteCache* global_code_complete_cache,
CodeCompleteCache* non_global_code_complete_cache,
CodeCompleteCache* signature_cache) {
2017-12-24 00:25:18 +00:00
auto* queue = QueueManager::instance();
bool did_work = false;
2017-09-22 01:14:57 +00:00
std::vector<std::unique_ptr<BaseIpcMessage>> messages =
2017-12-24 00:25:18 +00:00
queue->for_querydb.DequeueAll();
2017-03-05 19:48:05 +00:00
for (auto& message : messages) {
did_work = true;
2017-03-05 19:48:05 +00:00
for (MessageHandler* handler : *MessageHandler::message_handlers) {
if (handler->GetId() == message->method_id) {
handler->Run(std::move(message));
2017-04-23 20:19:09 +00:00
break;
}
}
2017-12-06 03:32:33 +00:00
if (message) {
LOG_S(FATAL) << "Exiting; unhandled IPC message "
<< IpcIdToString(message->method_id);
exit(1);
2017-03-15 04:59:05 +00:00
}
}
2017-09-22 01:14:57 +00:00
// TODO: consider rate-limiting and checking for IPC messages so we don't
// block requests / we can serve partial requests.
if (QueryDb_ImportMain(config, db, import_manager, status, semantic_cache,
working_files)) {
did_work = true;
}
return did_work;
2017-03-05 02:16:23 +00:00
}
2017-03-15 04:59:05 +00:00
2017-09-22 01:14:57 +00:00
void RunQueryDbThread(const std::string& bin_name,
Config* config,
MultiQueueWaiter* querydb_waiter,
MultiQueueWaiter* indexer_waiter) {
Project project;
SemanticHighlightSymbolCache semantic_cache;
WorkingFiles working_files;
2017-12-29 16:29:47 +00:00
FileConsumerSharedState file_consumer_shared;
2017-09-27 06:03:43 +00:00
ClangCompleteManager clang_complete(
2017-09-22 01:14:57 +00:00
config, &project, &working_files,
std::bind(&EmitDiagnostics, &working_files, std::placeholders::_1,
2017-09-27 06:03:43 +00:00
std::placeholders::_2),
2017-12-24 00:25:18 +00:00
std::bind(&IndexWithTuFromCodeCompletion, &file_consumer_shared,
2017-09-27 06:03:43 +00:00
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4));
IncludeComplete include_complete(config, &project);
auto global_code_complete_cache = MakeUnique<CodeCompleteCache>();
auto non_global_code_complete_cache = MakeUnique<CodeCompleteCache>();
auto signature_cache = MakeUnique<CodeCompleteCache>();
ImportManager import_manager;
2017-12-28 16:55:46 +00:00
ImportPipelineStatus import_pipeline_status;
TimestampManager timestamp_manager;
QueryDatabase db;
// Setup shared references.
for (MessageHandler* handler : *MessageHandler::message_handlers) {
handler->config = config;
handler->db = &db;
handler->waiter = indexer_waiter;
handler->project = &project;
handler->file_consumer_shared = &file_consumer_shared;
handler->import_manager = &import_manager;
2017-12-28 16:55:46 +00:00
handler->import_pipeline_status = &import_pipeline_status;
handler->timestamp_manager = &timestamp_manager;
2017-12-06 03:32:33 +00:00
handler->semantic_cache = &semantic_cache;
handler->working_files = &working_files;
handler->clang_complete = &clang_complete;
handler->include_complete = &include_complete;
handler->global_code_complete_cache = global_code_complete_cache.get();
handler->non_global_code_complete_cache =
non_global_code_complete_cache.get();
handler->signature_cache = signature_cache.get();
}
2017-03-25 19:18:25 +00:00
// Run query db main loop.
2017-04-19 00:05:14 +00:00
SetCurrentThreadName("querydb");
2017-03-15 04:59:05 +00:00
while (true) {
bool did_work = QueryDbMainLoop(
config, &db, querydb_waiter, &project, &file_consumer_shared,
&import_manager, &import_pipeline_status, &timestamp_manager,
&semantic_cache, &working_files, &clang_complete, &include_complete,
global_code_complete_cache.get(), non_global_code_complete_cache.get(),
signature_cache.get());
2017-08-17 18:02:47 +00:00
// Cleanup and free any unused memory.
FreeUnusedMemory();
if (!did_work) {
2017-12-24 00:25:18 +00:00
auto* queue = QueueManager::instance();
querydb_waiter->Wait(&queue->on_indexed, &queue->for_querydb,
&queue->do_id_map);
}
2017-03-15 04:59:05 +00:00
}
}
2017-09-22 01:32:55 +00:00
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// STDIN MAIN //////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
2017-03-05 19:48:05 +00:00
// Separate thread whose only job is to read from stdin and
// dispatch read commands to the actual indexer program. This
// cannot be done on the main thread because reading from std::cin
// blocks.
//
// |ipc| is connected to a server.
2017-09-22 01:14:57 +00:00
void LaunchStdinLoop(Config* config,
std::unordered_map<IpcId, Timer>* request_times) {
2017-12-20 17:09:05 +00:00
// If flushing cin requires flushing cout there could be deadlocks in some
// clients.
std::cin.tie(nullptr);
WorkThread::StartThread("stdin", [request_times]() {
2017-12-24 00:25:18 +00:00
auto* queue = QueueManager::instance();
while (true) {
std::unique_ptr<BaseIpcMessage> message;
optional<std::string> err =
MessageRegistry::instance()->ReadMessageFromStdin(
g_log_stdin_stdout_to_stderr, &message);
// Message parsing can fail if we don't recognize the method.
2018-01-19 08:14:47 +00:00
if (err) {
// The message may be partially deserialized.
// Emit an error ResponseMessage if |id| is available.
if (message) {
2018-01-20 03:05:26 +00:00
lsRequestId id = message->GetRequestId();
if (!std::holds_alternative<std::monostate>(id)) {
Out_Error out;
out.id = id;
out.error.code = lsErrorCodes::InvalidParams;
out.error.message = std::move(*err);
queue->WriteStdout(IpcId::Unknown, out);
}
2018-01-19 08:14:47 +00:00
}
continue;
2018-01-19 08:14:47 +00:00
}
// Cache |method_id| so we can access it after moving |message|.
IpcId method_id = message->method_id;
(*request_times)[method_id] = Timer();
switch (method_id) {
case IpcId::Initialized: {
// TODO: don't send output until we get this notification
break;
}
case IpcId::CancelRequest: {
// TODO: support cancellation
break;
}
2018-01-10 06:05:09 +00:00
case IpcId::Shutdown:
case IpcId::Exit:
case IpcId::Initialize:
case IpcId::TextDocumentDidOpen:
case IpcId::CqueryTextDocumentDidView:
case IpcId::TextDocumentDidChange:
case IpcId::TextDocumentDidClose:
case IpcId::TextDocumentDidSave:
2017-12-31 22:52:06 +00:00
case IpcId::TextDocumentFormatting:
case IpcId::TextDocumentRangeFormatting:
case IpcId::TextDocumentOnTypeFormatting:
case IpcId::TextDocumentRename:
case IpcId::TextDocumentCompletion:
case IpcId::TextDocumentSignatureHelp:
case IpcId::TextDocumentDefinition:
case IpcId::TextDocumentDocumentHighlight:
case IpcId::TextDocumentHover:
case IpcId::TextDocumentReferences:
case IpcId::TextDocumentDocumentSymbol:
case IpcId::TextDocumentDocumentLink:
case IpcId::TextDocumentCodeAction:
case IpcId::TextDocumentCodeLens:
case IpcId::WorkspaceDidChangeWatchedFiles:
case IpcId::WorkspaceSymbol:
case IpcId::CqueryFreshenIndex:
case IpcId::CqueryTypeHierarchyTree:
case IpcId::CqueryCallTreeInitial:
case IpcId::CqueryCallTreeExpand:
case IpcId::CqueryMemberHierarchyInitial:
case IpcId::CqueryMemberHierarchyExpand:
case IpcId::CqueryVars:
case IpcId::CqueryCallers:
case IpcId::CqueryBase:
case IpcId::CqueryDerived:
case IpcId::CqueryIndexFile:
case IpcId::CqueryWait: {
queue->for_querydb.Enqueue(std::move(message));
break;
}
default: {
LOG_S(ERROR) << "Unhandled IPC message " << IpcIdToString(method_id);
exit(1);
}
2017-09-22 01:14:57 +00:00
}
// If the message was to exit then querydb will take care of the actual
// exit. Stop reading from stdin since it might be detached.
if (method_id == IpcId::Exit)
2017-09-22 01:14:57 +00:00
break;
2017-03-03 08:12:11 +00:00
}
2017-09-13 03:35:27 +00:00
});
2017-03-03 08:12:11 +00:00
}
2017-09-22 01:14:57 +00:00
void LaunchStdoutThread(std::unordered_map<IpcId, Timer>* request_times,
2017-12-24 00:25:18 +00:00
MultiQueueWaiter* waiter) {
2017-09-13 03:35:27 +00:00
WorkThread::StartThread("stdout", [=]() {
2017-12-24 00:25:18 +00:00
auto* queue = QueueManager::instance();
while (true) {
std::vector<Stdout_Request> messages = queue->for_stdout.DequeueAll();
if (messages.empty()) {
waiter->Wait(&queue->for_stdout);
continue;
}
for (auto& message : messages) {
if (ShouldDisplayIpcTiming(message.id)) {
Timer time = (*request_times)[message.id];
time.ResetAndPrint("[e2e] Running " +
std::string(IpcIdToString(message.id)));
}
if (g_log_stdin_stdout_to_stderr) {
2018-01-15 06:53:51 +00:00
std::cerr << "[COUT] |" << message.content << "|\n";
std::cerr.flush();
}
2018-01-15 06:53:51 +00:00
std::cout << message.content;
std::cout.flush();
}
}
2017-09-13 03:35:27 +00:00
});
}
2017-09-22 01:14:57 +00:00
void LanguageServerMain(const std::string& bin_name,
Config* config,
MultiQueueWaiter* querydb_waiter,
MultiQueueWaiter* indexer_waiter,
MultiQueueWaiter* stdout_waiter) {
std::unordered_map<IpcId, Timer> request_times;
2017-09-13 03:35:27 +00:00
LaunchStdinLoop(config, &request_times);
2017-04-23 20:19:09 +00:00
// We run a dedicated thread for writing to stdout because there can be an
// unknown number of delays when output information.
LaunchStdoutThread(&request_times, stdout_waiter);
2017-09-13 03:35:27 +00:00
// Start querydb which takes over this thread. The querydb will launch
// indexer threads as needed.
RunQueryDbThread(bin_name, config, querydb_waiter, indexer_waiter);
2017-03-16 07:36:49 +00:00
}
2017-03-03 08:12:11 +00:00
2017-09-22 01:32:55 +00:00
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// MAIN ////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
2017-03-25 01:28:09 +00:00
int main(int argc, char** argv) {
TraceMe();
std::unordered_map<std::string, std::string> options =
ParseOptions(argc, argv);
2018-01-29 23:43:22 +00:00
if (HasOption(options, "-h") || HasOption(options, "--help")) {
PrintHelp();
return 0;
}
if (!HasOption(options, "--log-all-to-stderr"))
loguru::g_stderr_verbosity = loguru::Verbosity_WARNING;
2017-07-28 02:14:33 +00:00
loguru::g_flush_interval_ms = 0;
loguru::init(argc, argv);
2017-07-28 02:14:33 +00:00
MultiQueueWaiter querydb_waiter, indexer_waiter, stdout_waiter;
QueueManager::CreateInstance(&querydb_waiter, &indexer_waiter,
&stdout_waiter);
2017-03-25 20:27:28 +00:00
PlatformInit();
g_debug = HasOption(options, "--debug");
IndexInit();
bool language_server = true;
if (HasOption(options, "--log-file")) {
loguru::add_file(options["--log-file"].c_str(), loguru::Truncate,
loguru::Verbosity_MAX);
}
if (HasOption(options, "--log-stdin-stdout-to-stderr"))
g_log_stdin_stdout_to_stderr = true;
if (HasOption(options, "--clang-sanity-check")) {
language_server = false;
ClangSanityCheck();
}
if (HasOption(options, "--test-unit")) {
g_debug = true;
language_server = false;
2017-03-17 23:45:10 +00:00
doctest::Context context;
context.applyCommandLine(argc, argv);
int res = context.run();
2017-12-27 15:54:03 +00:00
if (res != 0 || context.shouldExit())
2017-03-25 19:18:25 +00:00
return res;
}
2017-03-17 23:45:10 +00:00
if (HasOption(options, "--test-index")) {
g_debug = true;
language_server = false;
if (!RunIndexTests(options["--test-index"], !HasOption(options, "--ci")))
2018-01-29 23:43:22 +00:00
return 1;
}
if (language_server) {
if (HasOption(options, "--init")) {
// We check syntax error here but override client-side initializationOptions
// in messages/initialize.cc
g_init_options = options["--init"];
rapidjson::Document reader;
rapidjson::ParseResult ok = reader.Parse(g_init_options.c_str());
if (!ok) {
std::cerr << "Failed to parse --init as JSON: "
<< rapidjson::GetParseError_En(ok.Code()) << " (" << ok.Offset()
<< ")\n";
return 1;
}
JsonReader json_reader{&reader};
try {
Config config;
Reflect(json_reader, config);
} catch (std::invalid_argument& e) {
std::cerr << "Fail to parse --init "
<< static_cast<JsonReader&>(json_reader).GetPath()
<< ", expected " << e.what() << "\n";
return 1;
}
}
2017-09-22 01:14:57 +00:00
// std::cerr << "Running language server" << std::endl;
2017-09-24 00:36:28 +00:00
auto config = MakeUnique<Config>();
LanguageServerMain(argv[0], config.get(), &querydb_waiter, &indexer_waiter,
&stdout_waiter);
}
2018-01-29 23:43:22 +00:00
if (HasOption(options, "--wait-for-input")) {
std::cerr << std::endl << "[Enter] to exit" << std::endl;
getchar();
}
return 0;
2017-02-25 23:59:09 +00:00
}