Clean Server (#93)

This commit is contained in:
ykiko
2025-03-09 19:19:52 +08:00
committed by GitHub
parent 74f1f13aaf
commit 49af4a9292
38 changed files with 1412 additions and 1309 deletions

View File

@@ -1,60 +0,0 @@
#pragma once
#include <deque>
#include "Async/Async.h"
#include "Database.h"
#include "Compiler/Module.h"
#include "Compiler/Preamble.h"
#include "llvm/ADT/StringMap.h"
namespace clice {
struct CacheOption {
/// The directory to store the cache files.
std::string dir;
};
/// This class is responsible for PCH and PCM building.
class CacheController {
public:
CacheController(CacheOption& option, CompilationDatabase& database);
/// Generate `cache.json` to store the cache information.
void loadFromDisk();
/// Load the cache information from `cache.json`.
void saveToDisk();
/// Complete the PCH or PCM information required for the compilation arguments.
/// If no suitable PCH or PCM is available, a build will be triggered.
async::Task<> prepare(CompilationParams& params);
async::Task<> updatePCH();
private:
const CacheOption& option;
CompilationDatabase& database;
struct CachedPCHInfo : PCHInfo {
/// The hash of the preamble, for fast comparison.
std::uint64_t hash;
/// The reference count of this PCH. When server exit, all PCH with zero
/// reference count will be removed.
std::uint32_t reference;
};
/// All PCHs.
std::deque<CachedPCHInfo> pchs;
/// A map between source file and its PCH.
llvm::StringMap<CachedPCHInfo*> pchMap;
/// [module name] -> [PCMInfo]
llvm::StringMap<PCMInfo> pcms;
};
} // namespace clice

View File

@@ -1,6 +1,7 @@
#pragma once
#include <vector>
#include <expected>
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
@@ -8,7 +9,7 @@
namespace clice::config {
/// Read the config file, call when the program starts.
void load(llvm::StringRef execute, llvm::StringRef filename);
std::expected<void, std::string> load(llvm::StringRef execute, llvm::StringRef filename);
/// Initialize the config, replace all predefined variables in the config file.
/// called in `Server::initialize`.

View File

@@ -1,53 +0,0 @@
#pragma once
#include "llvm/ADT/StringMap.h"
namespace clice {
/// `CompilationDatabase` is responsible for managing the compile commands.
///
/// FIXME: currently we assume that a file only occurs once in the CDB.
/// This is not always correct, but it is enough for now.
class CompilationDatabase {
public:
/// Update the compile commands with the given file.
void updateCommands(llvm::StringRef file);
/// Update the compile commands with the given file and compile command.
void updateCommand(llvm::StringRef file, llvm::StringRef command);
/// Update the module map with the given file and module name.
void updateModule(llvm::StringRef file, llvm::StringRef name);
/// Lookup the compile commands of the given file.
llvm::StringRef getCommand(llvm::StringRef file);
/// Lookup the module interface unit file path of the given module name.
llvm::StringRef getModuleFile(llvm::StringRef name);
auto size() const {
return commands.size();
}
auto begin() {
return commands.begin();
}
auto end() {
return commands.end();
}
private:
/// A map between file path and compile commands.
llvm::StringMap<std::string> commands;
/// For C++20 module, we only can got dependent module name
/// in source context. But we need dependent module file path
/// to build PCM. So we will scan(preprocess) all project files
/// to build a module map between module name and module file path.
/// **Note that** this only includes module interface unit, for module
/// implementation unit, the scan could be delayed until compiling it.
llvm::StringMap<std::string> moduleMap;
};
} // namespace clice

View File

@@ -1,10 +1,10 @@
#pragma once
#include "Config.h"
#include "Database.h"
#include "Protocol.h"
#include "Async/Async.h"
#include "Basic/SourceConverter.h"
#include "Compiler/Command.h"
#include "Compiler/Compilation.h"
#include "Support/JSON.h"
#include "Index/SymbolIndex.h"
@@ -25,11 +25,12 @@ struct HeaderIndex {
};
struct Context {
/// The include chain that introduces this context.
uint32_t include = -1;
/// The index information of this context.
/// The index of header context in indices.
uint32_t index = -1;
/// The location index in corresponding tu's
/// all include locations.
uint32_t include = -1;
};
struct IncludeLocation {
@@ -43,22 +44,10 @@ struct IncludeLocation {
/// a header may be included by multiple files, so we use
/// a string pool to cache the file name to reduce the memory
/// usage.
uint32_t filename = -1;
uint32_t file = -1;
};
struct Header {
/// The path of the header file.
std::string srcPath;
/// All indices of this header.
std::vector<HeaderIndex> indices;
/// All header contexts of this header.
llvm::DenseMap<TranslationUnit*, std::vector<Context>> contexts;
/// The active translation unit and the index of the context.
std::pair<TranslationUnit*, uint32_t> active = {nullptr, -1};
};
struct Header;
struct TranslationUnit {
/// The source file path.
@@ -83,33 +72,46 @@ struct TranslationUnit {
uint32_t version = 0;
};
namespace proto {
struct IncludeLocation {
/// The line number of the include directive.
uint32_t line;
/// The filename of the included header.
std::string filename;
};
struct HeaderContext {
/// The path of the source file.
std::string srcFile;
TranslationUnit* tu = nullptr;
/// The path of the context file.
std::string contextFile;
Context context;
/// The index of the context.
uint32_t index = -1;
/// The version of the context.
uint32_t version = 0;
bool valid() {
return tu != nullptr;
}
};
using HeaderContextGroups = std::vector<std::vector<HeaderContext>>;
struct Header {
/// The path of the header file.
std::string srcPath;
} // namespace proto
/// The active header context.
HeaderContext active;
/// All indices of the header.
std::vector<HeaderIndex> indices;
/// All header contexts of this header.
llvm::DenseMap<TranslationUnit*, std::vector<Context>> contexts;
/// Given a translation unit and a include location, return its
/// its corresponding index.
std::optional<uint32_t> getIndex(TranslationUnit* tu, uint32_t include) {
auto it = contexts.find(tu);
if(it == contexts.end()) {
return std::nullopt;
}
for(auto& context: it->second) {
if(context.include == include) {
return context.index;
}
}
return std::nullopt;
}
};
class IncludeGraph {
protected:
@@ -123,62 +125,6 @@ protected:
async::Task<> index(llvm::StringRef file, CompilationDatabase& database);
public:
/// Return all header context of the given file.
/// FIXME: The results are grouped by the index file. And a header actually
/// may have thousands of contexts, of course, users don't want to see all
/// of them. For each index file, we return the first 10 contexts. In the future
/// we may add a parameter to control the number of contexts or set filter.
proto::HeaderContextGroups contextAll(llvm::StringRef file);
/// Return current header context of the given file.
std::optional<proto::HeaderContext> contextCurrent(llvm::StringRef file);
/// Switch to the given header context.
void contextSwitch(const proto::HeaderContext& context);
/// Resolve the header context to the include chain.
std::vector<proto::IncludeLocation> contextResolve(const proto::HeaderContext& context);
private:
struct SymbolID {
uint64_t hash;
std::string name;
};
/// Return all indices of the given translation unit. If the file is empty,
/// return all indices of the IncludeGraph.
std::vector<std::string> indices(TranslationUnit* tu = nullptr);
/// Resolve the symbol at the given position.
async::Task<std::vector<SymbolID>> resolve(const proto::TextDocumentPositionParams& params);
using LookupCallback = llvm::unique_function<bool(llvm::StringRef path,
llvm::StringRef content,
const index::SymbolIndex::Symbol& symbol)>;
async::Task<> lookup(llvm::ArrayRef<SymbolID> targets,
llvm::ArrayRef<std::string> files,
LookupCallback callback);
public:
/// Lookup the reference information according to the given position.
async::Task<proto::ReferenceResult> lookup(const proto::ReferenceParams& params,
RelationKind kind);
/// According to the given file and offset, resolve the symbol at the offset.
async::Task<proto::HierarchyPrepareResult>
prepareHierarchy(const proto::HierarchyPrepareParams& params);
async::Task<proto::CallHierarchyIncomingCallsResult>
incomingCalls(const proto::HierarchyParams& params);
async::Task<proto::CallHierarchyOutgoingCallsResult>
outgoingCalls(const proto::HierarchyParams& params);
async::Task<proto::TypeHierarchyResult> typeHierarchy(const proto::HierarchyParams& params,
bool super);
private:
std::string getIndexPath(llvm::StringRef file);
@@ -190,7 +136,8 @@ private:
uint32_t addIncludeChain(std::vector<IncludeLocation>& locations,
llvm::DenseMap<clang::FileID, uint32_t>& files,
clang::SourceManager& SM,
clang::FileID fid);
clang::FileID fid,
ASTInfo& AST);
void addContexts(ASTInfo& info,
TranslationUnit* tu,
@@ -200,7 +147,7 @@ private:
TranslationUnit* tu,
llvm::DenseMap<clang::FileID, uint32_t>& files);
private:
protected:
const config::IndexOptions& options;
llvm::StringMap<Header*> headers;
llvm::StringMap<TranslationUnit*> tus;

View File

@@ -1,15 +1,14 @@
#pragma once
#include "Config.h"
#include "Database.h"
#include "Async/Async.h"
#include "llvm/ADT/StringSet.h"
#include "IncludeGraph.h"
#include "Async/Async.h"
#include "Compiler/Command.h"
#include "Index/FeatureIndex.h"
#include "llvm/ADT/StringSet.h"
namespace clice {
class IncludeGraph;
class Indexer : public IncludeGraph {
public:
Indexer(CompilationDatabase& database, const config::IndexOptions& options);
@@ -26,6 +25,78 @@ public:
void load();
public:
Header* getHeader(llvm::StringRef file) const;
TranslationUnit* getTranslationUnit(llvm::StringRef file) const;
/// Return current header context of given header file. If the header
/// does't have an active context, the result will be invalid.
std::optional<proto::HeaderContext> currentContext(llvm::StringRef header) const;
/// Switch the context of the header to given context. If success,
/// return true.
bool switchContext(llvm::StringRef header, proto::HeaderContext context);
/// Resolve the given header context to a group of locations.
std::vector<proto::IncludeLocation> resolveContext(proto::HeaderContext context) const;
/// Return all header contexts of given header file, note that a header may have thousands
/// of header contexts, of course we won't return them all at once. We would return a group
/// of contexts for each different header context. The maximum of group count is determined
/// by limit. Optionally, you can specify a 'contextFile' to filter the results, returning only
/// contexts related to that file.
std::vector<proto::HeaderContextGroup>
allContexts(llvm::StringRef headerFile,
uint32_t limit = 10,
llvm::StringRef contextFile = llvm::StringRef()) const;
public:
struct SymbolID {
uint64_t hash;
std::string name;
};
/// Return all indices of the given translation unit. If the file is empty,
/// return all indices of the IncludeGraph.
std::vector<std::string> indices(TranslationUnit* tu = nullptr);
/// Resolve the symbol at the given position.
async::Task<std::vector<SymbolID>> resolve(const proto::TextDocumentPositionParams& params);
using LookupCallback = llvm::unique_function<bool(llvm::StringRef path,
llvm::StringRef content,
const index::SymbolIndex::Symbol& symbol)>;
async::Task<> lookup(llvm::ArrayRef<SymbolID> targets,
llvm::ArrayRef<std::string> files,
LookupCallback callback);
/// Lookup the reference information according to the given position.
async::Task<proto::ReferenceResult> lookup(const proto::ReferenceParams& params,
RelationKind kind);
/// According to the given file and offset, resolve the symbol at the offset.
async::Task<proto::HierarchyPrepareResult>
prepareHierarchy(const proto::HierarchyPrepareParams& params);
async::Task<proto::CallHierarchyIncomingCallsResult>
incomingCalls(const proto::HierarchyParams& params);
async::Task<proto::CallHierarchyOutgoingCallsResult>
outgoingCalls(const proto::HierarchyParams& params);
async::Task<proto::TypeHierarchyResult> typeHierarchy(const proto::HierarchyParams& params,
bool super);
public:
async::Task<std::optional<index::FeatureIndex>> getFeatureIndex(std::string& buffer,
llvm::StringRef file) const;
async::Task<std::vector<feature::SemanticToken>> semanticTokens(llvm::StringRef file) const;
async::Task<std::vector<feature::FoldingRange>> foldingRanges(llvm::StringRef file) const;
private:
async::Task<> index(std::string file);

View File

@@ -7,6 +7,7 @@
#include "Feature/FoldingRange.h"
#include "Feature/DocumentSymbol.h"
#include "Feature/SemanticTokens.h"
#include "Server/Protocol.h"
namespace clice {
@@ -15,6 +16,26 @@ class LSPConverter {
public:
using Result = async::Task<json::Value>;
proto::InitializeResult initialize(json::Value value);
auto encoding() {
return params.capabilities.general.positionEncodings[0];
}
auto& capabilities() {
return params.capabilities;
}
/// The path of the workspace.
llvm::StringRef workspace();
public:
proto::SemanticTokens transform(llvm::StringRef content,
llvm::ArrayRef<feature::SemanticToken> tokens);
std::vector<proto::FoldingRange> transform(llvm::StringRef content,
llvm::ArrayRef<feature::FoldingRange> foldings);
Result convert(llvm::StringRef path, llvm::ArrayRef<feature::SemanticToken> tokens);
Result convert(llvm::StringRef path, llvm::ArrayRef<feature::FoldingRange> foldings);
@@ -22,7 +43,8 @@ public:
Result convert(const feature::Hover& hover);
private:
proto::PositionEncodingKind kind;
proto::InitializeParams params;
std::string workspacePath;
};
} // namespace clice

View File

@@ -1,3 +1,77 @@
#pragma once
#include "Basic/Lifecycle.h"
#include "Basic/Lifecycle.h"
namespace clice::proto {
struct TextDocumentParams {
/// The text document.
TextDocumentIdentifier textDocument;
};
enum class SemanticTokenTypes {
Namespace,
Type,
Class,
Enum,
Interface,
Struct,
TypeParameter,
Parameter,
Variable,
Property,
EnumMember,
Event,
Function,
Method,
Macro,
Keyword,
Modifier,
Comment,
String,
Number,
Regexp,
Operator,
Decorator
};
using SemanticTokensParams = TextDocumentParams;
using FoldingRangeParams = TextDocumentParams;
struct HeaderContext {
/// The path of context file.
std::string file;
/// The version of context file's AST.
uint32_t version;
/// The include location id for further resolving.
uint32_t include;
};
struct IncludeLocation {
/// The line of include drective.
uint32_t line = -1;
/// The file path of include drective.
std::string file;
};
struct HeaderContextGroup {
/// The index path of this header Context.
std::string indexFile;
/// The header contexts.
std::vector<HeaderContext> contexts;
};
struct HeaderContextSwitchParams {
/// The header file path which wants to switch context.
std::string header;
/// The context
HeaderContext context;
};
} // namespace clice::proto

View File

@@ -1,46 +0,0 @@
#pragma once
#include "Cache.h"
namespace clice {
struct Rule {
/// The file name pattern.
std::string pattern;
/// ...
std::vector<std::string> append;
/// ...
std::vector<std::string> remove;
std::string readonly;
std::string header;
std::vector<std::string> context;
};
/// This class is responsible for managing all opened files.
class Scheduler {
public:
Scheduler(CompilationDatabase& database, llvm::ArrayRef<Rule> rules) :
database(database), rules(rules) {}
async::Task<> open(llvm::StringRef path);
async::Task<> update(llvm::StringRef path);
async::Task<> close(llvm::StringRef path);
private:
CompilationDatabase& database;
llvm::ArrayRef<Rule> rules;
struct File {};
llvm::StringMap<File> files;
};
} // namespace clice

View File

@@ -3,19 +3,92 @@
#include "Config.h"
#include "Indexer.h"
#include "Protocol.h"
#include "Database.h"
#include "Scheduler.h"
#include "LSPConverter.h"
#include "Async/Async.h"
#include "Compiler/Command.h"
namespace clice {
namespace proto {
enum class ErrorCodes {
// Defined by JSON-RPC
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
/**
* Error code indicating that a server received a notification or
* request before the server has received the `initialize` request.
*/
ServerNotInitialized = -32002,
UnknownErrorCode = -32001,
/**
* A request failed but it was syntactically correct, e.g the
* method name was known and the parameters were valid. The error
* message should contain human readable information about why
* the request failed.
*
* @since 3.17.0
*/
RequestFailed = -32803,
/**
* The server cancelled the request. This error code should
* only be used for requests that explicitly support being
* server cancellable.
*
* @since 3.17.0
*/
ServerCancelled = -32802,
/**
* The server detected that the content of a document got
* modified outside normal conditions. A server should
* NOT send this error code if it detects a content change
* in it unprocessed messages. The result even computed
* on an older state might still be useful for the client.
*
* If a client decides that a result is not of any use anymore
* the client should cancel the request.
*/
ContentModified = -32801,
/**
* The client has canceled a request and a server has detected
* the cancel.
*/
RequestCancelled = -32800,
};
}
class Server {
public:
Server();
async::Task<> onReceive(json::Value value);
/// Handle requests, a request must have a response.
async::Task<json::Value> onRequest(llvm::StringRef method, json::Value value);
/// Handle requests started with `textDocument/`.
async::Task<json::Value> onTextDocument(llvm::StringRef method, json::Value value);
/// Handle requests started with `context/`.
async::Task<json::Value> onContext(llvm::StringRef method, json::Value value);
/// Handle requests started with `index/`.
async::Task<json::Value> onIndex(llvm::StringRef method, json::Value value);
/// Handle notifications, a notification doesn't require response.
async::Task<> onNotification(llvm::StringRef method, json::Value value);
private:
/// Send a request to the client.
async::Task<> request(llvm::StringRef method, json::Value params);
@@ -25,146 +98,21 @@ public:
/// Send a response to the client.
async::Task<> response(json::Value id, json::Value result);
async::Task<> response(json::Value id, proto::ErrorCodes code, llvm::StringRef message = "");
/// Send an register capability to the client.
async::Task<> registerCapacity(llvm::StringRef id,
llvm::StringRef method,
json::Value registerOptions);
private:
async::Task<> initialize(json::Value value);
public:
std::uint32_t id = 0;
private:
using onRequest = llvm::unique_function<async::Task<>(json::Value, json::Value)>;
using onNotification = llvm::unique_function<async::Task<>(json::Value)>;
template <typename Param>
void addMethod(llvm::StringRef name,
async::Task<> (Server::*method)(json::Value, const Param&)) {
requests.try_emplace(name,
[this, method](json::Value id, json::Value value) -> async::Task<> {
co_await (this->*method)(std::move(id),
json::deserialize<Param>(value));
});
}
template <typename Param>
void addMethod(llvm::StringRef name, async::Task<> (Server::*method)(const Param&)) {
notifications.try_emplace(name, [this, method](json::Value value) -> async::Task<> {
co_await (this->*method)(json::deserialize<Param>(value));
});
}
llvm::StringMap<onRequest> requests;
llvm::StringMap<onNotification> notifications;
private:
/// ============================================================================
/// Lifecycle Message
/// ============================================================================
async::Task<> onInitialize(json::Value id, const proto::InitializeParams& params);
async::Task<> onInitialized(const proto::InitializedParams& params);
async::Task<> onShutdown(json::Value id, const proto::None&);
async::Task<> onExit(const proto::None&);
/// ============================================================================
/// Document Synchronization
/// ============================================================================
async::Task<> onDidOpen(const proto::DidOpenTextDocumentParams& document);
async::Task<> onDidChange(const proto::DidChangeTextDocumentParams& document);
async::Task<> onDidSave(const proto::DidSaveTextDocumentParams& document);
async::Task<> onDidClose(const proto::DidCloseTextDocumentParams& document);
/// ============================================================================
/// Language Features
/// ============================================================================
// async::Task<> onGotoDeclaration(json::Value id, const proto::DeclarationParams& params);
//
// async::Task<> onGotoDefinition(json::Value id, const proto::DefinitionParams& params);
//
// async::Task<> onGotoTypeDefinition(json::Value id, const proto::TypeDefinitionParams&
// params);
//
// async::Task<> onGotoImplementation(json::Value id, const proto::ImplementationParams&
// params);
//
// async::Task<> onFindReferences(json::Value id, const proto::ReferenceParams& params);
//
// async::Task<> onPrepareCallHierarchy(json::Value id,
// const proto::CallHierarchyPrepareParams& params);
//
// async::Task<> onIncomingCall(json::Value id,
// const proto::CallHierarchyIncomingCallsParams& params);
//
// async::Task<> onOutgoingCall(json::Value id,
// const proto::CallHierarchyOutgoingCallsParams& params);
//
// async::Task<> onPrepareTypeHierarchy(json::Value id,
// const proto::TypeHierarchyPrepareParams& params);
//
// async::Task<> onSupertypes(json::Value id, const proto::TypeHierarchySupertypesParams&
// params);
//
// async::Task<> onSubtypes(json::Value id, const proto::TypeHierarchySubtypesParams& params);
// async::Task<> onDocumentHighlight(json::Value id, const proto::DocumentHighlightParams&
// params);
//
// async::Task<> onDocumentLink(json::Value id, const proto::DocumentLinkParams& params);
//
// async::Task<> onHover(json::Value id, const proto::HoverParams& params);
//
// async::Task<> onCodeLens(json::Value id, const proto::CodeLensParams& params);
//
// async::Task<> onFoldingRange(json::Value id, const proto::FoldingRangeParams& params);
//
// async::Task<> onDocumentSymbol(json::Value id, const proto::DocumentSymbolParams& params);
//
// async::Task<> onSemanticTokens(json::Value id, const proto::SemanticTokensParams& params);
//
// async::Task<> onInlayHint(json::Value id, const proto::InlayHintParams& params);
//
// async::Task<> onCodeCompletion(json::Value id, const proto::CompletionParams& params);
//
// async::Task<> onSignatureHelp(json::Value id, const proto::SignatureHelpParams& params);
//
// async::Task<> onCodeAction(json::Value id, const proto::CodeActionParams& params);
//
// async::Task<> onFormatting(json::Value id, const proto::DocumentFormattingParams& params);
//
// async::Task<> onRangeFormatting(json::Value id,
// const proto::DocumentRangeFormattingParams& params);
/// ============================================================================
/// Workspace Features
/// ============================================================================
async::Task<> onDidChangeWatchedFiles(const proto::DidChangeWatchedFilesParams& params);
/// ============================================================================
/// Extension
/// ============================================================================
async::Task<> onIndexCurrent(const proto::TextDocumentIdentifier& params);
async::Task<> onIndexAll(const proto::None&);
async::Task<> onContextCurrent(const proto::TextDocumentIdentifier& params);
async::Task<> onContextAll(const proto::TextDocumentIdentifier& params);
async::Task<> onContextSwitch(const proto::TextDocumentIdentifier& params);
SourceConverter converter;
CompilationDatabase database;
Indexer indexer;
Scheduler scheduler;
LSPConverter converter;
CompilationDatabase database;
};
} // namespace clice