Skip to content

Commit

Permalink
.ccls: add %h and %hpp; make .ccls augment compile_commands.json
Browse files Browse the repository at this point in the history
  • Loading branch information
MaskRay committed Dec 22, 2018
1 parent 9d7a1e3 commit 05f95d1
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 91 deletions.
174 changes: 84 additions & 90 deletions src/project.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,10 @@ std::pair<LanguageId, bool> lookupExtension(std::string_view filename) {

namespace {

enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand };

struct ProjectConfig {
std::unordered_set<std::string> quote_dirs;
std::unordered_set<std::string> angle_dirs;
std::string root;
ProjectMode mode = ProjectMode::CompileCommandsJson;
};

enum OptionClass {
Expand All @@ -98,7 +95,7 @@ struct ProjectProcessor {
// Expand %c %cpp %clang
std::vector<const char *> args;
args.reserve(entry.args.size() + g_config->clang.extraArgs.size() + 1);
const LanguageId lang = lookupExtension(entry.filename).first;
auto [lang, header] = lookupExtension(entry.filename);
for (const char *arg : entry.args) {
StringRef A(arg);
if (A == "%clang") {
Expand All @@ -110,8 +107,12 @@ struct ProjectProcessor {
for (;;) {
if (A.consume_front("%c "))
ok |= lang == LanguageId::C;
else if (A.consume_front("%h "))
ok |= lang == LanguageId::C && header;
else if (A.consume_front("%cpp "))
ok |= lang == LanguageId::Cpp;
else if (A.consume_front("%hpp "))
ok |= lang == LanguageId::Cpp && header;
else if (A.consume_front("%objective-c "))
ok |= lang == LanguageId::ObjC;
else if (A.consume_front("%objective-cpp "))
Expand Down Expand Up @@ -221,20 +222,19 @@ ReadCompilerArgumentsFromFile(const std::string &path) {
return args;
}

std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
std::vector<Project::Entry> result;
config->mode = ProjectMode::DotCcls;

void LoadDirectoryListing(ProjectProcessor &proc, const StringSet<> &Seen,
std::vector<Project::Entry> &result) {
ProjectConfig *config = proc.config;
std::unordered_map<std::string, std::vector<const char *>> folder_args;
std::vector<std::string> files;
const std::string &root = config->root;

GetFilesInFolder(root, true /*recursive*/,
true /*add_folder_to_path*/,
[&folder_args, &files](const std::string &path) {
GetFilesInFolder(root, true /*recursive*/, true /*add_folder_to_path*/,
[&folder_args, &files, &Seen](const std::string &path) {
std::pair<LanguageId, bool> lang = lookupExtension(path);
if (lang.first != LanguageId::Unknown && !lang.second) {
files.push_back(path);
if (!Seen.count(path))
files.push_back(path);
} else if (sys::path::filename(path) == ".ccls") {
std::vector<const char *> args = ReadCompilerArgumentsFromFile(path);
folder_args.emplace(sys::path::parent_path(path), args);
Expand Down Expand Up @@ -263,7 +263,6 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
return folder_args[root];
};

ProjectProcessor proc(config);
for (const std::string &file : files) {
Project::Entry e;
e.root = config->root;
Expand All @@ -276,34 +275,43 @@ std::vector<Project::Entry> LoadFromDirectoryListing(ProjectConfig *config) {
proc.Process(e);
result.push_back(e);
}

return result;
}
// Computes a score based on how well |a| and |b| match. This is used for
// argument guessing.
int ComputeGuessScore(std::string_view a, std::string_view b) {
// Increase score based on common prefix and suffix. Prefixes are prioritized.
if (a.size() > b.size())
std::swap(a, b);
size_t i = std::mismatch(a.begin(), a.end(), b.begin()).first - a.begin();
size_t j = std::mismatch(a.rbegin(), a.rend(), b.rbegin()).first - a.rbegin();
int score = 10 * i + j;
if (i + j < b.size())
score -= 100 * (std::count(a.begin() + i, a.end() - j, '/') +
std::count(b.begin() + i, b.end() - j, '/'));
return score;
}

std::vector<Project::Entry>
LoadEntriesFromDirectory(ProjectConfig *project,
const std::string &opt_compdb_dir) {
// If there is a .ccls file always load using directory listing.
SmallString<256> Path, CclsPath;
sys::path::append(CclsPath, project->root, ".ccls");
if (sys::fs::exists(CclsPath))
return LoadFromDirectoryListing(project);
} // namespace

// If |compilationDatabaseCommand| is specified, execute it to get the compdb.
void Project::LoadDirectory(const std::string &root, Project::Folder &folder) {
SmallString<256> Path;
std::string comp_db_dir;

folder.entries.clear();
if (g_config->compilationDatabaseCommand.empty()) {
project->mode = ProjectMode::CompileCommandsJson;
// Try to load compile_commands.json, but fallback to a project listing.
comp_db_dir = opt_compdb_dir.empty() ? project->root : opt_compdb_dir;
comp_db_dir = g_config->compilationDatabaseDirectory.size()
? g_config->compilationDatabaseDirectory
: root;
sys::path::append(Path, comp_db_dir, "compile_commands.json");
} else {
project->mode = ProjectMode::ExternalCommand;
// If `compilationDatabaseCommand` is specified, execute it to get the
// compdb.
#ifdef _WIN32
// TODO
#else
char tmpdir[] = "/tmp/ccls-compdb-XXXXXX";
if (!mkdtemp(tmpdir))
return {};
return;
comp_db_dir = tmpdir;
sys::path::append(Path, comp_db_dir, "compile_commands.json");
rapidjson::StringBuffer input;
Expand All @@ -312,7 +320,7 @@ LoadEntriesFromDirectory(ProjectConfig *project,
Reflect(json_writer, *g_config);
std::string contents = GetExternalCommandOutput(
std::vector<std::string>{g_config->compilationDatabaseCommand,
project->root},
root},
input.GetString());
FILE *fout = fopen(Path.c_str(), "wb");
fwrite(contents.c_str(), contents.size(), 1, fout);
Expand All @@ -331,79 +339,65 @@ LoadEntriesFromDirectory(ProjectConfig *project,
rmdir(comp_db_dir.c_str());
#endif
}
if (!CDB) {
LOG_S(WARNING) << "no .ccls or compile_commands.json . Consider adding one";
return LoadFromDirectoryListing(project);
}

LOG_S(INFO) << "loaded " << Path.c_str();

ProjectConfig project;
project.root = root;
ProjectProcessor proc(&project);
StringSet<> Seen;
std::vector<Project::Entry> result;
ProjectProcessor proc(project);
for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) {
static bool once;
Project::Entry entry;
entry.root = project->root;
DoPathMapping(entry.root);
entry.directory = NormalizePath(Cmd.Directory);
DoPathMapping(entry.directory);
entry.filename =
NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename));
DoPathMapping(entry.filename);
std::vector<std::string> args = std::move(Cmd.CommandLine);
entry.args.reserve(args.size());
for (std::string &arg : args) {
DoPathMapping(arg);
entry.args.push_back(Intern(arg));
}
if (CDB) {
LOG_S(INFO) << "loaded " << Path.c_str();
for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) {
static bool once;
Project::Entry entry;
entry.root = root;
DoPathMapping(entry.root);
entry.directory = NormalizePath(Cmd.Directory);
DoPathMapping(entry.directory);
entry.filename =
NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename));
DoPathMapping(entry.filename);
std::vector<std::string> args = std::move(Cmd.CommandLine);
entry.args.reserve(args.size());
for (std::string &arg : args) {
DoPathMapping(arg);
entry.args.push_back(Intern(arg));
}

// Work around relative --sysroot= as it isn't affected by
// -working-directory=. chdir is thread hostile but this function runs
// before indexers do actual work and it works when there is only one
// workspace folder.
if (!once) {
once = true;
llvm::vfs::getRealFileSystem()->setCurrentWorkingDirectory(
entry.directory);
}
proc.Process(entry);
// Work around relative --sysroot= as it isn't affected by
// -working-directory=. chdir is thread hostile but this function runs
// before indexers do actual work and it works when there is only one
// workspace folder.
if (!once) {
once = true;
llvm::vfs::getRealFileSystem()->setCurrentWorkingDirectory(
entry.directory);
}
proc.Process(entry);

if (Seen.insert(entry.filename).second)
result.push_back(entry);
if (Seen.insert(entry.filename).second)
folder.entries.push_back(entry);
}
}
return result;
}

// Computes a score based on how well |a| and |b| match. This is used for
// argument guessing.
int ComputeGuessScore(std::string_view a, std::string_view b) {
// Increase score based on common prefix and suffix. Prefixes are prioritized.
if (a.size() > b.size())
std::swap(a, b);
size_t i = std::mismatch(a.begin(), a.end(), b.begin()).first - a.begin();
size_t j = std::mismatch(a.rbegin(), a.rend(), b.rbegin()).first - a.rbegin();
int score = 10 * i + j;
if (i + j < b.size())
score -= 100 * (std::count(a.begin() + i, a.end() - j, '/') +
std::count(b.begin() + i, b.end() - j, '/'));
return score;
// Use directory listing if .ccls exists or compile_commands.json does not
// exist.
Path.clear();
sys::path::append(Path, root, ".ccls");
if (!CDB || sys::fs::exists(Path))
LoadDirectoryListing(proc, Seen, folder.entries);
}

} // namespace

void Project::Load(const std::string &root) {
assert(root.back() == '/');
ProjectConfig project;
project.root = root;
Folder &folder = root2folder[root];

folder.entries = LoadEntriesFromDirectory(
&project, g_config->compilationDatabaseDirectory);
folder.quote_search_list.assign(project.quote_dirs.begin(),
project.quote_dirs.end());
folder.angle_search_list.assign(project.angle_dirs.begin(),
project.angle_dirs.end());
LoadDirectory(root, folder);
//folder.entries = LoadDirectory(root);
//folder.quote_search_list.assign(project.quote_dirs.begin(),
// project.quote_dirs.end());
//folder.angle_search_list.assign(project.angle_dirs.begin(),
// project.angle_dirs.end());
for (std::string &path : folder.angle_search_list) {
EnsureEndsInSlash(path);
LOG_S(INFO) << "angle search: " << path;
Expand Down
3 changes: 2 additions & 1 deletion src/project.hh
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ struct Project {
// will affect flags in their subtrees (relative paths are relative to the
// project root, not subdirectories). For compile_commands.json, its entries
// are indexed.
void Load(const std::string &root_directory);
void Load(const std::string &root);
void LoadDirectory(const std::string &root, Folder &folder);

// Lookup the CompilationEntry for |filename|. If no entry was found this
// will infer one based on existing project structure.
Expand Down

0 comments on commit 05f95d1

Please sign in to comment.