Refactor message handler and namespace ccls

This commit is contained in:
Fangrui Song 2018-10-28 10:49:31 -07:00
parent 8fa853c321
commit 3cd0ee2fc7
73 changed files with 2419 additions and 3126 deletions

View File

@ -212,11 +212,8 @@ target_sources(ccls PRIVATE
src/messages/ccls_navigate.cc src/messages/ccls_navigate.cc
src/messages/ccls_reload.cc src/messages/ccls_reload.cc
src/messages/ccls_vars.cc src/messages/ccls_vars.cc
src/messages/exit.cc
src/messages/initialize.cc src/messages/initialize.cc
src/messages/shutdown.cc src/messages/textDocument_code.cc
src/messages/textDocument_codeAction.cc
src/messages/textDocument_codeLens.cc
src/messages/textDocument_completion.cc src/messages/textDocument_completion.cc
src/messages/textDocument_definition.cc src/messages/textDocument_definition.cc
src/messages/textDocument_did.cc src/messages/textDocument_did.cc
@ -227,7 +224,5 @@ target_sources(ccls PRIVATE
src/messages/textDocument_references.cc src/messages/textDocument_references.cc
src/messages/textDocument_rename.cc src/messages/textDocument_rename.cc
src/messages/textDocument_signatureHelp.cc src/messages/textDocument_signatureHelp.cc
src/messages/textDocument_typeDefinition.cc src/messages/workspace.cc
src/messages/workspace_did.cc
src/messages/workspace_symbol.cc
) )

View File

@ -563,8 +563,6 @@ std::shared_ptr<PreambleData> CompletionSession::GetPreamble() {
return preamble; return preamble;
} }
} // namespace ccls
CompletionManager::CompletionManager(Project *project, CompletionManager::CompletionManager(Project *project,
WorkingFiles *working_files, WorkingFiles *working_files,
OnDiagnostic on_diagnostic, OnDiagnostic on_diagnostic,
@ -674,3 +672,4 @@ void CompletionManager::FlushAllSessions() {
preloads.Clear(); preloads.Clear();
sessions.Clear(); sessions.Clear();
} }
} // namespace ccls

View File

@ -5,9 +5,8 @@
#include "clang_tu.hh" #include "clang_tu.hh"
#include "lru_cache.h" #include "lru_cache.h"
#include "lsp.h" #include "lsp.hh"
#include "lsp_completion.h" #include "project.hh"
#include "project.h"
#include "threaded_queue.h" #include "threaded_queue.h"
#include "working_files.h" #include "working_files.h"
@ -61,7 +60,6 @@ struct CompletionSession
std::shared_ptr<PreambleData> GetPreamble(); std::shared_ptr<PreambleData> GetPreamble();
}; };
}
struct CompletionManager { struct CompletionManager {
using OnDiagnostic = std::function<void( using OnDiagnostic = std::function<void(
@ -181,3 +179,4 @@ struct CompleteConsumerCache {
return this->path == path && this->position == position; return this->path == path && this->position == position;
} }
}; };
} // namespace ccls

View File

@ -12,6 +12,7 @@
using namespace clang; using namespace clang;
namespace ccls {
std::string PathFromFileEntry(const FileEntry &file) { std::string PathFromFileEntry(const FileEntry &file) {
StringRef Name = file.tryGetRealPathName(); StringRef Name = file.tryGetRealPathName();
if (Name.empty()) if (Name.empty())
@ -241,3 +242,4 @@ const char *ClangBuiltinTypeName(int kind) {
return ""; return "";
} }
} }
} // namespace ccls

View File

