mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-22 15:45:08 +00:00
Add Config->largeFileSize; pure virtual or defaulted methods are definitions; fix $ccls/callHierarchy
This commit is contained in:
parent
f0559bba54
commit
806a05b234
@ -69,11 +69,15 @@ set(_Clang_REQUIRED_VARS Clang_LIBRARY Clang_INCLUDE_DIR Clang_EXECUTABLE
|
|||||||
LLVM_INCLUDE_DIR LLVM_BUILD_INCLUDE_DIR)
|
LLVM_INCLUDE_DIR LLVM_BUILD_INCLUDE_DIR)
|
||||||
|
|
||||||
_Clang_find_library(Clang_LIBRARY clang)
|
_Clang_find_library(Clang_LIBRARY clang)
|
||||||
|
_Clang_find_add_library(clangAST)
|
||||||
|
_Clang_find_add_library(clangLex)
|
||||||
_Clang_find_add_library(clangDriver)
|
_Clang_find_add_library(clangDriver)
|
||||||
_Clang_find_add_library(clangBasic)
|
_Clang_find_add_library(clangBasic)
|
||||||
if(USE_SHARED_LLVM)
|
if(USE_SHARED_LLVM)
|
||||||
_Clang_find_add_library(LLVM)
|
_Clang_find_add_library(LLVM)
|
||||||
else()
|
else()
|
||||||
|
_Clang_find_add_library(LLVMCore)
|
||||||
|
_Clang_find_add_library(LLVMBinaryFormat)
|
||||||
_Clang_find_add_library(LLVMOption)
|
_Clang_find_add_library(LLVMOption)
|
||||||
_Clang_find_add_library(LLVMSupport)
|
_Clang_find_add_library(LLVMSupport)
|
||||||
_Clang_find_add_library(LLVMDemangle)
|
_Clang_find_add_library(LLVMDemangle)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
std::unique_ptr<Config> g_config;
|
Config* g_config;
|
||||||
thread_local int g_thread_id;
|
thread_local int g_thread_id;
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -192,6 +191,9 @@ struct Config {
|
|||||||
std::vector<std::string> whitelist;
|
std::vector<std::string> whitelist;
|
||||||
} index;
|
} index;
|
||||||
|
|
||||||
|
// Disable semantic highlighting for files larger than the size.
|
||||||
|
int64_t largeFileSize = 2 * 1024 * 1024;
|
||||||
|
|
||||||
struct WorkspaceSymbol {
|
struct WorkspaceSymbol {
|
||||||
int caseSensitivity = 1;
|
int caseSensitivity = 1;
|
||||||
// Maximum workspace search results.
|
// Maximum workspace search results.
|
||||||
@ -252,8 +254,9 @@ MAKE_REFLECT_STRUCT(Config,
|
|||||||
diagnostics,
|
diagnostics,
|
||||||
highlight,
|
highlight,
|
||||||
index,
|
index,
|
||||||
|
largeFileSize,
|
||||||
workspaceSymbol,
|
workspaceSymbol,
|
||||||
xref);
|
xref);
|
||||||
|
|
||||||
extern std::unique_ptr<Config> g_config;
|
extern Config* g_config;
|
||||||
thread_local extern int g_thread_id;
|
thread_local extern int g_thread_id;
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
#include "type_printer.h"
|
#include "type_printer.h"
|
||||||
|
|
||||||
|
#include <clang/AST/AST.h>
|
||||||
#include <llvm/Support/Timer.h>
|
#include <llvm/Support/Timer.h>
|
||||||
|
using namespace clang;
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@ -151,26 +153,24 @@ lsSymbolKind GetSymbolKind(CXIdxEntityKind kind) {
|
|||||||
return lsSymbolKind::Unknown;
|
return lsSymbolKind::Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageClass GetStorageClass(CX_StorageClass storage) {
|
StorageClass GetStorageC(CX_StorageClass storage) {
|
||||||
switch (storage) {
|
switch (storage) {
|
||||||
|
default:
|
||||||
case CX_SC_Invalid:
|
case CX_SC_Invalid:
|
||||||
case CX_SC_OpenCLWorkGroupLocal:
|
case CX_SC_OpenCLWorkGroupLocal:
|
||||||
return StorageClass::Invalid;
|
|
||||||
case CX_SC_None:
|
case CX_SC_None:
|
||||||
return StorageClass::None;
|
return SC_None;
|
||||||
case CX_SC_Extern:
|
case CX_SC_Extern:
|
||||||
return StorageClass::Extern;
|
return SC_Extern;
|
||||||
case CX_SC_Static:
|
case CX_SC_Static:
|
||||||
return StorageClass::Static;
|
return SC_Static;
|
||||||
case CX_SC_PrivateExtern:
|
case CX_SC_PrivateExtern:
|
||||||
return StorageClass::PrivateExtern;
|
return SC_PrivateExtern;
|
||||||
case CX_SC_Auto:
|
case CX_SC_Auto:
|
||||||
return StorageClass::Auto;
|
return SC_Auto;
|
||||||
case CX_SC_Register:
|
case CX_SC_Register:
|
||||||
return StorageClass::Register;
|
return SC_Register;
|
||||||
}
|
}
|
||||||
|
|
||||||
return StorageClass::None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Caches all instances of constructors, regardless if they are indexed or not.
|
// Caches all instances of constructors, regardless if they are indexed or not.
|
||||||
@ -583,7 +583,7 @@ void SetVarDetail(IndexVar& var,
|
|||||||
type_name = "lambda";
|
type_name = "lambda";
|
||||||
if (g_config->index.comments)
|
if (g_config->index.comments)
|
||||||
def.comments = cursor.get_comments();
|
def.comments = cursor.get_comments();
|
||||||
def.storage = GetStorageClass(clang_Cursor_getStorageClass(cursor.cx_cursor));
|
def.storage = GetStorageC(clang_Cursor_getStorageClass(cursor.cx_cursor));
|
||||||
|
|
||||||
// TODO how to make PrettyPrint'ed variable name qualified?
|
// TODO how to make PrettyPrint'ed variable name qualified?
|
||||||
#if 0 && CINDEX_HAVE_PRETTY
|
#if 0 && CINDEX_HAVE_PRETTY
|
||||||
@ -1526,7 +1526,7 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) {
|
|||||||
func.def.comments = cursor.get_comments();
|
func.def.comments = cursor.get_comments();
|
||||||
func.def.kind = GetSymbolKind(decl->entityInfo->kind);
|
func.def.kind = GetSymbolKind(decl->entityInfo->kind);
|
||||||
func.def.storage =
|
func.def.storage =
|
||||||
GetStorageClass(clang_Cursor_getStorageClass(decl->cursor));
|
GetStorageC(clang_Cursor_getStorageClass(decl->cursor));
|
||||||
|
|
||||||
// We don't actually need to know the return type, but we need to mark it
|
// We don't actually need to know the return type, but we need to mark it
|
||||||
// as an interesting usage.
|
// as an interesting usage.
|
||||||
@ -1536,11 +1536,13 @@ void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) {
|
|||||||
// Add definition or declaration. This is a bit tricky because we treat
|
// Add definition or declaration. This is a bit tricky because we treat
|
||||||
// template specializations as declarations, even though they are
|
// template specializations as declarations, even though they are
|
||||||
// technically definitions.
|
// technically definitions.
|
||||||
// TODO: Support multiple function definitions, which is common for
|
bool is_def = decl->isDefinition;
|
||||||
// template specializations.
|
if (!is_def) {
|
||||||
if (decl->isDefinition && !is_template_specialization) {
|
auto* D = static_cast<const Decl*>(decl->cursor.data[0]);
|
||||||
// assert(!func->def.spell);
|
auto* Method = dyn_cast_or_null<CXXMethodDecl>(D->getAsFunction());
|
||||||
// assert(!func->def.extent);
|
is_def = Method && (Method->isDefaulted() || Method->isPure());
|
||||||
|
}
|
||||||
|
if (is_def && !is_template_specialization) {
|
||||||
func.def.spell = SetUse(db, spell, sem_parent, Role::Definition);
|
func.def.spell = SetUse(db, spell, sem_parent, Role::Definition);
|
||||||
func.def.extent = SetUse(db, extent, lex_parent, Role::None);
|
func.def.extent = SetUse(db, extent, lex_parent, Role::None);
|
||||||
} else {
|
} else {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "symbol.h"
|
#include "symbol.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include <clang/Basic/Specifiers.h>
|
||||||
#include <llvm/ADT/StringMap.h>
|
#include <llvm/ADT/StringMap.h>
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@ -62,6 +63,8 @@ struct Use : Reference {
|
|||||||
void Reflect(Reader& visitor, Reference& value);
|
void Reflect(Reader& visitor, Reference& value);
|
||||||
void Reflect(Writer& visitor, Reference& value);
|
void Reflect(Writer& visitor, Reference& value);
|
||||||
|
|
||||||
|
MAKE_REFLECT_TYPE_PROXY2(clang::StorageClass, uint8_t);
|
||||||
|
|
||||||
template <typename D>
|
template <typename D>
|
||||||
struct NameMixin {
|
struct NameMixin {
|
||||||
std::string_view Name(bool qualified) const {
|
std::string_view Name(bool qualified) const {
|
||||||
@ -99,7 +102,7 @@ struct FuncDef : NameMixin<FuncDef> {
|
|||||||
int16_t short_name_offset = 0;
|
int16_t short_name_offset = 0;
|
||||||
int16_t short_name_size = 0;
|
int16_t short_name_size = 0;
|
||||||
lsSymbolKind kind = lsSymbolKind::Unknown;
|
lsSymbolKind kind = lsSymbolKind::Unknown;
|
||||||
StorageClass storage = StorageClass::Invalid;
|
clang::StorageClass storage = clang::SC_None;
|
||||||
|
|
||||||
std::vector<Usr> GetBases() const { return bases; }
|
std::vector<Usr> GetBases() const { return bases; }
|
||||||
bool operator==(const FuncDef& o) const {
|
bool operator==(const FuncDef& o) const {
|
||||||
@ -213,11 +216,11 @@ struct VarDef : NameMixin<VarDef> {
|
|||||||
lsSymbolKind kind = lsSymbolKind::Unknown;
|
lsSymbolKind kind = lsSymbolKind::Unknown;
|
||||||
// Note a variable may have instances of both |None| and |Extern|
|
// Note a variable may have instances of both |None| and |Extern|
|
||||||
// (declaration).
|
// (declaration).
|
||||||
StorageClass storage = StorageClass::Invalid;
|
clang::StorageClass storage = clang::SC_None;
|
||||||
|
|
||||||
bool is_local() const {
|
bool is_local() const {
|
||||||
return spell && spell->kind != SymbolKind::File &&
|
return spell && spell->kind != SymbolKind::File &&
|
||||||
storage == StorageClass::None;
|
storage == clang::SC_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Usr> GetBases() const { return {}; }
|
std::vector<Usr> GetBases() const { return {}; }
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include "pipeline.hh"
|
#include "pipeline.hh"
|
||||||
using namespace ccls;
|
using namespace ccls;
|
||||||
|
|
||||||
|
using namespace clang;
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
MAKE_HASHABLE(SymbolIdx, t.usr, t.kind);
|
MAKE_HASHABLE(SymbolIdx, t.usr, t.kind);
|
||||||
@ -187,10 +189,11 @@ void EmitInactiveLines(WorkingFile* working_file,
|
|||||||
|
|
||||||
void EmitSemanticHighlighting(DB* db,
|
void EmitSemanticHighlighting(DB* db,
|
||||||
SemanticHighlightSymbolCache* semantic_cache,
|
SemanticHighlightSymbolCache* semantic_cache,
|
||||||
WorkingFile* working_file,
|
WorkingFile* wfile,
|
||||||
QueryFile* file) {
|
QueryFile* file) {
|
||||||
assert(file->def);
|
assert(file->def);
|
||||||
if (!semantic_cache->match_->IsMatch(file->def->path))
|
if (wfile->buffer_content.size() > g_config->largeFileSize ||
|
||||||
|
!semantic_cache->match_->IsMatch(file->def->path))
|
||||||
return;
|
return;
|
||||||
auto semantic_cache_for_file =
|
auto semantic_cache_for_file =
|
||||||
semantic_cache->GetCacheForFile(file->def->path);
|
semantic_cache->GetCacheForFile(file->def->path);
|
||||||
@ -202,7 +205,7 @@ void EmitSemanticHighlighting(DB* db,
|
|||||||
std::string_view detailed_name;
|
std::string_view detailed_name;
|
||||||
lsSymbolKind parent_kind = lsSymbolKind::Unknown;
|
lsSymbolKind parent_kind = lsSymbolKind::Unknown;
|
||||||
lsSymbolKind kind = lsSymbolKind::Unknown;
|
lsSymbolKind kind = lsSymbolKind::Unknown;
|
||||||
StorageClass storage = StorageClass::Invalid;
|
StorageClass storage = SC_None;
|
||||||
// This switch statement also filters out symbols that are not highlighted.
|
// This switch statement also filters out symbols that are not highlighted.
|
||||||
switch (sym.kind) {
|
switch (sym.kind) {
|
||||||
case SymbolKind::Func: {
|
case SymbolKind::Func: {
|
||||||
@ -237,9 +240,9 @@ void EmitSemanticHighlighting(DB* db,
|
|||||||
detailed_name.substr(0, detailed_name.find('<'));
|
detailed_name.substr(0, detailed_name.find('<'));
|
||||||
int16_t start_line = sym.range.start.line;
|
int16_t start_line = sym.range.start.line;
|
||||||
int16_t start_col = sym.range.start.column;
|
int16_t start_col = sym.range.start.column;
|
||||||
if (start_line < 0 || start_line >= working_file->index_lines.size())
|
if (start_line < 0 || start_line >= wfile->index_lines.size())
|
||||||
continue;
|
continue;
|
||||||
std::string_view line = working_file->index_lines[start_line];
|
std::string_view line = wfile->index_lines[start_line];
|
||||||
sym.range.end.line = start_line;
|
sym.range.end.line = start_line;
|
||||||
if (!(start_col + concise_name.size() <= line.size() &&
|
if (!(start_col + concise_name.size() <= line.size() &&
|
||||||
line.compare(start_col, concise_name.size(), concise_name) == 0))
|
line.compare(start_col, concise_name.size(), concise_name) == 0))
|
||||||
@ -280,7 +283,7 @@ void EmitSemanticHighlighting(DB* db,
|
|||||||
continue; // applies to for loop
|
continue; // applies to for loop
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<lsRange> loc = GetLsRange(working_file, sym.range);
|
std::optional<lsRange> loc = GetLsRange(wfile, sym.range);
|
||||||
if (loc) {
|
if (loc) {
|
||||||
auto it = grouped_symbols.find(sym);
|
auto it = grouped_symbols.find(sym);
|
||||||
if (it != grouped_symbols.end()) {
|
if (it != grouped_symbols.end()) {
|
||||||
@ -339,7 +342,7 @@ void EmitSemanticHighlighting(DB* db,
|
|||||||
|
|
||||||
// Publish.
|
// Publish.
|
||||||
Out_CclsPublishSemanticHighlighting out;
|
Out_CclsPublishSemanticHighlighting out;
|
||||||
out.params.uri = lsDocumentUri::FromPath(working_file->filename);
|
out.params.uri = lsDocumentUri::FromPath(wfile->filename);
|
||||||
for (auto& entry : grouped_symbols)
|
for (auto& entry : grouped_symbols)
|
||||||
if (entry.second.ranges.size())
|
if (entry.second.ranges.size())
|
||||||
out.params.symbols.push_back(entry.second);
|
out.params.symbols.push_back(entry.second);
|
||||||
|
@ -63,7 +63,7 @@ struct Out_CclsPublishSemanticHighlighting
|
|||||||
int stableId = 0;
|
int stableId = 0;
|
||||||
lsSymbolKind parentKind;
|
lsSymbolKind parentKind;
|
||||||
lsSymbolKind kind;
|
lsSymbolKind kind;
|
||||||
StorageClass storage;
|
clang::StorageClass storage;
|
||||||
std::vector<lsRange> ranges;
|
std::vector<lsRange> ranges;
|
||||||
};
|
};
|
||||||
struct Params {
|
struct Params {
|
||||||
|
@ -95,7 +95,7 @@ bool Expand(MessageHandler* m,
|
|||||||
entry->numChildren = 0;
|
entry->numChildren = 0;
|
||||||
if (!def)
|
if (!def)
|
||||||
return false;
|
return false;
|
||||||
auto handle = [&](Use use, CallType call_type) {
|
auto handle = [&](Use use, CallType call_type1) {
|
||||||
entry->numChildren++;
|
entry->numChildren++;
|
||||||
if (levels > 0) {
|
if (levels > 0) {
|
||||||
Out_CclsCallHierarchy::Entry entry1;
|
Out_CclsCallHierarchy::Entry entry1;
|
||||||
@ -103,7 +103,7 @@ bool Expand(MessageHandler* m,
|
|||||||
entry1.usr = use.usr;
|
entry1.usr = use.usr;
|
||||||
if (auto loc = GetLsLocation(m->db, m->working_files, use))
|
if (auto loc = GetLsLocation(m->db, m->working_files, use))
|
||||||
entry1.location = *loc;
|
entry1.location = *loc;
|
||||||
entry1.callType = call_type;
|
entry1.callType = call_type1;
|
||||||
if (Expand(m, &entry1, callee, call_type, qualified, levels - 1))
|
if (Expand(m, &entry1, callee, call_type, qualified, levels - 1))
|
||||||
entry->children.push_back(std::move(entry1));
|
entry->children.push_back(std::move(entry1));
|
||||||
}
|
}
|
||||||
|
@ -438,9 +438,9 @@ struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
|
|||||||
|
|
||||||
{
|
{
|
||||||
if (params.initializationOptions)
|
if (params.initializationOptions)
|
||||||
g_config = std::make_unique<Config>(*params.initializationOptions);
|
g_config = new Config(*params.initializationOptions);
|
||||||
else
|
else
|
||||||
g_config = std::make_unique<Config>();
|
g_config = new Config;
|
||||||
rapidjson::Document reader;
|
rapidjson::Document reader;
|
||||||
reader.Parse(g_init_options.c_str());
|
reader.Parse(g_init_options.c_str());
|
||||||
if (!reader.HasParseError()) {
|
if (!reader.HasParseError()) {
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#include "pipeline.hh"
|
#include "pipeline.hh"
|
||||||
#include "query_utils.h"
|
#include "query_utils.h"
|
||||||
using namespace ccls;
|
using namespace ccls;
|
||||||
|
using namespace clang;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
MethodType kMethodType = "textDocument/documentSymbol";
|
MethodType kMethodType = "textDocument/documentSymbol";
|
||||||
@ -51,9 +52,8 @@ struct Handler_TextDocumentDocumentSymbol
|
|||||||
if (!def || !def->spell)
|
if (!def || !def->spell)
|
||||||
continue;
|
continue;
|
||||||
// Ignore local variables.
|
// Ignore local variables.
|
||||||
if (def->spell->kind == SymbolKind::Func &&
|
if (def->spell->kind == SymbolKind::Func && def->storage != SC_Static &&
|
||||||
def->storage != StorageClass::Static &&
|
def->storage != SC_Extern)
|
||||||
def->storage != StorageClass::Extern)
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,7 +450,9 @@ TEST_SUITE("Project") {
|
|||||||
void CheckFlags(const std::string& directory, const std::string& file,
|
void CheckFlags(const std::string& directory, const std::string& file,
|
||||||
std::vector<std::string> raw,
|
std::vector<std::string> raw,
|
||||||
std::vector<std::string> expected) {
|
std::vector<std::string> expected) {
|
||||||
g_config = std::make_unique<Config>();
|
if (g_config)
|
||||||
|
delete g_config;
|
||||||
|
g_config = new Config;
|
||||||
g_config->clang.resourceDir = "/w/resource_dir/";
|
g_config->clang.resourceDir = "/w/resource_dir/";
|
||||||
ProjectConfig project;
|
ProjectConfig project;
|
||||||
project.project_dir = "/w/c/s/";
|
project.project_dir = "/w/c/s/";
|
||||||
@ -536,7 +538,9 @@ TEST_SUITE("Project") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Directory extraction") {
|
TEST_CASE("Directory extraction") {
|
||||||
g_config = std::make_unique<Config>();
|
if (g_config)
|
||||||
|
delete g_config;
|
||||||
|
g_config = new Config;
|
||||||
ProjectConfig config;
|
ProjectConfig config;
|
||||||
config.project_dir = "/w/c/s/";
|
config.project_dir = "/w/c/s/";
|
||||||
|
|
||||||
|
21
src/symbol.h
21
src/symbol.h
@ -8,27 +8,6 @@
|
|||||||
enum class SymbolKind : uint8_t { Invalid, File, Type, Func, Var };
|
enum class SymbolKind : uint8_t { Invalid, File, Type, Func, Var };
|
||||||
MAKE_REFLECT_TYPE_PROXY(SymbolKind);
|
MAKE_REFLECT_TYPE_PROXY(SymbolKind);
|
||||||
|
|
||||||
// clang/Basic/Specifiers.h clang::StorageClass
|
|
||||||
enum class StorageClass : uint8_t {
|
|
||||||
// In |CX_StorageClass| but not in |clang::StorageClass|
|
|
||||||
// e.g. non-type template parameters
|
|
||||||
Invalid,
|
|
||||||
|
|
||||||
// These are legal on both functions and variables.
|
|
||||||
// e.g. global functions/variables, local variables
|
|
||||||
None,
|
|
||||||
Extern,
|
|
||||||
Static,
|
|
||||||
// e.g. |__private_extern__ int a;|
|
|
||||||
PrivateExtern,
|
|
||||||
|
|
||||||
// These are only legal on variables.
|
|
||||||
// e.g. explicit |auto int a;|
|
|
||||||
Auto,
|
|
||||||
Register
|
|
||||||
};
|
|
||||||
MAKE_REFLECT_TYPE_PROXY(StorageClass);
|
|
||||||
|
|
||||||
enum class Role : uint16_t {
|
enum class Role : uint16_t {
|
||||||
None = 0,
|
None = 0,
|
||||||
Declaration = 1 << 0,
|
Declaration = 1 << 0,
|
||||||
|
@ -288,7 +288,7 @@ bool RunIndexTests(const std::string& filter_path, bool enable_update) {
|
|||||||
flags.push_back(path);
|
flags.push_back(path);
|
||||||
|
|
||||||
// Run test.
|
// Run test.
|
||||||
g_config = std::make_unique<Config>();
|
g_config = new Config;
|
||||||
VFS vfs;
|
VFS vfs;
|
||||||
auto dbs = index.Index(&vfs, path, flags, {});
|
auto dbs = index.Index(&vfs, path, flags, {});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user