mirror of
https://github.com/MaskRay/ccls.git
synced 2025-02-20 23:49:28 +00:00
Simplify IpcManager.
It no longer supports multi-process IPC; remove some of the extra code that was used to support that.
This commit is contained in:
parent
b64b8b5493
commit
6c5f1d9c88
@ -126,7 +126,7 @@ bool FindFileOrFail(QueryDatabase* db,
|
||||
out.id = *id;
|
||||
out.error.code = lsErrorCodes::InternalError;
|
||||
out.error.message = "Unable to find file " + absolute_path;
|
||||
IpcManager::instance()->SendOutMessageToClient(IpcId::Cout, out);
|
||||
IpcManager::WriteStdout(IpcId::Cout, out);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -141,8 +141,7 @@ void EmitInactiveLines(WorkingFile* working_file,
|
||||
if (ls_skipped)
|
||||
out.params.inactiveRegions.push_back(*ls_skipped);
|
||||
}
|
||||
IpcManager::instance()->SendOutMessageToClient(
|
||||
IpcId::CqueryPublishInactiveRegions, out);
|
||||
IpcManager::WriteStdout(IpcId::CqueryPublishInactiveRegions, out);
|
||||
}
|
||||
|
||||
// Caches symbols for a single file for semantic highlighting to provide
|
||||
@ -276,8 +275,7 @@ void EmitSemanticHighlighting(QueryDatabase* db,
|
||||
out.params.uri = lsDocumentUri::FromPath(working_file->filename);
|
||||
for (auto& entry : grouped_symbols)
|
||||
out.params.symbols.push_back(entry.second);
|
||||
IpcManager::instance()->SendOutMessageToClient(
|
||||
IpcId::CqueryPublishSemanticHighlighting, out);
|
||||
IpcManager::WriteStdout(IpcId::CqueryPublishSemanticHighlighting, out);
|
||||
}
|
||||
|
||||
optional<int> FindIncludeLine(const std::vector<std::string>& lines,
|
||||
@ -522,11 +520,10 @@ void EmitDiagnostics(WorkingFiles* working_files,
|
||||
std::string path,
|
||||
NonElidedVector<lsDiagnostic> diagnostics) {
|
||||
// Emit diagnostics.
|
||||
Out_TextDocumentPublishDiagnostics diagnostic_response;
|
||||
diagnostic_response.params.uri = lsDocumentUri::FromPath(path);
|
||||
diagnostic_response.params.diagnostics = diagnostics;
|
||||
IpcManager::instance()->SendOutMessageToClient(
|
||||
IpcId::TextDocumentPublishDiagnostics, diagnostic_response);
|
||||
Out_TextDocumentPublishDiagnostics out;
|
||||
out.params.uri = lsDocumentUri::FromPath(path);
|
||||
out.params.diagnostics = diagnostics;
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out);
|
||||
|
||||
// Cache diagnostics so we can show fixits.
|
||||
working_files->DoActionOnFile(path, [&](WorkingFile* working_file) {
|
||||
@ -879,7 +876,7 @@ void EmitProgress(Config* config, QueueManager* queue) {
|
||||
out.params.onIdMappedCount = queue->on_id_mapped.Size();
|
||||
out.params.onIndexedCount = queue->on_indexed.Size();
|
||||
|
||||
IpcManager::instance()->SendOutMessageToClient(IpcId::Cout, out);
|
||||
IpcManager::WriteStdout(IpcId::Cout, out);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1486,7 +1483,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
bool did_work = false;
|
||||
|
||||
std::vector<std::unique_ptr<BaseIpcMessage>> messages =
|
||||
ipc->GetMessages(IpcManager::Destination::Server);
|
||||
ipc->for_querydb.DequeueAll();
|
||||
for (auto& message : messages) {
|
||||
did_work = true;
|
||||
|
||||
@ -1560,67 +1557,63 @@ bool QueryDbMainLoop(Config* config,
|
||||
// TODO: query request->params.capabilities.textDocument and support
|
||||
// only things the client supports.
|
||||
|
||||
auto response = Out_InitializeResponse();
|
||||
response.id = request->id;
|
||||
Out_InitializeResponse out;
|
||||
out.id = request->id;
|
||||
|
||||
// response.result.capabilities.textDocumentSync =
|
||||
// out.result.capabilities.textDocumentSync =
|
||||
// lsTextDocumentSyncOptions();
|
||||
// response.result.capabilities.textDocumentSync->openClose = true;
|
||||
// response.result.capabilities.textDocumentSync->change =
|
||||
// out.result.capabilities.textDocumentSync->openClose = true;
|
||||
// out.result.capabilities.textDocumentSync->change =
|
||||
// lsTextDocumentSyncKind::Full;
|
||||
// response.result.capabilities.textDocumentSync->willSave = true;
|
||||
// response.result.capabilities.textDocumentSync->willSaveWaitUntil =
|
||||
// out.result.capabilities.textDocumentSync->willSave = true;
|
||||
// out.result.capabilities.textDocumentSync->willSaveWaitUntil =
|
||||
// true;
|
||||
response.result.capabilities.textDocumentSync =
|
||||
out.result.capabilities.textDocumentSync =
|
||||
lsTextDocumentSyncKind::Incremental;
|
||||
|
||||
response.result.capabilities.renameProvider = true;
|
||||
out.result.capabilities.renameProvider = true;
|
||||
|
||||
response.result.capabilities.completionProvider =
|
||||
lsCompletionOptions();
|
||||
response.result.capabilities.completionProvider->resolveProvider =
|
||||
false;
|
||||
out.result.capabilities.completionProvider = lsCompletionOptions();
|
||||
out.result.capabilities.completionProvider->resolveProvider = false;
|
||||
// vscode doesn't support trigger character sequences, so we use ':'
|
||||
// for
|
||||
// '::' and '>' for '->'. See
|
||||
// https://github.com/Microsoft/language-server-protocol/issues/138.
|
||||
response.result.capabilities.completionProvider->triggerCharacters = {
|
||||
out.result.capabilities.completionProvider->triggerCharacters = {
|
||||
".", ":", ">", "#"};
|
||||
|
||||
response.result.capabilities.signatureHelpProvider =
|
||||
out.result.capabilities.signatureHelpProvider =
|
||||
lsSignatureHelpOptions();
|
||||
// NOTE: If updating signature help tokens make sure to also update
|
||||
// WorkingFile::FindClosestCallNameInBuffer.
|
||||
response.result.capabilities.signatureHelpProvider
|
||||
->triggerCharacters = {"(", ","};
|
||||
out.result.capabilities.signatureHelpProvider->triggerCharacters = {
|
||||
"(", ","};
|
||||
|
||||
response.result.capabilities.codeLensProvider = lsCodeLensOptions();
|
||||
response.result.capabilities.codeLensProvider->resolveProvider =
|
||||
false;
|
||||
out.result.capabilities.codeLensProvider = lsCodeLensOptions();
|
||||
out.result.capabilities.codeLensProvider->resolveProvider = false;
|
||||
|
||||
response.result.capabilities.definitionProvider = true;
|
||||
response.result.capabilities.documentHighlightProvider = true;
|
||||
response.result.capabilities.hoverProvider = true;
|
||||
response.result.capabilities.referencesProvider = true;
|
||||
out.result.capabilities.definitionProvider = true;
|
||||
out.result.capabilities.documentHighlightProvider = true;
|
||||
out.result.capabilities.hoverProvider = true;
|
||||
out.result.capabilities.referencesProvider = true;
|
||||
|
||||
response.result.capabilities.codeActionProvider = true;
|
||||
out.result.capabilities.codeActionProvider = true;
|
||||
|
||||
response.result.capabilities.documentSymbolProvider = true;
|
||||
response.result.capabilities.workspaceSymbolProvider = true;
|
||||
out.result.capabilities.documentSymbolProvider = true;
|
||||
out.result.capabilities.workspaceSymbolProvider = true;
|
||||
|
||||
response.result.capabilities.documentLinkProvider =
|
||||
out.result.capabilities.documentLinkProvider =
|
||||
lsDocumentLinkOptions();
|
||||
response.result.capabilities.documentLinkProvider->resolveProvider =
|
||||
false;
|
||||
out.result.capabilities.documentLinkProvider->resolveProvider = false;
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::Initialize, response);
|
||||
IpcManager::WriteStdout(IpcId::Initialize, out);
|
||||
|
||||
// Set project root.
|
||||
config->projectRoot =
|
||||
NormalizePath(request->params.rootUri->GetPath());
|
||||
EnsureEndsInSlash(config->projectRoot);
|
||||
MakeDirectoryRecursive(
|
||||
config->cacheDirectory + EscapeFileName(config->projectRoot));
|
||||
MakeDirectoryRecursive(config->cacheDirectory +
|
||||
EscapeFileName(config->projectRoot));
|
||||
|
||||
// Start indexer threads.
|
||||
if (config->indexerCount == 0) {
|
||||
@ -1725,24 +1718,24 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_CqueryTypeHierarchyTree response;
|
||||
response.id = msg->id;
|
||||
Out_CqueryTypeHierarchyTree out;
|
||||
out.id = msg->id;
|
||||
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
if (ref.idx.kind == SymbolKind::Type) {
|
||||
response.result = BuildInheritanceHierarchyForType(
|
||||
out.result = BuildInheritanceHierarchyForType(
|
||||
db, working_files, QueryTypeId(ref.idx.idx));
|
||||
break;
|
||||
}
|
||||
if (ref.idx.kind == SymbolKind::Func) {
|
||||
response.result = BuildInheritanceHierarchyForFunc(
|
||||
out.result = BuildInheritanceHierarchyForFunc(
|
||||
db, working_files, QueryFuncId(ref.idx.idx));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::CqueryTypeHierarchyTree, response);
|
||||
IpcManager::WriteStdout(IpcId::CqueryTypeHierarchyTree, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1757,34 +1750,33 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_CqueryCallTree response;
|
||||
response.id = msg->id;
|
||||
Out_CqueryCallTree out;
|
||||
out.id = msg->id;
|
||||
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
if (ref.idx.kind == SymbolKind::Func) {
|
||||
response.result = BuildInitialCallTree(db, working_files,
|
||||
QueryFuncId(ref.idx.idx));
|
||||
out.result = BuildInitialCallTree(db, working_files,
|
||||
QueryFuncId(ref.idx.idx));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::CqueryCallTreeInitial, response);
|
||||
IpcManager::WriteStdout(IpcId::CqueryCallTreeInitial, out);
|
||||
break;
|
||||
}
|
||||
|
||||
case IpcId::CqueryCallTreeExpand: {
|
||||
auto msg = message->As<Ipc_CqueryCallTreeExpand>();
|
||||
|
||||
Out_CqueryCallTree response;
|
||||
response.id = msg->id;
|
||||
Out_CqueryCallTree out;
|
||||
out.id = msg->id;
|
||||
|
||||
auto func_id = db->usr_to_func.find(msg->params.usr);
|
||||
if (func_id != db->usr_to_func.end())
|
||||
response.result =
|
||||
BuildExpandCallTree(db, working_files, func_id->second);
|
||||
out.result = BuildExpandCallTree(db, working_files, func_id->second);
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::CqueryCallTreeExpand, response);
|
||||
IpcManager::WriteStdout(IpcId::CqueryCallTreeExpand, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1799,18 +1791,18 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_LocationList response;
|
||||
response.id = msg->id;
|
||||
Out_LocationList out;
|
||||
out.id = msg->id;
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
if (ref.idx.kind == SymbolKind::Type) {
|
||||
QueryType& type = db->types[ref.idx.idx];
|
||||
std::vector<QueryLocation> locations =
|
||||
ToQueryLocation(db, type.instances);
|
||||
response.result = GetLsLocations(db, working_files, locations);
|
||||
out.result = GetLsLocations(db, working_files, locations);
|
||||
}
|
||||
}
|
||||
ipc->SendOutMessageToClient(IpcId::CqueryVars, response);
|
||||
IpcManager::WriteStdout(IpcId::CqueryVars, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1825,8 +1817,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_LocationList response;
|
||||
response.id = msg->id;
|
||||
Out_LocationList out;
|
||||
out.id = msg->id;
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
if (ref.idx.kind == SymbolKind::Func) {
|
||||
@ -1840,10 +1832,10 @@ bool QueryDbMainLoop(Config* config,
|
||||
GetCallersForAllDerivedFunctions(db, func))
|
||||
locations.push_back(func_ref.loc);
|
||||
|
||||
response.result = GetLsLocations(db, working_files, locations);
|
||||
out.result = GetLsLocations(db, working_files, locations);
|
||||
}
|
||||
}
|
||||
ipc->SendOutMessageToClient(IpcId::CqueryCallers, response);
|
||||
IpcManager::WriteStdout(IpcId::CqueryCallers, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1858,8 +1850,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_LocationList response;
|
||||
response.id = msg->id;
|
||||
Out_LocationList out;
|
||||
out.id = msg->id;
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
if (ref.idx.kind == SymbolKind::Type) {
|
||||
@ -1868,7 +1860,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
continue;
|
||||
std::vector<QueryLocation> locations =
|
||||
ToQueryLocation(db, type.def->parents);
|
||||
response.result = GetLsLocations(db, working_files, locations);
|
||||
out.result = GetLsLocations(db, working_files, locations);
|
||||
} else if (ref.idx.kind == SymbolKind::Func) {
|
||||
QueryFunc& func = db->funcs[ref.idx.idx];
|
||||
optional<QueryLocation> location =
|
||||
@ -1879,10 +1871,10 @@ bool QueryDbMainLoop(Config* config,
|
||||
GetLsLocation(db, working_files, *location);
|
||||
if (!ls_loc)
|
||||
continue;
|
||||
response.result.push_back(*ls_loc);
|
||||
out.result.push_back(*ls_loc);
|
||||
}
|
||||
}
|
||||
ipc->SendOutMessageToClient(IpcId::CqueryBase, response);
|
||||
IpcManager::WriteStdout(IpcId::CqueryBase, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1897,23 +1889,23 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_LocationList response;
|
||||
response.id = msg->id;
|
||||
Out_LocationList out;
|
||||
out.id = msg->id;
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
if (ref.idx.kind == SymbolKind::Type) {
|
||||
QueryType& type = db->types[ref.idx.idx];
|
||||
std::vector<QueryLocation> locations =
|
||||
ToQueryLocation(db, type.derived);
|
||||
response.result = GetLsLocations(db, working_files, locations);
|
||||
out.result = GetLsLocations(db, working_files, locations);
|
||||
} else if (ref.idx.kind == SymbolKind::Func) {
|
||||
QueryFunc& func = db->funcs[ref.idx.idx];
|
||||
std::vector<QueryLocation> locations =
|
||||
ToQueryLocation(db, func.derived);
|
||||
response.result = GetLsLocations(db, working_files, locations);
|
||||
out.result = GetLsLocations(db, working_files, locations);
|
||||
}
|
||||
}
|
||||
ipc->SendOutMessageToClient(IpcId::CqueryDerived, response);
|
||||
IpcManager::WriteStdout(IpcId::CqueryDerived, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1989,10 +1981,9 @@ bool QueryDbMainLoop(Config* config,
|
||||
std::string path = msg->params.textDocument.uri.GetPath();
|
||||
|
||||
// Clear any diagnostics for the file.
|
||||
Out_TextDocumentPublishDiagnostics diag;
|
||||
diag.params.uri = msg->params.textDocument.uri;
|
||||
IpcManager::instance()->SendOutMessageToClient(
|
||||
IpcId::TextDocumentPublishDiagnostics, diag);
|
||||
Out_TextDocumentPublishDiagnostics out;
|
||||
out.params.uri = msg->params.textDocument.uri;
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out);
|
||||
|
||||
// Remove internal state.
|
||||
working_files->OnClose(msg->params);
|
||||
@ -2039,19 +2030,19 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_TextDocumentRename response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentRename out;
|
||||
out.id = msg->id;
|
||||
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
// Found symbol. Return references to rename.
|
||||
std::vector<QueryLocation> uses = GetUsesOfSymbol(db, ref.idx);
|
||||
response.result =
|
||||
out.result =
|
||||
BuildWorkspaceEdit(db, working_files, uses, msg->params.newName);
|
||||
break;
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentRename, response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentRename, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2070,22 +2061,21 @@ bool QueryDbMainLoop(Config* config,
|
||||
buffer_line = file->all_buffer_lines[msg->params.position.line];
|
||||
|
||||
if (ShouldRunIncludeCompletion(buffer_line)) {
|
||||
Out_TextDocumentComplete complete_response;
|
||||
complete_response.id = msg->id;
|
||||
Out_TextDocumentComplete out;
|
||||
out.id = msg->id;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(
|
||||
include_complete->completion_items_mutex, std::defer_lock);
|
||||
if (include_complete->is_scanning)
|
||||
lock.lock();
|
||||
complete_response.result.items.assign(
|
||||
include_complete->completion_items.begin(),
|
||||
include_complete->completion_items.end());
|
||||
out.result.items.assign(include_complete->completion_items.begin(),
|
||||
include_complete->completion_items.end());
|
||||
if (lock)
|
||||
lock.unlock();
|
||||
|
||||
// Update textEdit params.
|
||||
for (lsCompletionItem& item : complete_response.result.items) {
|
||||
for (lsCompletionItem& item : out.result.items) {
|
||||
item.textEdit->range.start.line = msg->params.position.line;
|
||||
item.textEdit->range.start.character = 0;
|
||||
item.textEdit->range.end.line = msg->params.position.line;
|
||||
@ -2093,9 +2083,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
}
|
||||
}
|
||||
|
||||
FilterCompletionResponse(&complete_response, buffer_line);
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentCompletion,
|
||||
complete_response);
|
||||
FilterCompletionResponse(&out, buffer_line);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentCompletion, out);
|
||||
} else {
|
||||
bool is_global_completion = false;
|
||||
std::string existing_completion;
|
||||
@ -2110,15 +2099,13 @@ bool QueryDbMainLoop(Config* config,
|
||||
is_global_completion, existing_completion,
|
||||
msg](const NonElidedVector<lsCompletionItem>& results,
|
||||
bool is_cached_result) {
|
||||
Out_TextDocumentComplete complete_response;
|
||||
complete_response.id = msg->id;
|
||||
complete_response.result.items = results;
|
||||
Out_TextDocumentComplete out;
|
||||
out.id = msg->id;
|
||||
out.result.items = results;
|
||||
|
||||
// Emit completion results.
|
||||
FilterCompletionResponse(&complete_response,
|
||||
existing_completion);
|
||||
IpcManager::instance()->SendOutMessageToClient(
|
||||
IpcId::TextDocumentCompletion, complete_response);
|
||||
FilterCompletionResponse(&out, existing_completion);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentCompletion, out);
|
||||
|
||||
// Cache completion results.
|
||||
if (!is_cached_result) {
|
||||
@ -2203,8 +2190,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
auto msg = message->As<Ipc_TextDocumentSignatureHelp>();
|
||||
auto ipc = IpcManager::instance();
|
||||
|
||||
Out_TextDocumentSignatureHelp response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentSignatureHelp out;
|
||||
out.id = msg->id;
|
||||
|
||||
for (auto& result : results) {
|
||||
if (result.label != search)
|
||||
@ -2217,25 +2204,24 @@ bool QueryDbMainLoop(Config* config,
|
||||
ls_param.label = parameter;
|
||||
signature.parameters.push_back(ls_param);
|
||||
}
|
||||
response.result.signatures.push_back(signature);
|
||||
out.result.signatures.push_back(signature);
|
||||
}
|
||||
|
||||
// Guess the signature the user wants based on available parameter
|
||||
// count.
|
||||
response.result.activeSignature = 0;
|
||||
for (size_t i = 0; i < response.result.signatures.size(); ++i) {
|
||||
if (active_param < response.result.signatures.size()) {
|
||||
response.result.activeSignature = (int)i;
|
||||
out.result.activeSignature = 0;
|
||||
for (size_t i = 0; i < out.result.signatures.size(); ++i) {
|
||||
if (active_param < out.result.signatures.size()) {
|
||||
out.result.activeSignature = (int)i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Set signature to what we parsed from the working file.
|
||||
response.result.activeParameter = active_param;
|
||||
out.result.activeParameter = active_param;
|
||||
|
||||
Timer timer;
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentSignatureHelp,
|
||||
response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentSignatureHelp, out);
|
||||
|
||||
if (!is_cached_result) {
|
||||
signature_cache->WithLock([&]() {
|
||||
@ -2276,8 +2262,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_TextDocumentDefinition response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentDefinition out;
|
||||
out.id = msg->id;
|
||||
|
||||
int target_line = msg->params.position.line + 1;
|
||||
int target_column = msg->params.position.character + 1;
|
||||
@ -2317,36 +2303,35 @@ bool QueryDbMainLoop(Config* config,
|
||||
optional<lsLocation> ls_declaration =
|
||||
GetLsLocation(db, working_files, declaration);
|
||||
if (ls_declaration)
|
||||
response.result.push_back(*ls_declaration);
|
||||
out.result.push_back(*ls_declaration);
|
||||
}
|
||||
// We found some declarations. Break so we don't add the definition
|
||||
// location.
|
||||
if (!response.result.empty())
|
||||
if (!out.result.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
if (def_loc) {
|
||||
PushBack(&response.result,
|
||||
GetLsLocation(db, working_files, *def_loc));
|
||||
PushBack(&out.result, GetLsLocation(db, working_files, *def_loc));
|
||||
}
|
||||
|
||||
if (!response.result.empty())
|
||||
if (!out.result.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
// No symbols - check for includes.
|
||||
if (response.result.empty()) {
|
||||
if (out.result.empty()) {
|
||||
for (const IndexInclude& include : file->def->includes) {
|
||||
if (include.line == target_line) {
|
||||
lsLocation result;
|
||||
result.uri = lsDocumentUri::FromPath(include.resolved_path);
|
||||
response.result.push_back(result);
|
||||
out.result.push_back(result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentDefinition, response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentDefinition, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2362,14 +2347,14 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_TextDocumentDocumentHighlight response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentDocumentHighlight out;
|
||||
out.id = msg->id;
|
||||
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
// Found symbol. Return references to highlight.
|
||||
std::vector<QueryLocation> uses = GetUsesOfSymbol(db, ref.idx);
|
||||
response.result.reserve(uses.size());
|
||||
out.result.reserve(uses.size());
|
||||
for (const QueryLocation& use : uses) {
|
||||
if (use.path != file_id)
|
||||
continue;
|
||||
@ -2382,13 +2367,12 @@ bool QueryDbMainLoop(Config* config,
|
||||
lsDocumentHighlight highlight;
|
||||
highlight.kind = lsDocumentHighlightKind::Text;
|
||||
highlight.range = ls_location->range;
|
||||
response.result.push_back(highlight);
|
||||
out.result.push_back(highlight);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentDocumentHighlight,
|
||||
response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentDocumentHighlight, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2403,8 +2387,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_TextDocumentHover response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentHover out;
|
||||
out.id = msg->id;
|
||||
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
@ -2414,14 +2398,14 @@ bool QueryDbMainLoop(Config* config,
|
||||
if (!ls_range)
|
||||
continue;
|
||||
|
||||
response.result.contents.value = GetHoverForSymbol(db, ref.idx);
|
||||
response.result.contents.language = file->def->language;
|
||||
out.result.contents.value = GetHoverForSymbol(db, ref.idx);
|
||||
out.result.contents.language = file->def->language;
|
||||
|
||||
response.result.range = *ls_range;
|
||||
out.result.range = *ls_range;
|
||||
break;
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentHover, response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentHover, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2436,8 +2420,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
WorkingFile* working_file =
|
||||
working_files->GetFileByFilename(file->def->path);
|
||||
|
||||
Out_TextDocumentReferences response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentReferences out;
|
||||
out.id = msg->id;
|
||||
|
||||
for (const SymbolRef& ref :
|
||||
FindSymbolsAtLocation(working_file, file, msg->params.position)) {
|
||||
@ -2449,7 +2433,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
|
||||
// Found symbol. Return references.
|
||||
std::vector<QueryLocation> uses = GetUsesOfSymbol(db, ref.idx);
|
||||
response.result.reserve(uses.size());
|
||||
out.result.reserve(uses.size());
|
||||
for (const QueryLocation& use : uses) {
|
||||
if (excluded_declaration.has_value() &&
|
||||
use == *excluded_declaration)
|
||||
@ -2458,20 +2442,20 @@ bool QueryDbMainLoop(Config* config,
|
||||
optional<lsLocation> ls_location =
|
||||
GetLsLocation(db, working_files, use);
|
||||
if (ls_location)
|
||||
response.result.push_back(*ls_location);
|
||||
out.result.push_back(*ls_location);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentReferences, response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentReferences, out);
|
||||
break;
|
||||
}
|
||||
|
||||
case IpcId::TextDocumentDocumentSymbol: {
|
||||
auto msg = message->As<Ipc_TextDocumentDocumentSymbol>();
|
||||
|
||||
Out_TextDocumentDocumentSymbol response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentDocumentSymbol out;
|
||||
out.id = msg->id;
|
||||
|
||||
QueryFile* file;
|
||||
if (!FindFileOrFail(db, msg->id, msg->params.textDocument.uri.GetPath(),
|
||||
@ -2489,19 +2473,18 @@ bool QueryDbMainLoop(Config* config,
|
||||
if (!location)
|
||||
continue;
|
||||
info->location = *location;
|
||||
response.result.push_back(*info);
|
||||
out.result.push_back(*info);
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentDocumentSymbol,
|
||||
response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentDocumentSymbol, out);
|
||||
break;
|
||||
}
|
||||
|
||||
case IpcId::TextDocumentDocumentLink: {
|
||||
auto msg = message->As<Ipc_TextDocumentDocumentLink>();
|
||||
|
||||
Out_TextDocumentDocumentLink response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentDocumentLink out;
|
||||
out.id = msg->id;
|
||||
|
||||
if (config->showDocumentLinksOnIncludes) {
|
||||
QueryFile* file;
|
||||
@ -2534,11 +2517,11 @@ bool QueryDbMainLoop(Config* config,
|
||||
lsDocumentLink link;
|
||||
link.target = lsDocumentUri::FromPath(include.resolved_path);
|
||||
link.range = *between_quotes;
|
||||
response.result.push_back(link);
|
||||
out.result.push_back(link);
|
||||
}
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentDocumentLink, response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentDocumentLink, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2570,8 +2553,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
break;
|
||||
}
|
||||
|
||||
Out_TextDocumentCodeAction response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentCodeAction out;
|
||||
out.id = msg->id;
|
||||
|
||||
// TODO: auto-insert namespace?
|
||||
|
||||
@ -2637,7 +2620,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
command.title = "Auto-Implement " + std::to_string(num_edits) +
|
||||
" methods on " + type.def->short_name;
|
||||
command.command = "cquery._autoImplement";
|
||||
response.result.push_back(command);
|
||||
out.result.push_back(command);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2665,7 +2648,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
if (edit->range.start.line >= default_line)
|
||||
edit->newText.insert(0, "\n");
|
||||
command.arguments.edits.push_back(*edit);
|
||||
response.result.push_back(command);
|
||||
out.result.push_back(command);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -2673,7 +2656,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
}
|
||||
|
||||
// Only show one auto-impl section.
|
||||
if (!response.result.empty())
|
||||
if (!out.result.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
@ -2762,7 +2745,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
" includes to insert";
|
||||
command.command = "cquery._insertInclude";
|
||||
command.arguments.textDocumentUri = msg->params.textDocument.uri;
|
||||
response.result.push_back(command);
|
||||
out.result.push_back(command);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2775,19 +2758,19 @@ bool QueryDbMainLoop(Config* config,
|
||||
command.command = "cquery._applyFixIt";
|
||||
command.arguments.textDocumentUri = msg->params.textDocument.uri;
|
||||
command.arguments.edits = diag.fixits_;
|
||||
response.result.push_back(command);
|
||||
out.result.push_back(command);
|
||||
}
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentCodeAction, response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentCodeAction, out);
|
||||
break;
|
||||
}
|
||||
|
||||
case IpcId::TextDocumentCodeLens: {
|
||||
auto msg = message->As<Ipc_TextDocumentCodeLens>();
|
||||
|
||||
Out_TextDocumentCodeLens response;
|
||||
response.id = msg->id;
|
||||
Out_TextDocumentCodeLens out;
|
||||
out.id = msg->id;
|
||||
|
||||
lsDocumentUri file_as_uri = msg->params.textDocument.uri;
|
||||
std::string path = file_as_uri.GetPath();
|
||||
@ -2800,7 +2783,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
break;
|
||||
|
||||
CommonCodeLensParams common;
|
||||
common.result = &response.result;
|
||||
common.result = &out.result;
|
||||
common.db = db;
|
||||
common.working_files = working_files;
|
||||
common.working_file = working_files->GetFileByFilename(file->def->path);
|
||||
@ -2884,7 +2867,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
code_lens.command->arguments.uri = ls_base->uri;
|
||||
code_lens.command->arguments.position =
|
||||
ls_base->range.start;
|
||||
response.result.push_back(code_lens);
|
||||
out.result.push_back(code_lens);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2918,7 +2901,7 @@ bool QueryDbMainLoop(Config* config,
|
||||
};
|
||||
}
|
||||
|
||||
ipc->SendOutMessageToClient(IpcId::TextDocumentCodeLens, response);
|
||||
IpcManager::WriteStdout(IpcId::TextDocumentCodeLens, out);
|
||||
|
||||
break;
|
||||
}
|
||||
@ -2929,8 +2912,8 @@ bool QueryDbMainLoop(Config* config,
|
||||
// inspiration
|
||||
auto msg = message->As<Ipc_WorkspaceSymbol>();
|
||||
|
||||
Out_WorkspaceSymbol response;
|
||||
response.id = msg->id;
|
||||
Out_WorkspaceSymbol out;
|
||||
out.id = msg->id;
|
||||
|
||||
LOG_S(INFO) << "[querydb] Considering " << db->detailed_names.size()
|
||||
<< " candidates for query " << msg->params.query;
|
||||
@ -2947,13 +2930,13 @@ bool QueryDbMainLoop(Config* config,
|
||||
continue;
|
||||
|
||||
InsertSymbolIntoResult(db, working_files, db->symbols[i],
|
||||
&response.result);
|
||||
if (response.result.size() >= config->maxWorkspaceSearchResults)
|
||||
&out.result);
|
||||
if (out.result.size() >= config->maxWorkspaceSearchResults)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (response.result.size() < config->maxWorkspaceSearchResults) {
|
||||
if (out.result.size() < config->maxWorkspaceSearchResults) {
|
||||
for (int i = 0; i < db->detailed_names.size(); ++i) {
|
||||
if (SubstringMatch(query, db->detailed_names[i])) {
|
||||
// Do not show the same entry twice.
|
||||
@ -2961,16 +2944,16 @@ bool QueryDbMainLoop(Config* config,
|
||||
continue;
|
||||
|
||||
InsertSymbolIntoResult(db, working_files, db->symbols[i],
|
||||
&response.result);
|
||||
if (response.result.size() >= config->maxWorkspaceSearchResults)
|
||||
&out.result);
|
||||
if (out.result.size() >= config->maxWorkspaceSearchResults)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_S(INFO) << "[querydb] Found " << response.result.size()
|
||||
LOG_S(INFO) << "[querydb] Found " << out.result.size()
|
||||
<< " results for query " << query;
|
||||
ipc->SendOutMessageToClient(IpcId::WorkspaceSymbol, response);
|
||||
IpcManager::WriteStdout(IpcId::WorkspaceSymbol, out);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -3076,8 +3059,8 @@ void RunQueryDbThread(const std::string& bin_name,
|
||||
FreeUnusedMemory();
|
||||
|
||||
if (!did_work) {
|
||||
waiter->Wait({IpcManager::instance()->threaded_queue_for_server_.get(),
|
||||
&queue->do_id_map, &queue->on_indexed});
|
||||
waiter->Wait({&IpcManager::instance()->for_querydb, &queue->do_id_map,
|
||||
&queue->on_indexed});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3142,7 +3125,7 @@ void LaunchStdinLoop(Config* config,
|
||||
// loop to exit the thread. If we keep parsing input stdin is likely
|
||||
// closed so cquery will exit.
|
||||
LOG_S(INFO) << "cquery will exit when all threads are idle";
|
||||
ipc->SendMessage(IpcManager::Destination::Server, std::move(message));
|
||||
ipc->for_querydb.Enqueue(std::move(message));
|
||||
return WorkThread::Result::ExitThread;
|
||||
}
|
||||
|
||||
@ -3174,7 +3157,7 @@ void LaunchStdinLoop(Config* config,
|
||||
case IpcId::CqueryDerived:
|
||||
case IpcId::CqueryIndexFile:
|
||||
case IpcId::CqueryQueryDbWaitForIdleIndexer: {
|
||||
ipc->SendMessage(IpcManager::Destination::Server, std::move(message));
|
||||
ipc->for_querydb.Enqueue(std::move(message));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -3195,42 +3178,32 @@ void LaunchStdoutThread(std::unordered_map<IpcId, Timer>* request_times,
|
||||
WorkThread::StartThread("stdout", [=]() {
|
||||
IpcManager* ipc = IpcManager::instance();
|
||||
|
||||
std::vector<std::unique_ptr<BaseIpcMessage>> messages =
|
||||
ipc->GetMessages(IpcManager::Destination::Client);
|
||||
std::vector<IpcManager::StdoutMessage> messages =
|
||||
ipc->for_stdout.DequeueAll();
|
||||
if (messages.empty()) {
|
||||
waiter->Wait({ipc->threaded_queue_for_client_.get()});
|
||||
waiter->Wait({&ipc->for_stdout});
|
||||
return queue->HasWork() ? WorkThread::Result::MoreWork
|
||||
: WorkThread::Result::NoWork;
|
||||
}
|
||||
|
||||
for (auto& message : messages) {
|
||||
switch (message->method_id) {
|
||||
case IpcId::Cout: {
|
||||
auto msg = message->As<Ipc_Cout>();
|
||||
|
||||
if (ShouldDisplayIpcTiming(msg->original_ipc_id)) {
|
||||
Timer time = (*request_times)[msg->original_ipc_id];
|
||||
time.ResetAndPrint("[e2e] Running " + std::string(IpcIdToString(
|
||||
msg->original_ipc_id)));
|
||||
}
|
||||
|
||||
if (g_log_stdin_stdout_to_stderr) {
|
||||
std::string printed = "[COUT] |" + msg->content + "|\n";
|
||||
std::cerr << printed;
|
||||
std::cerr.flush();
|
||||
}
|
||||
|
||||
std::cout << msg->content;
|
||||
std::cout.flush();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
LOG_S(FATAL) << "Exiting; unhandled IPC message "
|
||||
<< IpcIdToString(message->method_id);
|
||||
exit(1);
|
||||
}
|
||||
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) {
|
||||
std::ostringstream sstream;
|
||||
sstream << "[COUT] |";
|
||||
sstream << message.content;
|
||||
sstream << "|\n";
|
||||
std::cerr << sstream.str();
|
||||
std::cerr.flush();
|
||||
}
|
||||
|
||||
std::cout << message.content;
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
return WorkThread::Result::MoreWork;
|
||||
|
@ -1,5 +1,9 @@
|
||||
#include "ipc_manager.h"
|
||||
|
||||
#include "language_server_api.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
IpcManager* IpcManager::instance_ = nullptr;
|
||||
|
||||
// static
|
||||
@ -12,35 +16,16 @@ void IpcManager::CreateInstance(MultiQueueWaiter* waiter) {
|
||||
instance_ = new IpcManager(waiter);
|
||||
}
|
||||
|
||||
ThreadedQueue<std::unique_ptr<BaseIpcMessage>>* IpcManager::GetThreadedQueue(
|
||||
Destination destination) {
|
||||
return destination == Destination::Client ? threaded_queue_for_client_.get()
|
||||
: threaded_queue_for_server_.get();
|
||||
}
|
||||
|
||||
void IpcManager::SendOutMessageToClient(IpcId id, lsBaseOutMessage& response) {
|
||||
// static
|
||||
void IpcManager::WriteStdout(IpcId id, lsBaseOutMessage& response) {
|
||||
std::ostringstream sstream;
|
||||
response.Write(sstream);
|
||||
|
||||
auto out = MakeUnique<Ipc_Cout>();
|
||||
out->content = sstream.str();
|
||||
out->original_ipc_id = id;
|
||||
GetThreadedQueue(Destination::Client)->Enqueue(std::move(out));
|
||||
StdoutMessage out;
|
||||
out.content = sstream.str();
|
||||
out.id = id;
|
||||
instance()->for_stdout.Enqueue(std::move(out));
|
||||
}
|
||||
|
||||
void IpcManager::SendMessage(Destination destination,
|
||||
std::unique_ptr<BaseIpcMessage> message) {
|
||||
GetThreadedQueue(destination)->Enqueue(std::move(message));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<BaseIpcMessage>> IpcManager::GetMessages(
|
||||
Destination destination) {
|
||||
return GetThreadedQueue(destination)->DequeueAll();
|
||||
}
|
||||
|
||||
IpcManager::IpcManager(MultiQueueWaiter* waiter) {
|
||||
threaded_queue_for_client_ =
|
||||
MakeUnique<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>(waiter);
|
||||
threaded_queue_for_server_ =
|
||||
MakeUnique<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>(waiter);
|
||||
}
|
||||
IpcManager::IpcManager(MultiQueueWaiter* waiter)
|
||||
: for_stdout(waiter), for_querydb(waiter) {}
|
@ -1,34 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "ipc.h"
|
||||
#include "language_server_api.h"
|
||||
#include "threaded_queue.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class lsBaseOutMessage;
|
||||
|
||||
struct IpcManager {
|
||||
static IpcManager* instance_;
|
||||
struct StdoutMessage {
|
||||
IpcId id;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
ThreadedQueue<StdoutMessage> for_stdout;
|
||||
ThreadedQueue<std::unique_ptr<BaseIpcMessage>> for_querydb;
|
||||
|
||||
static IpcManager* instance();
|
||||
static void CreateInstance(MultiQueueWaiter* waiter);
|
||||
|
||||
std::unique_ptr<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>
|
||||
threaded_queue_for_client_;
|
||||
std::unique_ptr<ThreadedQueue<std::unique_ptr<BaseIpcMessage>>>
|
||||
threaded_queue_for_server_;
|
||||
|
||||
enum class Destination { Client, Server };
|
||||
|
||||
ThreadedQueue<std::unique_ptr<BaseIpcMessage>>* GetThreadedQueue(
|
||||
Destination destination);
|
||||
|
||||
void SendOutMessageToClient(IpcId id, lsBaseOutMessage& response);
|
||||
|
||||
void SendMessage(Destination destination,
|
||||
std::unique_ptr<BaseIpcMessage> message);
|
||||
|
||||
std::vector<std::unique_ptr<BaseIpcMessage>> GetMessages(
|
||||
Destination destination);
|
||||
static void WriteStdout(IpcId id, lsBaseOutMessage& response);
|
||||
|
||||
private:
|
||||
IpcManager(MultiQueueWaiter* waiter);
|
||||
explicit IpcManager(MultiQueueWaiter* waiter);
|
||||
|
||||
static IpcManager* instance_;
|
||||
};
|
||||
|
@ -33,7 +33,7 @@ optional<Matcher> Matcher::Create(const std::string& search) {
|
||||
out.params.type = lsMessageType::Error;
|
||||
out.params.message = "cquery: Parsing EMCAScript regex \"" + search +
|
||||
"\" failed; " + e.what();
|
||||
IpcManager::instance()->SendOutMessageToClient(IpcId::Cout, out);
|
||||
IpcManager::WriteStdout(IpcId::Cout, out);
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user