@ -17,6 +17,7 @@ namespace vfs = clang::vfs;
} }
#endif #endif
namespace ccls {
std::string PathFromFileEntry(const clang::FileEntry &file); std::string PathFromFileEntry(const clang::FileEntry &file);
Range FromCharSourceRange(const clang::SourceManager &SM, Range FromCharSourceRange(const clang::SourceManager &SM,
@ -42,3 +43,4 @@ BuildCompilerInvocation(std::vector<const char *> args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS); llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS);
const char *ClangBuiltinTypeName(int); const char *ClangBuiltinTypeName(int);
} // namespace ccls

View File

@ -3,9 +3,9 @@
#include "config.h" #include "config.h"
namespace ccls {
Config *g_config; Config *g_config;
namespace ccls {
void DoPathMapping(std::string &arg) { void DoPathMapping(std::string &arg) {
for (const std::string &mapping : g_config->clang.pathMappings) { for (const std::string &mapping : g_config->clang.pathMappings) {
auto sep = mapping.find('>'); auto sep = mapping.find('>');

View File

@ -3,10 +3,11 @@
#pragma once #pragma once
#include "serializer.h" #include "serializer.hh"
#include <string> #include <string>
namespace ccls {
/* /*
The language client plugin needs to send initialization options in the The language client plugin needs to send initialization options in the
`initialize` request to the ccls language server. `initialize` request to the ccls language server.
@ -269,6 +270,5 @@ MAKE_REFLECT_STRUCT(Config, compilationDatabaseCommand,
extern Config *g_config; extern Config *g_config;
namespace ccls {
void DoPathMapping(std::string &arg); void DoPathMapping(std::string &arg);
} }

View File

@ -11,7 +11,7 @@ using namespace llvm;
void GetFilesInFolder(std::string folder, bool recursive, bool dir_prefix, void GetFilesInFolder(std::string folder, bool recursive, bool dir_prefix,
const std::function<void(const std::string &)> &handler) { const std::function<void(const std::string &)> &handler) {
EnsureEndsInSlash(folder); ccls::EnsureEndsInSlash(folder);
sys::fs::file_status Status; sys::fs::file_status Status;
if (sys::fs::status(folder, Status, true)) if (sys::fs::status(folder, Status, true))
return; return;

View File

@ -8,10 +8,11 @@
#include <stdio.h> #include <stdio.h>
#include <vector> #include <vector>
namespace ccls {
namespace {
enum CharClass { Other, Lower, Upper }; enum CharClass { Other, Lower, Upper };
enum CharRole { None, Tail, Head }; enum CharRole { None, Tail, Head };
namespace {
CharClass GetCharClass(int c) { CharClass GetCharClass(int c) {
if (islower(c)) if (islower(c))
return Lower; return Lower;
@ -133,6 +134,7 @@ int FuzzyMatcher::Match(std::string_view text) {
ret = std::max(ret, dp[pat.size() & 1][j][1] - 2 * (n - j)); ret = std::max(ret, dp[pat.size() & 1][j][1] - 2 * (n - j));
return ret; return ret;
} }
} // namespace ccls
#if 0 #if 0
TEST_SUITE("fuzzy_match") { TEST_SUITE("fuzzy_match") {

View File

@ -7,6 +7,7 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
namespace ccls {
class FuzzyMatcher { class FuzzyMatcher {
public: public:
constexpr static int kMaxPat = 100; constexpr static int kMaxPat = 100;
@ -30,3 +31,4 @@ private:
int MatchScore(int i, int j, bool last); int MatchScore(int i, int j, bool last);
int MissScore(int j, bool last); int MissScore(int j, bool last);
}; };
} // namespace ccls

View File

@ -3,11 +3,12 @@
#pragma once #pragma once
#include "lsp.h" #include "lsp.hh"
#include <algorithm> #include <algorithm>
#include <queue> #include <queue>
namespace ccls {
template <typename Node> template <typename Node>
std::vector<lsLocation> FlattenHierarchy(const std::optional<Node> &root) { std::vector<lsLocation> FlattenHierarchy(const std::optional<Node> &root) {
if (!root) if (!root)
@ -28,3 +29,4 @@ std::vector<lsLocation> FlattenHierarchy(const std::optional<Node> &root) {
ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); ret.erase(std::unique(ret.begin(), ret.end()), ret.end());
return ret; return ret;
} }
} // namespace ccls

View File

@ -6,15 +6,18 @@
#include "filesystem.hh" #include "filesystem.hh"
#include "match.h" #include "match.h"
#include "platform.h" #include "platform.h"
#include "project.h" #include "project.hh"
#include <llvm/ADT/Twine.h> #include <llvm/ADT/Twine.h>
#include <llvm/Support/Threading.h> #include <llvm/Support/Threading.h>
#include <llvm/Support/Timer.h> #include <llvm/Support/Timer.h>
#include <unordered_set>
using namespace llvm; using namespace llvm;
#include <thread> #include <thread>
namespace ccls {
namespace { namespace {
struct CompletionCandidate { struct CompletionCandidate {
@ -83,7 +86,6 @@ lsCompletionItem BuildCompletionItem(const std::string &path,
item.priority_ = 0; item.priority_ = 0;
return item; return item;
} }
} // namespace } // namespace
IncludeComplete::IncludeComplete(Project *project) IncludeComplete::IncludeComplete(Project *project)
@ -198,3 +200,4 @@ IncludeComplete::FindCompletionItemForAbsolutePath(
return std::nullopt; return std::nullopt;
return completion_items[it->second]; return completion_items[it->second];
} }
} // namespace ccls

View File

@ -3,12 +3,12 @@
#pragma once #pragma once
#include "lsp_completion.h" #include "message_handler.hh"
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <unordered_set>
namespace ccls {
struct GroupMatch; struct GroupMatch;
struct Project; struct Project;
@ -26,18 +26,18 @@ struct IncludeComplete {
void InsertIncludesFromDirectory(std::string directory, void InsertIncludesFromDirectory(std::string directory,
bool use_angle_brackets); bool use_angle_brackets);
std::optional<lsCompletionItem> std::optional<ccls::lsCompletionItem>
FindCompletionItemForAbsolutePath(const std::string &absolute_path); FindCompletionItemForAbsolutePath(const std::string &absolute_path);
// Insert item to |completion_items|. // Insert item to |completion_items|.
// Update |absolute_path_to_completion_item| and |inserted_paths|. // Update |absolute_path_to_completion_item| and |inserted_paths|.
void InsertCompletionItem(const std::string &absolute_path, void InsertCompletionItem(const std::string &absolute_path,
lsCompletionItem &&item); ccls::lsCompletionItem &&item);
// Guards |completion_items| when |is_scanning| is true. // Guards |completion_items| when |is_scanning| is true.
std::mutex completion_items_mutex; std::mutex completion_items_mutex;
std::atomic<bool> is_scanning; std::atomic<bool> is_scanning;
std::vector<lsCompletionItem> completion_items; std::vector<ccls::lsCompletionItem> completion_items;
// Absolute file path to the completion item in |completion_items|. // Absolute file path to the completion item in |completion_items|.
// Keep the one with shortest include path. // Keep the one with shortest include path.
@ -50,3 +50,4 @@ struct IncludeComplete {
Project *project_; Project *project_;
std::unique_ptr<GroupMatch> match_; std::unique_ptr<GroupMatch> match_;
}; };
} // namespace ccls

View File

@ -9,7 +9,7 @@
#include "match.h" #include "match.h"
#include "pipeline.hh" #include "pipeline.hh"
#include "platform.h" #include "platform.h"
#include "serializer.h" #include "serializer.hh"
#include <clang/AST/AST.h> #include <clang/AST/AST.h>
#include <clang/Frontend/FrontendAction.h> #include <clang/Frontend/FrontendAction.h>
@ -26,9 +26,9 @@
#include <map> #include <map>
#include <unordered_set> #include <unordered_set>
using namespace ccls;
using namespace clang; using namespace clang;
namespace ccls {
namespace { namespace {
constexpr int kInitializerMaxLines = 3; constexpr int kInitializerMaxLines = 3;
@ -1195,7 +1195,7 @@ template <typename T> void Uniquify(std::vector<T> &a) {
a.resize(n); a.resize(n);
} }
namespace ccls::idx { namespace idx {
void Init() { void Init() {
multiVersionMatcher = new GroupMatch(g_config->index.multiVersionWhitelist, multiVersionMatcher = new GroupMatch(g_config->index.multiVersionWhitelist,
g_config->index.multiVersionBlacklist); g_config->index.multiVersionBlacklist);
@ -1334,7 +1334,7 @@ Index(CompletionManager *completion, WorkingFiles *wfiles, VFS *vfs,
return result; return result;
} }
} // namespace ccls::idx } // namespace idx
void Reflect(Reader &vis, SymbolRef &v) { void Reflect(Reader &vis, SymbolRef &v) {
if (vis.Format() == SerializeFormat::Json) { if (vis.Format() == SerializeFormat::Json) {
@ -1422,3 +1422,4 @@ void Reflect(Writer &vis, DeclRef &v) {
Reflect(vis, v.extent); Reflect(vis, v.extent);
} }
} }
} // namespace ccls

View File

@ -3,10 +3,10 @@
#pragma once #pragma once
#include "lsp.h" #include "lsp.hh"
#include "maybe.h" #include "maybe.h"
#include "position.h" #include "position.h"
#include "serializer.h" #include "serializer.hh"
#include "utils.h" #include "utils.h"
#include <clang/Basic/FileManager.h> #include <clang/Basic/FileManager.h>
@ -19,6 +19,17 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
namespace std {
template <> struct hash<llvm::sys::fs::UniqueID> {
std::size_t operator()(llvm::sys::fs::UniqueID ID) const {
size_t ret = ID.getDevice();
ccls::hash_combine(ret, ID.getFile());
return ret;
}
};
} // namespace std
namespace ccls {
using Usr = uint64_t; using Usr = uint64_t;
enum class LanguageId; enum class LanguageId;
@ -48,7 +59,6 @@ struct SymbolRef {
bool operator==(const SymbolRef &o) const { return ToTuple() == o.ToTuple(); } bool operator==(const SymbolRef &o) const { return ToTuple() == o.ToTuple(); }
bool Valid() const { return range.Valid(); } bool Valid() const { return range.Valid(); }
}; };
MAKE_HASHABLE(SymbolRef, t.range, t.usr, t.kind, t.role);
struct ExtentRef : SymbolRef { struct ExtentRef : SymbolRef {
Range extent; Range extent;
@ -57,7 +67,6 @@ struct ExtentRef : SymbolRef {
} }
bool operator==(const ExtentRef &o) const { return ToTuple() == o.ToTuple(); } bool operator==(const ExtentRef &o) const { return ToTuple() == o.ToTuple(); }
}; };
MAKE_HASHABLE(ExtentRef, t.range, t.usr, t.kind, t.role, t.extent);
struct Ref { struct Ref {
Range range; Range range;
@ -81,12 +90,10 @@ struct Use : Ref {
return range == o.range && file_id == o.file_id; return range == o.range && file_id == o.file_id;
} }
}; };
MAKE_HASHABLE(Use, t.range, t.file_id)
struct DeclRef : Use { struct DeclRef : Use {
Range extent; Range extent;
}; };
MAKE_HASHABLE(DeclRef, t.range, t.file_id)
void Reflect(Reader &visitor, SymbolRef &value); void Reflect(Reader &visitor, SymbolRef &value);
void Reflect(Writer &visitor, SymbolRef &value); void Reflect(Writer &visitor, SymbolRef &value);
@ -233,16 +240,6 @@ struct IndexInclude {
const char *resolved_path; const char *resolved_path;
}; };
namespace std {
template <> struct hash<llvm::sys::fs::UniqueID> {
std::size_t operator()(llvm::sys::fs::UniqueID ID) const {
size_t ret = ID.getDevice();
hash_combine(ret, ID.getFile());
return ret;
}
};
} // namespace std
struct IndexFile { struct IndexFile {
// For both JSON and MessagePack cache files. // For both JSON and MessagePack cache files.
static const int kMajorVersion; static const int kMajorVersion;
@ -296,7 +293,7 @@ struct CompletionManager;
struct WorkingFiles; struct WorkingFiles;
struct VFS; struct VFS;
namespace ccls::idx { namespace idx {
void Init(); void Init();
std::vector<std::unique_ptr<IndexFile>> std::vector<std::unique_ptr<IndexFile>>
Index(CompletionManager *complete, WorkingFiles *wfiles, VFS *vfs, Index(CompletionManager *complete, WorkingFiles *wfiles, VFS *vfs,
@ -304,4 +301,10 @@ Index(CompletionManager *complete, WorkingFiles *wfiles, VFS *vfs,
const std::vector<const char *> &args, const std::vector<const char *> &args,
const std::vector<std::pair<std::string, std::string>> &remapped, const std::vector<std::pair<std::string, std::string>> &remapped,
bool &ok); bool &ok);
} // namespace ccls::idx } // namespace idx
} // namespace ccls
MAKE_HASHABLE(ccls::SymbolRef, t.range, t.usr, t.kind, t.role);
MAKE_HASHABLE(ccls::ExtentRef, t.range, t.usr, t.kind, t.role, t.extent);
MAKE_HASHABLE(ccls::Use, t.range, t.file_id)
MAKE_HASHABLE(ccls::DeclRef, t.range, t.file_id)

View File

@ -1,18 +1,15 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "lsp.h" #include "lsp.hh"
#include "log.hh" #include "log.hh"
#include "serializers/json.h" #include "serializers/json.h"
#include <rapidjson/document.h>
#include <algorithm> #include <algorithm>
#include <stdio.h> #include <stdio.h>
MethodType kMethodType_Exit = "exit"; namespace ccls {
void Reflect(Reader &visitor, lsRequestId &value) { void Reflect(Reader &visitor, lsRequestId &value) {
if (visitor.IsInt64()) { if (visitor.IsInt64()) {
value.type = lsRequestId::kInt; value.type = lsRequestId::kInt;
@ -44,10 +41,6 @@ void Reflect(Writer &visitor, lsRequestId &value) {
} }
} }
InMessage::~InMessage() {}
MessageRegistry *MessageRegistry::instance_ = nullptr;
lsTextDocumentIdentifier lsTextDocumentIdentifier
lsVersionedTextDocumentIdentifier::AsTextDocumentIdentifier() const { lsVersionedTextDocumentIdentifier::AsTextDocumentIdentifier() const {
lsTextDocumentIdentifier result; lsTextDocumentIdentifier result;
@ -55,120 +48,6 @@ lsVersionedTextDocumentIdentifier::AsTextDocumentIdentifier() const {
return result; return result;
} }
// Reads a JsonRpc message. |read| returns the next input character.
std::optional<std::string>
ReadJsonRpcContentFrom(std::function<std::optional<char>()> read) {
// Read the content length. It is terminated by the "\r\n" sequence.
int exit_seq = 0;
std::string stringified_content_length;
while (true) {
std::optional<char> opt_c = read();
if (!opt_c) {
LOG_S(INFO) << "No more input when reading content length header";
return std::nullopt;
}
char c = *opt_c;
if (exit_seq == 0 && c == '\r')
++exit_seq;
if (exit_seq == 1 && c == '\n')
break;
stringified_content_length += c;
}
const char *kContentLengthStart = "Content-Length: ";
assert(StartsWith(stringified_content_length, kContentLengthStart));
int content_length =
atoi(stringified_content_length.c_str() + strlen(kContentLengthStart));
// There is always a "\r\n" sequence before the actual content.
auto expect_char = [&](char expected) {
std::optional<char> opt_c = read();
return opt_c && *opt_c == expected;
};
if (!expect_char('\r') || !expect_char('\n')) {
LOG_S(INFO) << "Unexpected token (expected \\r\\n sequence)";
return std::nullopt;
}
// Read content.
std::string content;
content.reserve(content_length);
for (int i = 0; i < content_length; ++i) {
std::optional<char> c = read();
if (!c) {
LOG_S(INFO) << "No more input when reading content body";
return std::nullopt;
}
content += *c;
}
return content;
}
std::optional<char> ReadCharFromStdinBlocking() {
// We do not use std::cin because it does not read bytes once stuck in
// cin.bad(). We can call cin.clear() but C++ iostream has other annoyance
// like std::{cin,cout} is tied by default, which causes undesired cout flush
// for cin operations.
int c = getchar();
if (c >= 0)
return c;
return std::nullopt;
}
std::optional<std::string>
MessageRegistry::ReadMessageFromStdin(std::unique_ptr<InMessage> *message) {
std::optional<std::string> content =
ReadJsonRpcContentFrom(&ReadCharFromStdinBlocking);
if (!content) {
LOG_S(ERROR) << "Failed to read JsonRpc input; exiting";
exit(1);
}
rapidjson::Document document;
document.Parse(content->c_str(), content->length());
assert(!document.HasParseError());
JsonReader json_reader{&document};
return Parse(json_reader, message);
}
std::optional<std::string>
MessageRegistry::Parse(Reader &visitor, std::unique_ptr<InMessage> *message) {
if (!visitor.HasMember("jsonrpc") ||
std::string(visitor["jsonrpc"]->GetString()) != "2.0") {
LOG_S(FATAL) << "Bad or missing jsonrpc version";
exit(1);
}
std::string method;
ReflectMember(visitor, "method", method);
if (allocators.find(method) == allocators.end())
return std::string("Unable to find registered handler for method '") +
method + "'";
Allocator &allocator = allocators[method];
try {
allocator(visitor, message);
return std::nullopt;
} catch (std::invalid_argument &e) {
// *message is partially deserialized but some field (e.g. |id|) are likely
// available.
return std::string("Fail to parse '") + method + "' " +
static_cast<JsonReader &>(visitor).GetPath() + ", expected " +
e.what();
}
}
MessageRegistry *MessageRegistry::instance() {
if (!instance_)
instance_ = new MessageRegistry();
return instance_;
}
lsDocumentUri lsDocumentUri::FromPath(const std::string &path) { lsDocumentUri lsDocumentUri::FromPath(const std::string &path) {
lsDocumentUri result; lsDocumentUri result;
result.SetPath(path); result.SetPath(path);
@ -260,16 +139,4 @@ std::string lsPosition::ToString() const {
bool lsTextEdit::operator==(const lsTextEdit &that) { bool lsTextEdit::operator==(const lsTextEdit &that) {
return range == that.range && newText == that.newText; return range == that.range && newText == that.newText;
} }
} // namespace ccls
void Reflect(Writer &visitor, lsMarkedString &value) {
// If there is a language, emit a `{language:string, value:string}` object. If
// not, emit a string.
if (value.language) {
REFLECT_MEMBER_START();
REFLECT_MEMBER(language);
REFLECT_MEMBER(value);
REFLECT_MEMBER_END();
} else {
Reflect(visitor, value.value);
}
}

View File

@ -4,15 +4,15 @@
#pragma once #pragma once
#include "config.h" #include "config.h"
#include "serializer.h" #include "serializer.hh"
#include "utils.h" #include "utils.h"
#include <rapidjson/fwd.h>
#include <iosfwd> #include <iosfwd>
#include <unordered_map> #include <unordered_map>
using MethodType = const char *; namespace ccls {
extern MethodType kMethodType_Exit;
struct lsRequestId { struct lsRequestId {
// The client can send the request id as an int or a string. We should output // The client can send the request id as an int or a string. We should output
// the same format we received. // the same format we received.
@ -27,47 +27,10 @@ void Reflect(Reader &visitor, lsRequestId &value);
void Reflect(Writer &visitor, lsRequestId &value); void Reflect(Writer &visitor, lsRequestId &value);
struct InMessage { struct InMessage {
virtual ~InMessage();
virtual MethodType GetMethodType() const = 0;
virtual lsRequestId GetRequestId() const { return {}; }
};
struct NotificationMessage : InMessage {};
struct RequestMessage : public InMessage {
lsRequestId id; lsRequestId id;
lsRequestId GetRequestId() const override { return id; } std::string method;
}; std::unique_ptr<char[]> message;
std::unique_ptr<rapidjson::Document> document;
#define REGISTER_IN_MESSAGE(type) \
static MessageRegistryRegister<type> type##message_handler_instance_;
struct MessageRegistry {
static MessageRegistry *instance_;
static MessageRegistry *instance();
using Allocator =
std::function<void(Reader &visitor, std::unique_ptr<InMessage> *)>;
std::unordered_map<std::string, Allocator> allocators;
std::optional<std::string>
ReadMessageFromStdin(std::unique_ptr<InMessage> *message);
std::optional<std::string> Parse(Reader &visitor,
std::unique_ptr<InMessage> *message);
};
template <typename T> struct MessageRegistryRegister {
MessageRegistryRegister() {
T dummy;
std::string method_name = dummy.GetMethodType();
MessageRegistry::instance()->allocators[method_name] =
[](Reader &visitor, std::unique_ptr<InMessage> *message) {
*message = std::make_unique<T>();
// Reflect may throw and *message will be partially deserialized.
Reflect(visitor, static_cast<T &>(**message));
};
}
}; };
enum class lsErrorCodes { enum class lsErrorCodes {
@ -214,7 +177,6 @@ struct lsSymbolInformation {
lsLocation location; lsLocation location;
std::optional<std::string_view> containerName; std::optional<std::string_view> containerName;
}; };
MAKE_REFLECT_STRUCT(lsSymbolInformation, name, kind, location, containerName);
struct lsTextDocumentIdentifier { struct lsTextDocumentIdentifier {
lsDocumentUri uri; lsDocumentUri uri;
@ -230,15 +192,6 @@ struct lsVersionedTextDocumentIdentifier {
}; };
MAKE_REFLECT_STRUCT(lsVersionedTextDocumentIdentifier, uri, version); MAKE_REFLECT_STRUCT(lsVersionedTextDocumentIdentifier, uri, version);
struct lsTextDocumentPositionParams {
// The text document.
lsTextDocumentIdentifier textDocument;
// The position inside the text document.
lsPosition position;
};
MAKE_REFLECT_STRUCT(lsTextDocumentPositionParams, textDocument, position);
struct lsTextEdit { struct lsTextEdit {
// The range of the text document to be manipulated. To insert // The range of the text document to be manipulated. To insert
// text into a document create a range where start === end. // text into a document create a range where start === end.
@ -289,26 +242,7 @@ struct lsWorkspaceEdit {
}; };
MAKE_REFLECT_STRUCT(lsWorkspaceEdit, documentChanges); MAKE_REFLECT_STRUCT(lsWorkspaceEdit, documentChanges);
// MarkedString can be used to render human readable text. It is either a struct TextDocumentContentChangeEvent {
// markdown string or a code-block that provides a language and a code snippet.
// The language identifier is sematically equal to the std::optional language
// identifier in fenced code blocks in GitHub issues. See
// https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting
//
// The pair of a language and a value is an equivalent to markdown:
// ```${language}
// ${value}
// ```
//
// Note that markdown strings will be sanitized - that means html will be
// escaped.
struct lsMarkedString {
std::optional<std::string> language;
std::string value;
};
void Reflect(Writer &visitor, lsMarkedString &value);
struct lsTextDocumentContentChangeEvent {
// The range of the document that changed. // The range of the document that changed.
std::optional<lsRange> range; std::optional<lsRange> range;
// The length of the range that got replaced. // The length of the range that got replaced.
@ -316,24 +250,21 @@ struct lsTextDocumentContentChangeEvent {
// The new text of the range/document. // The new text of the range/document.
std::string text; std::string text;
}; };
MAKE_REFLECT_STRUCT(lsTextDocumentContentChangeEvent, range, rangeLength, text);
struct lsTextDocumentDidChangeParams { struct TextDocumentDidChangeParam {
lsVersionedTextDocumentIdentifier textDocument; lsVersionedTextDocumentIdentifier textDocument;
std::vector<lsTextDocumentContentChangeEvent> contentChanges; std::vector<TextDocumentContentChangeEvent> contentChanges;
}; };
MAKE_REFLECT_STRUCT(lsTextDocumentDidChangeParams, textDocument,
contentChanges);
struct lsWorkspaceFolder { struct WorkspaceFolder {
lsDocumentUri uri; lsDocumentUri uri;
std::string name; std::string name;
}; };
MAKE_REFLECT_STRUCT(lsWorkspaceFolder, uri, name); MAKE_REFLECT_STRUCT(WorkspaceFolder, uri, name);
// Show a message to the user. // Show a message to the user.
enum class lsMessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 }; enum class MessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 };
MAKE_REFLECT_TYPE_PROXY(lsMessageType) MAKE_REFLECT_TYPE_PROXY(MessageType)
enum class lsDiagnosticSeverity { enum class lsDiagnosticSeverity {
// Reports an error. // Reports an error.
@ -380,7 +311,7 @@ struct lsPublishDiagnosticsParams {
MAKE_REFLECT_STRUCT(lsPublishDiagnosticsParams, uri, diagnostics); MAKE_REFLECT_STRUCT(lsPublishDiagnosticsParams, uri, diagnostics);
struct lsShowMessageParams { struct lsShowMessageParams {
lsMessageType type = lsMessageType::Error; MessageType type = MessageType::Error;
std::string message; std::string message;
}; };
MAKE_REFLECT_STRUCT(lsShowMessageParams, type, message); MAKE_REFLECT_STRUCT(lsShowMessageParams, type, message);
@ -418,3 +349,4 @@ inline uint16_t operator&(Role lhs, Role rhs) {
inline Role operator|(Role lhs, Role rhs) { inline Role operator|(Role lhs, Role rhs) {
return Role(uint16_t(lhs) | uint16_t(rhs)); return Role(uint16_t(lhs) | uint16_t(rhs));
} }
} // namespace ccls

View File

@ -1,122 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "lsp.h"
// The kind of a completion entry.
enum class lsCompletionItemKind {
Text = 1,
Method = 2,
Function = 3,
Constructor = 4,
Field = 5,
Variable = 6,
Class = 7,
Interface = 8,
Module = 9,
Property = 10,
Unit = 11,
Value = 12,
Enum = 13,
Keyword = 14,
Snippet = 15,
Color = 16,
File = 17,
Reference = 18,
Folder = 19,
EnumMember = 20,
Constant = 21,
Struct = 22,
Event = 23,
Operator = 24,
TypeParameter = 25,
};
MAKE_REFLECT_TYPE_PROXY(lsCompletionItemKind);
// Defines whether the insert text in a completion item should be interpreted as
// plain text or a snippet.
enum class lsInsertTextFormat {
// The primary text to be inserted is treated as a plain string.
PlainText = 1,
// The primary text to be inserted is treated as a snippet.
//
// A snippet can define tab stops and placeholders with `$1`, `$2`
// and `${3:foo}`. `$0` defines the final tab stop, it defaults to
// the end of the snippet. Placeholders with equal identifiers are linked,
// that is typing in one will update others too.
//
// See also:
// https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md
Snippet = 2
};
MAKE_REFLECT_TYPE_PROXY(lsInsertTextFormat);
struct lsCompletionItem {
// A set of function parameters. Used internally for signature help. Not sent
// to vscode.
std::vector<std::string> parameters_;
// The label of this completion item. By default
// also the text that is inserted when selecting
// this completion.
std::string label;
// The kind of this completion item. Based of the kind
// an icon is chosen by the editor.
lsCompletionItemKind kind = lsCompletionItemKind::Text;
// A human-readable string with additional information
// about this item, like type or symbol information.
std::string detail;
// A human-readable string that represents a doc-comment.
std::optional<std::string> documentation;
// Internal information to order candidates.
int score_;
unsigned priority_;
// Use <> or "" by default as include path.
bool use_angle_brackets_ = false;
// A string that shoud be used when comparing this item
// with other items. When `falsy` the label is used.
std::string sortText;
// A string that should be used when filtering a set of
// completion items. When `falsy` the label is used.
std::optional<std::string> filterText;
// A string that should be inserted a document when selecting
// this completion. When `falsy` the label is used.
std::string insertText;
// The format of the insert text. The format applies to both the `insertText`
// property and the `newText` property of a provided `textEdit`.
lsInsertTextFormat insertTextFormat = lsInsertTextFormat::PlainText;
// An edit which is applied to a document when selecting this completion. When
// an edit is provided the value of `insertText` is ignored.
//
// *Note:* The range of the edit must be a single line range and it must
// contain the position at which completion has been requested.
lsTextEdit textEdit;
// An std::optional array of additional text edits that are applied when
// selecting this completion. Edits must not overlap with the main edit
// nor with themselves.
std::vector<lsTextEdit> additionalTextEdits;
// An std::optional command that is executed *after* inserting this
// completion. *Note* that additional modifications to the current document
// should be described with the additionalTextEdits-property. Command command;
// An data entry field that is preserved on a completion item between
// a completion and a completion resolve request.
// data ? : any
};
MAKE_REFLECT_STRUCT(lsCompletionItem, label, kind, detail, documentation,
sortText, filterText, insertText, insertTextFormat,
textEdit, additionalTextEdits);

View File

@ -4,7 +4,7 @@
#include "log.hh" #include "log.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "platform.h" #include "platform.h"
#include "serializer.h" #include "serializer.hh"
#include "serializers/json.h" #include "serializers/json.h"
#include "test.h" #include "test.h"
#include "working_files.h" #include "working_files.h"
@ -28,7 +28,9 @@ using namespace ccls;
using namespace llvm; using namespace llvm;
using namespace llvm::cl; using namespace llvm::cl;
namespace ccls {
std::string g_init_options; std::string g_init_options;
}
namespace { namespace {
OptionCategory C("ccls options"); OptionCategory C("ccls options");
@ -92,7 +94,7 @@ int main(int argc, char **argv) {
if (opt_test_index != "!") { if (opt_test_index != "!") {
language_server = false; language_server = false;
if (!RunIndexTests(opt_test_index, sys::Process::StandardInIsUserInput())) if (!ccls::RunIndexTests(opt_test_index, sys::Process::StandardInIsUserInput()))
return 1; return 1;
} }

View File

@ -3,10 +3,10 @@
#include "match.h" #include "match.h"
#include "lsp.h" #include "lsp.hh"
#include "pipeline.hh" #include "pipeline.hh"
using namespace ccls;
namespace ccls {
std::optional<Matcher> Matcher::Create(const std::string &search) { std::optional<Matcher> Matcher::Create(const std::string &search) {
try { try {
Matcher m; Matcher m;
@ -19,7 +19,7 @@ std::optional<Matcher> Matcher::Create(const std::string &search) {
return m; return m;
} catch (const std::exception &e) { } catch (const std::exception &e) {
lsShowMessageParams params; lsShowMessageParams params;
params.type = lsMessageType::Error; params.type = MessageType::Error;
params.message = params.message =
"failed to parse EMCAScript regex " + search + " : " + e.what(); "failed to parse EMCAScript regex " + search + " : " + e.what();
pipeline::Notify(window_showMessage, params); pipeline::Notify(window_showMessage, params);
@ -62,3 +62,4 @@ bool GroupMatch::IsMatch(const std::string &value,
return true; return true;
} }
} // namespace ccls

View File

@ -9,6 +9,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
namespace ccls {
struct Matcher { struct Matcher {
static std::optional<Matcher> Create(const std::string &search); static std::optional<Matcher> Create(const std::string &search);
@ -29,3 +30,4 @@ struct GroupMatch {
std::vector<Matcher> whitelist; std::vector<Matcher> whitelist;
std::vector<Matcher> blacklist; std::vector<Matcher> blacklist;
}; };
} // namespace ccls

View File

@ -7,6 +7,7 @@
#include <utility> #include <utility>
namespace ccls {
// Like std::optional, but the stored data is responsible for containing the // Like std::optional, but the stored data is responsible for containing the
// empty state. T should define a function `bool T::Valid()`. // empty state. T should define a function `bool T::Valid()`.
template <typename T> class Maybe { template <typename T> class Maybe {
@ -44,3 +45,4 @@ public:
bool operator==(const Maybe &o) const { return storage == o.storage; } bool operator==(const Maybe &o) const { return storage == o.storage; }
bool operator!=(const Maybe &o) const { return !(*this == o); } bool operator!=(const Maybe &o) const { return !(*this == o); }
}; };
} // namespace ccls

View File

@ -1,23 +1,55 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "log.hh" #include "log.hh"
#include "match.h" #include "match.h"
#include "pipeline.hh" #include "pipeline.hh"
#include "project.h" #include "project.hh"
#include "query_utils.h" #include "query_utils.h"
using namespace ccls; #include "serializers/json.h"
using namespace clang; using namespace clang;
#include <algorithm> #include <algorithm>
MAKE_HASHABLE(SymbolIdx, t.usr, t.kind); MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind);
namespace ccls {
MAKE_REFLECT_STRUCT(EmptyParam, placeholder);
MAKE_REFLECT_STRUCT(TextDocumentParam, textDocument);
MAKE_REFLECT_STRUCT(DidOpenTextDocumentParam, textDocument);
MAKE_REFLECT_STRUCT(TextDocumentContentChangeEvent, range, rangeLength, text);
MAKE_REFLECT_STRUCT(TextDocumentDidChangeParam, textDocument, contentChanges);
MAKE_REFLECT_STRUCT(TextDocumentPositionParam, textDocument, position);
MAKE_REFLECT_STRUCT(RenameParam, textDocument, position, newName);
// code*
MAKE_REFLECT_STRUCT(CodeActionParam::Context, diagnostics);
MAKE_REFLECT_STRUCT(CodeActionParam, textDocument, range, context);
// completion
MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind);
MAKE_REFLECT_STRUCT(lsCompletionContext, triggerKind, triggerCharacter);
MAKE_REFLECT_STRUCT(lsCompletionParams, textDocument, position, context);
// formatting
MAKE_REFLECT_STRUCT(FormattingOptions, tabSize, insertSpaces);
MAKE_REFLECT_STRUCT(DocumentFormattingParam, textDocument, options);
MAKE_REFLECT_STRUCT(DocumentOnTypeFormattingParam, textDocument, position,
ch, options);
MAKE_REFLECT_STRUCT(DocumentRangeFormattingParam, textDocument, range, options);
// workspace
MAKE_REFLECT_TYPE_PROXY(FileChangeType);
MAKE_REFLECT_STRUCT(DidChangeWatchedFilesParam::Event, uri, type);
MAKE_REFLECT_STRUCT(DidChangeWatchedFilesParam, changes);
MAKE_REFLECT_STRUCT(DidChangeWorkspaceFoldersParam::Event, added, removed);
MAKE_REFLECT_STRUCT(DidChangeWorkspaceFoldersParam, event);
MAKE_REFLECT_STRUCT(WorkspaceSymbolParam, query);
namespace { namespace {
struct CclsSemanticHighlightSymbol { struct CclsSemanticHighlightSymbol {
int id = 0; int id = 0;
lsSymbolKind parentKind; lsSymbolKind parentKind;
@ -43,6 +75,7 @@ struct CclsSetSkippedRangesParams {
}; };
MAKE_REFLECT_STRUCT(CclsSetSkippedRangesParams, uri, skippedRanges); MAKE_REFLECT_STRUCT(CclsSetSkippedRangesParams, uri, skippedRanges);
struct ScanLineEvent { struct ScanLineEvent {
lsPosition pos; lsPosition pos;
lsPosition end_pos; // Second key when there is a tie for insertion events. lsPosition end_pos; // Second key when there is a tie for insertion events.
@ -61,55 +94,161 @@ struct ScanLineEvent {
}; };
} // namespace } // namespace
MessageHandler::MessageHandler() { void MessageHandler::Bind(const char *method, void (MessageHandler::*handler)(Reader &)) {
// Dynamically allocate |message_handlers|, otherwise there will be static method2notification[method] = [this, handler](Reader &reader) {
// initialization order races. (this->*handler)(reader);
if (!message_handlers) };
message_handlers = new std::vector<MessageHandler *>();
message_handlers->push_back(this);
} }
std::vector<MessageHandler *> *MessageHandler::message_handlers = nullptr; template <typename Param>
void MessageHandler::Bind(const char *method,
void (MessageHandler::*handler)(Param &)) {
method2notification[method] = [this, handler](Reader &reader) {
Param param{};
Reflect(reader, param);
(this->*handler)(param);
};
}
bool FindFileOrFail(DB *db, Project *project, std::optional<lsRequestId> id, void MessageHandler::Bind(const char *method,
const std::string &absolute_path, void (MessageHandler::*handler)(Reader &,
QueryFile **out_query_file, int *out_file_id) { ReplyOnce &)) {
*out_query_file = nullptr; method2request[method] = [this, handler](Reader &reader, ReplyOnce &reply) {
(this->*handler)(reader, reply);
};
}
auto it = db->name2file_id.find(LowerPathIfInsensitive(absolute_path)); template <typename Param>
void MessageHandler::Bind(const char *method,
void (MessageHandler::*handler)(Param &,
ReplyOnce &)) {
method2request[method] = [this, handler](Reader &reader, ReplyOnce &reply) {
Param param{};
Reflect(reader, param);
(this->*handler)(param, reply);
};
}
MessageHandler::MessageHandler() {
Bind("$ccls/call", &MessageHandler::ccls_call);
Bind("$ccls/fileInfo", &MessageHandler::ccls_fileInfo);
Bind("$ccls/info", &MessageHandler::ccls_info);
Bind("$ccls/inheritance", &MessageHandler::ccls_inheritance);
Bind("$ccls/member", &MessageHandler::ccls_member);
Bind("$ccls/navigate", &MessageHandler::ccls_navigate);
Bind("$ccls/reload", &MessageHandler::ccls_reload);
Bind("$ccls/vars", &MessageHandler::ccls_vars);
Bind("exit", &MessageHandler::exit);
Bind("initialize", &MessageHandler::initialize);
Bind("shutdown", &MessageHandler::shutdown);
Bind("textDocument/codeAction", &MessageHandler::textDocument_codeAction);
Bind("textDocument/codeLens", &MessageHandler::textDocument_codeLens);
Bind("textDocument/completion", &MessageHandler::textDocument_completion);
Bind("textDocument/definition", &MessageHandler::textDocument_definition);
Bind("textDocument/didChange", &MessageHandler::textDocument_didChange);
Bind("textDocument/didClose", &MessageHandler::textDocument_didClose);
Bind("textDocument/didOpen", &MessageHandler::textDocument_didOpen);
Bind("textDocument/didSave", &MessageHandler::textDocument_didSave);
Bind("textDocument/documentHighlight",
&MessageHandler::textDocument_documentHighlight);
Bind("textDocument/documentLink", &MessageHandler::textDocument_documentLink);
Bind("textDocument/documentSymbol",
&MessageHandler::textDocument_documentSymbol);
Bind("textDocument/foldingRange", &MessageHandler::textDocument_foldingRange);
Bind("textDocument/formatting", &MessageHandler::textDocument_formatting);
Bind("textDocument/hover", &MessageHandler::textDocument_hover);
Bind("textDocument/implementation",
&MessageHandler::textDocument_implementation);
Bind("textDocument/onTypeFormatting",
&MessageHandler::textDocument_onTypeFormatting);
Bind("textDocument/rangeFormatting",
&MessageHandler::textDocument_rangeFormatting);
Bind("textDocument/references", &MessageHandler::textDocument_references);
Bind("textDocument/rename", &MessageHandler::textDocument_rename);
Bind("textDocument/signatureHelp",
&MessageHandler::textDocument_signatureHelp);
Bind("textDocument/typeDefinition",
&MessageHandler::textDocument_typeDefinition);
Bind("workspace/didChangeConfiguration",
&MessageHandler::workspace_didChangeConfiguration);
Bind("workspace/didChangeWatchedFiles",
&MessageHandler::workspace_didChangeWatchedFiles);
Bind("workspace/didChangeWorkspaceFolders",
&MessageHandler::workspace_didChangeWorkspaceFolders);
Bind("workspace/executeCommand", &MessageHandler::workspace_executeCommand);
Bind("workspace/symbol", &MessageHandler::workspace_symbol);
}
void MessageHandler::Run(InMessage &msg) {
rapidjson::Document &doc = *msg.document;
rapidjson::Value param;
auto it = doc.FindMember("params");
if (it != doc.MemberEnd())
param = it->value;
JsonReader reader(&param);
if (msg.id.Valid()) {
ReplyOnce reply{msg.id};
auto it = method2request.find(msg.method);
if (it != method2request.end()) {
try {
it->second(reader, reply);
} catch (...) {
LOG_S(ERROR) << "failed to process request " << msg.method;
}
} else {
lsResponseError err;
err.code = lsErrorCodes::MethodNotFound;
err.message = "unknown request " + msg.method;
reply.Error(err);
}
} else {
auto it = method2notification.find(msg.method);
if (it != method2notification.end())
try {
it->second(reader);
} catch (...) {
LOG_S(ERROR) << "failed to process " << msg.method;
}
}
}
QueryFile *MessageHandler::FindFile(ReplyOnce &reply,
const std::string &path,
int *out_file_id) {
QueryFile *ret = nullptr;
auto it = db->name2file_id.find(LowerPathIfInsensitive(path));
if (it != db->name2file_id.end()) { if (it != db->name2file_id.end()) {
QueryFile &file = db->files[it->second]; QueryFile &file = db->files[it->second];
if (file.def) { if (file.def) {
*out_query_file = &file; ret = &file;
if (out_file_id) if (out_file_id)
*out_file_id = it->second; *out_file_id = it->second;
return true; return ret;
} }
} }
if (out_file_id) if (out_file_id)
*out_file_id = -1; *out_file_id = -1;
bool has_entry = false; if (reply.id.Valid()) {
{ bool has_entry = false;
std::lock_guard<std::mutex> lock(project->mutex_); {
for (auto &[root, folder] : project->root2folder) std::lock_guard<std::mutex> lock(project->mutex_);
has_entry |= folder.path2entry_index.count(absolute_path); for (auto &[root, folder] : project->root2folder)
} has_entry |= folder.path2entry_index.count(path);
}
if (id) {
lsResponseError err; lsResponseError err;
if (has_entry) { if (has_entry) {
err.code = lsErrorCodes::ServerNotInitialized; err.code = lsErrorCodes::ServerNotInitialized;
err.message = absolute_path + " is being indexed"; err.message = path + " is being indexed";
} else { } else {
err.code = lsErrorCodes::InternalError; err.code = lsErrorCodes::InternalError;
err.message = "unable to find " + absolute_path; err.message = "unable to find " + path;
} }
pipeline::ReplyError(*id, err); reply.Error(err);
} }
return false; return ret;
} }
void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file) { void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file) {
@ -310,3 +449,4 @@ void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
params.symbols.push_back(std::move(entry.second)); params.symbols.push_back(std::move(entry.second));
pipeline::Notify("$ccls/publishSemanticHighlight", params); pipeline::Notify("$ccls/publishSemanticHighlight", params);
} }
} // namespace ccls

View File

@ -1,71 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "lsp.h"
#include "query.h"
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
struct CompletionManager;
struct Config;
struct GroupMatch;
struct VFS;
struct IncludeComplete;
struct MultiQueueWaiter;
struct Project;
struct DB;
struct WorkingFile;
struct WorkingFiles;
// Usage:
//
// struct FooHandler : MessageHandler {
// // ...
// };
// REGISTER_MESSAGE_HANDLER(FooHandler);
//
// Then there will be a global FooHandler instance in
// |MessageHandler::message_handlers|.
#define REGISTER_MESSAGE_HANDLER(type) \
static type type##message_handler_instance_;
struct MessageHandler {
DB *db = nullptr;
Project *project = nullptr;
VFS *vfs = nullptr;
WorkingFiles *working_files = nullptr;
CompletionManager *clang_complete = nullptr;
IncludeComplete *include_complete = nullptr;
virtual MethodType GetMethodType() const = 0;
virtual void Run(std::unique_ptr<InMessage> message) = 0;
static std::vector<MessageHandler *> *message_handlers;
protected:
MessageHandler();
};
template <typename TMessage> struct BaseMessageHandler : MessageHandler {
virtual void Run(TMessage *message) = 0;
// MessageHandler:
void Run(std::unique_ptr<InMessage> message) override {
Run(static_cast<TMessage *>(message.get()));
}
};
bool FindFileOrFail(DB *db, Project *project, std::optional<lsRequestId> id,
const std::string &absolute_path,
QueryFile **out_query_file, int *out_file_id = nullptr);
void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file);
void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file);

253
src/message_handler.hh Normal file
View File

@ -0,0 +1,253 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "lsp.hh"
#include "query.h"
#include <functional>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
namespace ccls {
struct CompletionManager;
struct Config;
struct GroupMatch;
struct VFS;
struct IncludeComplete;
struct MultiQueueWaiter;
struct Project;
struct DB;
struct WorkingFile;
struct WorkingFiles;
namespace pipeline {
void Reply(lsRequestId id, const std::function<void(Writer &)> &fn);
void ReplyError(lsRequestId id, const std::function<void(Writer &)> &fn);
}
struct EmptyParam {
bool placeholder;
};
struct TextDocumentParam {
lsTextDocumentIdentifier textDocument;
};
struct DidOpenTextDocumentParam {
lsTextDocumentItem textDocument;
};
struct TextDocumentPositionParam {
lsTextDocumentIdentifier textDocument;
lsPosition position;
};
struct RenameParam {
lsTextDocumentIdentifier textDocument;
lsPosition position;
std::string newName;
};
// code*
struct CodeActionParam {
lsTextDocumentIdentifier textDocument;
lsRange range;
struct Context {
std::vector<lsDiagnostic> diagnostics;
} context;
};
// completion
enum class lsCompletionTriggerKind {
Invoked = 1,
TriggerCharacter = 2,
TriggerForIncompleteCompletions = 3,
};
struct lsCompletionContext {
lsCompletionTriggerKind triggerKind = lsCompletionTriggerKind::Invoked;
std::optional<std::string> triggerCharacter;
};
struct lsCompletionParams : TextDocumentPositionParam {
lsCompletionContext context;
};
enum class lsCompletionItemKind {
Text = 1,
Method = 2,
Function = 3,
Constructor = 4,
Field = 5,
Variable = 6,
Class = 7,
Interface = 8,
Module = 9,
Property = 10,
Unit = 11,
Value = 12,
Enum = 13,
Keyword = 14,
Snippet = 15,
Color = 16,
File = 17,
Reference = 18,
Folder = 19,
EnumMember = 20,
Constant = 21,
Struct = 22,
Event = 23,
Operator = 24,
TypeParameter = 25,
};
enum class lsInsertTextFormat {
PlainText = 1,
Snippet = 2
};
struct lsCompletionItem {
std::string label;
lsCompletionItemKind kind = lsCompletionItemKind::Text;
std::string detail;
std::optional<std::string> documentation;
std::string sortText;
std::optional<std::string> filterText;
std::string insertText;
lsInsertTextFormat insertTextFormat = lsInsertTextFormat::PlainText;
lsTextEdit textEdit;
std::vector<lsTextEdit> additionalTextEdits;
std::vector<std::string> parameters_;
int score_;
unsigned priority_;
bool use_angle_brackets_ = false;
};
// formatting
struct FormattingOptions {
int tabSize;
bool insertSpaces;
};
struct DocumentFormattingParam {
lsTextDocumentIdentifier textDocument;
FormattingOptions options;
};
struct DocumentOnTypeFormattingParam {
lsTextDocumentIdentifier textDocument;
lsPosition position;
std::string ch;
FormattingOptions options;
};
struct DocumentRangeFormattingParam {
lsTextDocumentIdentifier textDocument;
lsRange range;
FormattingOptions options;
};
// workspace
enum class FileChangeType {
Created = 1,
Changed = 2,
Deleted = 3,
};
struct DidChangeWatchedFilesParam {
struct Event {
lsDocumentUri uri;
FileChangeType type;
};
std::vector<Event> changes;
};
struct DidChangeWorkspaceFoldersParam {
struct Event {
std::vector<WorkspaceFolder> added, removed;
} event;
};
struct WorkspaceSymbolParam {
std::string query;
};
// TODO llvm 8 llvm::unique_function
template <typename Res>
using Callback = std::function<void(Res*)>;
struct ReplyOnce {
lsRequestId id;
template <typename Res> void operator()(Res &result) const {
if (id.Valid())
pipeline::Reply(id, [&](Writer &w) { Reflect(w, result); });
}
template <typename Err> void Error(Err &err) const {
if (id.Valid())
pipeline::ReplyError(id, [&](Writer &w) { Reflect(w, err); });
}
};
struct MessageHandler {
DB *db = nullptr;
Project *project = nullptr;
VFS *vfs = nullptr;
WorkingFiles *working_files = nullptr;
CompletionManager *clang_complete = nullptr;
IncludeComplete *include_complete = nullptr;
llvm::StringMap<std::function<void(Reader &)>> method2notification;
llvm::StringMap<std::function<void(Reader &, ReplyOnce &)>> method2request;
MessageHandler();
void Run(InMessage &msg);
QueryFile *FindFile(ReplyOnce &reply, const std::string &path,
int *out_file_id = nullptr);
private:
void Bind(const char *method, void (MessageHandler::*handler)(Reader &));
template <typename Param>
void Bind(const char *method, void (MessageHandler::*handler)(Param &));
void Bind(const char *method,
void (MessageHandler::*handler)(Reader &, ReplyOnce &));
template <typename Param>
void Bind(const char *method,
void (MessageHandler::*handler)(Param &, ReplyOnce &));
void ccls_call(Reader &, ReplyOnce &);
void ccls_fileInfo(TextDocumentParam &, ReplyOnce &);
void ccls_info(EmptyParam &, ReplyOnce &);
void ccls_inheritance(Reader &, ReplyOnce &);
void ccls_member(Reader &, ReplyOnce &);
void ccls_navigate(Reader &, ReplyOnce &);
void ccls_reload(Reader &);
void ccls_vars(Reader &, ReplyOnce &);
void exit(EmptyParam &);
void initialize(Reader &, ReplyOnce &);
void shutdown(EmptyParam &, ReplyOnce &);
void textDocument_codeAction(CodeActionParam &, ReplyOnce &);
void textDocument_codeLens(TextDocumentParam &, ReplyOnce &);
void textDocument_completion(lsCompletionParams &, ReplyOnce &);
void textDocument_definition(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_didChange(TextDocumentDidChangeParam &);
void textDocument_didClose(TextDocumentParam &);
void textDocument_didOpen(DidOpenTextDocumentParam &);
void textDocument_didSave(TextDocumentParam &);
void textDocument_documentHighlight(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_documentLink(TextDocumentParam &, ReplyOnce &);
void textDocument_documentSymbol(Reader &, ReplyOnce &);
void textDocument_foldingRange(TextDocumentParam &, ReplyOnce &);
void textDocument_formatting(DocumentFormattingParam &, ReplyOnce &);
void textDocument_hover(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_implementation(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &,
ReplyOnce &);
void textDocument_rangeFormatting(DocumentRangeFormattingParam &,
ReplyOnce &);
void textDocument_references(Reader &, ReplyOnce &);
void textDocument_rename(RenameParam &, ReplyOnce &);
void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &);
void workspace_didChangeConfiguration(EmptyParam &);
void workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam &);
void workspace_didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParam &);
void workspace_executeCommand(Reader &, ReplyOnce &);
void workspace_symbol(WorkspaceSymbolParam &, ReplyOnce &);
};
void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file);
void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file);
} // namespace ccls

View File

@ -2,18 +2,16 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "hierarchy.hh" #include "hierarchy.hh"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
#include <unordered_set> #include <unordered_set>
using namespace ccls; namespace ccls {
namespace { namespace {
MethodType kMethodType = "$ccls/call";
enum class CallType : uint8_t { enum class CallType : uint8_t {
Direct = 0, Direct = 0,
Base = 1, Base = 1,
@ -26,34 +24,24 @@ bool operator&(CallType lhs, CallType rhs) {
return uint8_t(lhs) & uint8_t(rhs); return uint8_t(lhs) & uint8_t(rhs);
} }
struct In_cclsCall : public RequestMessage { struct Param : TextDocumentPositionParam {
MethodType GetMethodType() const override { return kMethodType; } // If id is specified, expand a node; otherwise textDocument+position should
// be specified for building the root and |levels| of nodes below.
Usr usr;
std::string id;
struct Params { // true: callee tree (functions called by this function); false: caller tree
// If id is specified, expand a node; otherwise textDocument+position should // (where this function is called)
// be specified for building the root and |levels| of nodes below. bool callee = false;
lsTextDocumentIdentifier textDocument; // Base: include base functions; All: include both base and derived
lsPosition position; // functions.
CallType callType = CallType::All;
Usr usr; bool qualified = true;
std::string id; int levels = 1;
bool hierarchy = false;
// true: callee tree (functions called by this function); false: caller tree
// (where this function is called)
bool callee = false;
// Base: include base functions; All: include both base and derived
// functions.
CallType callType = CallType::All;
bool qualified = true;
int levels = 1;
bool hierarchy = false;
};
Params params;
}; };
MAKE_REFLECT_STRUCT(In_cclsCall::Params, textDocument, position, id, callee, MAKE_REFLECT_STRUCT(Param, textDocument, position, id, callee, callType,
callType, qualified, levels, hierarchy); qualified, levels, hierarchy);
MAKE_REFLECT_STRUCT(In_cclsCall, id, params);
REGISTER_IN_MESSAGE(In_cclsCall);
struct Out_cclsCall { struct Out_cclsCall {
Usr usr; Usr usr;
@ -163,70 +151,64 @@ bool Expand(MessageHandler *m, Out_cclsCall *entry, bool callee,
return true; return true;
} }
struct Handler_cclsCall : BaseMessageHandler<In_cclsCall> { std::optional<Out_cclsCall> BuildInitial(MessageHandler *m, Usr root_usr,
MethodType GetMethodType() const override { return kMethodType; } bool callee, CallType call_type,
bool qualified, int levels) {
const auto *def = m->db->Func(root_usr).AnyDef();
if (!def)
return {};
std::optional<Out_cclsCall> BuildInitial(Usr root_usr, bool callee, Out_cclsCall entry;
CallType call_type, bool qualified, entry.id = std::to_string(root_usr);
int levels) { entry.usr = root_usr;
const auto *def = db->Func(root_usr).AnyDef(); entry.callType = CallType::Direct;
if (!def) if (def->spell) {
return {}; if (auto loc = GetLsLocation(m->db, m->working_files, *def->spell))
entry.location = *loc;
Out_cclsCall entry;
entry.id = std::to_string(root_usr);
entry.usr = root_usr;
entry.callType = CallType::Direct;
if (def->spell) {
if (std::optional<lsLocation> loc =
GetLsLocation(db, working_files, *def->spell))
entry.location = *loc;
}
Expand(this, &entry, callee, call_type, qualified, levels);
return entry;
} }
Expand(m, &entry, callee, call_type, qualified, levels);
void Run(In_cclsCall *request) override { return entry;
auto &params = request->params; }
std::optional<Out_cclsCall> result;
if (params.id.size()) {
try {
params.usr = std::stoull(params.id);
} catch (...) {
return;
}
result.emplace();
result->id = std::to_string(params.usr);
result->usr = params.usr;
result->callType = CallType::Direct;
if (db->HasFunc(params.usr))
Expand(this, &*result, params.callee, params.callType, params.qualified,
params.levels);
} else {
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *working_file =
working_files->GetFileByFilename(file->def->path);
for (SymbolRef sym :
FindSymbolsAtLocation(working_file, file, params.position)) {
if (sym.kind == SymbolKind::Func) {
result = BuildInitial(sym.usr, params.callee, params.callType,
params.qualified, params.levels);
break;
}
}
}
if (params.hierarchy)
pipeline::Reply(request->id, result);
else {
auto out = FlattenHierarchy(result);
pipeline::Reply(request->id, out);
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_cclsCall);
} // namespace } // namespace
void MessageHandler::ccls_call(Reader &reader, ReplyOnce &reply) {
Param param;
Reflect(reader, param);
std::optional<Out_cclsCall> result;
if (param.id.size()) {
try {
param.usr = std::stoull(param.id);
} catch (...) {
return;
}
result.emplace();
result->id = std::to_string(param.usr);
result->usr = param.usr;
result->callType = CallType::Direct;
if (db->HasFunc(param.usr))
Expand(this, &*result, param.callee, param.callType, param.qualified,
param.levels);
} else {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *working_file =
working_files->GetFileByFilename(file->def->path);
for (SymbolRef sym :
FindSymbolsAtLocation(working_file, file, param.position)) {
if (sym.kind == SymbolKind::Func) {
result = BuildInitial(this, sym.usr, param.callee, param.callType,
param.qualified, param.levels);
break;
}
}
}
if (param.hierarchy)
reply(result);
else {
auto out = FlattenHierarchy(result);
reply(out);
}
}
} // namespace ccls

View File

@ -1,24 +1,16 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "project.h" #include "project.hh"
#include "query_utils.h" #include "query_utils.h"
using namespace ccls;
namespace ccls {
MAKE_REFLECT_STRUCT(QueryFile::Def, path, args, language, skipped_ranges, MAKE_REFLECT_STRUCT(QueryFile::Def, path, args, language, skipped_ranges,
dependencies); dependencies);
namespace { namespace {
MethodType cclsInfo = "$ccls/info", fileInfo = "$ccls/fileInfo";
struct In_cclsInfo : public RequestMessage {
MethodType GetMethodType() const override { return cclsInfo; }
};
MAKE_REFLECT_STRUCT(In_cclsInfo, id);
REGISTER_IN_MESSAGE(In_cclsInfo);
struct Out_cclsInfo { struct Out_cclsInfo {
struct DB { struct DB {
int files, funcs, types, vars; int files, funcs, types, vars;
@ -34,51 +26,33 @@ MAKE_REFLECT_STRUCT(Out_cclsInfo::DB, files, funcs, types, vars);
MAKE_REFLECT_STRUCT(Out_cclsInfo::Pipeline, pendingIndexRequests); MAKE_REFLECT_STRUCT(Out_cclsInfo::Pipeline, pendingIndexRequests);
MAKE_REFLECT_STRUCT(Out_cclsInfo::Project, entries); MAKE_REFLECT_STRUCT(Out_cclsInfo::Project, entries);
MAKE_REFLECT_STRUCT(Out_cclsInfo, db, pipeline, project); MAKE_REFLECT_STRUCT(Out_cclsInfo, db, pipeline, project);
struct Handler_cclsInfo : BaseMessageHandler<In_cclsInfo> {
MethodType GetMethodType() const override { return cclsInfo; }
void Run(In_cclsInfo *request) override {
Out_cclsInfo result;
result.db.files = db->files.size();
result.db.funcs = db->funcs.size();
result.db.types = db->types.size();
result.db.vars = db->vars.size();
result.pipeline.pendingIndexRequests = pipeline::pending_index_requests;
result.project.entries = 0;
for (auto &[_, folder] : project->root2folder)
result.project.entries += folder.entries.size();
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_cclsInfo);
struct In_cclsFileInfo : public RequestMessage {
MethodType GetMethodType() const override { return fileInfo; }
struct Params {
lsTextDocumentIdentifier textDocument;
} params;
};
MAKE_REFLECT_STRUCT(In_cclsFileInfo::Params, textDocument);
MAKE_REFLECT_STRUCT(In_cclsFileInfo, id, params);
REGISTER_IN_MESSAGE(In_cclsFileInfo);
struct Handler_cclsFileInfo : BaseMessageHandler<In_cclsFileInfo> {
MethodType GetMethodType() const override { return fileInfo; }
void Run(In_cclsFileInfo *request) override {
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
request->params.textDocument.uri.GetPath(), &file))
return;
QueryFile::Def result;
// Expose some fields of |QueryFile::Def|.
result.path = file->def->path;
result.args = file->def->args;
result.language = file->def->language;
result.includes = file->def->includes;
result.skipped_ranges = file->def->skipped_ranges;
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_cclsFileInfo);
} // namespace } // namespace
void MessageHandler::ccls_info(EmptyParam &, ReplyOnce &reply) {
Out_cclsInfo result;
result.db.files = db->files.size();
result.db.funcs = db->funcs.size();
result.db.types = db->types.size();
result.db.vars = db->vars.size();
result.pipeline.pendingIndexRequests = pipeline::pending_index_requests;
result.project.entries = 0;
for (auto &[_, folder] : project->root2folder)
result.project.entries += folder.entries.size();
reply(result);
}
void MessageHandler::ccls_fileInfo(TextDocumentParam &param, ReplyOnce &reply) {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
QueryFile::Def result;
// Expose some fields of |QueryFile::Def|.
result.path = file->def->path;
result.args = file->def->args;
result.language = file->def->language;
result.includes = file->def->includes;
result.skipped_ranges = file->def->skipped_ranges;
reply(result);
}
} // namespace ccls

View File

@ -2,41 +2,30 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "hierarchy.hh" #include "hierarchy.hh"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
using namespace ccls;
#include <unordered_set> #include <unordered_set>
namespace ccls {
namespace { namespace {
MethodType kMethodType = "$ccls/inheritance", struct Param : TextDocumentPositionParam {
implementation = "textDocument/implementation"; // If id+kind are specified, expand a node; otherwise textDocument+position
// should be specified for building the root and |levels| of nodes below.
Usr usr;
std::string id;
SymbolKind kind = SymbolKind::Invalid;
struct In_cclsInheritance : public RequestMessage { // true: derived classes/functions; false: base classes/functions
MethodType GetMethodType() const override { return kMethodType; } bool derived = false;
struct Params { bool qualified = true;
// If id+kind are specified, expand a node; otherwise textDocument+position int levels = 1;
// should be specified for building the root and |levels| of nodes below. bool hierarchy = false;
lsTextDocumentIdentifier textDocument;
lsPosition position;
Usr usr;
std::string id;
SymbolKind kind = SymbolKind::Invalid;
// true: derived classes/functions; false: base classes/functions
bool derived = false;
bool qualified = true;
int levels = 1;
bool hierarchy = false;
} params;
}; };
MAKE_REFLECT_STRUCT(In_cclsInheritance::Params, textDocument, position, id, MAKE_REFLECT_STRUCT(Param, textDocument, position, id, kind, derived, qualified,
kind, derived, qualified, levels, hierarchy); levels, hierarchy);
MAKE_REFLECT_STRUCT(In_cclsInheritance, id, params);
REGISTER_IN_MESSAGE(In_cclsInheritance);
struct Out_cclsInheritance { struct Out_cclsInheritance {
Usr usr; Usr usr;
@ -118,85 +107,68 @@ bool Expand(MessageHandler *m, Out_cclsInheritance *entry, bool derived,
m->db->Type(entry->usr)); m->db->Type(entry->usr));
} }
struct Handler_cclsInheritance : BaseMessageHandler<In_cclsInheritance> { std::optional<Out_cclsInheritance> BuildInitial(MessageHandler *m, SymbolRef sym, bool derived,
MethodType GetMethodType() const override { return kMethodType; } bool qualified, int levels) {
Out_cclsInheritance entry;
entry.id = std::to_string(sym.usr);
entry.usr = sym.usr;
entry.kind = sym.kind;
Expand(m, &entry, derived, qualified, levels);
return entry;
}
std::optional<Out_cclsInheritance> BuildInitial(SymbolRef sym, bool derived, void Inheritance(MessageHandler *m, Param &param, ReplyOnce &reply) {
bool qualified, int levels) { std::optional<Out_cclsInheritance> result;
Out_cclsInheritance entry; if (param.id.size()) {
entry.id = std::to_string(sym.usr); try {
entry.usr = sym.usr; param.usr = std::stoull(param.id);
entry.kind = sym.kind; } catch (...) {
Expand(this, &entry, derived, qualified, levels); return;
return entry; }
} result.emplace();
result->id = std::to_string(param.usr);
result->usr = param.usr;
result->kind = param.kind;
if (!(((param.kind == SymbolKind::Func && m->db->HasFunc(param.usr)) ||
(param.kind == SymbolKind::Type && m->db->HasType(param.usr))) &&
Expand(m, &*result, param.derived, param.qualified,
param.levels)))
result.reset();
} else {
QueryFile *file = m->FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *wfile = m->working_files->GetFileByFilename(file->def->path);
void Run(In_cclsInheritance *request) override { for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position))
auto &params = request->params; if (sym.kind == SymbolKind::Func || sym.kind == SymbolKind::Type) {
std::optional<Out_cclsInheritance> result; result = BuildInitial(m, sym, param.derived, param.qualified,
if (params.id.size()) { param.levels);
try { break;
params.usr = std::stoull(params.id);
} catch (...) {
return;
} }
result.emplace();
result->id = std::to_string(params.usr);
result->usr = params.usr;
result->kind = params.kind;
if (!(((params.kind == SymbolKind::Func && db->HasFunc(params.usr)) ||
(params.kind == SymbolKind::Type && db->HasType(params.usr))) &&
Expand(this, &*result, params.derived, params.qualified,
params.levels)))
result.reset();
} else {
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, params.position))
if (sym.kind == SymbolKind::Func || sym.kind == SymbolKind::Type) {
result = BuildInitial(sym, params.derived, params.qualified,
params.levels);
break;
}
}
if (params.hierarchy)
pipeline::Reply(request->id, result);
else {
auto out = FlattenHierarchy(result);
pipeline::Reply(request->id, out);
}
} }
};
REGISTER_MESSAGE_HANDLER(Handler_cclsInheritance);
struct In_textDocumentImplementation : public RequestMessage { if (param.hierarchy)
MethodType GetMethodType() const override { return implementation; } reply(result);
lsTextDocumentPositionParams params; else {
}; auto out = FlattenHierarchy(result);
MAKE_REFLECT_STRUCT(In_textDocumentImplementation, id, params); reply(out);
REGISTER_IN_MESSAGE(In_textDocumentImplementation);
struct Handler_textDocumentImplementation
: BaseMessageHandler<In_textDocumentImplementation> {
MethodType GetMethodType() const override { return implementation; }
void Run(In_textDocumentImplementation *request) override {
Handler_cclsInheritance handler;
handler.db = db;
handler.project = project;
handler.working_files = working_files;
In_cclsInheritance request1;
request1.id = request->id;
request1.params.textDocument = request->params.textDocument;
request1.params.position = request->params.position;
request1.params.derived = true;
handler.Run(&request1);
} }
}; }
REGISTER_MESSAGE_HANDLER(Handler_textDocumentImplementation);
} // namespace } // namespace
void MessageHandler::ccls_inheritance(Reader &reader, ReplyOnce &reply) {
Param param;
Reflect(reader, param);
Inheritance(this, param, reply);
}
void MessageHandler::textDocument_implementation(
TextDocumentPositionParam &param, ReplyOnce &reply) {
Param param1;
param1.textDocument = param.textDocument;
param1.position = param.position;
param1.derived = true;
Inheritance(this, param1, reply);
}
} // namespace ccls

View File

@ -3,7 +3,7 @@
#include "clang_tu.hh" #include "clang_tu.hh"
#include "hierarchy.hh" #include "hierarchy.hh"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
@ -12,38 +12,26 @@
#include <unordered_set> #include <unordered_set>
using namespace ccls; namespace ccls {
using namespace clang; using namespace clang;
namespace { namespace {
MethodType kMethodType = "$ccls/member"; struct Param : TextDocumentPositionParam {
// If id is specified, expand a node; otherwise textDocument+position should
// be specified for building the root and |levels| of nodes below.
Usr usr;
std::string id;
struct In_cclsMember : public RequestMessage { bool qualified = false;
MethodType GetMethodType() const override { return kMethodType; } int levels = 1;
// If SymbolKind::Func and the point is at a type, list member functions
struct Params { // instead of member variables.
// If id is specified, expand a node; otherwise textDocument+position should SymbolKind kind = SymbolKind::Var;
// be specified for building the root and |levels| of nodes below. bool hierarchy = false;
lsTextDocumentIdentifier textDocument;
lsPosition position;
// Type
Usr usr;
std::string id;
bool qualified = false;
int levels = 1;
// If SymbolKind::Func and the point is at a type, list member functions
// instead of member variables.
SymbolKind kind = SymbolKind::Var;
bool hierarchy = false;
} params;
}; };
MAKE_REFLECT_STRUCT(In_cclsMember::Params, textDocument, position, id, MAKE_REFLECT_STRUCT(Param, textDocument, position, id, qualified, levels, kind,
qualified, levels, kind, hierarchy); hierarchy);
MAKE_REFLECT_STRUCT(In_cclsMember, id, params);
REGISTER_IN_MESSAGE(In_cclsMember);
struct Out_cclsMember { struct Out_cclsMember {
Usr usr; Usr usr;
@ -221,106 +209,99 @@ bool Expand(MessageHandler *m, Out_cclsMember *entry, bool qualified,
return true; return true;
} }
struct Handler_cclsMember : BaseMessageHandler<In_cclsMember> { std::optional<Out_cclsMember> BuildInitial(MessageHandler *m, SymbolKind kind,
MethodType GetMethodType() const override { return kMethodType; } Usr root_usr, bool qualified,
int levels, SymbolKind memberKind) {
std::optional<Out_cclsMember> BuildInitial(SymbolKind kind, Usr root_usr, switch (kind) {
bool qualified, int levels, default:
SymbolKind memberKind) { return {};
switch (kind) { case SymbolKind::Func: {
default: const auto *def = m->db->Func(root_usr).AnyDef();
if (!def)
return {}; return {};
case SymbolKind::Func: {
const auto *def = db->Func(root_usr).AnyDef();
if (!def)
return {};
Out_cclsMember entry; Out_cclsMember entry;
// Not type, |id| is invalid. // Not type, |id| is invalid.
entry.name = def->Name(qualified); entry.name = def->Name(qualified);
if (def->spell) { if (def->spell) {
if (std::optional<lsLocation> loc = if (auto loc = GetLsLocation(m->db, m->working_files, *def->spell))
GetLsLocation(db, working_files, *def->spell)) entry.location = *loc;
entry.location = *loc;
}
for (Usr usr : def->vars) {
auto &var = db->Var(usr);
if (var.def.size())
DoField(this, &entry, var, -1, qualified, levels - 1);
}
return entry;
}
case SymbolKind::Type: {
const auto *def = db->Type(root_usr).AnyDef();
if (!def)
return {};
Out_cclsMember entry;
entry.id = std::to_string(root_usr);
entry.usr = root_usr;
if (def->spell) {
if (std::optional<lsLocation> loc =
GetLsLocation(db, working_files, *def->spell))
entry.location = *loc;
}
Expand(this, &entry, qualified, levels, memberKind);
return entry;
} }
for (Usr usr : def->vars) {
auto &var = m->db->Var(usr);
if (var.def.size())
DoField(m, &entry, var, -1, qualified, levels - 1);
} }
return entry;
} }
case SymbolKind::Type: {
const auto *def = m->db->Type(root_usr).AnyDef();
if (!def)
return {};
void Run(In_cclsMember *request) override { Out_cclsMember entry;
auto &params = request->params; entry.id = std::to_string(root_usr);
std::optional<Out_cclsMember> result; entry.usr = root_usr;
if (params.id.size()) { if (def->spell) {
try { if (auto loc = GetLsLocation(m->db, m->working_files, *def->spell))
params.usr = std::stoull(params.id); entry.location = *loc;
} catch (...) { }
return; Expand(m, &entry, qualified, levels, memberKind);
} return entry;
result.emplace(); }
result->id = std::to_string(params.usr); }
result->usr = params.usr; }
// entry.name is empty as it is known by the client. } // namespace
if (!(db->HasType(params.usr) && Expand(this, &*result, params.qualified,
params.levels, params.kind))) void MessageHandler::ccls_member(Reader &reader, ReplyOnce &reply) {
result.reset(); Param param;
} else { Reflect(reader, param);
QueryFile *file; std::optional<Out_cclsMember> result;
if (!FindFileOrFail(db, project, request->id, if (param.id.size()) {
params.textDocument.uri.GetPath(), &file)) try {
return; param.usr = std::stoull(param.id);
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path); } catch (...) {
for (SymbolRef sym : return;
FindSymbolsAtLocation(wfile, file, params.position)) { }
switch (sym.kind) { result.emplace();
case SymbolKind::Func: result->id = std::to_string(param.usr);
case SymbolKind::Type: result->usr = param.usr;
result = BuildInitial(sym.kind, sym.usr, params.qualified, // entry.name is empty as it is known by the client.
params.levels, params.kind); if (!(db->HasType(param.usr) && Expand(this, &*result, param.qualified,
break; param.levels, param.kind)))
case SymbolKind::Var: { result.reset();
const QueryVar::Def *def = db->GetVar(sym).AnyDef(); } else {
if (def && def->type) QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
result = BuildInitial(SymbolKind::Type, def->type, params.qualified, if (!file)
params.levels, params.kind); return;
break; WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
} for (SymbolRef sym :
default: FindSymbolsAtLocation(wfile, file, param.position)) {
continue; switch (sym.kind) {
} case SymbolKind::Func:
case SymbolKind::Type:
result = BuildInitial(this, sym.kind, sym.usr, param.qualified,
param.levels, param.kind);
break;
case SymbolKind::Var: {
const QueryVar::Def *def = db->GetVar(sym).AnyDef();
if (def && def->type)
result = BuildInitial(this, SymbolKind::Type, def->type, param.qualified,
param.levels, param.kind);
break; break;
} }
} default:
continue;
if (params.hierarchy) }
pipeline::Reply(request->id, result); break;
else {
auto out = FlattenHierarchy(result);
pipeline::Reply(request->id, out);
} }
} }
};
REGISTER_MESSAGE_HANDLER(Handler_cclsMember);
} // namespace if (param.hierarchy)
reply(result);
else {
auto out = FlattenHierarchy(result);
reply(out);
}
}
} // namespace ccls

View File

@ -1,25 +1,17 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
using namespace ccls;
namespace ccls {
namespace { namespace {
MethodType kMethodType = "$ccls/navigate"; struct Param {
lsTextDocumentIdentifier textDocument;
struct In_CclsNavigate : public RequestMessage { lsPosition position;
MethodType GetMethodType() const override { return kMethodType; } std::string direction;
struct Params {
lsTextDocumentIdentifier textDocument;
lsPosition position;
std::string direction;
} params;
}; };
MAKE_REFLECT_STRUCT(In_CclsNavigate::Params, textDocument, position, direction); MAKE_REFLECT_STRUCT(Param, textDocument, position, direction);
MAKE_REFLECT_STRUCT(In_CclsNavigate, id, params);
REGISTER_IN_MESSAGE(In_CclsNavigate);
Maybe<Range> FindParent(QueryFile *file, Position pos) { Maybe<Range> FindParent(QueryFile *file, Position pos) {
Maybe<Range> parent; Maybe<Range> parent;
@ -32,75 +24,72 @@ Maybe<Range> FindParent(QueryFile *file, Position pos) {
parent = sym.extent; parent = sym.extent;
return parent; return parent;
} }
struct Handler_CclsNavigate : BaseMessageHandler<In_CclsNavigate> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_CclsNavigate *request) override {
auto &params = request->params;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *wfile =
working_files->GetFileByFilename(file->def->path);
lsPosition ls_pos = request->params.position;
if (wfile && wfile->index_lines.size())
if (auto line = wfile->GetIndexPosFromBufferPos(
ls_pos.line, &ls_pos.character, false))
ls_pos.line = *line;
Position pos{(int16_t)ls_pos.line, (int16_t)ls_pos.character};
Maybe<Range> res;
switch (params.direction[0]) {
case 'D': {
Maybe<Range> parent = FindParent(file, pos);
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && pos < sym.extent.start &&
(!parent || sym.extent.end <= parent->end) &&
(!res || sym.extent.start < res->start))
res = sym.extent;
break;
}
case 'L':
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() && sym.extent.end <= pos &&
(!res || (res->end == sym.extent.end ? sym.extent.start < res->start
: res->end < sym.extent.end)))
res = sym.extent;
break;
case 'R': {
Maybe<Range> parent = FindParent(file, pos);
if (parent && parent->start.line == pos.line && pos < parent->end) {
pos = parent->end;
if (pos.column)
pos.column--;
}
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() && pos < sym.extent.start &&
(!res ||
(sym.extent.start == res->start ? res->end < sym.extent.end
: sym.extent.start < res->start)))
res = sym.extent;
break;
}
case 'U':
default:
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() && sym.extent.start < pos &&
pos < sym.extent.end && (!res || res->start < sym.extent.start))
res = sym.extent;
break;
}
std::vector<lsLocation> result;
if (res)
if (auto ls_range = GetLsRange(wfile, *res)) {
lsLocation &ls_loc = result.emplace_back();
ls_loc.uri = params.textDocument.uri;
ls_loc.range = *ls_range;
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_CclsNavigate);
} // namespace } // namespace
void MessageHandler::ccls_navigate(Reader &reader,
ReplyOnce &reply) {
Param param;
Reflect(reader, param);
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
lsPosition ls_pos = param.position;
if (wfile && wfile->index_lines.size())
if (auto line = wfile->GetIndexPosFromBufferPos(ls_pos.line,
&ls_pos.character, false))
ls_pos.line = *line;
Position pos{(int16_t)ls_pos.line, (int16_t)ls_pos.character};
Maybe<Range> res;
switch (param.direction[0]) {
case 'D': {
Maybe<Range> parent = FindParent(file, pos);
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && pos < sym.extent.start &&
(!parent || sym.extent.end <= parent->end) &&
(!res || sym.extent.start < res->start))
res = sym.extent;
break;
}
case 'L':
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() && sym.extent.end <= pos &&
(!res || (res->end == sym.extent.end ? sym.extent.start < res->start
: res->end < sym.extent.end)))
res = sym.extent;
break;
case 'R': {
Maybe<Range> parent = FindParent(file, pos);
if (parent && parent->start.line == pos.line && pos < parent->end) {
pos = parent->end;
if (pos.column)
pos.column--;
}
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() && pos < sym.extent.start &&
(!res ||
(sym.extent.start == res->start ? res->end < sym.extent.end
: sym.extent.start < res->start)))
res = sym.extent;
break;
}
case 'U':
default:
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() && sym.extent.start < pos &&
pos < sym.extent.end && (!res || res->start < sym.extent.start))
res = sym.extent;
break;
}
std::vector<lsLocation> result;
if (res)
if (auto ls_range = GetLsRange(wfile, *res)) {
lsLocation &ls_loc = result.emplace_back();
ls_loc.uri = param.textDocument.uri;
ls_loc.range = *ls_range;
}
reply(result);
}
} // namespace ccls

View File

@ -3,80 +3,34 @@
#include "clang_complete.hh" #include "clang_complete.hh"
#include "match.h" #include "match.h"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "project.h" #include "project.hh"
#include "working_files.h" #include "working_files.h"
#include <queue> #include <queue>
#include <unordered_set> #include <unordered_set>
using namespace ccls; namespace ccls {
namespace { namespace {
MethodType kMethodType = "$ccls/reload"; struct Param {
bool dependencies = true;
struct In_cclsReload : public NotificationMessage { std::vector<std::string> whitelist;
MethodType GetMethodType() const override { return kMethodType; } std::vector<std::string> blacklist;
struct Params {
bool dependencies = true;
std::vector<std::string> whitelist;
std::vector<std::string> blacklist;
} params;
}; };
MAKE_REFLECT_STRUCT(In_cclsReload::Params, dependencies, whitelist, blacklist); MAKE_REFLECT_STRUCT(Param, dependencies, whitelist, blacklist);
MAKE_REFLECT_STRUCT(In_cclsReload, params);
REGISTER_IN_MESSAGE(In_cclsReload);
struct Handler_cclsReload : BaseMessageHandler<In_cclsReload> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_cclsReload *request) override {
const auto &params = request->params;
// Send index requests for every file.
if (params.whitelist.empty() && params.blacklist.empty()) {
vfs->Clear();
db->clear();
project->Index(working_files, lsRequestId());
clang_complete->FlushAllSessions();
return;
}
// TODO
GroupMatch matcher(params.whitelist, params.blacklist);
std::queue<const QueryFile *> q;
// |need_index| stores every filename ever enqueued.
std::unordered_set<std::string> need_index;
// Reverse dependency graph.
std::unordered_map<std::string, std::vector<std::string>> graph;
// filename -> QueryFile mapping for files haven't enqueued.
std::unordered_map<std::string, const QueryFile *> path_to_file;
for (const auto &file : db->files)
if (file.def) {
if (matcher.IsMatch(file.def->path))
q.push(&file);
else
path_to_file[file.def->path] = &file;
for (const std::string &dependency : file.def->dependencies)
graph[dependency].push_back(file.def->path);
}
while (!q.empty()) {
const QueryFile *file = q.front();
q.pop();
need_index.insert(file->def->path);
if (request->params.dependencies)
for (const std::string &path : graph[file->def->path]) {
auto it = path_to_file.find(path);
if (it != path_to_file.end()) {
q.push(it->second);
path_to_file.erase(it);
}
}
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_cclsReload);
} // namespace } // namespace
void MessageHandler::ccls_reload(Reader &reader) {
Param param;
Reflect(reader, param);
// Send index requests for every file.
if (param.whitelist.empty() && param.blacklist.empty()) {
vfs->Clear();
db->clear();
project->Index(working_files, lsRequestId());
clang_complete->FlushAllSessions();
return;
}
}
} // namespace ccls

View File

@ -1,63 +1,50 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
using namespace ccls;
namespace ccls {
namespace { namespace {
MethodType kMethodType = "$ccls/vars"; struct Param : TextDocumentPositionParam {
// 1: field
struct In_cclsVars : public RequestMessage { // 2: local
MethodType GetMethodType() const override { return kMethodType; } // 4: parameter
struct Params : lsTextDocumentPositionParams { unsigned kind = ~0u;
// 1: field
// 2: local
// 4: parameter
unsigned kind = ~0u;
} params;
}; };
MAKE_REFLECT_STRUCT(In_cclsVars::Params, textDocument, position, kind); MAKE_REFLECT_STRUCT(Param, textDocument, position, kind);
MAKE_REFLECT_STRUCT(In_cclsVars, id, params);
REGISTER_IN_MESSAGE(In_cclsVars);
struct Handler_cclsVars : BaseMessageHandler<In_cclsVars> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_cclsVars *request) override {
auto &params = request->params;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *working_file =
working_files->GetFileByFilename(file->def->path);
std::vector<lsLocation> result;
for (SymbolRef sym :
FindSymbolsAtLocation(working_file, file, params.position)) {
Usr usr = sym.usr;
switch (sym.kind) {
default:
break;
case SymbolKind::Var: {
const QueryVar::Def *def = db->GetVar(sym).AnyDef();
if (!def || !def->type)
continue;
usr = def->type;
[[fallthrough]];
}
case SymbolKind::Type:
result = GetLsLocations(
db, working_files,
GetVarDeclarations(db, db->Type(usr).instances, params.kind));
break;
}
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_cclsVars);
} // namespace } // namespace
void MessageHandler::ccls_vars(Reader &reader, ReplyOnce &reply) {
Param param;
Reflect(reader, param);
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *working_file = working_files->GetFileByFilename(file->def->path);
std::vector<lsLocation> result;
for (SymbolRef sym :
FindSymbolsAtLocation(working_file, file, param.position)) {
Usr usr = sym.usr;
switch (sym.kind) {
default:
break;
case SymbolKind::Var: {
const QueryVar::Def *def = db->GetVar(sym).AnyDef();
if (!def || !def->type)
continue;
usr = def->type;
[[fallthrough]];
}
case SymbolKind::Type:
result = GetLsLocations(
db, working_files,
GetVarDeclarations(db, db->Type(usr).instances, param.kind));
break;
}
}
reply(result);
}
} // namespace ccls

View File

@ -1,19 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "message_handler.h"
namespace {
struct In_Exit : public NotificationMessage {
MethodType GetMethodType() const override { return kMethodType_Exit; }
};
MAKE_REFLECT_EMPTY_STRUCT(In_Exit);
REGISTER_IN_MESSAGE(In_Exit);
struct Handler_Exit : MessageHandler {
MethodType GetMethodType() const override { return kMethodType_Exit; }
void Run(std::unique_ptr<InMessage> request) override { exit(0); }
};
REGISTER_MESSAGE_HANDLER(Handler_Exit);
} // namespace

View File

@ -5,20 +5,21 @@
#include "filesystem.hh" #include "filesystem.hh"
#include "include_complete.h" #include "include_complete.h"
#include "log.hh" #include "log.hh"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "platform.h" #include "platform.h"
#include "project.h" #include "project.hh"
#include "serializers/json.h" #include "serializers/json.h"
#include "working_files.h" #include "working_files.h"
#include <llvm/ADT/Twine.h> #include <llvm/ADT/Twine.h>
#include <llvm/Support/Threading.h> #include <llvm/Support/Threading.h>
#include <stdlib.h>
#include <stdexcept> #include <stdexcept>
#include <thread> #include <thread>
using namespace ccls; namespace ccls {
using namespace llvm; using namespace llvm;
// TODO Cleanup global variables // TODO Cleanup global variables
@ -26,8 +27,6 @@ extern std::string g_init_options;
namespace { namespace {
MethodType kMethodType = "initialize";
// Code Lens options. // Code Lens options.
struct lsCodeLensOptions { struct lsCodeLensOptions {
// Code lens has a resolve provider as well. // Code lens has a resolve provider as well.
@ -316,7 +315,7 @@ struct lsInitializeParams {
// The initial trace setting. If omitted trace is disabled ('off'). // The initial trace setting. If omitted trace is disabled ('off').
lsTrace trace = lsTrace::Off; lsTrace trace = lsTrace::Off;
std::vector<lsWorkspaceFolder> workspaceFolders; std::vector<WorkspaceFolder> workspaceFolders;
}; };
void Reflect(Reader &reader, lsInitializeParams::lsTrace &value) { void Reflect(Reader &reader, lsInitializeParams::lsTrace &value) {
@ -336,14 +335,6 @@ void Reflect(Reader &reader, lsInitializeParams::lsTrace &value) {
MAKE_REFLECT_STRUCT(lsInitializeParams, rootUri, initializationOptions, MAKE_REFLECT_STRUCT(lsInitializeParams, rootUri, initializationOptions,
capabilities, trace, workspaceFolders); capabilities, trace, workspaceFolders);
struct In_InitializeRequest : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
lsInitializeParams params;
};
MAKE_REFLECT_STRUCT(In_InitializeRequest, id, params);
REGISTER_IN_MESSAGE(In_InitializeRequest);
struct lsInitializeResult { struct lsInitializeResult {
lsServerCapabilities capabilities; lsServerCapabilities capabilities;
}; };
@ -361,120 +352,124 @@ void *Indexer(void *arg_) {
h->working_files); h->working_files);
return nullptr; return nullptr;
} }
struct Handler_Initialize : BaseMessageHandler<In_InitializeRequest> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_InitializeRequest *request) override {
auto &params = request->params;
if (!params.rootUri)
return;
std::string project_path = NormalizePath(params.rootUri->GetPath());
LOG_S(INFO) << "initialize in directory " << project_path << " with uri "
<< params.rootUri->raw_uri;
{
g_config = new Config(params.initializationOptions);
rapidjson::Document reader;
reader.Parse(g_init_options.c_str());
if (!reader.HasParseError()) {
JsonReader json_reader{&reader};
try {
Reflect(json_reader, *g_config);
} catch (std::invalid_argument &) {
// This will not trigger because parse error is handled in
// MessageRegistry::Parse in lsp.cc
}
}
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
JsonWriter json_writer(&writer);
Reflect(json_writer, *g_config);
LOG_S(INFO) << "initializationOptions: " << output.GetString();
if (g_config->cacheDirectory.size()) {
g_config->cacheDirectory = NormalizePath(g_config->cacheDirectory);
EnsureEndsInSlash(g_config->cacheDirectory);
}
}
// Client capabilities
const auto &capabilities = params.capabilities;
g_config->client.snippetSupport &=
capabilities.textDocument.completion.completionItem.snippetSupport;
g_config->client.hierarchicalDocumentSymbolSupport &=
capabilities.textDocument.documentSymbol.hierarchicalDocumentSymbolSupport;
// Ensure there is a resource directory.
if (g_config->clang.resourceDir.empty())
g_config->clang.resourceDir = GetDefaultResourceDirectory();
DoPathMapping(g_config->clang.resourceDir);
LOG_S(INFO) << "Using -resource-dir=" << g_config->clang.resourceDir;
// Send initialization before starting indexers, so we don't send a
// status update too early.
{
lsInitializeResult result;
pipeline::Reply(request->id, result);
}
// Set project root.
EnsureEndsInSlash(project_path);
g_config->fallbackFolder = project_path;
for (const lsWorkspaceFolder &wf : request->params.workspaceFolders) {
std::string path = wf.uri.GetPath();
EnsureEndsInSlash(path);
g_config->workspaceFolders.push_back(path);
LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path;
}
if (request->params.workspaceFolders.empty())
g_config->workspaceFolders.push_back(project_path);
if (g_config->cacheDirectory.size())
for (const std::string &folder : g_config->workspaceFolders) {
// Create two cache directories for files inside and outside of the
// project.
std::string escaped =
EscapeFileName(folder.substr(0, folder.size() - 1));
sys::fs::create_directories(g_config->cacheDirectory + escaped);
sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped);
}
idx::Init();
for (const std::string &folder : g_config->workspaceFolders)
project->Load(folder);
// Start indexer threads. Start this after loading the project, as that
// may take a long time. Indexer threads will emit status/progress
// reports.
if (g_config->index.threads == 0)
g_config->index.threads = std::thread::hardware_concurrency();
LOG_S(INFO) << "start " << g_config->index.threads << " indexers";
for (int i = 0; i < g_config->index.threads; i++)
SpawnThread(Indexer, new std::pair<MessageHandler *, int>{this, i});
// Start scanning include directories before dispatching project
// files, because that takes a long time.
include_complete->Rescan();
LOG_S(INFO) << "dispatch initial index requests";
project->Index(working_files, request->id);
}
};
REGISTER_MESSAGE_HANDLER(Handler_Initialize);
} // namespace } // namespace
void StandaloneInitialize(const std::string &root, Project &project, void Initialize(MessageHandler *m, lsInitializeParams &param, ReplyOnce &reply) {
WorkingFiles &wfiles, VFS &vfs, std::string project_path = NormalizePath(param.rootUri->GetPath());
IncludeComplete &complete) { LOG_S(INFO) << "initialize in directory " << project_path << " with uri "
Handler_Initialize handler; << param.rootUri->raw_uri;
handler.project = &project;
handler.working_files = &wfiles;
handler.vfs = &vfs;
handler.include_complete = &complete;
In_InitializeRequest request; {
request.params.rootUri = lsDocumentUri::FromPath(root); g_config = new Config(param.initializationOptions);
handler.Run(&request); rapidjson::Document reader;
reader.Parse(g_init_options.c_str());
if (!reader.HasParseError()) {
JsonReader json_reader{&reader};
try {
Reflect(json_reader, *g_config);
} catch (std::invalid_argument &) {
// This will not trigger because parse error is handled in
// MessageRegistry::Parse in lsp.cc
}
}
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
JsonWriter json_writer(&writer);
Reflect(json_writer, *g_config);
LOG_S(INFO) << "initializationOptions: " << output.GetString();
if (g_config->cacheDirectory.size()) {
g_config->cacheDirectory = NormalizePath(g_config->cacheDirectory);
EnsureEndsInSlash(g_config->cacheDirectory);
}
}
// Client capabilities
const auto &capabilities = param.capabilities;
g_config->client.snippetSupport &=
capabilities.textDocument.completion.completionItem.snippetSupport;
g_config->client.hierarchicalDocumentSymbolSupport &=
capabilities.textDocument.documentSymbol
.hierarchicalDocumentSymbolSupport;
// Ensure there is a resource directory.
if (g_config->clang.resourceDir.empty())
g_config->clang.resourceDir = GetDefaultResourceDirectory();
DoPathMapping(g_config->clang.resourceDir);
LOG_S(INFO) << "use -resource-dir=" << g_config->clang.resourceDir;
// Send initialization before starting indexers, so we don't send a
// status update too early.
{
lsInitializeResult result;
reply(result);
}
// Set project root.
EnsureEndsInSlash(project_path);
g_config->fallbackFolder = project_path;
for (const WorkspaceFolder &wf : param.workspaceFolders) {
std::string path = wf.uri.GetPath();
EnsureEndsInSlash(path);
g_config->workspaceFolders.push_back(path);
LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path;
}
if (param.workspaceFolders.empty())
g_config->workspaceFolders.push_back(project_path);
if (g_config->cacheDirectory.size())
for (const std::string &folder : g_config->workspaceFolders) {
// Create two cache directories for files inside and outside of the
// project.
std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1));
sys::fs::create_directories(g_config->cacheDirectory + escaped);
sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped);
}
idx::Init();
for (const std::string &folder : g_config->workspaceFolders)
m->project->Load(folder);
// Start indexer threads. Start this after loading the project, as that
// may take a long time. Indexer threads will emit status/progress
// reports.
if (g_config->index.threads == 0)
g_config->index.threads = std::thread::hardware_concurrency();
LOG_S(INFO) << "start " << g_config->index.threads << " indexers";
for (int i = 0; i < g_config->index.threads; i++)
SpawnThread(Indexer, new std::pair<MessageHandler *, int>{m, i});
// Start scanning include directories before dispatching project
// files, because that takes a long time.
m->include_complete->Rescan();
LOG_S(INFO) << "dispatch initial index requests";
m->project->Index(m->working_files, reply.id);
} }
void MessageHandler::initialize(Reader &reader, ReplyOnce &reply) {
lsInitializeParams param;
Reflect(reader, param);
if (!param.rootUri)
return;
Initialize(this, param, reply);
}
void StandaloneInitialize(MessageHandler &handler, const std::string &root) {
lsInitializeParams param;
param.rootUri = lsDocumentUri::FromPath(root);
ReplyOnce reply;
Initialize(&handler, param, reply);
}
void MessageHandler::shutdown(EmptyParam &, ReplyOnce &reply) {
JsonNull result;
reply(result);
}
void MessageHandler::exit(EmptyParam &) {
// FIXME cancel threads
::exit(0);
}
} // namespace ccls

View File

@ -1,25 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "message_handler.h"
#include "pipeline.hh"
using namespace ccls;
namespace {
MethodType kMethodType = "shutdown";
struct In_Shutdown : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
};
MAKE_REFLECT_STRUCT(In_Shutdown, id);
REGISTER_IN_MESSAGE(In_Shutdown);
struct Handler_Shutdown : BaseMessageHandler<In_Shutdown> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_Shutdown *request) override {
JsonNull result;
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_Shutdown);
} // namespace

View File

@ -0,0 +1,222 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "message_handler.hh"
#include "pipeline.hh"
#include "query_utils.h"
#include "serializers/json.h"
#include <llvm/Support/FormatVariadic.h>
#include <unordered_set>
namespace ccls {
namespace {
struct CodeAction {
std::string title;
const char *kind = "quickfix";
lsWorkspaceEdit edit;
};
MAKE_REFLECT_STRUCT(CodeAction, title, kind, edit);
}
void MessageHandler::textDocument_codeAction(CodeActionParam &param,
ReplyOnce &reply) {
WorkingFile *wfile =
working_files->GetFileByFilename(param.textDocument.uri.GetPath());
if (!wfile) {
return;
}
std::vector<CodeAction> result;
std::vector<lsDiagnostic> diagnostics;
working_files->DoAction([&]() { diagnostics = wfile->diagnostics_; });
for (lsDiagnostic &diag : diagnostics)
if (diag.fixits_.size()) {
CodeAction &cmd = result.emplace_back();
cmd.title = "FixIt: " + diag.message;
auto &edit = cmd.edit.documentChanges.emplace_back();
edit.textDocument.uri = param.textDocument.uri;
edit.textDocument.version = wfile->version;
edit.edits = diag.fixits_;
}
reply(result);
}
namespace {
struct Cmd_xref {
Usr usr;
SymbolKind kind;
std::string field;
};
struct lsCommand {
std::string title;
std::string command;
std::vector<std::string> arguments;
};
struct lsCodeLens {
lsRange range;
std::optional<lsCommand> command;
};
MAKE_REFLECT_STRUCT(Cmd_xref, usr, kind, field);
MAKE_REFLECT_STRUCT(lsCommand, title, command, arguments);
MAKE_REFLECT_STRUCT(lsCodeLens, range, command);
template <typename T>
std::string ToString(T &v) {
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
JsonWriter json_writer(&writer);
Reflect(json_writer, v);
return output.GetString();
}
struct CommonCodeLensParams {
std::vector<lsCodeLens> *result;
DB *db;
WorkingFile *wfile;
};
} // namespace
void MessageHandler::textDocument_codeLens(TextDocumentParam &param,
ReplyOnce &reply) {
std::vector<lsCodeLens> result;
std::string path = param.textDocument.uri.GetPath();
QueryFile *file = FindFile(reply, path);
WorkingFile *wfile =
file ? working_files->GetFileByFilename(file->def->path) : nullptr;
if (!wfile) {
return;
}
auto Add = [&](const char *singular, Cmd_xref show, Range range, int num,
bool force_display = false) {
if (!num && !force_display)
return;
std::optional<lsRange> ls_range = GetLsRange(wfile, range);
if (!ls_range)
return;
lsCodeLens &code_lens = result.emplace_back();
code_lens.range = *ls_range;
code_lens.command = lsCommand();
code_lens.command->command = std::string(ccls_xref);
bool plural = num > 1 && singular[strlen(singular) - 1] != 'd';
code_lens.command->title =
llvm::formatv("{0} {1}{2}", num, singular, plural ? "s" : "").str();
code_lens.command->arguments.push_back(ToString(show));
};
std::unordered_set<Range> seen;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.Valid() || !seen.insert(sym.range).second)
continue;
switch (sym.kind) {
case SymbolKind::Func: {
QueryFunc &func = db->GetFunc(sym);
const QueryFunc::Def *def = func.AnyDef();
if (!def)
continue;
std::vector<Use> base_uses = GetUsesForAllBases(db, func);
std::vector<Use> derived_uses = GetUsesForAllDerived(db, func);
Add("ref", {sym.usr, SymbolKind::Func, "uses"}, sym.range,
func.uses.size(), base_uses.empty());
if (base_uses.size())
Add("b.ref", {sym.usr, SymbolKind::Func, "bases uses"}, sym.range,
base_uses.size());
if (derived_uses.size())
Add("d.ref", {sym.usr, SymbolKind::Func, "derived uses"}, sym.range,
derived_uses.size());
if (base_uses.empty())
Add("base", {sym.usr, SymbolKind::Func, "bases"}, sym.range,
def->bases.size());
Add("derived", {sym.usr, SymbolKind::Func, "derived"}, sym.range,
func.derived.size());
break;
}
case SymbolKind::Type: {
QueryType &type = db->GetType(sym);
Add("ref", {sym.usr, SymbolKind::Type, "uses"}, sym.range,
type.uses.size(), true);
Add("derived", {sym.usr, SymbolKind::Type, "derived"}, sym.range,
type.derived.size());
Add("var", {sym.usr, SymbolKind::Type, "instances"}, sym.range,
type.instances.size());
break;
}
case SymbolKind::Var: {
QueryVar &var = db->GetVar(sym);
const QueryVar::Def *def = var.AnyDef();
if (!def || (def->is_local() && !g_config->codeLens.localVariables))
continue;
Add("ref", {sym.usr, SymbolKind::Var, "uses"}, sym.range, var.uses.size(),
def->kind != lsSymbolKind::Macro);
break;
}
case SymbolKind::File:
case SymbolKind::Invalid:
llvm_unreachable("");
};
}
reply(result);
}
void MessageHandler::workspace_executeCommand(Reader &reader,
ReplyOnce &reply) {
lsCommand param;
Reflect(reader, param);
if (param.arguments.empty()) {
return;
}
rapidjson::Document reader1;
reader1.Parse(param.arguments[0].c_str());
JsonReader json_reader{&reader1};
if (param.command == ccls_xref) {
Cmd_xref cmd;
Reflect(json_reader, cmd);
std::vector<lsLocation> result;
auto Map = [&](auto &&uses) {
for (auto &use : uses)
if (auto loc = GetLsLocation(db, working_files, use))
result.push_back(std::move(*loc));
};
switch (cmd.kind) {
case SymbolKind::Func: {
QueryFunc &func = db->Func(cmd.usr);
if (cmd.field == "bases") {
if (auto *def = func.AnyDef())
Map(GetFuncDeclarations(db, def->bases));
} else if (cmd.field == "bases uses") {
Map(GetUsesForAllBases(db, func));
} else if (cmd.field == "derived") {
Map(GetFuncDeclarations(db, func.derived));
} else if (cmd.field == "derived uses") {
Map(GetUsesForAllDerived(db, func));
} else if (cmd.field == "uses") {
Map(func.uses);
}
break;
}
case SymbolKind::Type: {
QueryType &type = db->Type(cmd.usr);
if (cmd.field == "derived") {
Map(GetTypeDeclarations(db, type.derived));
} else if (cmd.field == "instances") {
Map(GetVarDeclarations(db, type.instances, 7));
} else if (cmd.field == "uses") {
Map(type.uses);
}
break;
}
case SymbolKind::Var: {
QueryVar &var = db->Var(cmd.usr);
if (cmd.field == "uses")
Map(var.uses);
break;
}
default:
break;
}
reply(result);
}
}
} // namespace ccls

View File

@ -1,71 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "message_handler.h"
#include "pipeline.hh"
#include "working_files.h"
using namespace ccls;
namespace {
MethodType kMethodType = "textDocument/codeAction";
struct In_TextDocumentCodeAction : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
// Contains additional diagnostic information about the context in which
// a code action is run.
struct lsCodeActionContext {
// An array of diagnostics.
std::vector<lsDiagnostic> diagnostics;
};
// Params for the CodeActionRequest
struct lsCodeActionParams {
// The document in which the command was invoked.
lsTextDocumentIdentifier textDocument;
// The range for which the command was invoked.
lsRange range;
// Context carrying additional information.
lsCodeActionContext context;
} params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentCodeAction::lsCodeActionContext,
diagnostics);
MAKE_REFLECT_STRUCT(In_TextDocumentCodeAction::lsCodeActionParams, textDocument,
range, context);
MAKE_REFLECT_STRUCT(In_TextDocumentCodeAction, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentCodeAction);
struct lsCodeAction {
std::string title;
const char *kind = "quickfix";
lsWorkspaceEdit edit;
};
MAKE_REFLECT_STRUCT(lsCodeAction, title, kind, edit);
struct Handler_TextDocumentCodeAction
: BaseMessageHandler<In_TextDocumentCodeAction> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_TextDocumentCodeAction *request) override {
const auto &params = request->params;
WorkingFile *wfile =
working_files->GetFileByFilename(params.textDocument.uri.GetPath());
if (!wfile)
return;
std::vector<lsCodeAction> result;
std::vector<lsDiagnostic> diagnostics;
working_files->DoAction([&]() { diagnostics = wfile->diagnostics_; });
for (lsDiagnostic &diag : diagnostics)
if (diag.fixits_.size()) {
lsCodeAction &cmd = result.emplace_back();
cmd.title = "FixIt: " + diag.message;
auto &edit = cmd.edit.documentChanges.emplace_back();
edit.textDocument.uri = params.textDocument.uri;
edit.textDocument.version = wfile->version;
edit.edits = diag.fixits_;
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentCodeAction);
} // namespace

View File

@ -1,219 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "message_handler.h"
#include "pipeline.hh"
#include "query_utils.h"
#include "serializers/json.h"
#include <llvm/Support/FormatVariadic.h>
#include <unordered_set>
using namespace ccls;
namespace {
const MethodType codeLens = "textDocument/codeLens",
executeCommand = "workspace/executeCommand";
struct lsCommand {
std::string title;
std::string command;
std::vector<std::string> arguments;
};
MAKE_REFLECT_STRUCT(lsCommand, title, command, arguments);
struct lsCodeLens {
lsRange range;
std::optional<lsCommand> command;
};
MAKE_REFLECT_STRUCT(lsCodeLens, range, command);
struct Cmd_xref {
Usr usr;
SymbolKind kind;
std::string field;
};
MAKE_REFLECT_STRUCT(Cmd_xref, usr, kind, field);
template <typename T>
std::string ToString(T &v) {
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
JsonWriter json_writer(&writer);
Reflect(json_writer, v);
return output.GetString();
}
struct CommonCodeLensParams {
std::vector<lsCodeLens> *result;
DB *db;
WorkingFile *wfile;
};
struct In_TextDocumentCodeLens : public RequestMessage {
MethodType GetMethodType() const override { return codeLens; }
struct Params {
lsTextDocumentIdentifier textDocument;
} params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentCodeLens::Params, textDocument);
MAKE_REFLECT_STRUCT(In_TextDocumentCodeLens, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentCodeLens);
struct Handler_TextDocumentCodeLens
: BaseMessageHandler<In_TextDocumentCodeLens> {
MethodType GetMethodType() const override { return codeLens; }
void Run(In_TextDocumentCodeLens *request) override {
auto &params = request->params;
std::vector<lsCodeLens> result;
std::string path = params.textDocument.uri.GetPath();
QueryFile *file;
if (!FindFileOrFail(db, project, request->id, path, &file))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
auto Add = [&](const char *singular, Cmd_xref show, Range range, int num,
bool force_display = false) {
if (!num && !force_display)
return;
std::optional<lsRange> ls_range = GetLsRange(wfile, range);
if (!ls_range)
return;
lsCodeLens &code_lens = result.emplace_back();
code_lens.range = *ls_range;
code_lens.command = lsCommand();
code_lens.command->command = std::string(ccls_xref);
bool plural = num > 1 && singular[strlen(singular) - 1] != 'd';
code_lens.command->title =
llvm::formatv("{0} {1}{2}", num, singular, plural ? "s" : "").str();
code_lens.command->arguments.push_back(ToString(show));
};
std::unordered_set<Range> seen;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.Valid() || !seen.insert(sym.range).second)
continue;
switch (sym.kind) {
case SymbolKind::Func: {
QueryFunc &func = db->GetFunc(sym);
const QueryFunc::Def *def = func.AnyDef();
if (!def)
continue;
std::vector<Use> base_uses = GetUsesForAllBases(db, func);
std::vector<Use> derived_uses = GetUsesForAllDerived(db, func);
Add("ref", {sym.usr, SymbolKind::Func, "uses"}, sym.range,
func.uses.size(), base_uses.empty());
if (base_uses.size())
Add("b.ref", {sym.usr, SymbolKind::Func, "bases uses"}, sym.range,
base_uses.size());
if (derived_uses.size())
Add("d.ref", {sym.usr, SymbolKind::Func, "derived uses"}, sym.range,
derived_uses.size());
if (base_uses.empty())
Add("base", {sym.usr, SymbolKind::Func, "bases"}, sym.range,
def->bases.size());
Add("derived", {sym.usr, SymbolKind::Func, "derived"}, sym.range,
func.derived.size());
break;
}
case SymbolKind::Type: {
QueryType &type = db->GetType(sym);
Add("ref", {sym.usr, SymbolKind::Type, "uses"}, sym.range, type.uses.size(),
true);
Add("derived", {sym.usr, SymbolKind::Type, "derived"}, sym.range,
type.derived.size());
Add("var", {sym.usr, SymbolKind::Type, "instances"}, sym.range,
type.instances.size());
break;
}
case SymbolKind::Var: {
QueryVar &var = db->GetVar(sym);
const QueryVar::Def *def = var.AnyDef();
if (!def || (def->is_local() && !g_config->codeLens.localVariables))
continue;
Add("ref", {sym.usr, SymbolKind::Var, "uses"}, sym.range, var.uses.size(),
def->kind != lsSymbolKind::Macro);
break;
}
case SymbolKind::File:
case SymbolKind::Invalid:
llvm_unreachable("");
};
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentCodeLens);
struct In_WorkspaceExecuteCommand : public RequestMessage {
MethodType GetMethodType() const override { return executeCommand; }
lsCommand params;
};
MAKE_REFLECT_STRUCT(In_WorkspaceExecuteCommand, id, params);
REGISTER_IN_MESSAGE(In_WorkspaceExecuteCommand);
struct Handler_WorkspaceExecuteCommand
: BaseMessageHandler<In_WorkspaceExecuteCommand> {
MethodType GetMethodType() const override { return executeCommand; }
void Run(In_WorkspaceExecuteCommand *request) override {
const auto &params = request->params;
if (params.arguments.empty())
return;
rapidjson::Document reader;
reader.Parse(params.arguments[0].c_str());
JsonReader json_reader{&reader};
if (params.command == ccls_xref) {
Cmd_xref cmd;
Reflect(json_reader, cmd);
std::vector<lsLocation> result;
auto Map = [&](auto &&uses) {
for (auto &use : uses)
if (auto loc = GetLsLocation(db, working_files, use))
result.push_back(std::move(*loc));
};
switch (cmd.kind) {
case SymbolKind::Func: {
QueryFunc &func = db->Func(cmd.usr);
if (cmd.field == "bases") {
if (auto *def = func.AnyDef())
Map(GetFuncDeclarations(db, def->bases));
} else if (cmd.field == "bases uses") {
Map(GetUsesForAllBases(db, func));
} else if (cmd.field == "derived") {
Map(GetFuncDeclarations(db, func.derived));
} else if (cmd.field == "derived uses") {
Map(GetUsesForAllDerived(db, func));
} else if (cmd.field == "uses") {
Map(func.uses);
}
break;
}
case SymbolKind::Type: {
QueryType &type = db->Type(cmd.usr);
if (cmd.field == "derived") {
Map(GetTypeDeclarations(db, type.derived));
} else if (cmd.field == "instances") {
Map(GetVarDeclarations(db, type.instances, 7));
} else if (cmd.field == "uses") {
Map(type.uses);
}
break;
}
case SymbolKind::Var: {
QueryVar &var = db->Var(cmd.usr);
if (cmd.field == "uses")
Map(var.uses);
break;
}
default:
break;
}
pipeline::Reply(request->id, result);
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_WorkspaceExecuteCommand);
} // namespace

View File

@ -5,7 +5,7 @@
#include "fuzzy_match.h" #include "fuzzy_match.h"
#include "include_complete.h" #include "include_complete.h"
#include "log.hh" #include "log.hh"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "working_files.h" #include "working_files.h"
@ -14,62 +14,23 @@
#include <regex> #include <regex>
using namespace ccls; namespace ccls {
using namespace clang; using namespace clang;
using namespace llvm; using namespace llvm;
namespace {
MethodType kMethodType = "textDocument/completion";
// How a completion was triggered
enum class lsCompletionTriggerKind {
// Completion was triggered by typing an identifier (24x7 code
// complete), manual invocation (e.g Ctrl+Space) or via API.
Invoked = 1,
// Completion was triggered by a trigger character specified by
// the `triggerCharacters` properties of the `CompletionRegistrationOptions`.
TriggerCharacter = 2,
// Completion was re-triggered as the current completion list is incomplete.
TriggerForIncompleteCompletions = 3,
};
MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind);
// Contains additional information about the context in which a completion
// request is triggered.
struct lsCompletionContext {
// How the completion was triggered.
lsCompletionTriggerKind triggerKind = lsCompletionTriggerKind::Invoked;
// The trigger character (a single character) that has trigger code complete.
// Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
std::optional<std::string> triggerCharacter;
};
MAKE_REFLECT_STRUCT(lsCompletionContext, triggerKind, triggerCharacter);
struct lsCompletionParams : lsTextDocumentPositionParams {
// The completion context. This is only available it the client specifies to
// send this using
// `ClientCapabilities.textDocument.completion.contextSupport === true`
lsCompletionContext context;
};
MAKE_REFLECT_STRUCT(lsCompletionParams, textDocument, position, context);
struct In_TextDocumentComplete : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
lsCompletionParams params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentComplete, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentComplete);
struct lsCompletionList { struct lsCompletionList {
// This list it not complete. Further typing should result in recomputing
// this list.
bool isIncomplete = false; bool isIncomplete = false;
// The completion items.
std::vector<lsCompletionItem> items; std::vector<lsCompletionItem> items;
}; };
MAKE_REFLECT_TYPE_PROXY(lsInsertTextFormat);
MAKE_REFLECT_TYPE_PROXY(lsCompletionItemKind);
MAKE_REFLECT_STRUCT(lsCompletionItem, label, kind, detail, documentation,
sortText, filterText, insertText, insertTextFormat,
textEdit, additionalTextEdits);
MAKE_REFLECT_STRUCT(lsCompletionList, isIncomplete, items); MAKE_REFLECT_STRUCT(lsCompletionList, isIncomplete, items);
namespace {
void DecorateIncludePaths(const std::smatch &match, void DecorateIncludePaths(const std::smatch &match,
std::vector<lsCompletionItem> *items) { std::vector<lsCompletionItem> *items) {
std::string spaces_after_include = " "; std::string spaces_after_include = " ";
@ -473,139 +434,129 @@ public:
CodeCompletionAllocator &getAllocator() override { return *Alloc; } CodeCompletionAllocator &getAllocator() override { return *Alloc; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
}; };
} // namespace
struct Handler_TextDocumentCompletion void MessageHandler::textDocument_completion(lsCompletionParams &param,
: BaseMessageHandler<In_TextDocumentComplete> { ReplyOnce &reply) {
MethodType GetMethodType() const override { return kMethodType; } static CompleteConsumerCache<std::vector<lsCompletionItem>> cache;
lsCompletionList result;
std::string path = param.textDocument.uri.GetPath();
WorkingFile *file = working_files->GetFileByFilename(path);
if (!file) {
return;
}
void Run(In_TextDocumentComplete *request) override { // It shouldn't be possible, but sometimes vscode will send queries out
static CompleteConsumerCache<std::vector<lsCompletionItem>> cache; // of order, ie, we get completion request before buffer content update.
std::string buffer_line;
if (param.position.line >= 0 &&
param.position.line < file->buffer_lines.size())
buffer_line = file->buffer_lines[param.position.line];
const auto &params = request->params; // Check for - and : before completing -> or ::, since vscode does not
lsCompletionList result; // support multi-character trigger characters.
if (param.context.triggerKind == lsCompletionTriggerKind::TriggerCharacter &&
param.context.triggerCharacter) {
bool did_fail_check = false;
std::string path = params.textDocument.uri.GetPath(); std::string character = *param.context.triggerCharacter;
WorkingFile *file = working_files->GetFileByFilename(path); int preceding_index = param.position.character - 2;
if (!file) {
pipeline::Reply(request->id, result); // If the character is '"', '<' or '/', make sure that the line starts
// with '#'.
if (character == "\"" || character == "<" || character == "/") {
size_t i = 0;
while (i < buffer_line.size() && isspace(buffer_line[i]))
++i;
if (i >= buffer_line.size() || buffer_line[i] != '#')
did_fail_check = true;
}
// If the character is > or : and we are at the start of the line, do not
// show completion results.
else if ((character == ">" || character == ":") && preceding_index < 0) {
did_fail_check = true;
}
// If the character is > but - does not preced it, or if it is : and :
// does not preced it, do not show completion results.
else if (preceding_index >= 0 &&
preceding_index < (int)buffer_line.size()) {
char preceding = buffer_line[preceding_index];
did_fail_check = (preceding != '-' && character == ">") ||
(preceding != ':' && character == ":");
}
if (did_fail_check) {
reply(result);
return; return;
} }
}
// It shouldn't be possible, but sometimes vscode will send queries out std::string completion_text;
// of order, ie, we get completion request before buffer content update. lsPosition end_pos = param.position;
std::string buffer_line; lsPosition begin_pos = file->FindStableCompletionSource(
if (params.position.line >= 0 && param.position, &completion_text, &end_pos);
params.position.line < file->buffer_lines.size())
buffer_line = file->buffer_lines[params.position.line];
// Check for - and : before completing -> or ::, since vscode does not ParseIncludeLineResult preprocess = ParseIncludeLine(buffer_line);
// support multi-character trigger characters.
if (params.context.triggerKind ==
lsCompletionTriggerKind::TriggerCharacter &&
params.context.triggerCharacter) {
bool did_fail_check = false;
std::string character = *params.context.triggerCharacter; if (preprocess.ok && preprocess.keyword.compare("include") == 0) {
int preceding_index = params.position.character - 2; lsCompletionList result;
{
// If the character is '"', '<' or '/', make sure that the line starts std::unique_lock<std::mutex> lock(
// with '#'. include_complete->completion_items_mutex, std::defer_lock);
if (character == "\"" || character == "<" || character == "/") { if (include_complete->is_scanning)
size_t i = 0; lock.lock();
while (i < buffer_line.size() && isspace(buffer_line[i])) std::string quote = preprocess.match[5];
++i; for (auto &item : include_complete->completion_items)
if (i >= buffer_line.size() || buffer_line[i] != '#') if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\""))
did_fail_check = true; result.items.push_back(item);
}
// If the character is > or : and we are at the start of the line, do not
// show completion results.
else if ((character == ">" || character == ":") && preceding_index < 0) {
did_fail_check = true;
}
// If the character is > but - does not preced it, or if it is : and :
// does not preced it, do not show completion results.
else if (preceding_index >= 0 &&
preceding_index < (int)buffer_line.size()) {
char preceding = buffer_line[preceding_index];
did_fail_check = (preceding != '-' && character == ">") ||
(preceding != ':' && character == ":");
}
if (did_fail_check) {
pipeline::Reply(request->id, result);
return;
}
} }
begin_pos.character = 0;
end_pos.character = (int)buffer_line.size();
FilterCandidates(result, preprocess.pattern, begin_pos, end_pos,
buffer_line);
DecorateIncludePaths(preprocess.match, &result.items);
reply(result);
} else {
std::string path = param.textDocument.uri.GetPath();
CompletionManager::OnComplete callback =
[completion_text, path, begin_pos, end_pos, reply,
buffer_line](CodeCompleteConsumer *OptConsumer) {
if (!OptConsumer)
return;
auto *Consumer = static_cast<CompletionConsumer *>(OptConsumer);
lsCompletionList result;
result.items = Consumer->ls_items;
std::string completion_text; FilterCandidates(result, completion_text, begin_pos, end_pos,
lsPosition end_pos = params.position; buffer_line);
lsPosition begin_pos = file->FindStableCompletionSource( reply(result);
params.position, &completion_text, &end_pos); if (!Consumer->from_cache) {
cache.WithLock([&]() {
cache.path = path;
cache.position = begin_pos;
cache.result = Consumer->ls_items;
});
}
};
ParseIncludeLineResult preprocess = ParseIncludeLine(buffer_line); clang::CodeCompleteOptions CCOpts;
CCOpts.IncludeBriefComments = true;
if (preprocess.ok && preprocess.keyword.compare("include") == 0) { CCOpts.IncludeCodePatterns = preprocess.ok; // if there is a #
lsCompletionList result;
{
std::unique_lock<std::mutex> lock(
include_complete->completion_items_mutex, std::defer_lock);
if (include_complete->is_scanning)
lock.lock();
std::string quote = preprocess.match[5];
for (auto &item : include_complete->completion_items)
if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\""))
result.items.push_back(item);
}
begin_pos.character = 0;
end_pos.character = (int)buffer_line.size();
FilterCandidates(result, preprocess.pattern, begin_pos, end_pos,
buffer_line);
DecorateIncludePaths(preprocess.match, &result.items);
pipeline::Reply(request->id, result);
} else {
std::string path = params.textDocument.uri.GetPath();
CompletionManager::OnComplete callback =
[completion_text, path, begin_pos, end_pos,
id = request->id, buffer_line](CodeCompleteConsumer *OptConsumer) {
if (!OptConsumer)
return;
auto *Consumer = static_cast<CompletionConsumer *>(OptConsumer);
lsCompletionList result;
result.items = Consumer->ls_items;
FilterCandidates(result, completion_text, begin_pos, end_pos,
buffer_line);
pipeline::Reply(id, result);
if (!Consumer->from_cache) {
cache.WithLock([&]() {
cache.path = path;
cache.position = begin_pos;
cache.result = Consumer->ls_items;
});
}
};
clang::CodeCompleteOptions CCOpts;
CCOpts.IncludeBriefComments = true;
CCOpts.IncludeCodePatterns = preprocess.ok; // if there is a #
#if LLVM_VERSION_MAJOR >= 7 #if LLVM_VERSION_MAJOR >= 7
CCOpts.IncludeFixIts = true; CCOpts.IncludeFixIts = true;
#endif #endif
CCOpts.IncludeMacros = true; CCOpts.IncludeMacros = true;
if (cache.IsCacheValid(path, begin_pos)) { if (cache.IsCacheValid(path, begin_pos)) {
CompletionConsumer Consumer(CCOpts, true); CompletionConsumer Consumer(CCOpts, true);
cache.WithLock([&]() { Consumer.ls_items = cache.result; }); cache.WithLock([&]() { Consumer.ls_items = cache.result; });
callback(&Consumer); callback(&Consumer);
} else { } else {
clang_complete->completion_request_.PushBack( clang_complete->completion_request_.PushBack(
std::make_unique<CompletionManager::CompletionRequest>( std::make_unique<CompletionManager::CompletionRequest>(
request->id, params.textDocument, begin_pos, reply.id, param.textDocument, begin_pos,
std::make_unique<CompletionConsumer>(CCOpts, false), CCOpts, std::make_unique<CompletionConsumer>(CCOpts, false), CCOpts,
callback)); callback));
}
} }
} }
}; }
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentCompletion); } // namespace ccls
} // namespace

View File

@ -1,26 +1,15 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include <stdlib.h> #include <stdlib.h>
using namespace ccls; namespace ccls {
namespace { namespace {
MethodType kMethodType = "textDocument/definition";
struct In_TextDocumentDefinition : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
lsTextDocumentPositionParams params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentDefinition, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentDefinition);
std::vector<Use> GetNonDefDeclarationTargets(DB *db, SymbolRef sym) { std::vector<Use> GetNonDefDeclarationTargets(DB *db, SymbolRef sym) {
switch (sym.kind) { switch (sym.kind) {
case SymbolKind::Var: { case SymbolKind::Var: {
@ -42,133 +31,173 @@ std::vector<Use> GetNonDefDeclarationTargets(DB *db, SymbolRef sym) {
return GetNonDefDeclarations(db, sym); return GetNonDefDeclarations(db, sym);
} }
} }
} // namespace
struct Handler_TextDocumentDefinition void MessageHandler::textDocument_definition(TextDocumentPositionParam &param,
: BaseMessageHandler<In_TextDocumentDefinition> { ReplyOnce &reply) {
MethodType GetMethodType() const override { return kMethodType; } int file_id;
void Run(In_TextDocumentDefinition *request) override { QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id);
auto &params = request->params; if (!file)
int file_id; return;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file, &file_id))
return;
std::vector<lsLocation> result; std::vector<lsLocation> result;
Maybe<Use> on_def; Maybe<Use> on_def;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path); WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
lsPosition &ls_pos = params.position; lsPosition &ls_pos = param.position;
for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, ls_pos, true)) { for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, ls_pos, true)) {
// Special cases which are handled: // Special cases which are handled:
// - symbol has declaration but no definition (ie, pure virtual) // - symbol has declaration but no definition (ie, pure virtual)
// - goto declaration while in definition of recursive type // - goto declaration while in definition of recursive type
std::vector<Use> uses; std::vector<Use> uses;
EachEntityDef(db, sym, [&](const auto &def) { EachEntityDef(db, sym, [&](const auto &def) {
if (def.spell) { if (def.spell) {
Use spell = *def.spell; Use spell = *def.spell;
if (spell.file_id == file_id && if (spell.file_id == file_id &&
spell.range.Contains(ls_pos.line, ls_pos.character)) { spell.range.Contains(ls_pos.line, ls_pos.character)) {
on_def = spell; on_def = spell;
uses.clear(); uses.clear();
return false; return false;
}
uses.push_back(spell);
} }
return true; uses.push_back(spell);
});
// |uses| is empty if on a declaration/definition, otherwise it includes
// all declarations/definitions.
if (uses.empty()) {
for (Use use : GetNonDefDeclarationTargets(db, sym))
if (!(use.file_id == file_id &&
use.range.Contains(ls_pos.line, ls_pos.character)))
uses.push_back(use);
// There is no declaration but the cursor is on a definition.
if (uses.empty() && on_def)
uses.push_back(*on_def);
} }
auto locs = GetLsLocations(db, working_files, uses); return true;
result.insert(result.end(), locs.begin(), locs.end()); });
}
if (result.size()) { // |uses| is empty if on a declaration/definition, otherwise it includes
std::sort(result.begin(), result.end()); // all declarations/definitions.
result.erase(std::unique(result.begin(), result.end()), result.end()); if (uses.empty()) {
} else { for (Use use : GetNonDefDeclarationTargets(db, sym))
Maybe<Range> range; if (!(use.file_id == file_id &&
// Check #include use.range.Contains(ls_pos.line, ls_pos.character)))
for (const IndexInclude &include : file->def->includes) { uses.push_back(use);
if (include.line == ls_pos.line) { // There is no declaration but the cursor is on a definition.
result.push_back( if (uses.empty() && on_def)
lsLocation{lsDocumentUri::FromPath(include.resolved_path)}); uses.push_back(*on_def);
range = {{0, 0}, {0, 0}}; }
auto locs = GetLsLocations(db, working_files, uses);
result.insert(result.end(), locs.begin(), locs.end());
}
if (result.size()) {
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
} else {
Maybe<Range> range;
// Check #include
for (const IndexInclude &include : file->def->includes) {
if (include.line == ls_pos.line) {
result.push_back(
lsLocation{lsDocumentUri::FromPath(include.resolved_path)});
range = {{0, 0}, {0, 0}};
break;
}
}
// Find the best match of the identifier at point.
if (!range) {
lsPosition position = param.position;
const std::string &buffer = wfile->buffer_content;
std::string_view query = LexIdentifierAroundPos(position, buffer);
std::string_view short_query = query;
{
auto pos = query.rfind(':');
if (pos != std::string::npos)
short_query = query.substr(pos + 1);
}
// For symbols whose short/detailed names contain |query| as a
// substring, we use the tuple <length difference, negative position,
// not in the same file, line distance> to find the best match.
std::tuple<int, int, bool, int> best_score{INT_MAX, 0, true, 0};
SymbolIdx best_sym;
best_sym.kind = SymbolKind::Invalid;
auto fn = [&](SymbolIdx sym) {
std::string_view short_name = db->GetSymbolName(sym, false),
name = short_query.size() < query.size()
? db->GetSymbolName(sym, true)
: short_name;
if (short_name != short_query)
return;
if (Maybe<DeclRef> dr = GetDefinitionSpell(db, sym)) {
std::tuple<int, int, bool, int> score{
int(name.size() - short_query.size()), 0, dr->file_id != file_id,
std::abs(dr->range.start.line - position.line)};
// Update the score with qualified name if the qualified name
// occurs in |name|.
auto pos = name.rfind(query);
if (pos != std::string::npos) {
std::get<0>(score) = int(name.size() - query.size());
std::get<1>(score) = -int(pos);
}
if (score < best_score) {
best_score = score;
best_sym = sym;
}
}
};
for (auto &func : db->funcs)
fn({func.usr, SymbolKind::Func});
for (auto &type : db->types)
fn({type.usr, SymbolKind::Type});
for (auto &var : db->vars)
if (var.def.size() && !var.def[0].is_local())
fn({var.usr, SymbolKind::Var});
if (best_sym.kind != SymbolKind::Invalid) {
Maybe<DeclRef> dr = GetDefinitionSpell(db, best_sym);
assert(dr);
if (auto loc = GetLsLocation(db, working_files, *dr))
result.push_back(*loc);
}
}
}
reply(result);
}
void MessageHandler::textDocument_typeDefinition(
TextDocumentPositionParam &param, ReplyOnce &reply) {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *working_file = working_files->GetFileByFilename(file->def->path);
std::vector<lsLocation> result;
auto Add = [&](const QueryType &type) {
for (const auto &def : type.def)
if (def.spell) {
if (auto ls_loc = GetLsLocation(db, working_files, *def.spell))
result.push_back(*ls_loc);
}
if (result.empty())
for (const DeclRef &dr : type.declarations)
if (auto ls_loc = GetLsLocation(db, working_files, dr))
result.push_back(*ls_loc);
};
for (SymbolRef sym :
FindSymbolsAtLocation(working_file, file, param.position)) {
switch (sym.kind) {
case SymbolKind::Var: {
const QueryVar::Def *def = db->GetVar(sym).AnyDef();
if (def && def->type)
Add(db->Type(def->type));
break;
}
case SymbolKind::Type: {
for (auto &def : db->GetType(sym).def)
if (def.alias_of) {
Add(db->Type(def.alias_of));
break; break;
} }
} break;
// Find the best match of the identifier at point. }
if (!range) { default:
lsPosition position = request->params.position; break;
const std::string &buffer = wfile->buffer_content;
std::string_view query = LexIdentifierAroundPos(position, buffer);
std::string_view short_query = query;
{
auto pos = query.rfind(':');
if (pos != std::string::npos)
short_query = query.substr(pos + 1);
}
// For symbols whose short/detailed names contain |query| as a
// substring, we use the tuple <length difference, negative position,
// not in the same file, line distance> to find the best match.
std::tuple<int, int, bool, int> best_score{INT_MAX, 0, true, 0};
SymbolIdx best_sym;
best_sym.kind = SymbolKind::Invalid;
auto fn = [&](SymbolIdx sym) {
std::string_view short_name = db->GetSymbolName(sym, false),
name = short_query.size() < query.size()
? db->GetSymbolName(sym, true)
: short_name;
if (short_name != short_query)
return;
if (Maybe<DeclRef> dr = GetDefinitionSpell(db, sym)) {
std::tuple<int, int, bool, int> score{
int(name.size() - short_query.size()), 0,
dr->file_id != file_id,
std::abs(dr->range.start.line - position.line)};
// Update the score with qualified name if the qualified name
// occurs in |name|.
auto pos = name.rfind(query);
if (pos != std::string::npos) {
std::get<0>(score) = int(name.size() - query.size());
std::get<1>(score) = -int(pos);
}
if (score < best_score) {
best_score = score;
best_sym = sym;
}
}
};
for (auto &func : db->funcs)
fn({func.usr, SymbolKind::Func});
for (auto &type : db->types)
fn({type.usr, SymbolKind::Type});
for (auto &var : db->vars)
if (var.def.size() && !var.def[0].is_local())
fn({var.usr, SymbolKind::Var});
if (best_sym.kind != SymbolKind::Invalid) {
Maybe<DeclRef> dr = GetDefinitionSpell(db, best_sym);
assert(dr);
if (auto loc = GetLsLocation(db, working_files, *dr))
result.push_back(*loc);
}
}
} }
pipeline::Reply(request->id, result);
} }
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDefinition); std::sort(result.begin(), result.end());
} // namespace result.erase(std::unique(result.begin(), result.end()), result.end());
reply(result);
}
} // namespace ccls

View File

@ -3,148 +3,56 @@
#include "clang_complete.hh" #include "clang_complete.hh"
#include "include_complete.h" #include "include_complete.h"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "project.h" #include "project.hh"
#include "working_files.h" #include "working_files.h"
using namespace ccls;
namespace { namespace ccls {
MethodType didChange = "textDocument/didChange"; void MessageHandler::textDocument_didChange(TextDocumentDidChangeParam &param) {
MethodType didClose = "textDocument/didClose"; std::string path = param.textDocument.uri.GetPath();
MethodType didOpen = "textDocument/didOpen"; working_files->OnChange(param);
MethodType didSave = "textDocument/didSave"; if (g_config->index.onChange)
pipeline::Index(path, {}, IndexMode::OnChange);
clang_complete->NotifyView(path);
if (g_config->diagnostics.onChange >= 0)
clang_complete->DiagnosticsUpdate(path, g_config->diagnostics.onChange);
}
struct In_TextDocumentDidChange : public NotificationMessage { void MessageHandler::textDocument_didClose(TextDocumentParam &param) {
MethodType GetMethodType() const override { return didChange; } std::string path = param.textDocument.uri.GetPath();
lsTextDocumentDidChangeParams params; working_files->OnClose(param.textDocument);
}; clang_complete->OnClose(path);
MAKE_REFLECT_STRUCT(In_TextDocumentDidChange, params); }
REGISTER_IN_MESSAGE(In_TextDocumentDidChange);
struct Handler_TextDocumentDidChange void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam &param) {
: BaseMessageHandler<In_TextDocumentDidChange> { std::string path = param.textDocument.uri.GetPath();
MethodType GetMethodType() const override { return didChange; } WorkingFile *working_file = working_files->OnOpen(param.textDocument);
if (std::optional<std::string> cached_file_contents =
pipeline::LoadIndexedContent(path))
working_file->SetIndexContent(*cached_file_contents);
void Run(In_TextDocumentDidChange *request) override { ReplyOnce reply;
const auto &params = request->params; QueryFile *file = FindFile(reply, path);
std::string path = params.textDocument.uri.GetPath(); if (file) {
working_files->OnChange(params); EmitSkippedRanges(working_file, *file);
if (g_config->index.onChange) EmitSemanticHighlight(db, working_file, *file);
pipeline::Index(path, {}, IndexMode::OnChange);
clang_complete->NotifyView(path);
if (g_config->diagnostics.onChange >= 0)
clang_complete->DiagnosticsUpdate(path, g_config->diagnostics.onChange);
} }
}; include_complete->AddFile(working_file->filename);
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidChange);
struct In_TextDocumentDidClose : public NotificationMessage { // Submit new index request if it is not a header file or there is no
MethodType GetMethodType() const override { return didClose; } // pending index request.
struct Params { std::pair<LanguageId, bool> lang = lookupExtension(path);
lsTextDocumentIdentifier textDocument; if ((lang.first != LanguageId::Unknown && !lang.second) ||
} params; !pipeline::pending_index_requests)
};
MAKE_REFLECT_STRUCT(In_TextDocumentDidClose::Params, textDocument);
MAKE_REFLECT_STRUCT(In_TextDocumentDidClose, params);
REGISTER_IN_MESSAGE(In_TextDocumentDidClose);
struct Handler_TextDocumentDidClose
: BaseMessageHandler<In_TextDocumentDidClose> {
MethodType GetMethodType() const override { return didClose; }
void Run(In_TextDocumentDidClose *request) override {
std::string path = request->params.textDocument.uri.GetPath();
working_files->OnClose(request->params.textDocument);
clang_complete->OnClose(path);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidClose);
struct In_TextDocumentDidOpen : public NotificationMessage {
MethodType GetMethodType() const override { return didOpen; }
struct Params {
lsTextDocumentItem textDocument;
// ccls extension
// If specified (e.g. ["clang++", "-DM", "a.cc"]), it overrides the project
// entry (e.g. loaded from compile_commands.json or .ccls).
std::vector<std::string> args;
} params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentDidOpen::Params, textDocument, args);
MAKE_REFLECT_STRUCT(In_TextDocumentDidOpen, params);
REGISTER_IN_MESSAGE(In_TextDocumentDidOpen);
struct Handler_TextDocumentDidOpen
: BaseMessageHandler<In_TextDocumentDidOpen> {
MethodType GetMethodType() const override { return didOpen; }
void Run(In_TextDocumentDidOpen *request) override {
// NOTE: This function blocks code lens. If it starts taking a long time
// we will need to find a way to unblock the code lens request.
const auto &params = request->params;
const std::string &path = params.textDocument.uri.GetPath();
WorkingFile *working_file = working_files->OnOpen(params.textDocument);
if (std::optional<std::string> cached_file_contents =
pipeline::LoadIndexedContent(path))
working_file->SetIndexContent(*cached_file_contents);
QueryFile *file = nullptr;
FindFileOrFail(db, project, std::nullopt, path, &file);
if (file) {
EmitSkippedRanges(working_file, *file);
EmitSemanticHighlight(db, working_file, *file);
}
include_complete->AddFile(working_file->filename);
std::vector<const char *> args;
for (const std::string &arg : params.args)
args.push_back(Intern(arg));
if (args.size())
project->SetArgsForFile(args, path);
// Submit new index request if it is not a header file or there is no
// pending index request.
std::pair<LanguageId, bool> lang = lookupExtension(path);
if ((lang.first != LanguageId::Unknown && !lang.second) ||
!pipeline::pending_index_requests)
pipeline::Index(path, args, IndexMode::Normal);
clang_complete->NotifyView(path);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidOpen);
struct In_TextDocumentDidSave : public NotificationMessage {
MethodType GetMethodType() const override { return didSave; }
struct Params {
// The document that was saved.
lsTextDocumentIdentifier textDocument;
// Optional the content when saved. Depends on the includeText value
// when the save notifcation was requested.
// std::string text;
} params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentDidSave::Params, textDocument);
MAKE_REFLECT_STRUCT(In_TextDocumentDidSave, params);
REGISTER_IN_MESSAGE(In_TextDocumentDidSave);
struct Handler_TextDocumentDidSave
: BaseMessageHandler<In_TextDocumentDidSave> {
MethodType GetMethodType() const override { return didSave; }
void Run(In_TextDocumentDidSave *request) override {
const auto &params = request->params;
const std::string &path = params.textDocument.uri.GetPath();
pipeline::Index(path, {}, IndexMode::Normal); pipeline::Index(path, {}, IndexMode::Normal);
clang_complete->NotifySave(path);
} clang_complete->NotifyView(path);
}; }
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDidSave);
} // namespace void MessageHandler::textDocument_didSave(TextDocumentParam &param) {
const std::string &path = param.textDocument.uri.GetPath();
pipeline::Index(path, {}, IndexMode::Normal);
clang_complete->NotifySave(path);
}
} // namespace ccls

View File

@ -1,21 +1,19 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
#include <algorithm> #include <algorithm>
MAKE_HASHABLE(SymbolIdx, t.usr, t.kind); MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind);
namespace ccls { namespace ccls {
namespace { MAKE_REFLECT_STRUCT(lsSymbolInformation, name, kind, location, containerName);
MethodType documentHighlight = "textDocument/documentHighlight",
documentLink = "textDocument/documentLink",
documentSymbol = "textDocument/documentSymbol";
struct lsDocumentHighlight { namespace {
struct DocumentHighlight {
enum Kind { Text = 1, Read = 2, Write = 3 }; enum Kind { Text = 1, Read = 2, Write = 3 };
lsRange range; lsRange range;
@ -24,109 +22,81 @@ struct lsDocumentHighlight {
// ccls extension // ccls extension
Role role = Role::None; Role role = Role::None;
bool operator<(const lsDocumentHighlight &o) const { bool operator<(const DocumentHighlight &o) const {
return !(range == o.range) ? range < o.range : kind < o.kind; return !(range == o.range) ? range < o.range : kind < o.kind;
} }
}; };
MAKE_REFLECT_STRUCT(lsDocumentHighlight, range, kind, role); MAKE_REFLECT_STRUCT(DocumentHighlight, range, kind, role);
} // namespace
struct In_TextDocumentDocumentHighlight : public RequestMessage { void MessageHandler::textDocument_documentHighlight(
MethodType GetMethodType() const override { return documentHighlight; } TextDocumentPositionParam &param, ReplyOnce &reply) {
lsTextDocumentPositionParams params; int file_id;
}; QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id);
MAKE_REFLECT_STRUCT(In_TextDocumentDocumentHighlight, id, params); if (!file)
REGISTER_IN_MESSAGE(In_TextDocumentDocumentHighlight); return;
struct Handler_TextDocumentDocumentHighlight WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
: BaseMessageHandler<In_TextDocumentDocumentHighlight> { std::vector<DocumentHighlight> result;
MethodType GetMethodType() const override { return documentHighlight; }
void Run(In_TextDocumentDocumentHighlight *request) override {
int file_id;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
request->params.textDocument.uri.GetPath(), &file,
&file_id))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path); std::vector<SymbolRef> syms =
std::vector<lsDocumentHighlight> result; FindSymbolsAtLocation(wfile, file, param.position, true);
for (auto [sym, refcnt] : file->symbol2refcnt) {
std::vector<SymbolRef> syms = if (refcnt <= 0)
FindSymbolsAtLocation(wfile, file, request->params.position, true); continue;
for (auto [sym, refcnt] : file->symbol2refcnt) { Usr usr = sym.usr;
if (refcnt <= 0) SymbolKind kind = sym.kind;
continue; if (std::none_of(syms.begin(), syms.end(), [&](auto &sym1) {
Usr usr = sym.usr; return usr == sym1.usr && kind == sym1.kind;
SymbolKind kind = sym.kind; }))
if (std::none_of(syms.begin(), syms.end(), [&](auto &sym1) { continue;
return usr == sym1.usr && kind == sym1.kind; if (auto loc = GetLsLocation(db, working_files, sym, file_id)) {
})) DocumentHighlight highlight;
continue; highlight.range = loc->range;
if (auto loc = GetLsLocation(db, working_files, sym, file_id)) { if (sym.role & Role::Write)
lsDocumentHighlight highlight; highlight.kind = DocumentHighlight::Write;
highlight.range = loc->range; else if (sym.role & Role::Read)
if (sym.role & Role::Write) highlight.kind = DocumentHighlight::Read;
highlight.kind = lsDocumentHighlight::Write; else
else if (sym.role & Role::Read) highlight.kind = DocumentHighlight::Text;
highlight.kind = lsDocumentHighlight::Read; highlight.role = sym.role;
else result.push_back(highlight);
highlight.kind = lsDocumentHighlight::Text;
highlight.role = sym.role;
result.push_back(highlight);
}
} }
std::sort(result.begin(), result.end());
pipeline::Reply(request->id, result);
} }
}; std::sort(result.begin(), result.end());
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentDocumentHighlight); reply(result);
}
struct In_textDocumentDocumentLink : public RequestMessage {
MethodType GetMethodType() const override { return documentLink; }
lsTextDocumentPositionParams params;
};
MAKE_REFLECT_STRUCT(In_textDocumentDocumentLink, id, params);
REGISTER_IN_MESSAGE(In_textDocumentDocumentLink);
namespace {
struct lsDocumentLink { struct lsDocumentLink {
lsRange range; lsRange range;
lsDocumentUri target; lsDocumentUri target;
}; };
MAKE_REFLECT_STRUCT(lsDocumentLink, range, target); MAKE_REFLECT_STRUCT(lsDocumentLink, range, target);
} // namespace
struct Handler_textDocumentDocumentLink void MessageHandler::textDocument_documentLink(TextDocumentParam &param,
: BaseMessageHandler<In_textDocumentDocumentLink> { ReplyOnce &reply) {
MethodType GetMethodType() const override { return documentLink; } QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
void Run(In_textDocumentDocumentLink *request) override { if (!file)
QueryFile *file; return;
if (!FindFileOrFail(db, project, request->id,
request->params.textDocument.uri.GetPath(), &file))
return;
std::vector<lsDocumentLink> result; std::vector<lsDocumentLink> result;
for (const IndexInclude &include : file->def->includes) for (const IndexInclude &include : file->def->includes)
result.push_back({lsRange{{include.line, 0}, {include.line + 1, 0}}, result.push_back({lsRange{{include.line, 0}, {include.line + 1, 0}},
lsDocumentUri::FromPath(include.resolved_path)}); lsDocumentUri::FromPath(include.resolved_path)});
pipeline::Reply(request->id, result); reply(result);
} } // namespace ccls
namespace {
struct DocumentSymbolParam : TextDocumentParam {
// false: outline; true: all symbols
bool all = false;
// If >= 0, return Range[] instead of SymbolInformation[] to reduce output.
int startLine = -1;
int endLine = -1;
}; };
REGISTER_MESSAGE_HANDLER(Handler_textDocumentDocumentLink); MAKE_REFLECT_STRUCT(DocumentSymbolParam, textDocument, all, startLine, endLine);
struct In_textDocumentDocumentSymbol : public RequestMessage {
MethodType GetMethodType() const override { return documentSymbol; }
struct Params {
lsTextDocumentIdentifier textDocument;
// false: outline; true: all symbols
bool all = false;
// If >= 0, return Range[] instead of SymbolInformation[] to reduce output.
int startLine = -1;
int endLine = -1;
} params;
};
MAKE_REFLECT_STRUCT(In_textDocumentDocumentSymbol::Params, textDocument, all,
startLine, endLine);
MAKE_REFLECT_STRUCT(In_textDocumentDocumentSymbol, id, params);
REGISTER_IN_MESSAGE(In_textDocumentDocumentSymbol);
struct lsDocumentSymbol { struct lsDocumentSymbol {
std::string name; std::string name;
@ -155,131 +125,126 @@ template<>
bool Ignore(const QueryVar::Def *def) { bool Ignore(const QueryVar::Def *def) {
return !def || def->is_local(); return !def || def->is_local();
} }
struct Handler_textDocumentDocumentSymbol
: BaseMessageHandler<In_textDocumentDocumentSymbol> {
MethodType GetMethodType() const override { return documentSymbol; }
void Run(In_textDocumentDocumentSymbol *request) override {
auto &params = request->params;
QueryFile *file;
int file_id;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file, &file_id))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
if (params.startLine >= 0) {
std::vector<lsRange> result;
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && (params.all || sym.extent.Valid()) &&
params.startLine <= sym.range.start.line &&
sym.range.start.line <= params.endLine)
if (auto loc = GetLsLocation(db, working_files, sym, file_id))
result.push_back(loc->range);
std::sort(result.begin(), result.end());
pipeline::Reply(request->id, result);
} else if (g_config->client.hierarchicalDocumentSymbolSupport) {
std::unordered_map<SymbolIdx, std::unique_ptr<lsDocumentSymbol>> sym2ds;
std::vector<std::pair<std::vector<const void *>, lsDocumentSymbol *>>
funcs, types;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.Valid())
continue;
auto r = sym2ds.try_emplace(SymbolIdx{sym.usr, sym.kind});
if (!r.second)
continue;
auto &ds = r.first->second;
ds = std::make_unique<lsDocumentSymbol>();
std::vector<const void *> def_ptrs;
WithEntity(db, sym, [&, sym = sym](const auto &entity) {
auto *def = entity.AnyDef();
if (!def)
return;
ds->name = def->Name(false);
ds->detail = def->Name(true);
if (auto ls_range = GetLsRange(wfile, sym.range)) {
ds->selectionRange = *ls_range;
ds->range = ds->selectionRange;
if (sym.extent.Valid())
if (auto ls_range1 = GetLsRange(wfile, sym.extent))
ds->range = *ls_range1;
}
for (auto &def : entity.def)
if (def.file_id == file_id && !Ignore(&def)) {
ds->kind = def.kind;
if (def.spell || def.kind == lsSymbolKind::Namespace)
def_ptrs.push_back(&def);
}
});
if (def_ptrs.empty() || !(params.all || sym.role & Role::Definition ||
ds->kind == lsSymbolKind::Namespace)) {
ds.reset();
continue;
}
if (sym.kind == SymbolKind::Func)
funcs.emplace_back(std::move(def_ptrs), ds.get());
else if (sym.kind == SymbolKind::Type)
types.emplace_back(std::move(def_ptrs), ds.get());
}
for (auto &[def_ptrs, ds] : funcs)
for (const void *def_ptr : def_ptrs)
for (Usr usr1 : ((const QueryFunc::Def *)def_ptr)->vars) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (auto &[def_ptrs, ds] : types)
for (const void *def_ptr : def_ptrs) {
auto *def = (const QueryType::Def *)def_ptr;
for (Usr usr1 : def->funcs) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Func});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (Usr usr1 : def->types) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Type});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (auto [usr1, _] : def->vars) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
}
std::vector<std::unique_ptr<lsDocumentSymbol>> result;
for (auto &[_, ds] : sym2ds)
if (ds)
result.push_back(std::move(ds));
pipeline::Reply(request->id, result);
} else {
std::vector<lsSymbolInformation> result;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.Valid() ||
!(params.all || sym.role & Role::Definition))
continue;
if (std::optional<lsSymbolInformation> info =
GetSymbolInfo(db, sym, false)) {
if ((sym.kind == SymbolKind::Type &&
Ignore(db->GetType(sym).AnyDef())) ||
(sym.kind == SymbolKind::Var &&
Ignore(db->GetVar(sym).AnyDef())))
continue;
if (auto loc = GetLsLocation(db, working_files, sym, file_id)) {
info->location = *loc;
result.push_back(*info);
}
}
}
pipeline::Reply(request->id, result);
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_textDocumentDocumentSymbol);
} // namespace } // namespace
void MessageHandler::textDocument_documentSymbol(Reader &reader,
ReplyOnce &reply) {
DocumentSymbolParam param;
Reflect(reader, param);
int file_id;
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id);
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
if (param.startLine >= 0) {
std::vector<lsRange> result;
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && (param.all || sym.extent.Valid()) &&
param.startLine <= sym.range.start.line &&
sym.range.start.line <= param.endLine)
if (auto loc = GetLsLocation(db, working_files, sym, file_id))
result.push_back(loc->range);
std::sort(result.begin(), result.end());
reply(result);
} else if (g_config->client.hierarchicalDocumentSymbolSupport) {
std::unordered_map<SymbolIdx, std::unique_ptr<lsDocumentSymbol>> sym2ds;
std::vector<std::pair<std::vector<const void *>, lsDocumentSymbol *>> funcs,
types;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.Valid())
continue;
auto r = sym2ds.try_emplace(SymbolIdx{sym.usr, sym.kind});
if (!r.second)
continue;
auto &ds = r.first->second;
ds = std::make_unique<lsDocumentSymbol>();
std::vector<const void *> def_ptrs;
WithEntity(db, sym, [&, sym = sym](const auto &entity) {
auto *def = entity.AnyDef();
if (!def)
return;
ds->name = def->Name(false);
ds->detail = def->Name(true);
if (auto ls_range = GetLsRange(wfile, sym.range)) {
ds->selectionRange = *ls_range;
ds->range = ds->selectionRange;
if (sym.extent.Valid())
if (auto ls_range1 = GetLsRange(wfile, sym.extent))
ds->range = *ls_range1;
}
for (auto &def : entity.def)
if (def.file_id == file_id && !Ignore(&def)) {
ds->kind = def.kind;
if (def.spell || def.kind == lsSymbolKind::Namespace)
def_ptrs.push_back(&def);
}
});
if (def_ptrs.empty() || !(param.all || sym.role & Role::Definition ||
ds->kind == lsSymbolKind::Namespace)) {
ds.reset();
continue;
}
if (sym.kind == SymbolKind::Func)
funcs.emplace_back(std::move(def_ptrs), ds.get());
else if (sym.kind == SymbolKind::Type)
types.emplace_back(std::move(def_ptrs), ds.get());
}
for (auto &[def_ptrs, ds] : funcs)
for (const void *def_ptr : def_ptrs)
for (Usr usr1 : ((const QueryFunc::Def *)def_ptr)->vars) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (auto &[def_ptrs, ds] : types)
for (const void *def_ptr : def_ptrs) {
auto *def = (const QueryType::Def *)def_ptr;
for (Usr usr1 : def->funcs) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Func});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (Usr usr1 : def->types) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Type});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
for (auto [usr1, _] : def->vars) {
auto it = sym2ds.find(SymbolIdx{usr1, SymbolKind::Var});
if (it != sym2ds.end() && it->second)
ds->children.push_back(std::move(it->second));
}
}
std::vector<std::unique_ptr<lsDocumentSymbol>> result;
for (auto &[_, ds] : sym2ds)
if (ds)
result.push_back(std::move(ds));
reply(result);
} else {
std::vector<lsSymbolInformation> result;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.Valid() ||
!(param.all || sym.role & Role::Definition))
continue;
if (std::optional<lsSymbolInformation> info =
GetSymbolInfo(db, sym, false)) {
if ((sym.kind == SymbolKind::Type &&
Ignore(db->GetType(sym).AnyDef())) ||
(sym.kind == SymbolKind::Var && Ignore(db->GetVar(sym).AnyDef())))
continue;
if (auto loc = GetLsLocation(db, working_files, sym, file_id)) {
info->location = *loc;
result.push_back(*info);
}
}
}
reply(result);
}
}
} // namespace ccls } // namespace ccls

View File

@ -1,61 +1,43 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "project.h" #include "project.hh"
#include "query_utils.h" #include "query_utils.h"
#include "working_files.h" #include "working_files.h"
namespace ccls { namespace ccls {
namespace { namespace {
MethodType foldingRange = "textDocument/foldingRange";
struct In_textDocumentFoldingRange : public RequestMessage {
MethodType GetMethodType() const override { return foldingRange; }
struct Params {
lsTextDocumentIdentifier textDocument;
} params;
};
MAKE_REFLECT_STRUCT(In_textDocumentFoldingRange::Params, textDocument);
MAKE_REFLECT_STRUCT(In_textDocumentFoldingRange, id, params);
REGISTER_IN_MESSAGE(In_textDocumentFoldingRange);
struct FoldingRange { struct FoldingRange {
int startLine, startCharacter, endLine, endCharacter; int startLine, startCharacter, endLine, endCharacter;
std::string kind = "region"; std::string kind = "region";
}; };
MAKE_REFLECT_STRUCT(FoldingRange, startLine, startCharacter, endLine, endCharacter, kind); MAKE_REFLECT_STRUCT(FoldingRange, startLine, startCharacter, endLine,
endCharacter, kind);
struct Handler_textDocumentFoldingRange
: BaseMessageHandler<In_textDocumentFoldingRange> {
MethodType GetMethodType() const override { return foldingRange; }
void Run(In_textDocumentFoldingRange *request) override {
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
request->params.textDocument.uri.GetPath(), &file,
nullptr))
return;
WorkingFile *wfile =
working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
std::vector<FoldingRange> result;
std::optional<lsRange> ls_range;
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() &&
(sym.kind == SymbolKind::Func || sym.kind == SymbolKind::Type) &&
(ls_range = GetLsRange(wfile, sym.extent))) {
FoldingRange &fold = result.emplace_back();
fold.startLine = ls_range->start.line;
fold.startCharacter = ls_range->start.character;
fold.endLine = ls_range->end.line;
fold.endCharacter = ls_range->end.character;
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_textDocumentFoldingRange);
} // namespace } // namespace
void MessageHandler::textDocument_foldingRange(TextDocumentParam &param,
ReplyOnce &reply) {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
std::vector<FoldingRange> result;
std::optional<lsRange> ls_range;
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.Valid() &&
(sym.kind == SymbolKind::Func || sym.kind == SymbolKind::Type) &&
(ls_range = GetLsRange(wfile, sym.extent))) {
FoldingRange &fold = result.emplace_back();
fold.startLine = ls_range->start.line;
fold.startCharacter = ls_range->start.character;
fold.endLine = ls_range->end.line;
fold.endCharacter = ls_range->end.character;
}
reply(result);
}
} // namespace ccls } // namespace ccls

View File

@ -1,29 +1,17 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "working_files.h" #include "working_files.h"
#include <clang/Format/Format.h> #include <clang/Format/Format.h>
#include <clang/Tooling/Core/Replacement.h> #include <clang/Tooling/Core/Replacement.h>
using namespace ccls; namespace ccls {
using namespace clang; using namespace clang;
namespace { namespace {
const MethodType formatting = "textDocument/formatting",
onTypeFormatting = "textDocument/onTypeFormatting",
rangeFormatting = "textDocument/rangeFormatting";
struct lsFormattingOptions {
// Size of a tab in spaces.
int tabSize;
// Prefer spaces over tabs.
bool insertSpaces;
};
MAKE_REFLECT_STRUCT(lsFormattingOptions, tabSize, insertSpaces);
llvm::Expected<tooling::Replacements> llvm::Expected<tooling::Replacements>
FormatCode(std::string_view code, std::string_view file, tooling::Range Range) { FormatCode(std::string_view code, std::string_view file, tooling::Range Range) {
StringRef Code(code.data(), code.size()), File(file.data(), file.size()); StringRef Code(code.data(), code.size()), File(file.data(), file.size());
@ -67,116 +55,59 @@ ReplacementsToEdits(std::string_view code, const tooling::Replacements &Repls) {
return ret; return ret;
} }
void Format(WorkingFile *wfile, tooling::Range range, lsRequestId id) { void Format(ReplyOnce &reply, WorkingFile *wfile, tooling::Range range) {
std::string_view code = wfile->buffer_content; std::string_view code = wfile->buffer_content;
auto ReplsOrErr = auto ReplsOrErr = FormatCode(code, wfile->filename, range);
FormatCode(code, wfile->filename, range);
if (ReplsOrErr) { if (ReplsOrErr) {
auto result = ReplacementsToEdits(code, *ReplsOrErr); auto result = ReplacementsToEdits(code, *ReplsOrErr);
pipeline::Reply(id, result); reply(result);
} else { } else {
lsResponseError err; lsResponseError err;
err.code = lsErrorCodes::UnknownErrorCode; err.code = lsErrorCodes::UnknownErrorCode;
err.message = llvm::toString(ReplsOrErr.takeError()); err.message = llvm::toString(ReplsOrErr.takeError());
pipeline::ReplyError(id, err); reply.Error(err);
} }
} }
struct In_TextDocumentFormatting : public RequestMessage {
MethodType GetMethodType() const override { return formatting; }
struct Params {
lsTextDocumentIdentifier textDocument;
lsFormattingOptions options;
} params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentFormatting::Params, textDocument, options);
MAKE_REFLECT_STRUCT(In_TextDocumentFormatting, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentFormatting);
struct Handler_TextDocumentFormatting
: BaseMessageHandler<In_TextDocumentFormatting> {
MethodType GetMethodType() const override { return formatting; }
void Run(In_TextDocumentFormatting *request) override {
auto &params = request->params;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
Format(wfile, {0, (unsigned)wfile->buffer_content.size()}, request->id);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentFormatting);
struct In_TextDocumentOnTypeFormatting : public RequestMessage {
MethodType GetMethodType() const override { return onTypeFormatting; }
struct Params {
lsTextDocumentIdentifier textDocument;
lsPosition position;
std::string ch;
lsFormattingOptions options;
} params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentOnTypeFormatting::Params, textDocument,
position, ch, options);
MAKE_REFLECT_STRUCT(In_TextDocumentOnTypeFormatting, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentOnTypeFormatting);
struct Handler_TextDocumentOnTypeFormatting
: BaseMessageHandler<In_TextDocumentOnTypeFormatting> {
MethodType GetMethodType() const override { return onTypeFormatting; }
void Run(In_TextDocumentOnTypeFormatting *request) override {
auto &params = request->params;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
std::string_view code = wfile->buffer_content;
int pos = GetOffsetForPosition(params.position, code);
auto lbrace = code.find_last_of('{', pos);
if (lbrace == std::string::npos)
lbrace = pos;
Format(wfile, {(unsigned)lbrace, unsigned(pos - lbrace)}, request->id);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentOnTypeFormatting);
struct In_TextDocumentRangeFormatting : public RequestMessage {
MethodType GetMethodType() const override { return rangeFormatting; }
struct Params {
lsTextDocumentIdentifier textDocument;
lsRange range;
lsFormattingOptions options;
} params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentRangeFormatting::Params, textDocument, range,
options);
MAKE_REFLECT_STRUCT(In_TextDocumentRangeFormatting, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentRangeFormatting);
struct Handler_TextDocumentRangeFormatting
: BaseMessageHandler<In_TextDocumentRangeFormatting> {
MethodType GetMethodType() const override { return rangeFormatting; }
void Run(In_TextDocumentRangeFormatting *request) override {
auto &params = request->params;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
std::string_view code = wfile->buffer_content;
int begin = GetOffsetForPosition(params.range.start, code),
end = GetOffsetForPosition(params.range.end, code);
Format(wfile, {(unsigned)begin, unsigned(end - begin)}, request->id);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentRangeFormatting);
} // namespace } // namespace
void MessageHandler::textDocument_formatting(DocumentFormattingParam &param,
ReplyOnce &reply) {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
Format(reply, wfile, {0, (unsigned)wfile->buffer_content.size()});
}
void MessageHandler::textDocument_onTypeFormatting(
DocumentOnTypeFormattingParam &param, ReplyOnce &reply) {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
std::string_view code = wfile->buffer_content;
int pos = GetOffsetForPosition(param.position, code);
auto lbrace = code.find_last_of('{', pos);
if (lbrace == std::string::npos)
lbrace = pos;
Format(reply, wfile, {(unsigned)lbrace, unsigned(pos - lbrace)});
}
void MessageHandler::textDocument_rangeFormatting(
DocumentRangeFormattingParam &param, ReplyOnce &reply) {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!wfile)
return;
std::string_view code = wfile->buffer_content;
int begin = GetOffsetForPosition(param.range.start, code),
end = GetOffsetForPosition(param.range.end, code);
Format(reply, wfile, {(unsigned)begin, unsigned(end - begin)});
}
} // namespace ccls

View File

@ -1,13 +1,33 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
using namespace ccls;
namespace ccls {
namespace { namespace {
MethodType kMethodType = "textDocument/hover"; struct lsMarkedString {
std::optional<std::string> language;
std::string value;
};
struct Hover {
std::vector<lsMarkedString> contents;
std::optional<lsRange> range;
};
void Reflect(Writer &visitor, lsMarkedString &value) {
// If there is a language, emit a `{language:string, value:string}` object. If
// not, emit a string.
if (value.language) {
REFLECT_MEMBER_START();
REFLECT_MEMBER(language);
REFLECT_MEMBER(value);
REFLECT_MEMBER_END();
} else {
Reflect(visitor, value.value);
}
}
MAKE_REFLECT_STRUCT(Hover, contents, range);
const char *LanguageIdentifier(LanguageId lang) { const char *LanguageIdentifier(LanguageId lang) {
switch (lang) { switch (lang) {
@ -57,51 +77,34 @@ GetHover(DB *db, LanguageId lang, SymbolRef sym, int file_id) {
}); });
return {hover, ls_comments}; return {hover, ls_comments};
} }
struct In_TextDocumentHover : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
lsTextDocumentPositionParams params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentHover, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentHover);
struct lsHover {
std::vector<lsMarkedString> contents;
std::optional<lsRange> range;
};
MAKE_REFLECT_STRUCT(lsHover, contents, range);
struct Handler_TextDocumentHover : BaseMessageHandler<In_TextDocumentHover> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_TextDocumentHover *request) override {
auto &params = request->params;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
lsHover result;
for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, params.position)) {
std::optional<lsRange> ls_range = GetLsRange(
working_files->GetFileByFilename(file->def->path), sym.range);
if (!ls_range)
continue;
auto [hover, comments] = GetHover(db, file->def->language, sym, file->id);
if (comments || hover) {
result.range = *ls_range;
if (comments)
result.contents.push_back(*comments);
if (hover)
result.contents.push_back(*hover);
break;
}
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentHover);
} // namespace } // namespace
void MessageHandler::textDocument_hover(TextDocumentPositionParam &param,
ReplyOnce &reply) {
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
Hover result;
for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position)) {
std::optional<lsRange> ls_range = GetLsRange(
working_files->GetFileByFilename(file->def->path), sym.range);
if (!ls_range)
continue;
auto [hover, comments] = GetHover(db, file->def->language, sym, file->id);
if (comments || hover) {
result.range = *ls_range;
if (comments)
result.contents.push_back(*comments);
if (hover)
result.contents.push_back(*hover);
break;
}
}
reply(result);
}
} // namespace ccls

View File

@ -1,20 +1,15 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
#include <unordered_set> #include <unordered_set>
using namespace ccls; namespace ccls {
namespace { namespace {
MethodType kMethodType = "textDocument/references"; struct ReferenceParam : public TextDocumentPositionParam {
struct Context {
struct In_TextDocumentReferences : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
struct lsReferenceContext {
bool base = true; bool base = true;
// Exclude references with any |Role| bits set. // Exclude references with any |Role| bits set.
Role excludeRole = Role::None; Role excludeRole = Role::None;
@ -22,116 +17,99 @@ struct In_TextDocumentReferences : public RequestMessage {
bool includeDeclaration = false; bool includeDeclaration = false;
// Include references with all |Role| bits set. // Include references with all |Role| bits set.
Role role = Role::None; Role role = Role::None;
}; } context;
struct Params {
lsTextDocumentIdentifier textDocument;
lsPosition position;
lsReferenceContext context;
};
Params params;
}; };
MAKE_REFLECT_STRUCT(In_TextDocumentReferences::lsReferenceContext, base, MAKE_REFLECT_STRUCT(ReferenceParam::Context, base, excludeRole,
excludeRole, includeDeclaration, role); includeDeclaration, role);
MAKE_REFLECT_STRUCT(In_TextDocumentReferences::Params, textDocument, position, MAKE_REFLECT_STRUCT(ReferenceParam, textDocument, position, context);
context); } // namespace
MAKE_REFLECT_STRUCT(In_TextDocumentReferences, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentReferences);
struct Handler_TextDocumentReferences void MessageHandler::textDocument_references(Reader &reader, ReplyOnce &reply) {
: BaseMessageHandler<In_TextDocumentReferences> { ReferenceParam param;
MethodType GetMethodType() const override { return kMethodType; } Reflect(reader, param);
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath());
if (!file)
return;
std::vector<lsLocation> result;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!file)
return;
void Run(In_TextDocumentReferences *request) override { std::unordered_set<Use> seen_uses;
auto &params = request->params; int line = param.position.line;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
params.textDocument.uri.GetPath(), &file))
return;
std::vector<lsLocation> result;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
if (!file) {
pipeline::Reply(request->id, result);
return;
}
std::unordered_set<Use> seen_uses; for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position)) {
int line = params.position.line; // Found symbol. Return references.
std::unordered_set<Usr> seen;
for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, params.position)) { seen.insert(sym.usr);
// Found symbol. Return references. std::vector<Usr> stack{sym.usr};
std::unordered_set<Usr> seen; if (sym.kind != SymbolKind::Func)
seen.insert(sym.usr); param.context.base = false;
std::vector<Usr> stack{sym.usr}; while (stack.size()) {
if (sym.kind != SymbolKind::Func) sym.usr = stack.back();
params.context.base = false; stack.pop_back();
while (stack.size()) { auto fn = [&](Use use, lsSymbolKind parent_kind) {
sym.usr = stack.back(); if (Role(use.role & param.context.role) == param.context.role &&
stack.pop_back(); !(use.role & param.context.excludeRole) &&
auto fn = [&](Use use, lsSymbolKind parent_kind) { seen_uses.insert(use).second)
if (Role(use.role & params.context.role) == params.context.role && if (auto loc = GetLsLocation(db, working_files, use)) {
!(use.role & params.context.excludeRole) && result.push_back(*loc);
seen_uses.insert(use).second) }
if (auto loc = GetLsLocation(db, working_files, use)) { };
result.push_back(*loc); WithEntity(db, sym, [&](const auto &entity) {
} lsSymbolKind parent_kind = lsSymbolKind::Unknown;
}; for (auto &def : entity.def)
WithEntity(db, sym, [&](const auto &entity) { if (def.spell) {
lsSymbolKind parent_kind = lsSymbolKind::Unknown; parent_kind = GetSymbolKind(db, sym);
if (param.context.base)
for (Usr usr : def.GetBases())
if (!seen.count(usr)) {
seen.insert(usr);
stack.push_back(usr);
}
break;
}
for (Use use : entity.uses)
fn(use, parent_kind);
if (param.context.includeDeclaration) {
for (auto &def : entity.def) for (auto &def : entity.def)
if (def.spell) { if (def.spell)
parent_kind = GetSymbolKind(db, sym); fn(*def.spell, parent_kind);
if (params.context.base) for (Use use : entity.declarations)
for (Usr usr : def.GetBases()) fn(use, parent_kind);
if (!seen.count(usr)) { }
seen.insert(usr); });
stack.push_back(usr); }
} break;
}
if (result.empty()) {
// |path| is the #include line. If the cursor is not on such line but line
// = 0,
// use the current filename.
std::string path;
if (line == 0 || line >= (int)wfile->buffer_lines.size() - 1)
path = file->def->path;
for (const IndexInclude &include : file->def->includes)
if (include.line == param.position.line) {
path = include.resolved_path;
break;
}
if (path.size())
for (QueryFile &file1 : db->files)
if (file1.def)
for (const IndexInclude &include : file1.def->includes)
if (include.resolved_path == path) {
// Another file |file1| has the same include line.
lsLocation &loc = result.emplace_back();
loc.uri = lsDocumentUri::FromPath(file1.def->path);
loc.range.start.line = loc.range.end.line = include.line;
break; break;
} }
for (Use use : entity.uses)
fn(use, parent_kind);
if (params.context.includeDeclaration) {
for (auto &def : entity.def)
if (def.spell)
fn(*def.spell, parent_kind);
for (Use use : entity.declarations)
fn(use, parent_kind);
}
});
}
break;
}
if (result.empty()) {
// |path| is the #include line. If the cursor is not on such line but line
// = 0,
// use the current filename.
std::string path;
if (line == 0 || line >= (int)wfile->buffer_lines.size() - 1)
path = file->def->path;
for (const IndexInclude &include : file->def->includes)
if (include.line == params.position.line) {
path = include.resolved_path;
break;
}
if (path.size())
for (QueryFile &file1 : db->files)
if (file1.def)
for (const IndexInclude &include : file1.def->includes)
if (include.resolved_path == path) {
// Another file |file1| has the same include line.
lsLocation &loc = result.emplace_back();
loc.uri = lsDocumentUri::FromPath(file1.def->path);
loc.range.start.line = loc.range.end.line = include.line;
break;
}
}
if ((int)result.size() >= g_config->xref.maxNum)
result.resize(g_config->xref.maxNum);
pipeline::Reply(request->id, result);
} }
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentReferences); if ((int)result.size() >= g_config->xref.maxNum)
} // namespace result.resize(g_config->xref.maxNum);
reply(result);
}
} // namespace ccls

View File

@ -1,14 +1,11 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh"
#include "query_utils.h" #include "query_utils.h"
using namespace ccls;
namespace ccls {
namespace { namespace {
MethodType kMethodType = "textDocument/rename";
lsWorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *working_files, lsWorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *working_files,
SymbolRef sym, const std::string &new_text) { SymbolRef sym, const std::string &new_text) {
std::unordered_map<int, lsTextDocumentEdit> path_to_edit; std::unordered_map<int, lsTextDocumentEdit> path_to_edit;
@ -50,49 +47,21 @@ lsWorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *working_files,
edit.documentChanges.push_back(changes.second); edit.documentChanges.push_back(changes.second);
return edit; return edit;
} }
struct In_TextDocumentRename : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
struct Params {
// The document to format.
lsTextDocumentIdentifier textDocument;
// The position at which this request was sent.
lsPosition position;
// The new name of the symbol. If the given name is not valid the
// request must return a [ResponseError](#ResponseError) with an
// appropriate message set.
std::string newName;
};
Params params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentRename::Params, textDocument, position,
newName);
MAKE_REFLECT_STRUCT(In_TextDocumentRename, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentRename);
struct Handler_TextDocumentRename : BaseMessageHandler<In_TextDocumentRename> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_TextDocumentRename *request) override {
int file_id;
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
request->params.textDocument.uri.GetPath(), &file,
&file_id))
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
lsWorkspaceEdit result;
for (SymbolRef sym :
FindSymbolsAtLocation(wfile, file, request->params.position)) {
result =
BuildWorkspaceEdit(db, working_files, sym, request->params.newName);
break;
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentRename);
} // namespace } // namespace
void MessageHandler::textDocument_rename(RenameParam &param, ReplyOnce &reply) {
int file_id;
QueryFile *file = FindFile(reply, param.textDocument.uri.GetPath(), &file_id);
if (!file)
return;
WorkingFile *wfile = working_files->GetFileByFilename(file->def->path);
lsWorkspaceEdit result;
for (SymbolRef sym : FindSymbolsAtLocation(wfile, file, param.position)) {
result = BuildWorkspaceEdit(db, working_files, sym, param.newName);
break;
}
reply(result);
}
} // namespace ccls

View File

@ -2,56 +2,35 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "clang_complete.hh" #include "clang_complete.hh"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include <clang/Sema/Sema.h> #include <clang/Sema/Sema.h>
using namespace ccls; namespace ccls {
using namespace clang; using namespace clang;
namespace { namespace {
MethodType kMethodType = "textDocument/signatureHelp"; struct ParameterInformation {
// Represents a parameter of a callable-signature. A parameter can
// have a label and a doc-comment.
struct lsParameterInformation {
std::string label; std::string label;
// Not available in clang
// std::optional<std::string> documentation;
}; };
MAKE_REFLECT_STRUCT(lsParameterInformation, label); struct SignatureInformation {
// Represents the signature of something callable. A signature
// can have a label, like a function-name, a doc-comment, and
// a set of parameters.
struct lsSignatureInformation {
std::string label; std::string label;
std::optional<std::string> documentation; std::optional<std::string> documentation;
std::vector<lsParameterInformation> parameters; std::vector<ParameterInformation> parameters;
}; };
MAKE_REFLECT_STRUCT(lsSignatureInformation, label, documentation, parameters);
// Signature help represents the signature of something
// callable. There can be multiple signature but only one
// active and only one active parameter.
struct lsSignatureHelp { struct lsSignatureHelp {
std::vector<lsSignatureInformation> signatures; std::vector<SignatureInformation> signatures;
int activeSignature = 0; int activeSignature = 0;
int activeParameter = 0; int activeParameter = 0;
}; };
MAKE_REFLECT_STRUCT(ParameterInformation, label);
MAKE_REFLECT_STRUCT(SignatureInformation, label, documentation, parameters);
MAKE_REFLECT_STRUCT(lsSignatureHelp, signatures, activeSignature, MAKE_REFLECT_STRUCT(lsSignatureHelp, signatures, activeSignature,
activeParameter); activeParameter);
struct In_TextDocumentSignatureHelp : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
lsTextDocumentPositionParams params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentSignatureHelp, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentSignatureHelp);
std::string BuildOptional(const CodeCompletionString &CCS, std::string BuildOptional(const CodeCompletionString &CCS,
std::vector<lsParameterInformation> &ls_params) { std::vector<ParameterInformation> &ls_params) {
std::string ret; std::string ret;
for (const auto &Chunk : CCS) { for (const auto &Chunk : CCS) {
switch (Chunk.Kind) { switch (Chunk.Kind) {
@ -67,7 +46,7 @@ std::string BuildOptional(const CodeCompletionString &CCS,
// the code-completion location within a function call, message send, // the code-completion location within a function call, message send,
// macro invocation, etc. // macro invocation, etc.
ret += Chunk.Text; ret += Chunk.Text;
ls_params.push_back(lsParameterInformation{Chunk.Text}); ls_params.push_back({Chunk.Text});
break; break;
} }
case CodeCompletionString::CK_VerticalSpace: case CodeCompletionString::CK_VerticalSpace:
@ -113,7 +92,7 @@ public:
Cand.CreateSignatureString(CurrentArg, S, *Alloc, CCTUInfo, true); Cand.CreateSignatureString(CurrentArg, S, *Alloc, CCTUInfo, true);
const char *ret_type = nullptr; const char *ret_type = nullptr;
lsSignatureInformation &ls_sig = ls_sighelp.signatures.emplace_back(); SignatureInformation &ls_sig = ls_sighelp.signatures.emplace_back();
#if LLVM_VERSION_MAJOR >= 8 #if LLVM_VERSION_MAJOR >= 8
const RawComment *RC = getCompletionComment(S.getASTContext(), Cand.getFunction()); const RawComment *RC = getCompletionComment(S.getASTContext(), Cand.getFunction());
ls_sig.documentation = RC ? RC->getBriefText(S.getASTContext()) : ""; ls_sig.documentation = RC ? RC->getBriefText(S.getASTContext()) : "";
@ -126,7 +105,7 @@ public:
case CodeCompletionString::CK_Placeholder: case CodeCompletionString::CK_Placeholder:
case CodeCompletionString::CK_CurrentParameter: { case CodeCompletionString::CK_CurrentParameter: {
ls_sig.label += Chunk.Text; ls_sig.label += Chunk.Text;
ls_sig.parameters.push_back(lsParameterInformation{Chunk.Text}); ls_sig.parameters.push_back({Chunk.Text});
break; break;
} }
case CodeCompletionString::CK_Optional: case CodeCompletionString::CK_Optional:
@ -145,7 +124,7 @@ public:
} }
std::sort( std::sort(
ls_sighelp.signatures.begin(), ls_sighelp.signatures.end(), ls_sighelp.signatures.begin(), ls_sighelp.signatures.end(),
[](const lsSignatureInformation &l, const lsSignatureInformation &r) { [](const SignatureInformation &l, const SignatureInformation &r) {
if (l.parameters.size() != r.parameters.size()) if (l.parameters.size() != r.parameters.size())
return l.parameters.size() < r.parameters.size(); return l.parameters.size() < r.parameters.size();
if (l.label.size() != r.label.size()) if (l.label.size() != r.label.size())
@ -157,55 +136,50 @@ public:
CodeCompletionAllocator &getAllocator() override { return *Alloc; } CodeCompletionAllocator &getAllocator() override { return *Alloc; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
}; };
struct Handler_TextDocumentSignatureHelp
: BaseMessageHandler<In_TextDocumentSignatureHelp> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_TextDocumentSignatureHelp *request) override {
static CompleteConsumerCache<lsSignatureHelp> cache;
const auto &params = request->params;
std::string path = params.textDocument.uri.GetPath();
lsPosition begin_pos = params.position;
if (WorkingFile *file = working_files->GetFileByFilename(path)) {
std::string completion_text;
lsPosition end_pos = params.position;
begin_pos = file->FindStableCompletionSource(
request->params.position, &completion_text, &end_pos);
}
CompletionManager::OnComplete callback =
[id = request->id, path, begin_pos](CodeCompleteConsumer *OptConsumer) {
if (!OptConsumer)
return;
auto *Consumer = static_cast<SignatureHelpConsumer *>(OptConsumer);
pipeline::Reply(id, Consumer->ls_sighelp);
if (!Consumer->from_cache) {
cache.WithLock([&]() {
cache.path = path;
cache.position = begin_pos;
cache.result = Consumer->ls_sighelp;
});
}
};
CodeCompleteOptions CCOpts;
CCOpts.IncludeGlobals = false;
CCOpts.IncludeMacros = false;
CCOpts.IncludeBriefComments = false;
if (cache.IsCacheValid(path, begin_pos)) {
SignatureHelpConsumer Consumer(CCOpts, true);
cache.WithLock([&]() { Consumer.ls_sighelp = cache.result; });
callback(&Consumer);
} else {
clang_complete->completion_request_.PushBack(
std::make_unique<CompletionManager::CompletionRequest>(
request->id, params.textDocument, params.position,
std::make_unique<SignatureHelpConsumer>(CCOpts, false), CCOpts,
callback));
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentSignatureHelp);
} // namespace } // namespace
void MessageHandler::textDocument_signatureHelp(
TextDocumentPositionParam &param, ReplyOnce &reply) {
static CompleteConsumerCache<lsSignatureHelp> cache;
std::string path = param.textDocument.uri.GetPath();
lsPosition begin_pos = param.position;
if (WorkingFile *file = working_files->GetFileByFilename(path)) {
std::string completion_text;
lsPosition end_pos = param.position;
begin_pos = file->FindStableCompletionSource(param.position,
&completion_text, &end_pos);
}
CompletionManager::OnComplete callback =
[reply, path, begin_pos](CodeCompleteConsumer *OptConsumer) {
if (!OptConsumer)
return;
auto *Consumer = static_cast<SignatureHelpConsumer *>(OptConsumer);
reply(Consumer->ls_sighelp);
if (!Consumer->from_cache) {
cache.WithLock([&]() {
cache.path = path;
cache.position = begin_pos;
cache.result = Consumer->ls_sighelp;
});
}
};
CodeCompleteOptions CCOpts;
CCOpts.IncludeGlobals = false;
CCOpts.IncludeMacros = false;
CCOpts.IncludeBriefComments = false;
if (cache.IsCacheValid(path, begin_pos)) {
SignatureHelpConsumer Consumer(CCOpts, true);
cache.WithLock([&]() { Consumer.ls_sighelp = cache.result; });
callback(&Consumer);
} else {
clang_complete->completion_request_.PushBack(
std::make_unique<CompletionManager::CompletionRequest>(
reply.id, param.textDocument, param.position,
std::make_unique<SignatureHelpConsumer>(CCOpts, false), CCOpts,
callback));
}
}
} // namespace ccls

View File

@ -1,72 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "message_handler.h"
#include "pipeline.hh"
#include "query_utils.h"
using namespace ccls;
namespace {
MethodType kMethodType = "textDocument/typeDefinition";
struct In_TextDocumentTypeDefinition : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
lsTextDocumentPositionParams params;
};
MAKE_REFLECT_STRUCT(In_TextDocumentTypeDefinition, id, params);
REGISTER_IN_MESSAGE(In_TextDocumentTypeDefinition);
struct Handler_TextDocumentTypeDefinition
: BaseMessageHandler<In_TextDocumentTypeDefinition> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_TextDocumentTypeDefinition *request) override {
QueryFile *file;
if (!FindFileOrFail(db, project, request->id,
request->params.textDocument.uri.GetPath(), &file,
nullptr))
return;
WorkingFile *working_file =
working_files->GetFileByFilename(file->def->path);
std::vector<lsLocation> result;
auto Add = [&](const QueryType &type) {
for (const auto &def : type.def)
if (def.spell) {
if (auto ls_loc = GetLsLocation(db, working_files, *def.spell))
result.push_back(*ls_loc);
}
if (result.empty())
for (const DeclRef &dr : type.declarations)
if (auto ls_loc = GetLsLocation(db, working_files, dr))
result.push_back(*ls_loc);
};
for (SymbolRef sym :
FindSymbolsAtLocation(working_file, file, request->params.position)) {
switch (sym.kind) {
case SymbolKind::Var: {
const QueryVar::Def *def = db->GetVar(sym).AnyDef();
if (def && def->type)
Add(db->Type(def->type));
break;
}
case SymbolKind::Type: {
for (auto &def : db->GetType(sym).def)
if (def.alias_of) {
Add(db->Type(def.alias_of));
break;
}
break;
}
default:
break;
}
}
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_TextDocumentTypeDefinition);
} // namespace

177
src/messages/workspace.cc Normal file
View File

@ -0,0 +1,177 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "clang_complete.hh"
#include "fuzzy_match.h"
#include "log.hh"
#include "message_handler.hh"
#include "pipeline.hh"
#include "project.hh"
#include "query_utils.h"
#include <llvm/ADT/STLExtras.h>
#include <algorithm>
#include <ctype.h>
#include <functional>
#include <limits.h>
namespace ccls {
MAKE_REFLECT_STRUCT(lsSymbolInformation, name, kind, location, containerName);
void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) {
for (const std::string &folder : g_config->workspaceFolders)
project->Load(folder);
project->Index(working_files, lsRequestId());
clang_complete->FlushAllSessions();
};
void MessageHandler::workspace_didChangeWatchedFiles(
DidChangeWatchedFilesParam &param) {
for (auto &event : param.changes) {
std::string path = event.uri.GetPath();
IndexMode mode = working_files->GetFileByFilename(path)
? IndexMode::Normal
: IndexMode::NonInteractive;
switch (event.type) {
case FileChangeType::Created:
case FileChangeType::Changed: {
pipeline::Index(path, {}, mode);
if (mode == IndexMode::Normal)
clang_complete->NotifySave(path);
else
clang_complete->OnClose(path);
break;
}
case FileChangeType::Deleted:
pipeline::Index(path, {}, mode);
clang_complete->OnClose(path);
break;
}
}
}
void MessageHandler::workspace_didChangeWorkspaceFolders(
DidChangeWorkspaceFoldersParam &param) {
for (const WorkspaceFolder &wf : param.event.removed) {
std::string root = wf.uri.GetPath();
EnsureEndsInSlash(root);
LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root;
auto it = llvm::find(g_config->workspaceFolders, root);
if (it != g_config->workspaceFolders.end()) {
g_config->workspaceFolders.erase(it);
{
// auto &folder = project->root2folder[path];
// FIXME delete
}
project->root2folder.erase(root);
}
}
for (const WorkspaceFolder &wf : param.event.added) {
std::string root = wf.uri.GetPath();
EnsureEndsInSlash(root);
LOG_S(INFO) << "add workspace folder " << wf.name << ": " << root;
g_config->workspaceFolders.push_back(root);
project->Load(root);
}
project->Index(working_files, lsRequestId());
clang_complete->FlushAllSessions();
}
namespace {
// Lookup |symbol| in |db| and insert the value into |result|.
bool AddSymbol(
DB *db, WorkingFiles *working_files, SymbolIdx sym, bool use_detailed,
std::vector<std::tuple<lsSymbolInformation, int, SymbolIdx>> *result) {
std::optional<lsSymbolInformation> info = GetSymbolInfo(db, sym, true);
if (!info)
return false;
Use loc;
if (Maybe<DeclRef> dr = GetDefinitionSpell(db, sym))
loc = *dr;
else {
auto decls = GetNonDefDeclarations(db, sym);
if (decls.empty())
return false;
loc = decls[0];
}
std::optional<lsLocation> ls_location = GetLsLocation(db, working_files, loc);
if (!ls_location)
return false;
info->location = *ls_location;
result->emplace_back(*info, int(use_detailed), sym);
return true;
}
} // namespace
void MessageHandler::workspace_symbol(WorkspaceSymbolParam &param,
ReplyOnce &reply) {
std::vector<lsSymbolInformation> result;
std::string query = param.query;
// {symbol info, matching detailed_name or short_name, index}
std::vector<std::tuple<lsSymbolInformation, int, SymbolIdx>> cands;
bool sensitive = g_config->workspaceSymbol.caseSensitivity;
// Find subsequence matches.
std::string query_without_space;
query_without_space.reserve(query.size());
for (char c : query)
if (!isspace(c))
query_without_space += c;
auto Add = [&](SymbolIdx sym) {
std::string_view detailed_name = db->GetSymbolName(sym, true);
int pos = ReverseSubseqMatch(query_without_space, detailed_name, sensitive);
return pos >= 0 &&
AddSymbol(db, working_files, sym,
detailed_name.find(':', pos) != std::string::npos,
&cands) &&
cands.size() >= g_config->workspaceSymbol.maxNum;
};
for (auto &func : db->funcs)
if (Add({func.usr, SymbolKind::Func}))
goto done_add;
for (auto &type : db->types)
if (Add({type.usr, SymbolKind::Type}))
goto done_add;
for (auto &var : db->vars)
if (var.def.size() && !var.def[0].is_local() &&
Add({var.usr, SymbolKind::Var}))
goto done_add;
done_add:
if (g_config->workspaceSymbol.sort && query.size() <= FuzzyMatcher::kMaxPat) {
// Sort results with a fuzzy matching algorithm.
int longest = 0;
for (auto &cand : cands)
longest = std::max(
longest, int(db->GetSymbolName(std::get<2>(cand), true).size()));
FuzzyMatcher fuzzy(query, g_config->workspaceSymbol.caseSensitivity);
for (auto &cand : cands)
std::get<1>(cand) =
fuzzy.Match(db->GetSymbolName(std::get<2>(cand), std::get<1>(cand)));
std::sort(cands.begin(), cands.end(), [](const auto &l, const auto &r) {
return std::get<1>(l) > std::get<1>(r);
});
result.reserve(cands.size());
for (auto &cand : cands) {
// Discard awful candidates.
if (std::get<1>(cand) <= FuzzyMatcher::kMinScore)
break;
result.push_back(std::get<0>(cand));
}
} else {
result.reserve(cands.size());
for (auto &cand : cands)
result.push_back(std::get<0>(cand));
}
reply(result);
}
} // namespace ccls

View File

@ -1,163 +0,0 @@
/* Copyright 2017-2018 ccls Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "clang_complete.hh"
#include "log.hh"
#include "message_handler.h"
#include "pipeline.hh"
#include "project.h"
#include "working_files.h"
#include <llvm/ADT/STLExtras.h>
using namespace ccls;
namespace {
MethodType didChangeConfiguration = "workspace/didChangeConfiguration",
didChangeWatchedFiles = "workspace/didChangeWatchedFiles",
didChangeWorkspaceFolders = "workspace/didChangeWorkspaceFolders";
struct lsDidChangeConfigurationParams {
bool placeholder;
};
MAKE_REFLECT_STRUCT(lsDidChangeConfigurationParams, placeholder);
struct In_workspaceDidChangeConfiguration : public NotificationMessage {
MethodType GetMethodType() const override { return didChangeConfiguration; }
lsDidChangeConfigurationParams params;
};
MAKE_REFLECT_STRUCT(In_workspaceDidChangeConfiguration, params);
REGISTER_IN_MESSAGE(In_workspaceDidChangeConfiguration);
struct Handler_workspaceDidChangeConfiguration
: BaseMessageHandler<In_workspaceDidChangeConfiguration> {
MethodType GetMethodType() const override { return didChangeConfiguration; }
void Run(In_workspaceDidChangeConfiguration *request) override {
for (const std::string &folder : g_config->workspaceFolders)
project->Load(folder);
project->Index(working_files, lsRequestId());
clang_complete->FlushAllSessions();
}
};
REGISTER_MESSAGE_HANDLER(Handler_workspaceDidChangeConfiguration);
enum class lsFileChangeType {
Created = 1,
Changed = 2,
Deleted = 3,
};
MAKE_REFLECT_TYPE_PROXY(lsFileChangeType);
struct lsFileEvent {
lsDocumentUri uri;
lsFileChangeType type;
};
MAKE_REFLECT_STRUCT(lsFileEvent, uri, type);
struct lsDidChangeWatchedFilesParams {
std::vector<lsFileEvent> changes;
};
MAKE_REFLECT_STRUCT(lsDidChangeWatchedFilesParams, changes);
struct In_WorkspaceDidChangeWatchedFiles : public NotificationMessage {
MethodType GetMethodType() const override { return didChangeWatchedFiles; }
lsDidChangeWatchedFilesParams params;
};
MAKE_REFLECT_STRUCT(In_WorkspaceDidChangeWatchedFiles, params);
REGISTER_IN_MESSAGE(In_WorkspaceDidChangeWatchedFiles);
struct Handler_WorkspaceDidChangeWatchedFiles
: BaseMessageHandler<In_WorkspaceDidChangeWatchedFiles> {
MethodType GetMethodType() const override { return didChangeWatchedFiles; }
void Run(In_WorkspaceDidChangeWatchedFiles *request) override {
for (lsFileEvent &event : request->params.changes) {
std::string path = event.uri.GetPath();
IndexMode mode = working_files->GetFileByFilename(path)
? IndexMode::Normal
: IndexMode::NonInteractive;
switch (event.type) {
case lsFileChangeType::Created:
case lsFileChangeType::Changed: {
pipeline::Index(path, {}, mode);
if (mode == IndexMode::Normal)
clang_complete->NotifySave(path);
else
clang_complete->OnClose(path);
break;
}
case lsFileChangeType::Deleted:
pipeline::Index(path, {}, mode);
clang_complete->OnClose(path);
break;
}
}
}
};
REGISTER_MESSAGE_HANDLER(Handler_WorkspaceDidChangeWatchedFiles);
struct lsWorkspaceFoldersChangeEvent {
std::vector<lsWorkspaceFolder> added, removed;
};
MAKE_REFLECT_STRUCT(lsWorkspaceFoldersChangeEvent, added, removed);
struct In_workspaceDidChangeWorkspaceFolders : public NotificationMessage {
MethodType GetMethodType() const override {
return didChangeWorkspaceFolders;
}
struct Params {
lsWorkspaceFoldersChangeEvent event;
} params;
};
MAKE_REFLECT_STRUCT(In_workspaceDidChangeWorkspaceFolders::Params, event);
MAKE_REFLECT_STRUCT(In_workspaceDidChangeWorkspaceFolders, params);
REGISTER_IN_MESSAGE(In_workspaceDidChangeWorkspaceFolders);
struct Handler_workspaceDidChangeWorkspaceFolders
: BaseMessageHandler<In_workspaceDidChangeWorkspaceFolders> {
MethodType GetMethodType() const override {
return didChangeWorkspaceFolders;
}
void Run(In_workspaceDidChangeWorkspaceFolders *request) override {
const auto &event = request->params.event;
for (const lsWorkspaceFolder &wf : event.removed) {
std::string root = wf.uri.GetPath();
EnsureEndsInSlash(root);
LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root;
auto it = llvm::find(g_config->workspaceFolders, root);
if (it != g_config->workspaceFolders.end()) {
g_config->workspaceFolders.erase(it);
{
// auto &folder = project->root2folder[path];
// FIXME delete
}
project->root2folder.erase(root);
}
}
for (const lsWorkspaceFolder &wf : event.added) {
std::string root = wf.uri.GetPath();
EnsureEndsInSlash(root);
LOG_S(INFO) << "add workspace folder " << wf.name << ": " << root;
g_config->workspaceFolders.push_back(root);
project->Load(root);
}
project->Index(working_files, lsRequestId());
clang_complete->FlushAllSessions();
}
};
REGISTER_MESSAGE_HANDLER(Handler_workspaceDidChangeWorkspaceFolders);
} // namespace

View File

@ -1,127 +0,0 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#include "fuzzy_match.h"
#include "message_handler.h"
#include "pipeline.hh"
#include "query_utils.h"
#include <algorithm>
#include <ctype.h>
#include <functional>
#include <limits.h>
using namespace ccls;
namespace {
MethodType kMethodType = "workspace/symbol";
// Lookup |symbol| in |db| and insert the value into |result|.
bool AddSymbol(
DB *db, WorkingFiles *working_files, SymbolIdx sym, bool use_detailed,
std::vector<std::tuple<lsSymbolInformation, int, SymbolIdx>> *result) {
std::optional<lsSymbolInformation> info = GetSymbolInfo(db, sym, true);
if (!info)
return false;
Use loc;
if (Maybe<DeclRef> dr = GetDefinitionSpell(db, sym))
loc = *dr;
else {
auto decls = GetNonDefDeclarations(db, sym);
if (decls.empty())
return false;
loc = decls[0];
}
std::optional<lsLocation> ls_location = GetLsLocation(db, working_files, loc);
if (!ls_location)
return false;
info->location = *ls_location;
result->emplace_back(*info, int(use_detailed), sym);
return true;
}
struct In_WorkspaceSymbol : public RequestMessage {
MethodType GetMethodType() const override { return kMethodType; }
struct Params {
std::string query;
};
Params params;
};
MAKE_REFLECT_STRUCT(In_WorkspaceSymbol::Params, query);
MAKE_REFLECT_STRUCT(In_WorkspaceSymbol, id, params);
REGISTER_IN_MESSAGE(In_WorkspaceSymbol);
struct Handler_WorkspaceSymbol : BaseMessageHandler<In_WorkspaceSymbol> {
MethodType GetMethodType() const override { return kMethodType; }
void Run(In_WorkspaceSymbol *request) override {
std::vector<lsSymbolInformation> result;
std::string query = request->params.query;
// {symbol info, matching detailed_name or short_name, index}
std::vector<std::tuple<lsSymbolInformation, int, SymbolIdx>> cands;
bool sensitive = g_config->workspaceSymbol.caseSensitivity;
// Find subsequence matches.
std::string query_without_space;
query_without_space.reserve(query.size());
for (char c : query)
if (!isspace(c))
query_without_space += c;
auto Add = [&](SymbolIdx sym) {
std::string_view detailed_name = db->GetSymbolName(sym, true);
int pos =
ReverseSubseqMatch(query_without_space, detailed_name, sensitive);
return pos >= 0 &&
AddSymbol(db, working_files, sym,
detailed_name.find(':', pos) != std::string::npos,
&cands) &&
cands.size() >= g_config->workspaceSymbol.maxNum;
};
for (auto &func : db->funcs)
if (Add({func.usr, SymbolKind::Func}))
goto done_add;
for (auto &type : db->types)
if (Add({type.usr, SymbolKind::Type}))
goto done_add;
for (auto &var : db->vars)
if (var.def.size() && !var.def[0].is_local() &&
Add({var.usr, SymbolKind::Var}))
goto done_add;
done_add:
if (g_config->workspaceSymbol.sort &&
query.size() <= FuzzyMatcher::kMaxPat) {
// Sort results with a fuzzy matching algorithm.
int longest = 0;
for (auto &cand : cands)
longest = std::max(
longest, int(db->GetSymbolName(std::get<2>(cand), true).size()));
FuzzyMatcher fuzzy(query, g_config->workspaceSymbol.caseSensitivity);
for (auto &cand : cands)
std::get<1>(cand) = fuzzy.Match(
db->GetSymbolName(std::get<2>(cand), std::get<1>(cand)));
std::sort(cands.begin(), cands.end(), [](const auto &l, const auto &r) {
return std::get<1>(l) > std::get<1>(r);
});
result.reserve(cands.size());
for (auto &cand : cands) {
// Discard awful candidates.
if (std::get<1>(cand) <= FuzzyMatcher::kMinScore)
break;
result.push_back(std::get<0>(cand));
}
} else {
result.reserve(cands.size());
for (auto &cand : cands)
result.push_back(std::get<0>(cand));
}
pipeline::Reply(request->id, result);
}
};
REGISTER_MESSAGE_HANDLER(Handler_WorkspaceSymbol);
} // namespace

View File

@ -7,15 +7,16 @@
#include "config.h" #include "config.h"
#include "include_complete.h" #include "include_complete.h"
#include "log.hh" #include "log.hh"
#include "lsp.h" #include "lsp.hh"
#include "match.h" #include "match.h"
#include "message_handler.h" #include "message_handler.hh"
#include "pipeline.hh" #include "pipeline.hh"
#include "platform.h" #include "platform.h"
#include "project.h" #include "project.hh"
#include "query_utils.h" #include "query_utils.h"
#include "serializers/json.h" #include "serializers/json.h"
#include <rapidjson/document.h>
#include <rapidjson/writer.h> #include <rapidjson/writer.h>
#include <llvm/Support/Process.h> #include <llvm/Support/Process.h>
@ -31,9 +32,7 @@ using namespace llvm;
#include <unistd.h> #include <unistd.h>
#endif #endif
void StandaloneInitialize(const std::string &, Project &, WorkingFiles &, VFS &, namespace ccls {
IncludeComplete &);
void VFS::Clear() { void VFS::Clear() {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
state.clear(); state.clear();
@ -55,7 +54,10 @@ bool VFS::Stamp(const std::string &path, int64_t ts, int step) {
return false; return false;
} }
namespace ccls::pipeline { struct MessageHandler;
void StandaloneInitialize(MessageHandler &, const std::string &root);
namespace pipeline {
std::atomic<int64_t> loaded_ts = ATOMIC_VAR_INIT(0), std::atomic<int64_t> loaded_ts = ATOMIC_VAR_INIT(0),
pending_index_requests = ATOMIC_VAR_INIT(0); pending_index_requests = ATOMIC_VAR_INIT(0);
@ -74,7 +76,7 @@ struct Index_Request {
MultiQueueWaiter *main_waiter; MultiQueueWaiter *main_waiter;
MultiQueueWaiter *indexer_waiter; MultiQueueWaiter *indexer_waiter;
MultiQueueWaiter *stdout_waiter; MultiQueueWaiter *stdout_waiter;
ThreadedQueue<std::unique_ptr<InMessage>> *on_request; ThreadedQueue<InMessage> *on_request;
ThreadedQueue<Index_Request> *index_request; ThreadedQueue<Index_Request> *index_request;
ThreadedQueue<IndexUpdate> *on_indexed; ThreadedQueue<IndexUpdate> *on_indexed;
ThreadedQueue<std::string> *for_stdout; ThreadedQueue<std::string> *for_stdout;
@ -345,7 +347,7 @@ bool Indexer_Parse(CompletionManager *completion, WorkingFiles *wfiles,
void Init() { void Init() {
main_waiter = new MultiQueueWaiter; main_waiter = new MultiQueueWaiter;
on_request = new ThreadedQueue<std::unique_ptr<InMessage>>(main_waiter); on_request = new ThreadedQueue<InMessage>(main_waiter);
on_indexed = new ThreadedQueue<IndexUpdate>(main_waiter); on_indexed = new ThreadedQueue<IndexUpdate>(main_waiter);
indexer_waiter = new MultiQueueWaiter; indexer_waiter = new MultiQueueWaiter;
@ -402,35 +404,53 @@ void Main_OnIndexed(DB *db, WorkingFiles *working_files, IndexUpdate *update) {
void LaunchStdin() { void LaunchStdin() {
std::thread([]() { std::thread([]() {
set_thread_name("stdin"); set_thread_name("stdin");
std::string str;
while (true) { while (true) {
std::unique_ptr<InMessage> message; constexpr std::string_view kContentLength("Content-Length: ");
std::optional<std::string> error = int len = 0;
MessageRegistry::instance()->ReadMessageFromStdin(&message); str.clear();
while (true) {
// Message parsing can fail if we don't recognize the method. int c = getchar();
if (error) { if (c == EOF)
// The message may be partially deserialized. return;
// Emit an error ResponseMessage if |id| is available. if (c == '\n') {
if (message) { if (str.empty())
lsRequestId id = message->GetRequestId(); break;
if (id.Valid()) { if (!str.compare(0, kContentLength.size(), kContentLength))
lsResponseError err; len = atoi(str.c_str() + kContentLength.size());
err.code = lsErrorCodes::InvalidParams; str.clear();
err.message = std::move(*error); } else if (c != '\r') {
ReplyError(id, err); str += c;
}
} }
continue;
} }
// Cache |method_id| so we can access it after moving |message|. str.resize(len);
MethodType method_type = message->GetMethodType(); for (int i = 0; i < len; ++i) {
int c = getchar();
if (c == EOF)
return;
str[i] = c;
}
on_request->PushBack(std::move(message)); auto message = std::make_unique<char[]>(len);
std::copy(str.begin(), str.end(), message.get());
auto document = std::make_unique<rapidjson::Document>();
document->Parse(message.get(), len);
assert(!document->HasParseError());
// If the message was to exit then querydb will take care of the actual JsonReader reader{document.get()};
// exit. Stop reading from stdin since it might be detached. if (!reader.HasMember("jsonrpc") ||
if (method_type == kMethodType_Exit) std::string(reader["jsonrpc"]->GetString()) != "2.0")
return;
lsRequestId id;
std::string method;
ReflectMember(reader, "id", id);
ReflectMember(reader, "method", method);
auto param = std::make_unique<rapidjson::Value>();
on_request->PushBack(
{id, std::move(method), std::move(message), std::move(document)});
if (method == "exit")
break; break;
} }
}) })
@ -479,31 +499,20 @@ void MainLoop() {
DB db; DB db;
// Setup shared references. // Setup shared references.
for (MessageHandler *handler : *MessageHandler::message_handlers) { MessageHandler handler;
handler->db = &db; handler.db = &db;
handler->project = &project; handler.project = &project;
handler->vfs = &vfs; handler.vfs = &vfs;
handler->working_files = &working_files; handler.working_files = &working_files;
handler->clang_complete = &clang_complete; handler.clang_complete = &clang_complete;
handler->include_complete = &include_complete; handler.include_complete = &include_complete;
}
bool has_indexed = false; bool has_indexed = false;
while (true) { while (true) {
std::vector<std::unique_ptr<InMessage>> messages = on_request->DequeueAll(); std::vector<InMessage> messages = on_request->DequeueAll();
bool did_work = messages.size(); bool did_work = messages.size();
for (auto &message : messages) { for (InMessage &message : messages)
// TODO: Consider using std::unordered_map to lookup the handler handler.Run(message);
for (MessageHandler *handler : *MessageHandler::message_handlers) {
if (handler->GetMethodType() == message->GetMethodType()) {
handler->Run(std::move(message));
break;
}
}
if (message)
LOG_S(ERROR) << "No handler for " << message->GetMethodType();
}
bool indexed = false; bool indexed = false;
for (int i = 20; i--;) { for (int i = 20; i--;) {
@ -532,7 +541,14 @@ void Standalone(const std::string &root) {
WorkingFiles wfiles; WorkingFiles wfiles;
VFS vfs; VFS vfs;
IncludeComplete complete(&project); IncludeComplete complete(&project);
StandaloneInitialize(root, project, wfiles, vfs, complete);
MessageHandler handler;
handler.project = &project;
handler.working_files = &wfiles;
handler.vfs = &vfs;
handler.include_complete = &complete;
StandaloneInitialize(handler, root);
bool tty = sys::Process::StandardOutIsDisplayed(); bool tty = sys::Process::StandardOutIsDisplayed();
if (tty) { if (tty) {
@ -622,5 +638,5 @@ void Reply(lsRequestId id, const std::function<void(Writer &)> &fn) {
void ReplyError(lsRequestId id, const std::function<void(Writer &)> &fn) { void ReplyError(lsRequestId id, const std::function<void(Writer &)> &fn) {
Reply(id, "error", fn); Reply(id, "error", fn);
} }
} // namespace pipeline
} // namespace ccls::pipeline } // namespace ccls

View File

@ -3,7 +3,7 @@
#pragma once #pragma once
#include "lsp.h" #include "lsp.hh"
#include "query.h" #include "query.h"
#include <atomic> #include <atomic>
@ -12,9 +12,9 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
namespace ccls {
struct CompletionManager; struct CompletionManager;
struct GroupMatch; struct GroupMatch;
struct VFS;
struct Project; struct Project;
struct WorkingFiles; struct WorkingFiles;
@ -32,7 +32,6 @@ struct VFS {
bool Stamp(const std::string &path, int64_t ts, int step); bool Stamp(const std::string &path, int64_t ts, int step);
}; };
namespace ccls {
enum class IndexMode { enum class IndexMode {
NonInteractive, NonInteractive,
OnChange, OnChange,

View File

@ -3,12 +3,13 @@
#include "position.h" #include "position.h"
#include "serializer.h" #include "serializer.hh"
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
namespace ccls {
Position Position::FromString(const std::string &encoded) { Position Position::FromString(const std::string &encoded) {
char *p = const_cast<char *>(encoded.c_str()); char *p = const_cast<char *>(encoded.c_str());
int16_t line = int16_t(strtol(p, &p, 10)) - 1; int16_t line = int16_t(strtol(p, &p, 10)) - 1;
@ -100,3 +101,4 @@ void Reflect(Writer &visitor, Range &value) {
Reflect(visitor, value.end.column); Reflect(visitor, value.end.column);
} }
} }
} // namespace ccls

View File

@ -9,6 +9,7 @@
#include <stdint.h> #include <stdint.h>
#include <string> #include <string>
namespace ccls {
struct Position { struct Position {
int16_t line = -1; int16_t line = -1;
int16_t column = -1; int16_t column = -1;
@ -30,7 +31,6 @@ struct Position {
} }
bool operator<=(const Position &o) const { return !(o < *this); } bool operator<=(const Position &o) const { return !(o < *this); }
}; };
MAKE_HASHABLE(Position, t.line, t.column);
struct Range { struct Range {
Position start; Position start;
@ -52,20 +52,6 @@ struct Range {
} }
}; };
namespace std {
template <> struct hash<Range> {
std::size_t operator()(Range x) const {
union U {
Range range = {};
uint64_t u64;
} u;
static_assert(sizeof(Range) == 8);
u.range = x;
return hash<uint64_t>()(u.u64);
}
};
} // namespace std
// Reflection // Reflection
class Reader; class Reader;
class Writer; class Writer;
@ -73,3 +59,20 @@ void Reflect(Reader &visitor, Position &value);
void Reflect(Writer &visitor, Position &value); void Reflect(Writer &visitor, Position &value);
void Reflect(Reader &visitor, Range &value); void Reflect(Reader &visitor, Range &value);
void Reflect(Writer &visitor, Range &value); void Reflect(Writer &visitor, Range &value);
} // namespace ccls
namespace std {
template <> struct hash<ccls::Range> {
std::size_t operator()(ccls::Range x) const {
union U {
ccls::Range range = {};
uint64_t u64;
} u;
static_assert(sizeof(ccls::Range) == 8);
u.range = x;
return hash<uint64_t>()(u.u64);
}
};
} // namespace std
MAKE_HASHABLE(ccls::Position, t.line, t.column);

View File

@ -1,7 +1,7 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "project.h" #include "project.hh"
#include "filesystem.hh" #include "filesystem.hh"
#include "log.hh" #include "log.hh"
@ -31,10 +31,10 @@
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
using namespace ccls;
using namespace clang; using namespace clang;
using namespace llvm; using namespace llvm;
namespace ccls {
std::pair<LanguageId, bool> lookupExtension(std::string_view filename) { std::pair<LanguageId, bool> lookupExtension(std::string_view filename) {
using namespace clang::driver; using namespace clang::driver;
auto I = types::lookupTypeForExtension( auto I = types::lookupTypeForExtension(
@ -473,3 +473,4 @@ void Project::Index(WorkingFiles *wfiles, lsRequestId id) {
// trigger refreshing semantic highlight for all working files. // trigger refreshing semantic highlight for all working files.
pipeline::Index("", {}, IndexMode::NonInteractive); pipeline::Index("", {}, IndexMode::NonInteractive);
} }
} // namespace ccls

View File

@ -4,7 +4,7 @@
#pragma once #pragma once
#include "config.h" #include "config.h"
#include "lsp.h" #include "lsp.hh"
#include <functional> #include <functional>
#include <mutex> #include <mutex>
@ -12,6 +12,7 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
namespace ccls {
struct WorkingFiles; struct WorkingFiles;
std::pair<LanguageId, bool> lookupExtension(std::string_view filename); std::pair<LanguageId, bool> lookupExtension(std::string_view filename);
@ -64,3 +65,4 @@ struct Project {
void Index(WorkingFiles *wfiles, lsRequestId id); void Index(WorkingFiles *wfiles, lsRequestId id);
}; };
} // namespace ccls

View File

@ -4,7 +4,7 @@
#include "query.h" #include "query.h"
#include "indexer.h" #include "indexer.h"
#include "serializer.h" #include "serializer.hh"
#include "serializers/json.h" #include "serializers/json.h"
#include <cassert> #include <cassert>
@ -15,8 +15,8 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
namespace ccls {
namespace { namespace {
void AssignFileId(const Lid2file_id &lid2file_id, int file_id, Use &use) { void AssignFileId(const Lid2file_id &lid2file_id, int file_id, Use &use) {
if (use.file_id == -1) if (use.file_id == -1)
use.file_id = file_id; use.file_id = file_id;
@ -456,3 +456,4 @@ std::string_view DB::GetSymbolName(SymbolIdx sym, bool qualified) {
} }
return ""; return "";
} }
} // namespace ccls

View File

@ -4,23 +4,26 @@
#pragma once #pragma once
#include "indexer.h" #include "indexer.h"
#include "serializer.h" #include "serializer.hh"
#include <llvm/ADT/DenseMap.h> #include <llvm/ADT/DenseMap.h>
#include <llvm/ADT/SmallVector.h> #include <llvm/ADT/SmallVector.h>
#include <llvm/ADT/StringMap.h> #include <llvm/ADT/StringMap.h>
namespace llvm { namespace llvm {
template <> struct DenseMapInfo<ExtentRef> { template <> struct DenseMapInfo<ccls::ExtentRef> {
static inline ExtentRef getEmptyKey() { return {}; } static inline ccls::ExtentRef getEmptyKey() { return {}; }
static inline ExtentRef getTombstoneKey() { return {{Range(), Usr(-1)}}; } static inline ccls::ExtentRef getTombstoneKey() {
static unsigned getHashValue(ExtentRef sym) { return {{ccls::Range(), ccls::Usr(-1)}};
return std::hash<ExtentRef>()(sym);
} }
static bool isEqual(ExtentRef l, ExtentRef r) { return l == r; } static unsigned getHashValue(ccls::ExtentRef sym) {
return std::hash<ccls::ExtentRef>()(sym);
}
static bool isEqual(ccls::ExtentRef l, ccls::ExtentRef r) { return l == r; }
}; };
} } // namespace llvm
namespace ccls {
struct QueryFile { struct QueryFile {
struct Def { struct Def {
std::string path; std::string path;
@ -181,3 +184,4 @@ struct DB {
QueryType &GetType(SymbolIdx ref) { return Type(ref.usr); } QueryType &GetType(SymbolIdx ref) { return Type(ref.usr); }
QueryVar &GetVar(SymbolIdx ref) { return Var(ref.usr); } QueryVar &GetVar(SymbolIdx ref) { return Var(ref.usr); }
}; };
} // namespace ccls

View File

@ -8,6 +8,7 @@
#include <limits.h> #include <limits.h>
#include <unordered_set> #include <unordered_set>
namespace ccls {
namespace { namespace {
// Computes roughly how long |range| is. // Computes roughly how long |range| is.
@ -330,3 +331,4 @@ std::vector<SymbolRef> FindSymbolsAtLocation(WorkingFile *wfile,
return symbols; return symbols;
} }
} // namespace ccls

View File

@ -8,6 +8,7 @@
#include <optional> #include <optional>
namespace ccls {
Maybe<DeclRef> GetDefinitionSpell(DB *db, SymbolIdx sym); Maybe<DeclRef> GetDefinitionSpell(DB *db, SymbolIdx sym);
// Get defining declaration (if exists) or an arbitrary declaration (otherwise) // Get defining declaration (if exists) or an arbitrary declaration (otherwise)
@ -90,3 +91,4 @@ void EachDefinedFunc(DB *db, const std::vector<Usr> &usrs, Fn &&fn) {
fn(obj); fn(obj);
} }
} }
} // namespace ccls

View File

@ -1,7 +1,7 @@
// Copyright 2017-2018 ccls Authors // Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
#include "serializer.h" #include "serializer.hh"
#include "filesystem.hh" #include "filesystem.hh"
#include "indexer.h" #include "indexer.h"
@ -15,11 +15,11 @@
#include <mutex> #include <mutex>
#include <stdexcept> #include <stdexcept>
using namespace ccls;
using namespace llvm; using namespace llvm;
bool gTestOutputMode = false; bool gTestOutputMode = false;
namespace ccls {
Reader::~Reader() {} Reader::~Reader() {}
BinaryReader::~BinaryReader() {} BinaryReader::~BinaryReader() {}
JsonReader::~JsonReader() {} JsonReader::~JsonReader() {}
@ -350,7 +350,6 @@ void Reflect(Writer &visitor, SerializeFormat &value) {
} }
} }
namespace ccls {
static BumpPtrAllocator Alloc; static BumpPtrAllocator Alloc;
static DenseSet<CachedHashStringRef> Strings; static DenseSet<CachedHashStringRef> Strings;
static std::mutex AllocMutex; static std::mutex AllocMutex;

View File

@ -23,6 +23,7 @@ class CachedHashStringRef;
class StringRef; class StringRef;
} }
namespace ccls {
enum class SerializeFormat { Binary, Json }; enum class SerializeFormat { Binary, Json };
struct JsonNull {}; struct JsonNull {};
@ -91,12 +92,12 @@ struct IndexFile;
#define MAKE_REFLECT_TYPE_PROXY2(type, as_type) \ #define MAKE_REFLECT_TYPE_PROXY2(type, as_type) \
LLVM_ATTRIBUTE_UNUSED inline void Reflect(Reader &visitor, type &value) { \ LLVM_ATTRIBUTE_UNUSED inline void Reflect(Reader &visitor, type &value) { \
as_type value0; \ as_type value0; \
::Reflect(visitor, value0); \ ::ccls::Reflect(visitor, value0); \
value = static_cast<type>(value0); \ value = static_cast<type>(value0); \
} \ } \
LLVM_ATTRIBUTE_UNUSED inline void Reflect(Writer &visitor, type &value) { \ LLVM_ATTRIBUTE_UNUSED inline void Reflect(Writer &visitor, type &value) { \
auto value0 = static_cast<as_type>(value); \ auto value0 = static_cast<as_type>(value); \
::Reflect(visitor, value0); \ ::ccls::Reflect(visitor, value0); \
} }
#define _MAPPABLE_REFLECT_MEMBER(name) REFLECT_MEMBER(name); #define _MAPPABLE_REFLECT_MEMBER(name) REFLECT_MEMBER(name);
@ -293,7 +294,6 @@ template <typename T> void ReflectMember(Writer &vis, const char *name, T &v) {
// API // API
namespace ccls {
const char *Intern(llvm::StringRef str); const char *Intern(llvm::StringRef str);
llvm::CachedHashStringRef InternH(llvm::StringRef str); llvm::CachedHashStringRef InternH(llvm::StringRef str);
std::string Serialize(SerializeFormat format, IndexFile &file); std::string Serialize(SerializeFormat format, IndexFile &file);

View File

@ -3,10 +3,11 @@
#pragma once #pragma once
#include "serializer.h" #include "serializer.hh"
#include <string.h> #include <string.h>
namespace ccls {
class BinaryReader : public Reader { class BinaryReader : public Reader {
const char *p_; const char *p_;
@ -123,3 +124,4 @@ public:
void EndObject() override {} void EndObject() override {}
void Key(const char *name) override {} void Key(const char *name) override {}
}; };
} // namespace ccls

View File

@ -3,11 +3,12 @@
#pragma once #pragma once
#include "serializer.h" #include "serializer.hh"
#include <rapidjson/document.h> #include <rapidjson/document.h>
#include <rapidjson/prettywriter.h> #include <rapidjson/prettywriter.h>
namespace ccls {
class JsonReader : public Reader { class JsonReader : public Reader {
rapidjson::GenericValue<rapidjson::UTF8<>> *m_; rapidjson::GenericValue<rapidjson::UTF8<>> *m_;
std::vector<const char *> path_; std::vector<const char *> path_;
@ -104,3 +105,4 @@ public:
void EndObject() override { m_->EndObject(); } void EndObject() override { m_->EndObject(); }
void Key(const char *name) override { m_->Key(name); } void Key(const char *name) override { m_->Key(name); }
}; };
} // namespace ccls

View File

@ -8,7 +8,7 @@
#include "indexer.h" #include "indexer.h"
#include "pipeline.hh" #include "pipeline.hh"
#include "platform.h" #include "platform.h"
#include "serializer.h" #include "serializer.hh"
#include "utils.h" #include "utils.h"
#include <llvm/Config/llvm-config.h> #include <llvm/Config/llvm-config.h>
@ -32,6 +32,7 @@ using namespace llvm;
extern bool gTestOutputMode; extern bool gTestOutputMode;
namespace ccls {
std::string ToString(const rapidjson::Document &document) { std::string ToString(const rapidjson::Document &document) {
rapidjson::StringBuffer buffer; rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer); rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
@ -361,3 +362,4 @@ bool RunIndexTests(const std::string &filter_path, bool enable_update) {
return success; return success;
} }
} // namespace ccls

View File

@ -5,4 +5,6 @@
#include <string> #include <string>
namespace ccls {
bool RunIndexTests(const std::string &filter_path, bool enable_update); bool RunIndexTests(const std::string &filter_path, bool enable_update);
}

View File

@ -13,17 +13,18 @@
#include <tuple> #include <tuple>
#include <utility> #include <utility>
struct BaseThreadQueue {
virtual bool IsEmpty() = 0;
virtual ~BaseThreadQueue() = default;
};
// std::lock accepts two or more arguments. We define an overload for one // std::lock accepts two or more arguments. We define an overload for one
// argument. // argument.
namespace std { namespace std {
template <typename Lockable> void lock(Lockable &l) { l.lock(); } template <typename Lockable> void lock(Lockable &l) { l.lock(); }
} // namespace std } // namespace std
namespace ccls {
struct BaseThreadQueue {
virtual bool IsEmpty() = 0;
virtual ~BaseThreadQueue() = default;
};
template <typename... Queue> struct MultiQueueLock { template <typename... Queue> struct MultiQueueLock {
MultiQueueLock(Queue... lockable) : tuple_{lockable...} { lock(); } MultiQueueLock(Queue... lockable) : tuple_{lockable...} { lock(); }
~MultiQueueLock() { unlock(); } ~MultiQueueLock() { unlock(); }
@ -161,3 +162,4 @@ private:
MultiQueueWaiter *waiter_; MultiQueueWaiter *waiter_;
std::unique_ptr<MultiQueueWaiter> owned_waiter_; std::unique_ptr<MultiQueueWaiter> owned_waiter_;
}; };
} // namespace ccls

View File

@ -21,6 +21,7 @@ using namespace llvm;
#include <string.h> #include <string.h>
#include <unordered_map> #include <unordered_map>
namespace ccls {
uint64_t HashUsr(std::string_view s) { uint64_t HashUsr(std::string_view s) {
union { union {
uint64_t ret; uint64_t ret;
@ -162,3 +163,4 @@ int ReverseSubseqMatch(std::string_view pat, std::string_view text,
} }
std::string GetDefaultResourceDirectory() { return DEFAULT_RESOURCE_DIRECTORY; } std::string GetDefaultResourceDirectory() { return DEFAULT_RESOURCE_DIRECTORY; }
}

View File

@ -14,6 +14,7 @@ namespace llvm {
class StringRef; class StringRef;
} }
namespace ccls {
uint64_t HashUsr(std::string_view s); uint64_t HashUsr(std::string_view s);
uint64_t HashUsr(llvm::StringRef s); uint64_t HashUsr(llvm::StringRef s);
@ -69,10 +70,11 @@ inline void hash_combine(std::size_t &seed, const T &v, Rest... rest) {
template <> struct hash<type> { \ template <> struct hash<type> { \
std::size_t operator()(const type &t) const { \ std::size_t operator()(const type &t) const { \
std::size_t ret = 0; \ std::size_t ret = 0; \
hash_combine(ret, __VA_ARGS__); \ ccls::hash_combine(ret, __VA_ARGS__); \
return ret; \ return ret; \
} \ } \
}; \ }; \
} }
std::string GetDefaultResourceDirectory(); std::string GetDefaultResourceDirectory();
} // namespace ccls

View File

@ -11,6 +11,7 @@
#include <numeric> #include <numeric>
#include <sstream> #include <sstream>
namespace ccls {
namespace { namespace {
// When finding a best match of buffer line and index line, limit the max edit // When finding a best match of buffer line and index line, limit the max edit
@ -456,7 +457,7 @@ WorkingFile *WorkingFiles::OnOpen(const lsTextDocumentItem &open) {
return files[files.size() - 1].get(); return files[files.size() - 1].get();
} }
void WorkingFiles::OnChange(const lsTextDocumentDidChangeParams &change) { void WorkingFiles::OnChange(const TextDocumentDidChangeParam &change) {
std::lock_guard<std::mutex> lock(files_mutex); std::lock_guard<std::mutex> lock(files_mutex);
std::string filename = change.textDocument.uri.GetPath(); std::string filename = change.textDocument.uri.GetPath();
@ -471,7 +472,7 @@ void WorkingFiles::OnChange(const lsTextDocumentDidChangeParams &change) {
if (change.textDocument.version) if (change.textDocument.version)
file->version = *change.textDocument.version; file->version = *change.textDocument.version;
for (const lsTextDocumentContentChangeEvent &diff : change.contentChanges) { for (const TextDocumentContentChangeEvent &diff : change.contentChanges) {
// Per the spec replace everything if the rangeLength and range are not set. // Per the spec replace everything if the rangeLength and range are not set.
// See https://github.com/Microsoft/language-server-protocol/issues/9. // See https://github.com/Microsoft/language-server-protocol/issues/9.
if (!diff.range) { if (!diff.range) {
@ -563,3 +564,4 @@ std::string_view LexIdentifierAroundPos(lsPosition position,
return content.substr(start, end - start); return content.substr(start, end - start);
} }
}

View File

@ -3,13 +3,14 @@
#pragma once #pragma once
#include "lsp.h" #include "lsp.hh"
#include "utils.h" #include "utils.h"
#include <mutex> #include <mutex>
#include <optional> #include <optional>
#include <string> #include <string>
namespace ccls {
struct WorkingFile { struct WorkingFile {
int version = 0; int version = 0;
std::string filename; std::string filename;
@ -105,7 +106,7 @@ struct WorkingFiles {
const std::function<void(WorkingFile *file)> &action); const std::function<void(WorkingFile *file)> &action);
WorkingFile *OnOpen(const lsTextDocumentItem &open); WorkingFile *OnOpen(const lsTextDocumentItem &open);
void OnChange(const lsTextDocumentDidChangeParams &change); void OnChange(const TextDocumentDidChangeParam &change);
void OnClose(const lsTextDocumentIdentifier &close); void OnClose(const lsTextDocumentIdentifier &close);
// If |filter_paths| is non-empty, only files which contain any of the given // If |filter_paths| is non-empty, only files which contain any of the given
@ -123,3 +124,4 @@ int GetOffsetForPosition(lsPosition position, std::string_view content);
std::string_view LexIdentifierAroundPos(lsPosition position, std::string_view LexIdentifierAroundPos(lsPosition position,
std::string_view content); std::string_view content);
} // namespace ccls