mirror of
https://github.com/MaskRay/ccls.git
synced 2024-11-29 11:01:57 +00:00
Scan include directories for include completion candidates.
This commit is contained in:
parent
a058eb8e95
commit
864ff122d8
@ -20,8 +20,8 @@ std::string GetCachedBaseFileName(const std::string& cache_directory,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::unique_ptr<IndexFile> LoadCachedIndex(IndexerConfig* config,
|
std::unique_ptr<IndexFile> LoadCachedIndex(Config* config,
|
||||||
const std::string& filename) {
|
const std::string& filename) {
|
||||||
if (!config->enableCacheRead)
|
if (!config->enableCacheRead)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ std::unique_ptr<IndexFile> LoadCachedIndex(IndexerConfig* config,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<std::string> LoadCachedFileContents(IndexerConfig* config,
|
optional<std::string> LoadCachedFileContents(Config* config,
|
||||||
const std::string& filename) {
|
const std::string& filename) {
|
||||||
if (!config->enableCacheRead)
|
if (!config->enableCacheRead)
|
||||||
return nullopt;
|
return nullopt;
|
||||||
@ -45,7 +45,7 @@ optional<std::string> LoadCachedFileContents(IndexerConfig* config,
|
|||||||
return ReadContent(GetCachedBaseFileName(config->cacheDirectory, filename));
|
return ReadContent(GetCachedBaseFileName(config->cacheDirectory, filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WriteToCache(IndexerConfig* config,
|
void WriteToCache(Config* config,
|
||||||
const std::string& filename,
|
const std::string& filename,
|
||||||
IndexFile& file,
|
IndexFile& file,
|
||||||
const optional<std::string>& indexed_file_content) {
|
const optional<std::string>& indexed_file_content) {
|
||||||
|
@ -7,16 +7,16 @@
|
|||||||
using std::experimental::optional;
|
using std::experimental::optional;
|
||||||
using std::experimental::nullopt;
|
using std::experimental::nullopt;
|
||||||
|
|
||||||
struct IndexerConfig;
|
struct Config;
|
||||||
struct IndexFile;
|
struct IndexFile;
|
||||||
|
|
||||||
std::unique_ptr<IndexFile> LoadCachedIndex(IndexerConfig* config,
|
std::unique_ptr<IndexFile> LoadCachedIndex(Config* config,
|
||||||
const std::string& filename);
|
const std::string& filename);
|
||||||
|
|
||||||
optional<std::string> LoadCachedFileContents(IndexerConfig* config,
|
optional<std::string> LoadCachedFileContents(Config* config,
|
||||||
const std::string& filename);
|
const std::string& filename);
|
||||||
|
|
||||||
void WriteToCache(IndexerConfig* config,
|
void WriteToCache(Config* config,
|
||||||
const std::string& filename,
|
const std::string& filename,
|
||||||
IndexFile& file,
|
IndexFile& file,
|
||||||
const optional<std::string>& indexed_file_contents);
|
const optional<std::string>& indexed_file_contents);
|
@ -9,8 +9,6 @@
|
|||||||
-IC:/Users/jacob/Desktop/superindex/indexer/third_party/rapidjson/include
|
-IC:/Users/jacob/Desktop/superindex/indexer/third_party/rapidjson/include
|
||||||
-IC:/Users/jacob/Desktop/superindex/indexer/third_party/sparsepp
|
-IC:/Users/jacob/Desktop/superindex/indexer/third_party/sparsepp
|
||||||
-IC:/Program Files/LLVM/include
|
-IC:/Program Files/LLVM/include
|
||||||
-IC:/Program Files (x86)/Windows Kits/10/Include/10.0.14393.0
|
|
||||||
-IC:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include
|
|
||||||
|
|
||||||
|
|
||||||
# OSX
|
# OSX
|
||||||
|
@ -369,7 +369,7 @@ CompletionSession::CompletionSession(const Project::Entry& file, WorkingFiles* w
|
|||||||
|
|
||||||
CompletionSession::~CompletionSession() {}
|
CompletionSession::~CompletionSession() {}
|
||||||
|
|
||||||
CompletionManager::CompletionManager(IndexerConfig* config, Project* project, WorkingFiles* working_files)
|
CompletionManager::CompletionManager(Config* config, Project* project, WorkingFiles* working_files)
|
||||||
: config(config), project(project), working_files(working_files) {
|
: config(config), project(project), working_files(working_files) {
|
||||||
new std::thread([&]() {
|
new std::thread([&]() {
|
||||||
SetCurrentThreadName("completequery");
|
SetCurrentThreadName("completequery");
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
// TODO: rename this file to clang_completion.h/cc
|
||||||
|
|
||||||
struct CompletionSession {
|
struct CompletionSession {
|
||||||
Project::Entry file;
|
Project::Entry file;
|
||||||
WorkingFiles* working_files;
|
WorkingFiles* working_files;
|
||||||
@ -35,7 +37,7 @@ struct CompletionSession {
|
|||||||
|
|
||||||
struct CompletionManager {
|
struct CompletionManager {
|
||||||
std::vector<std::unique_ptr<CompletionSession>> sessions;
|
std::vector<std::unique_ptr<CompletionSession>> sessions;
|
||||||
IndexerConfig* config;
|
Config* config;
|
||||||
Project* project;
|
Project* project;
|
||||||
WorkingFiles* working_files;
|
WorkingFiles* working_files;
|
||||||
|
|
||||||
@ -51,7 +53,7 @@ struct CompletionManager {
|
|||||||
// Request that the given path be reparsed.
|
// Request that the given path be reparsed.
|
||||||
AtomicObject<std::string> reparse_request;
|
AtomicObject<std::string> reparse_request;
|
||||||
|
|
||||||
CompletionManager(IndexerConfig* config, Project* project, WorkingFiles* working_files);
|
CompletionManager(Config* config, Project* project, WorkingFiles* working_files);
|
||||||
|
|
||||||
// Start a code completion at the given location. |on_complete| will run when
|
// Start a code completion at the given location. |on_complete| will run when
|
||||||
// completion results are available. |on_complete| may run on any thread.
|
// completion results are available. |on_complete| may run on any thread.
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "code_completion.h"
|
#include "code_completion.h"
|
||||||
#include "file_consumer.h"
|
#include "file_consumer.h"
|
||||||
#include "fuzzy.h"
|
#include "match.h"
|
||||||
|
#include "include_completion.h"
|
||||||
#include "indexer.h"
|
#include "indexer.h"
|
||||||
#include "query.h"
|
#include "query.h"
|
||||||
#include "language_server_api.h"
|
#include "language_server_api.h"
|
||||||
@ -179,63 +180,7 @@ bool ShouldDisplayIpcTiming(IpcId id) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::string BaseName(const std::string& path) {
|
|
||||||
int i = path.size() - 1;
|
|
||||||
while (i > 0) {
|
|
||||||
char c = path[i - 1];
|
|
||||||
if (c == '/' || c == '\\')
|
|
||||||
break;
|
|
||||||
--i;
|
|
||||||
}
|
|
||||||
return path.substr(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
int TrimCommonPathPrefix(const std::string& result, const std::string& trimmer) {
|
|
||||||
size_t i = 0;
|
|
||||||
while (i < result.size() && i < trimmer.size()) {
|
|
||||||
char a = result[i];
|
|
||||||
char b = trimmer[i];
|
|
||||||
#if defined(_WIN32)
|
|
||||||
a = tolower(a);
|
|
||||||
b = tolower(b);
|
|
||||||
#endif
|
|
||||||
if (a != b)
|
|
||||||
break;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i == trimmer.size())
|
|
||||||
return i;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Returns true iff angle brackets should be used.
|
|
||||||
bool TrimPath(Project* project, const std::string& project_root, std::string& insert_path) {
|
|
||||||
int start = 0;
|
|
||||||
bool angle = false;
|
|
||||||
|
|
||||||
int len = TrimCommonPathPrefix(insert_path, project_root);
|
|
||||||
if (len > start)
|
|
||||||
start = len;
|
|
||||||
|
|
||||||
for (auto& include_dir : project->quote_include_directories) {
|
|
||||||
len = TrimCommonPathPrefix(insert_path, include_dir);
|
|
||||||
if (len > start)
|
|
||||||
start = len;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& include_dir : project->angle_include_directories) {
|
|
||||||
len = TrimCommonPathPrefix(insert_path, include_dir);
|
|
||||||
if (len > start) {
|
|
||||||
start = len;
|
|
||||||
angle = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
insert_path = insert_path.substr(start);
|
|
||||||
return angle;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ShouldRunIncludeCompletion(const std::string& line) {
|
bool ShouldRunIncludeCompletion(const std::string& line) {
|
||||||
size_t start = 0;
|
size_t start = 0;
|
||||||
@ -1094,7 +1039,7 @@ void RegisterMessageTypes() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool ImportCachedIndex(IndexerConfig* config,
|
bool ImportCachedIndex(Config* config,
|
||||||
FileConsumer::SharedState* file_consumer_shared,
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
Index_DoIdMapQueue* queue_do_id_map,
|
Index_DoIdMapQueue* queue_do_id_map,
|
||||||
const std::string& tu_path,
|
const std::string& tu_path,
|
||||||
@ -1135,7 +1080,7 @@ bool ImportCachedIndex(IndexerConfig* config,
|
|||||||
return needs_reparse;
|
return needs_reparse;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParseFile(IndexerConfig* config,
|
void ParseFile(Config* config,
|
||||||
WorkingFiles* working_files,
|
WorkingFiles* working_files,
|
||||||
FileConsumer::SharedState* file_consumer_shared,
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
Index_DoIdMapQueue* queue_do_id_map,
|
Index_DoIdMapQueue* queue_do_id_map,
|
||||||
@ -1231,7 +1176,7 @@ void ParseFile(IndexerConfig* config,
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ResetStaleFiles(IndexerConfig* config,
|
bool ResetStaleFiles(Config* config,
|
||||||
FileConsumer::SharedState* file_consumer_shared,
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
const std::string& tu_path) {
|
const std::string& tu_path) {
|
||||||
Timer time;
|
Timer time;
|
||||||
@ -1263,7 +1208,7 @@ bool ResetStaleFiles(IndexerConfig* config,
|
|||||||
return needs_reparse;
|
return needs_reparse;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IndexMain_DoIndex(IndexerConfig* config,
|
bool IndexMain_DoIndex(Config* config,
|
||||||
FileConsumer::SharedState* file_consumer_shared,
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
Project* project,
|
Project* project,
|
||||||
WorkingFiles* working_files,
|
WorkingFiles* working_files,
|
||||||
@ -1327,6 +1272,7 @@ bool IndexMain_DoCreateIndexUpdate(
|
|||||||
response->previous_index.get(), response->current_index.get());
|
response->previous_index.get(), response->current_index.get());
|
||||||
response->perf.index_make_delta = time.ElapsedMicrosecondsAndReset();
|
response->perf.index_make_delta = time.ElapsedMicrosecondsAndReset();
|
||||||
|
|
||||||
|
#if false
|
||||||
#define PRINT_SECTION(name) \
|
#define PRINT_SECTION(name) \
|
||||||
if (response->perf.name) {\
|
if (response->perf.name) {\
|
||||||
total += response->perf.name; \
|
total += response->perf.name; \
|
||||||
@ -1351,6 +1297,7 @@ bool IndexMain_DoCreateIndexUpdate(
|
|||||||
|
|
||||||
if (response->is_interactive)
|
if (response->is_interactive)
|
||||||
std::cerr << "Applying IndexUpdate" << std::endl << update.ToString() << std::endl;
|
std::cerr << "Applying IndexUpdate" << std::endl << update.ToString() << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
Index_OnIndexed reply(update, response->indexed_content, response->perf);
|
Index_OnIndexed reply(update, response->indexed_content, response->perf);
|
||||||
queue_on_indexed->Enqueue(std::move(reply));
|
queue_on_indexed->Enqueue(std::move(reply));
|
||||||
@ -1372,16 +1319,16 @@ bool IndexMergeIndexUpdates(Index_OnIndexedQueue* queue_on_indexed) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
did_merge = true;
|
did_merge = true;
|
||||||
Timer time;
|
//Timer time;
|
||||||
root->update.Merge(to_join->update);
|
root->update.Merge(to_join->update);
|
||||||
for (auto&& entry : to_join->indexed_content)
|
for (auto&& entry : to_join->indexed_content)
|
||||||
root->indexed_content.emplace(entry);
|
root->indexed_content.emplace(entry);
|
||||||
time.ResetAndPrint("[indexer] Joining two querydb updates");
|
//time.ResetAndPrint("[indexer] Joining two querydb updates");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndexMain(
|
void IndexMain(
|
||||||
IndexerConfig* config,
|
Config* config,
|
||||||
FileConsumer::SharedState* file_consumer_shared,
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
Project* project,
|
Project* project,
|
||||||
WorkingFiles* working_files,
|
WorkingFiles* working_files,
|
||||||
@ -1470,7 +1417,7 @@ void IndexMain(
|
|||||||
|
|
||||||
|
|
||||||
bool QueryDbMainLoop(
|
bool QueryDbMainLoop(
|
||||||
IndexerConfig* config,
|
Config* config,
|
||||||
QueryDatabase* db,
|
QueryDatabase* db,
|
||||||
MultiQueueWaiter* waiter,
|
MultiQueueWaiter* waiter,
|
||||||
Index_DoIndexQueue* queue_do_index,
|
Index_DoIndexQueue* queue_do_index,
|
||||||
@ -1481,6 +1428,7 @@ bool QueryDbMainLoop(
|
|||||||
FileConsumer::SharedState* file_consumer_shared,
|
FileConsumer::SharedState* file_consumer_shared,
|
||||||
WorkingFiles* working_files,
|
WorkingFiles* working_files,
|
||||||
CompletionManager* completion_manager,
|
CompletionManager* completion_manager,
|
||||||
|
IncludeCompletion* include_completion,
|
||||||
CodeCompleteCache* code_complete_cache,
|
CodeCompleteCache* code_complete_cache,
|
||||||
CodeCompleteCache* signature_cache) {
|
CodeCompleteCache* signature_cache) {
|
||||||
IpcManager* ipc = IpcManager::instance();
|
IpcManager* ipc = IpcManager::instance();
|
||||||
@ -1530,18 +1478,16 @@ bool QueryDbMainLoop(
|
|||||||
|
|
||||||
// Make sure cache directory is valid.
|
// Make sure cache directory is valid.
|
||||||
if (config->cacheDirectory.empty()) {
|
if (config->cacheDirectory.empty()) {
|
||||||
std::cerr << "No cache directory" << std::endl;
|
std::cerr << "[fatal] No cache directory" << std::endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
config->cacheDirectory = NormalizePath(config->cacheDirectory);
|
config->cacheDirectory = NormalizePath(config->cacheDirectory);
|
||||||
if (config->cacheDirectory[config->cacheDirectory.size() - 1] != '/')
|
EnsureEndsInSlash(config->cacheDirectory);
|
||||||
config->cacheDirectory += '/';
|
|
||||||
MakeDirectoryRecursive(config->cacheDirectory);
|
MakeDirectoryRecursive(config->cacheDirectory);
|
||||||
|
|
||||||
// Set project root.
|
// Set project root.
|
||||||
config->projectRoot = NormalizePath(request->params.rootUri->GetPath());
|
config->projectRoot = NormalizePath(request->params.rootUri->GetPath());
|
||||||
if (config->projectRoot.empty() || config->projectRoot[config->projectRoot.size() - 1] != '/')
|
EnsureEndsInSlash(config->projectRoot);
|
||||||
config->projectRoot += '/';
|
|
||||||
|
|
||||||
// Start indexer threads.
|
// Start indexer threads.
|
||||||
int indexer_count = std::max<int>(std::thread::hardware_concurrency(), 2) - 1;
|
int indexer_count = std::max<int>(std::thread::hardware_concurrency(), 2) - 1;
|
||||||
@ -1554,17 +1500,23 @@ bool QueryDbMainLoop(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer time;
|
||||||
|
|
||||||
// Open up / load the project.
|
// Open up / load the project.
|
||||||
project->Load(config->extraClangArguments, project_path);
|
project->Load(config->extraClangArguments, project_path);
|
||||||
std::cerr << "Loaded compilation entries (" << project->entries.size() << " files)" << std::endl;
|
time.ResetAndPrint("[perf] Loaded compilation entries (" + std::to_string(project->entries.size()) + " files)");
|
||||||
|
|
||||||
|
// Start scanning include directories before dispatching project files, because that takes a long time.
|
||||||
|
include_completion->Rescan();
|
||||||
|
|
||||||
|
time.Reset();
|
||||||
project->ForAllFilteredFiles(config, [&](int i, const Project::Entry& entry) {
|
project->ForAllFilteredFiles(config, [&](int i, const Project::Entry& entry) {
|
||||||
std::cerr << "[" << i << "/" << (project->entries.size() - 1)
|
//std::cerr << "[" << i << "/" << (project->entries.size() - 1)
|
||||||
<< "] Dispatching index request for file " << entry.filename
|
// << "] Dispatching index request for file " << entry.filename
|
||||||
<< std::endl;
|
// << std::endl;
|
||||||
|
|
||||||
queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::ImportThenParse, entry, nullopt, false /*is_interactive*/));
|
queue_do_index->Enqueue(Index_DoIndex(Index_DoIndex::Type::ImportThenParse, entry, nullopt, false /*is_interactive*/));
|
||||||
});
|
});
|
||||||
|
time.ResetAndPrint("[perf] Dispatched initial index requests");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: query request->params.capabilities.textDocument and support only things
|
// TODO: query request->params.capabilities.textDocument and support only things
|
||||||
@ -1768,6 +1720,8 @@ bool QueryDbMainLoop(
|
|||||||
|
|
||||||
time.ResetAndPrint("[querydb] Loading cached index file for DidOpen (blocks CodeLens)");
|
time.ResetAndPrint("[querydb] Loading cached index file for DidOpen (blocks CodeLens)");
|
||||||
|
|
||||||
|
include_completion->AddFile(working_file->filename);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1848,65 +1802,25 @@ bool QueryDbMainLoop(
|
|||||||
complete_response.id = msg->id;
|
complete_response.id = msg->id;
|
||||||
complete_response.result.isIncomplete = false;
|
complete_response.result.isIncomplete = false;
|
||||||
|
|
||||||
for (const char* stl_header : kStandardLibraryIncludes) {
|
{
|
||||||
lsCompletionItem item;
|
std::unique_lock<std::mutex> lock(include_completion->completion_items_mutex, std::defer_lock);
|
||||||
item.label = "#include <" + std::string(stl_header) + ">";
|
if (include_completion->is_scanning)
|
||||||
item.insertTextFormat = lsInsertTextFormat::PlainText;
|
lock.lock();
|
||||||
item.kind = lsCompletionItemKind::Module;
|
complete_response.result.items.assign(
|
||||||
|
include_completion->completion_items.begin(),
|
||||||
|
include_completion->completion_items.end());
|
||||||
|
if (lock)
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
// Replace the entire existing content.
|
// Update textEdit params.
|
||||||
item.textEdit = lsTextEdit();
|
for (lsCompletionItem& item : complete_response.result.items) {
|
||||||
item.textEdit->range.start.line = params.position.line;
|
item.textEdit->range.start.line = params.position.line;
|
||||||
item.textEdit->range.start.character = 0;
|
item.textEdit->range.start.character = 0;
|
||||||
item.textEdit->range.end.line = params.position.line;
|
item.textEdit->range.end.line = params.position.line;
|
||||||
item.textEdit->range.end.character = buffer_line.size();
|
item.textEdit->range.end.character = (int)buffer_line.size();
|
||||||
item.textEdit->newText = item.label;
|
|
||||||
|
|
||||||
complete_response.result.items.push_back(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (optional<QueryFile>& file : db->files) {
|
|
||||||
if (!file)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO: codify list of file extensions somewhere
|
|
||||||
if (EndsWith(file->def.path, ".c") ||
|
|
||||||
EndsWith(file->def.path, ".cc") ||
|
|
||||||
EndsWith(file->def.path, ".cpp"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
lsCompletionItem item;
|
|
||||||
|
|
||||||
// Standard library headers are handled differently.
|
|
||||||
std::string base_name = BaseName(file->def.path);
|
|
||||||
if (std::find(std::begin(kStandardLibraryIncludes), std::end(kStandardLibraryIncludes), base_name) != std::end(kStandardLibraryIncludes))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Trim the path so it is relative to an include directory.
|
|
||||||
std::string insert_path = file->def.path;
|
|
||||||
std::string start_quote = "\"";
|
|
||||||
std::string end_quote = "\"";
|
|
||||||
if (TrimPath(project, config->projectRoot, insert_path)) {
|
|
||||||
start_quote = "<";
|
|
||||||
end_quote = ">";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
item.label = "#include " + start_quote + insert_path + end_quote;
|
|
||||||
item.insertTextFormat = lsInsertTextFormat::PlainText;
|
|
||||||
item.kind = lsCompletionItemKind::File;
|
|
||||||
|
|
||||||
// Replace the entire existing content.
|
|
||||||
item.textEdit = lsTextEdit();
|
|
||||||
item.textEdit->range.start.line = params.position.line;
|
|
||||||
item.textEdit->range.start.character = 0;
|
|
||||||
item.textEdit->range.end.line = params.position.line;
|
|
||||||
item.textEdit->range.end.character = buffer_line.size();
|
|
||||||
item.textEdit->newText = item.label;
|
|
||||||
|
|
||||||
complete_response.result.items.push_back(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
complete_response.Write(std::cerr);
|
|
||||||
ipc->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
|
ipc->SendOutMessageToClient(IpcId::TextDocumentCompletion, complete_response);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -2445,6 +2359,7 @@ bool QueryDbMainLoop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
case IpcId::WorkspaceSymbol: {
|
case IpcId::WorkspaceSymbol: {
|
||||||
|
// TODO: implement fuzzy search, see https://github.com/junegunn/fzf/blob/master/src/matcher.go for inspiration
|
||||||
auto msg = static_cast<Ipc_WorkspaceSymbol*>(message.get());
|
auto msg = static_cast<Ipc_WorkspaceSymbol*>(message.get());
|
||||||
|
|
||||||
Out_WorkspaceSymbol response;
|
Out_WorkspaceSymbol response;
|
||||||
@ -2559,7 +2474,7 @@ bool QueryDbMainLoop(
|
|||||||
return did_work;
|
return did_work;
|
||||||
}
|
}
|
||||||
|
|
||||||
void QueryDbMain(IndexerConfig* config, MultiQueueWaiter* waiter) {
|
void QueryDbMain(Config* config, MultiQueueWaiter* waiter) {
|
||||||
// Create queues.
|
// Create queues.
|
||||||
Index_DoIndexQueue queue_do_index(waiter);
|
Index_DoIndexQueue queue_do_index(waiter);
|
||||||
Index_DoIdMapQueue queue_do_id_map(waiter);
|
Index_DoIdMapQueue queue_do_id_map(waiter);
|
||||||
@ -2569,6 +2484,7 @@ void QueryDbMain(IndexerConfig* config, MultiQueueWaiter* waiter) {
|
|||||||
Project project;
|
Project project;
|
||||||
WorkingFiles working_files;
|
WorkingFiles working_files;
|
||||||
CompletionManager completion_manager(config, &project, &working_files);
|
CompletionManager completion_manager(config, &project, &working_files);
|
||||||
|
IncludeCompletion include_completion(config, &project);
|
||||||
CodeCompleteCache code_complete_cache;
|
CodeCompleteCache code_complete_cache;
|
||||||
CodeCompleteCache signature_cache;
|
CodeCompleteCache signature_cache;
|
||||||
FileConsumer::SharedState file_consumer_shared;
|
FileConsumer::SharedState file_consumer_shared;
|
||||||
@ -2577,7 +2493,10 @@ void QueryDbMain(IndexerConfig* config, MultiQueueWaiter* waiter) {
|
|||||||
SetCurrentThreadName("querydb");
|
SetCurrentThreadName("querydb");
|
||||||
QueryDatabase db;
|
QueryDatabase db;
|
||||||
while (true) {
|
while (true) {
|
||||||
bool did_work = QueryDbMainLoop(config, &db, waiter, &queue_do_index, &queue_do_id_map, &queue_on_id_mapped, &queue_on_indexed, &project, &file_consumer_shared, &working_files, &completion_manager, &code_complete_cache, &signature_cache);
|
bool did_work = QueryDbMainLoop(
|
||||||
|
config, &db, waiter, &queue_do_index, &queue_do_id_map, &queue_on_id_mapped, &queue_on_indexed,
|
||||||
|
&project, &file_consumer_shared, &working_files,
|
||||||
|
&completion_manager, &include_completion, &code_complete_cache, &signature_cache);
|
||||||
if (!did_work) {
|
if (!did_work) {
|
||||||
IpcManager* ipc = IpcManager::instance();
|
IpcManager* ipc = IpcManager::instance();
|
||||||
waiter->Wait({
|
waiter->Wait({
|
||||||
@ -2659,7 +2578,7 @@ void QueryDbMain(IndexerConfig* config, MultiQueueWaiter* waiter) {
|
|||||||
// blocks.
|
// blocks.
|
||||||
//
|
//
|
||||||
// |ipc| is connected to a server.
|
// |ipc| is connected to a server.
|
||||||
void LanguageServerStdinLoop(IndexerConfig* config, std::unordered_map<IpcId, Timer>* request_times) {
|
void LanguageServerStdinLoop(Config* config, std::unordered_map<IpcId, Timer>* request_times) {
|
||||||
IpcManager* ipc = IpcManager::instance();
|
IpcManager* ipc = IpcManager::instance();
|
||||||
|
|
||||||
SetCurrentThreadName("stdin");
|
SetCurrentThreadName("stdin");
|
||||||
@ -2801,7 +2720,7 @@ void StdoutMain(std::unordered_map<IpcId, Timer>* request_times, MultiQueueWaite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LanguageServerMain(IndexerConfig* config, MultiQueueWaiter* waiter) {
|
void LanguageServerMain(Config* config, MultiQueueWaiter* waiter) {
|
||||||
std::unordered_map<IpcId, Timer> request_times;
|
std::unordered_map<IpcId, Timer> request_times;
|
||||||
|
|
||||||
// Start stdin reader. Reading from stdin is a blocking operation so this
|
// Start stdin reader. Reading from stdin is a blocking operation so this
|
||||||
@ -2899,7 +2818,7 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
else if (HasOption(options, "--language-server")) {
|
else if (HasOption(options, "--language-server")) {
|
||||||
//std::cerr << "Running language server" << std::endl;
|
//std::cerr << "Running language server" << std::endl;
|
||||||
IndexerConfig config;
|
Config config;
|
||||||
LanguageServerMain(&config, &waiter);
|
LanguageServerMain(&config, &waiter);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2924,8 +2843,8 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
There are also a number of configuration options available when
|
There are also a number of configuration options available when
|
||||||
initializing the language server - your editor should have tooling to
|
initializing the language server - your editor should have tooling to
|
||||||
describe those options. See |IndexerConfig| in this source code for a
|
describe those options. See |Config| in this source code for a detailed
|
||||||
detailed list of all currently supported options.
|
list of all currently supported options.
|
||||||
)help";
|
)help";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
67
src/config.h
Normal file
67
src/config.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "serializer.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
// Root directory of the project. **Not serialized**
|
||||||
|
std::string projectRoot;
|
||||||
|
|
||||||
|
std::string cacheDirectory;
|
||||||
|
std::vector<std::string> indexWhitelist;
|
||||||
|
std::vector<std::string> indexBlacklist;
|
||||||
|
std::vector<std::string> extraClangArguments;
|
||||||
|
|
||||||
|
// Maximum workspace search results.
|
||||||
|
int maxWorkspaceSearchResults = 1000;
|
||||||
|
|
||||||
|
// Force a certain number of indexer threads. If less than 1 a default value
|
||||||
|
// should be used.
|
||||||
|
int indexerCount = 0;
|
||||||
|
// If false, the indexer will be disabled.
|
||||||
|
bool enableIndexing = true;
|
||||||
|
// If false, indexed files will not be written to disk.
|
||||||
|
bool enableCacheWrite = true;
|
||||||
|
// If false, the index will not be loaded from a previous run.
|
||||||
|
bool enableCacheRead = true;
|
||||||
|
|
||||||
|
// If true, document links are reported for #include directives.
|
||||||
|
bool showDocumentLinksOnIncludes = true;
|
||||||
|
|
||||||
|
// Whitelist that file paths will be tested against. If a file path does not
|
||||||
|
// end in one of these values, it will not be considered for auto-completion.
|
||||||
|
// An example value is { ".h", ".hpp" }
|
||||||
|
//
|
||||||
|
// This is significantly faster than using a regex.
|
||||||
|
std::vector<std::string> includeCompletionWhitelistLiteralEnding;
|
||||||
|
// Regex patterns to match include completion candidates against. They
|
||||||
|
// receive the absolute file path.
|
||||||
|
//
|
||||||
|
// For example, to hide all files in a /CACHE/ folder, use ".*/CACHE/.*"
|
||||||
|
std::vector<std::string> includeCompletionWhitelist;
|
||||||
|
std::vector<std::string> includeCompletionBlacklist;
|
||||||
|
|
||||||
|
// Enables code lens on parameter and function variables.
|
||||||
|
bool codeLensOnLocalVariables = true;
|
||||||
|
|
||||||
|
// Version of the client.
|
||||||
|
int clientVersion = 0;
|
||||||
|
};
|
||||||
|
MAKE_REFLECT_STRUCT(Config,
|
||||||
|
cacheDirectory,
|
||||||
|
indexWhitelist, indexBlacklist,
|
||||||
|
extraClangArguments,
|
||||||
|
|
||||||
|
maxWorkspaceSearchResults,
|
||||||
|
indexerCount,
|
||||||
|
enableIndexing, enableCacheWrite, enableCacheRead,
|
||||||
|
|
||||||
|
includeCompletionWhitelistLiteralEnding,
|
||||||
|
includeCompletionWhitelist, includeCompletionBlacklist,
|
||||||
|
|
||||||
|
showDocumentLinksOnIncludes,
|
||||||
|
|
||||||
|
codeLensOnLocalVariables,
|
||||||
|
|
||||||
|
clientVersion);
|
12
src/fuzzy.h
12
src/fuzzy.h
@ -1,12 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
struct Matcher {
|
|
||||||
Matcher(const std::string& search);
|
|
||||||
|
|
||||||
bool IsMatch(const std::string& value) const;
|
|
||||||
|
|
||||||
std::string regex_string;
|
|
||||||
std::regex regex;
|
|
||||||
};
|
|
151
src/include_completion.cc
Normal file
151
src/include_completion.cc
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "include_completion.h"
|
||||||
|
|
||||||
|
#include "match.h"
|
||||||
|
#include "platform.h"
|
||||||
|
#include "project.h"
|
||||||
|
#include "standard_includes.h"
|
||||||
|
#include "timer.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
size_t TrimCommonPathPrefix(const std::string& result, const std::string& trimmer) {
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < result.size() && i < trimmer.size()) {
|
||||||
|
char a = result[i];
|
||||||
|
char b = trimmer[i];
|
||||||
|
#if defined(_WIN32)
|
||||||
|
a = tolower(a);
|
||||||
|
b = tolower(b);
|
||||||
|
#endif
|
||||||
|
if (a != b)
|
||||||
|
break;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == trimmer.size())
|
||||||
|
return i;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true iff angle brackets should be used.
|
||||||
|
bool TrimPath(Project* project, const std::string& project_root, std::string& insert_path) {
|
||||||
|
size_t start = 0;
|
||||||
|
bool angle = false;
|
||||||
|
|
||||||
|
size_t len = TrimCommonPathPrefix(insert_path, project_root);
|
||||||
|
if (len > start)
|
||||||
|
start = len;
|
||||||
|
|
||||||
|
for (auto& include_dir : project->quote_include_directories) {
|
||||||
|
len = TrimCommonPathPrefix(insert_path, include_dir);
|
||||||
|
if (len > start)
|
||||||
|
start = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& include_dir : project->angle_include_directories) {
|
||||||
|
len = TrimCommonPathPrefix(insert_path, include_dir);
|
||||||
|
if (len > start) {
|
||||||
|
start = len;
|
||||||
|
angle = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_path = insert_path.substr(start);
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
lsCompletionItem BuildCompletionItem(std::string path, bool use_angle_brackets, bool is_stl) {
|
||||||
|
lsCompletionItem item;
|
||||||
|
if (use_angle_brackets)
|
||||||
|
item.label = "#include <" + std::string(path) + ">";
|
||||||
|
else
|
||||||
|
item.label = "#include \"" + std::string(path) + "\"";
|
||||||
|
|
||||||
|
item.insertTextFormat = lsInsertTextFormat::PlainText;
|
||||||
|
if (is_stl)
|
||||||
|
item.kind = lsCompletionItemKind::Module;
|
||||||
|
else
|
||||||
|
item.kind = lsCompletionItemKind::File;
|
||||||
|
|
||||||
|
// Replace the entire existing content.
|
||||||
|
// NOTE: When submitting completion items, textEdit->range must be updated.
|
||||||
|
item.textEdit = lsTextEdit();
|
||||||
|
item.textEdit->newText = item.label;
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
IncludeCompletion::IncludeCompletion(Config* config, Project* project)
|
||||||
|
: config_(config), project_(project), is_scanning(false) {}
|
||||||
|
|
||||||
|
void IncludeCompletion::Rescan() {
|
||||||
|
if (is_scanning)
|
||||||
|
return;
|
||||||
|
|
||||||
|
completion_items.clear();
|
||||||
|
|
||||||
|
if (!match_ && (!config_->includeCompletionWhitelist.empty() || !config_->includeCompletionBlacklist.empty()))
|
||||||
|
match_ = MakeUnique<GroupMatch>(config_->includeCompletionWhitelist, config_->includeCompletionBlacklist);
|
||||||
|
|
||||||
|
is_scanning = true;
|
||||||
|
new std::thread([this]() {
|
||||||
|
SetCurrentThreadName("include_scanner");
|
||||||
|
Timer timer;
|
||||||
|
|
||||||
|
InsertStlIncludes();
|
||||||
|
InsertIncludesFromDirectory(config_->projectRoot, false /*use_angle_brackets*/);
|
||||||
|
for (const std::string& dir : project_->quote_include_directories)
|
||||||
|
InsertIncludesFromDirectory(dir, false /*use_angle_brackets*/);
|
||||||
|
for (const std::string& dir : project_->angle_include_directories)
|
||||||
|
InsertIncludesFromDirectory(dir, true /*use_angle_brackets*/);
|
||||||
|
|
||||||
|
timer.ResetAndPrint("[perf] Scanning for includes");
|
||||||
|
is_scanning = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void IncludeCompletion::AddFile(std::string path) {
|
||||||
|
if (!EndsWithAny(path, config_->includeCompletionWhitelistLiteralEnding))
|
||||||
|
return;
|
||||||
|
if (match_ && !match_->IsMatch(path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool use_angle_brackets = TrimPath(project_, config_->projectRoot, path);
|
||||||
|
lsCompletionItem item = BuildCompletionItem(path, use_angle_brackets, false /*is_stl*/);
|
||||||
|
|
||||||
|
if (is_scanning) {
|
||||||
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
||||||
|
completion_items.insert(item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
completion_items.insert(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IncludeCompletion::InsertIncludesFromDirectory(
|
||||||
|
const std::string& directory,
|
||||||
|
bool use_angle_brackets) {
|
||||||
|
std::vector<lsCompletionItem> result;
|
||||||
|
GetFilesInFolder(directory, true /*recursive*/, false /*add_folder_to_path*/, [&](const std::string& path) {
|
||||||
|
if (!EndsWithAny(path, config_->includeCompletionWhitelistLiteralEnding))
|
||||||
|
return;
|
||||||
|
if (match_ && !match_->IsMatch(directory + path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
result.push_back(BuildCompletionItem(path, use_angle_brackets, false /*is_stl*/));
|
||||||
|
});
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
||||||
|
completion_items.insert(result.begin(), result.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void IncludeCompletion::InsertStlIncludes() {
|
||||||
|
std::lock_guard<std::mutex> lock(completion_items_mutex);
|
||||||
|
for (const char* stl_header : kStandardLibraryIncludes) {
|
||||||
|
completion_items.insert(BuildCompletionItem(stl_header, true /*use_angle_brackets*/, true /*is_stl*/));
|
||||||
|
}
|
||||||
|
}
|
36
src/include_completion.h
Normal file
36
src/include_completion.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "language_server_api.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
struct GroupMatch;
|
||||||
|
struct Project;
|
||||||
|
|
||||||
|
struct IncludeCompletion {
|
||||||
|
IncludeCompletion(Config* config, Project* project);
|
||||||
|
|
||||||
|
// Starts scanning directories. Clears existing cache.
|
||||||
|
void Rescan();
|
||||||
|
|
||||||
|
// Ensures the one-off file is inside |completion_items|.
|
||||||
|
void AddFile(std::string path);
|
||||||
|
|
||||||
|
// Scans the given directory and inserts all includes from this. This is a
|
||||||
|
// blocking function and should be run off the querydb thread.
|
||||||
|
void InsertIncludesFromDirectory(const std::string& directory, bool use_angle_brackets);
|
||||||
|
void InsertStlIncludes();
|
||||||
|
|
||||||
|
// Guards |completion_items| when |is_scanning| is true.
|
||||||
|
std::mutex completion_items_mutex;
|
||||||
|
std::atomic<bool> is_scanning;
|
||||||
|
std::unordered_set<lsCompletionItem> completion_items;
|
||||||
|
|
||||||
|
// Cached references
|
||||||
|
Config* config_;
|
||||||
|
Project* project_;
|
||||||
|
std::unique_ptr<GroupMatch> match_;
|
||||||
|
};
|
||||||
|
|
@ -1471,7 +1471,7 @@ void indexEntityReference(CXClientData client_data,
|
|||||||
|
|
||||||
|
|
||||||
std::vector<std::unique_ptr<IndexFile>> Parse(
|
std::vector<std::unique_ptr<IndexFile>> Parse(
|
||||||
IndexerConfig* config, FileConsumer::SharedState* file_consumer_shared,
|
Config* config, FileConsumer::SharedState* file_consumer_shared,
|
||||||
std::string file,
|
std::string file,
|
||||||
std::vector<std::string> args,
|
std::vector<std::string> args,
|
||||||
const std::string& file_contents_path,
|
const std::string& file_contents_path,
|
||||||
|
@ -550,7 +550,7 @@ struct IndexFile {
|
|||||||
// |desired_index_file| is the (h or cc) file which has actually changed.
|
// |desired_index_file| is the (h or cc) file which has actually changed.
|
||||||
// |dependencies| are the existing dependencies of |import_file| if this is a reparse.
|
// |dependencies| are the existing dependencies of |import_file| if this is a reparse.
|
||||||
std::vector<std::unique_ptr<IndexFile>> Parse(
|
std::vector<std::unique_ptr<IndexFile>> Parse(
|
||||||
IndexerConfig* config, FileConsumer::SharedState* file_consumer_shared,
|
Config* config, FileConsumer::SharedState* file_consumer_shared,
|
||||||
std::string file,
|
std::string file,
|
||||||
std::vector<std::string> args,
|
std::vector<std::string> args,
|
||||||
const std::string& file_contents_path,
|
const std::string& file_contents_path,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "ipc.h"
|
#include "ipc.h"
|
||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@ -50,57 +51,6 @@ void Reflect(Reader& visitor, lsRequestId& id);
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct IndexerConfig {
|
|
||||||
// Root directory of the project. **Not serialized**
|
|
||||||
std::string projectRoot;
|
|
||||||
|
|
||||||
std::string cacheDirectory;
|
|
||||||
NonElidedVector<std::string> whitelist;
|
|
||||||
NonElidedVector<std::string> blacklist;
|
|
||||||
std::vector<std::string> extraClangArguments;
|
|
||||||
|
|
||||||
// Maximum workspace search results.
|
|
||||||
int maxWorkspaceSearchResults = 1000;
|
|
||||||
|
|
||||||
// Force a certain number of indexer threads. If less than 1 a default value
|
|
||||||
// should be used.
|
|
||||||
int indexerCount = 0;
|
|
||||||
// If false, the indexer will be disabled.
|
|
||||||
bool enableIndexing = true;
|
|
||||||
// If false, indexed files will not be written to disk.
|
|
||||||
bool enableCacheWrite = true;
|
|
||||||
// If false, the index will not be loaded from a previous run.
|
|
||||||
bool enableCacheRead = true;
|
|
||||||
|
|
||||||
// If true, document links are reported for #include directives.
|
|
||||||
bool showDocumentLinksOnIncludes = true;
|
|
||||||
|
|
||||||
// Enables code lens on parameter and function variables.
|
|
||||||
bool codeLensOnLocalVariables = true;
|
|
||||||
|
|
||||||
// Version of the client.
|
|
||||||
int clientVersion = 0;
|
|
||||||
};
|
|
||||||
MAKE_REFLECT_STRUCT(IndexerConfig,
|
|
||||||
cacheDirectory,
|
|
||||||
whitelist, blacklist,
|
|
||||||
extraClangArguments,
|
|
||||||
|
|
||||||
maxWorkspaceSearchResults,
|
|
||||||
indexerCount,
|
|
||||||
enableIndexing, enableCacheWrite, enableCacheRead,
|
|
||||||
|
|
||||||
showDocumentLinksOnIncludes,
|
|
||||||
|
|
||||||
codeLensOnLocalVariables,
|
|
||||||
|
|
||||||
clientVersion);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -510,6 +460,8 @@ struct lsCompletionItem {
|
|||||||
// An data entry field that is preserved on a completion item between
|
// An data entry field that is preserved on a completion item between
|
||||||
// a completion and a completion resolve request.
|
// a completion and a completion resolve request.
|
||||||
// data ? : any
|
// data ? : any
|
||||||
|
|
||||||
|
inline bool operator==(const lsCompletionItem& other) const { return label == other.label; }
|
||||||
};
|
};
|
||||||
MAKE_REFLECT_STRUCT(lsCompletionItem,
|
MAKE_REFLECT_STRUCT(lsCompletionItem,
|
||||||
label,
|
label,
|
||||||
@ -520,7 +472,7 @@ MAKE_REFLECT_STRUCT(lsCompletionItem,
|
|||||||
insertText,
|
insertText,
|
||||||
insertTextFormat,
|
insertTextFormat,
|
||||||
textEdit);
|
textEdit);
|
||||||
|
MAKE_HASHABLE(lsCompletionItem, t.label);
|
||||||
|
|
||||||
struct lsTextDocumentItem {
|
struct lsTextDocumentItem {
|
||||||
// The text document's URI.
|
// The text document's URI.
|
||||||
@ -909,7 +861,7 @@ struct lsInitializeParams {
|
|||||||
optional<lsDocumentUri> rootUri;
|
optional<lsDocumentUri> rootUri;
|
||||||
|
|
||||||
// User provided initialization options.
|
// User provided initialization options.
|
||||||
optional<IndexerConfig> initializationOptions;
|
optional<Config> initializationOptions;
|
||||||
|
|
||||||
// The capabilities provided by the client (editor or tool)
|
// The capabilities provided by the client (editor or tool)
|
||||||
lsClientCapabilities capabilities;
|
lsClientCapabilities capabilities;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "fuzzy.h"
|
#include "match.h"
|
||||||
|
|
||||||
#include <doctest/doctest.h>
|
#include <doctest/doctest.h>
|
||||||
|
|
||||||
@ -28,6 +28,36 @@ bool Matcher::IsMatch(const std::string& value) const {
|
|||||||
return std::regex_match(value, regex, std::regex_constants::match_any);
|
return std::regex_match(value, regex, std::regex_constants::match_any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GroupMatch::GroupMatch(
|
||||||
|
const std::vector<std::string>& whitelist,
|
||||||
|
const std::vector<std::string>& blacklist) {
|
||||||
|
for (const std::string& entry : whitelist)
|
||||||
|
this->whitelist.push_back(Matcher(entry));
|
||||||
|
for (const std::string& entry : blacklist)
|
||||||
|
this->blacklist.push_back(Matcher(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GroupMatch::IsMatch(const std::string& value, std::string* match_failure_reason) const {
|
||||||
|
for (const Matcher& m : whitelist) {
|
||||||
|
if (!m.IsMatch(value)) {
|
||||||
|
if (match_failure_reason)
|
||||||
|
*match_failure_reason = "whitelist \"" + m.regex_string + "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const Matcher& m : blacklist) {
|
||||||
|
if (m.IsMatch(value)) {
|
||||||
|
if (match_failure_reason)
|
||||||
|
*match_failure_reason = "blacklist \"" + m.regex_string + "\"";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE("Matcher");
|
TEST_SUITE("Matcher");
|
||||||
|
|
||||||
TEST_CASE("sanity") {
|
TEST_CASE("sanity") {
|
25
src/match.h
Normal file
25
src/match.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include <string.>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct Matcher {
|
||||||
|
Matcher(const std::string& search);
|
||||||
|
|
||||||
|
bool IsMatch(const std::string& value) const;
|
||||||
|
|
||||||
|
std::string regex_string;
|
||||||
|
std::regex regex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check multiple |Matcher| instances at the same time.
|
||||||
|
struct GroupMatch {
|
||||||
|
GroupMatch(const std::vector<std::string>& whitelist,
|
||||||
|
const std::vector<std::string>& blacklist);
|
||||||
|
|
||||||
|
bool IsMatch(const std::string& value, std::string* match_failure_reason = nullptr) const;
|
||||||
|
|
||||||
|
std::vector<Matcher> whitelist;
|
||||||
|
std::vector<Matcher> blacklist;
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
|
||||||
#include "fuzzy.h"
|
#include "match.h"
|
||||||
#include "libclangmm/Utility.h"
|
#include "libclangmm/Utility.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "serializer.h"
|
#include "serializer.h"
|
||||||
@ -10,6 +10,8 @@
|
|||||||
#include <doctest/doctest.h>
|
#include <doctest/doctest.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct CompileCommandsEntry {
|
struct CompileCommandsEntry {
|
||||||
@ -187,9 +189,9 @@ Project::Entry GetCompilationEntryFromCompileCommandEntry(
|
|||||||
|
|
||||||
// Clang does not have good hueristics for determining source language. We
|
// Clang does not have good hueristics for determining source language. We
|
||||||
// default to C++11 if the user has not specified.
|
// default to C++11 if the user has not specified.
|
||||||
if (!StartsWithAny(result.args, "-x"))
|
if (!AnyStartsWith(result.args, "-x"))
|
||||||
result.args.push_back("-xc++");
|
result.args.push_back("-xc++");
|
||||||
if (!StartsWithAny(result.args, "-std="))
|
if (!AnyStartsWith(result.args, "-std="))
|
||||||
result.args.push_back("-std=c++11");
|
result.args.push_back("-std=c++11");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -321,11 +323,6 @@ int ComputeGuessScore(const std::string& a, const std::string& b) {
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnsureEndsInSlash(std::string& path) {
|
|
||||||
if (path.empty() || path[path.size() - 1] != '/')
|
|
||||||
path += '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void Project::Load(const std::vector<std::string>& extra_flags, const std::string& directory) {
|
void Project::Load(const std::vector<std::string>& extra_flags, const std::string& directory) {
|
||||||
@ -376,50 +373,18 @@ Project::Entry Project::FindCompilationEntryForFile(const std::string& filename)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Project::ForAllFilteredFiles(IndexerConfig* config, std::function<void(int i, const Entry& entry)> action) {
|
void Project::ForAllFilteredFiles(Config* config, std::function<void(int i, const Entry& entry)> action) {
|
||||||
std::vector<Matcher> whitelist;
|
GroupMatch matcher(config->indexWhitelist, config->indexBlacklist);
|
||||||
std::cerr << "Using whitelist" << std::endl;
|
|
||||||
for (const std::string& entry : config->whitelist) {
|
|
||||||
std::cerr << " - " << entry << std::endl;
|
|
||||||
whitelist.push_back(Matcher(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Matcher> blacklist;
|
|
||||||
std::cerr << "Using blacklist" << std::endl;
|
|
||||||
for (const std::string& entry : config->blacklist) {
|
|
||||||
std::cerr << " - " << entry << std::endl;
|
|
||||||
blacklist.push_back(Matcher(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < entries.size(); ++i) {
|
for (int i = 0; i < entries.size(); ++i) {
|
||||||
const Project::Entry& entry = entries[i];
|
const Project::Entry& entry = entries[i];
|
||||||
std::string filepath = entry.filename;
|
std::string failure_reason;
|
||||||
|
if (matcher.IsMatch(entry.filename, &failure_reason))
|
||||||
const Matcher* is_bad = nullptr;
|
action(i, entries[i]);
|
||||||
for (const Matcher& m : whitelist) {
|
else {
|
||||||
if (!m.IsMatch(filepath)) {
|
std::stringstream output;
|
||||||
is_bad = &m;
|
output << '[' << (i + 1) << '/' << entries.size() << "] Failed " << failure_reason << "; skipping " << entry.filename << std::endl;
|
||||||
break;
|
std::cerr << output.str();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (is_bad) {
|
|
||||||
std::cerr << "[" << i << "/" << (entries.size() - 1) << "] Failed whitelist check \"" << is_bad->regex_string << "\"; skipping " << filepath << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const Matcher& m : blacklist) {
|
|
||||||
if (m.IsMatch(filepath)) {
|
|
||||||
is_bad = &m;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is_bad) {
|
|
||||||
std::cerr << "[" << i << "/" << (entries.size() - 1) << "] Failed blacklist check \"" << is_bad->regex_string << "\"; skipping " << filepath << std::endl;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
action(i, entries[i]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "language_server_api.h"
|
#include "config.h"
|
||||||
|
|
||||||
#include <optional.h>
|
#include <optional.h>
|
||||||
#include <sparsepp/spp.h>
|
#include <sparsepp/spp.h>
|
||||||
@ -41,6 +41,6 @@ struct Project {
|
|||||||
// will infer one based on existing project structure.
|
// will infer one based on existing project structure.
|
||||||
Entry FindCompilationEntryForFile(const std::string& filename);
|
Entry FindCompilationEntryForFile(const std::string& filename);
|
||||||
|
|
||||||
void ForAllFilteredFiles(IndexerConfig* config, std::function<void(int i, const Entry& entry)> action);
|
void ForAllFilteredFiles(Config* config, std::function<void(int i, const Entry& entry)> action);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ void RunTests() {
|
|||||||
// Parse expected output from the test, parse it into JSON document.
|
// Parse expected output from the test, parse it into JSON document.
|
||||||
std::unordered_map<std::string, std::string> all_expected_output = ParseTestExpectation(path);
|
std::unordered_map<std::string, std::string> all_expected_output = ParseTestExpectation(path);
|
||||||
|
|
||||||
IndexerConfig config;
|
Config config;
|
||||||
FileConsumer::SharedState file_consumer_shared;
|
FileConsumer::SharedState file_consumer_shared;
|
||||||
|
|
||||||
// Run test.
|
// Run test.
|
||||||
|
46
src/utils.cc
46
src/utils.cc
@ -46,6 +46,18 @@ bool StartsWith(const std::string& value, const std::string& start) {
|
|||||||
return std::equal(start.begin(), start.end(), value.begin());
|
return std::equal(start.begin(), start.end(), value.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AnyStartsWith(const std::vector<std::string>& values, const std::string& start) {
|
||||||
|
return std::any_of(std::begin(values), std::end(values), [&start](const std::string& value) {
|
||||||
|
return StartsWith(value, start);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EndsWithAny(const std::string& value, const std::vector<std::string>& endings) {
|
||||||
|
return std::any_of(std::begin(endings), std::end(endings), [&value](const std::string& ending) {
|
||||||
|
return EndsWith(value, ending);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// See http://stackoverflow.com/a/29752943
|
// See http://stackoverflow.com/a/29752943
|
||||||
std::string ReplaceAll(const std::string& source, const std::string& from, const std::string& to) {
|
std::string ReplaceAll(const std::string& source, const std::string& from, const std::string& to) {
|
||||||
std::string result;
|
std::string result;
|
||||||
@ -83,9 +95,8 @@ std::vector<std::string> SplitString(const std::string& str, const std::string&
|
|||||||
return strings;
|
return strings;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<std::string> GetFilesInFolderHelper(std::string folder, bool recursive, std::string output_prefix) {
|
static void GetFilesInFolderHelper(
|
||||||
std::vector<std::string> result;
|
std::string folder, bool recursive, std::string output_prefix, const std::function<void(const std::string&)>& handler) {
|
||||||
|
|
||||||
tinydir_dir dir;
|
tinydir_dir dir;
|
||||||
if (tinydir_open(&dir, folder.c_str()) == -1) {
|
if (tinydir_open(&dir, folder.c_str()) == -1) {
|
||||||
perror("Error opening file");
|
perror("Error opening file");
|
||||||
@ -105,12 +116,11 @@ static std::vector<std::string> GetFilesInFolderHelper(std::string folder, bool
|
|||||||
if (recursive) {
|
if (recursive) {
|
||||||
// Note that we must always ignore the '.' and '..' directories, otherwise
|
// Note that we must always ignore the '.' and '..' directories, otherwise
|
||||||
// this will loop infinitely. The above check handles that for us.
|
// this will loop infinitely. The above check handles that for us.
|
||||||
for (std::string nested_file : GetFilesInFolderHelper(file.path, true /*recursive*/, output_prefix + file.name + "/"))
|
GetFilesInFolderHelper(file.path, true /*recursive*/, output_prefix + file.name + "/", handler);
|
||||||
result.push_back(nested_file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
result.push_back(output_prefix + file.name);
|
handler(output_prefix + file.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,17 +132,29 @@ static std::vector<std::string> GetFilesInFolderHelper(std::string folder, bool
|
|||||||
|
|
||||||
bail:
|
bail:
|
||||||
tinydir_close(&dir);
|
tinydir_close(&dir);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> GetFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path) {
|
std::vector<std::string> GetFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path) {
|
||||||
assert(folder.size() > 0);
|
EnsureEndsInSlash(folder);
|
||||||
if (folder[folder.size() - 1] != '/')
|
std::vector<std::string> result;
|
||||||
folder += '/';
|
GetFilesInFolderHelper(folder, recursive, add_folder_to_path ? folder : "", [&result](const std::string& path) {
|
||||||
|
result.push_back(path);
|
||||||
return GetFilesInFolderHelper(folder, recursive, add_folder_to_path ? folder : "");
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void GetFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path, const std::function<void(const std::string&)>& handler) {
|
||||||
|
EnsureEndsInSlash(folder);
|
||||||
|
GetFilesInFolderHelper(folder, recursive, add_folder_to_path ? folder : "", handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnsureEndsInSlash(std::string& path) {
|
||||||
|
if (path.empty() || path[path.size() - 1] != '/')
|
||||||
|
path += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// http://stackoverflow.com/a/6089413
|
// http://stackoverflow.com/a/6089413
|
||||||
std::istream& SafeGetline(std::istream& is, std::string& t) {
|
std::istream& SafeGetline(std::istream& is, std::string& t) {
|
||||||
t.clear();
|
t.clear();
|
||||||
|
14
src/utils.h
14
src/utils.h
@ -16,16 +16,13 @@ using std::experimental::nullopt;
|
|||||||
// Returns true if |value| starts/ends with |start| or |ending|.
|
// Returns true if |value| starts/ends with |start| or |ending|.
|
||||||
bool StartsWith(const std::string& value, const std::string& start);
|
bool StartsWith(const std::string& value, const std::string& start);
|
||||||
bool EndsWith(const std::string& value, const std::string& ending);
|
bool EndsWith(const std::string& value, const std::string& ending);
|
||||||
|
bool AnyStartsWith(const std::vector<std::string>& values, const std::string& start);
|
||||||
|
bool EndsWithAny(const std::string& value, const std::vector<std::string>& endings);
|
||||||
|
|
||||||
std::string ReplaceAll(const std::string& source, const std::string& from, const std::string& to);
|
std::string ReplaceAll(const std::string& source, const std::string& from, const std::string& to);
|
||||||
|
|
||||||
std::vector<std::string> SplitString(const std::string& str, const std::string& delimiter);
|
std::vector<std::string> SplitString(const std::string& str, const std::string& delimiter);
|
||||||
|
|
||||||
inline bool StartsWithAny(const std::vector<std::string>& values, const std::string& start) {
|
|
||||||
return std::any_of(std::begin(values), std::end(values), [&start](const std::string& value) {
|
|
||||||
return StartsWith(value, start);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename TValues>
|
template <typename TValues>
|
||||||
std::string StringJoin(const TValues& values) {
|
std::string StringJoin(const TValues& values) {
|
||||||
std::string result;
|
std::string result;
|
||||||
@ -41,6 +38,11 @@ std::string StringJoin(const TValues& values) {
|
|||||||
|
|
||||||
// Finds all files in the given folder. This is recursive.
|
// Finds all files in the given folder. This is recursive.
|
||||||
std::vector<std::string> GetFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path);
|
std::vector<std::string> GetFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path);
|
||||||
|
void GetFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path, const std::function<void(const std::string&)>& handler);
|
||||||
|
|
||||||
|
// Ensures that |path| ends in a slash.
|
||||||
|
void EnsureEndsInSlash(std::string& path);
|
||||||
|
|
||||||
optional<std::string> ReadContent(const std::string& filename);
|
optional<std::string> ReadContent(const std::string& filename);
|
||||||
std::vector<std::string> ReadLines(std::string filename);
|
std::vector<std::string> ReadLines(std::string filename);
|
||||||
std::vector<std::string> ToLines(const std::string& content, bool trim_whitespace);
|
std::vector<std::string> ToLines(const std::string& content, bool trim_whitespace);
|
||||||
|
Loading…
Reference in New Issue
Block a user