diff --git a/docs/configuration.md b/docs/configuration.md
index eb49e9bc..dc12dff0 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -177,3 +177,41 @@ If `index.instantiation` is `true`, clice will traverse declarations in template
If `index.instantiation` is `false`, clice will not index entities inside template instantiations, and `go-to-definition` will return no results.
+
+## Feature
+
+### Semantic Tokens
+
+| Name | Type | Default |
+| ------------------------------------ | ------------------ | ------- |
+| `feature.semanticTokens.typeMap` | `array` of `table` | `[]` |
+| `feature.semanticTokens.modifierMap` | `array` of `table` | `[]` |
+
+Maps the customized semantic symbol kinds or modifier to LSP semantic token types or modifiers.
+
+Example:
+
+```toml
+[feature.semanticTokens]
+typeMap = [
+ {"from": "header", "to": "string"},
+ {"from": "attribute", "to": "decorator"},
+]
+
+modifierMap = [
+ {"from": "const", "to": "readonly"},
+ {"from": "pureVirtual", "to": "abstract"},
+]
+```
+
+For all clice symbol kinds, please refer to [SymbolKind](https://github.com/clice-project/clice/blob/main/include/AST/SymbolKind.h). The first letter of the name should be translated to lowercase. For all LSP semantic token types, refer to [SemanticTokenKind](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_semanticTokens).
+
+
+| Name | Type | Default |
+| --------------------------------- | --------- | ------- |
+| `feature.semanticTokens.standard` | `boolean` | `false` |
+
+If `true`, clice will map the semantic token types and modifiers to the standard ones automatically.
+
+### Folding Range
+
diff --git a/include/Basic/Lifecycle.h b/include/Basic/Lifecycle.h
index eafc3cbe..d5cf4c57 100644
--- a/include/Basic/Lifecycle.h
+++ b/include/Basic/Lifecycle.h
@@ -78,6 +78,59 @@ struct SemanticTokensOptions {
bool full = true;
};
+struct SemanticTokens {
+ /// The actual tokens.
+ std::vector data;
+};
+
+/// A set of predefined range kinds.
+enum class FoldingRangeKind {
+ /// Folding range for a comment.
+ Comment,
+
+ /// Folding range for imports or includes.
+ Imports,
+
+ /// Folding range for a region.
+ Region,
+};
+
+/// Represents a folding range. To be valid, start and end line must be bigger
+/// than zero and smaller than the number of lines in the document. Clients
+/// are free to ignore invalid ranges.
+struct FoldingRange {
+ /// The zero-based start line of the range to fold. The folded area starts
+ /// after the line's last character. To be valid, the end must be zero or
+ /// larger and smaller than the number of lines in the document.
+ uint32_t startLine;
+
+ /// The zero-based character offset from where the folded range starts. If
+ /// not defined, defaults to the length of the start line.
+ std::optional startCharacter;
+
+ /// The zero-based end line of the range to fold. The folded area ends with
+ /// the line's last character. To be valid, the end must be zero or larger
+ /// and smaller than the number of lines in the document.
+ uint32_t endLine;
+
+ /// The zero-based character offset before the folded range ends. If not
+ /// defined, defaults to the length of the end line.
+ std::optional endCharacter;
+
+ /// Describes the kind of the folding range such as `comment` or `region`.
+ /// The kind is used to categorize folding ranges and used by commands like
+ /// 'Fold all comments'. See [FoldingRangeKind](#FoldingRangeKind) for an
+ /// enumeration of standardized kinds.
+ FoldingRangeKind kind;
+
+ /// The text that the client should show when the specified range is
+ /// collapsed. If not defined or not supported by the client, a default
+ /// will be chosen by the client.
+ ///
+ /// @since 3.17.0 - proposed
+ std::optional collapsedText;
+};
+
/// Server Capability.
struct ServerCapabilities {
/// The position encoding the server picked from the encodings offered
@@ -118,6 +171,9 @@ struct ServerCapabilities {
/// The server provides semantic tokens support.
SemanticTokensOptions semanticTokensProvider;
+
+ /// The server provides folding provider support.
+ bool foldingRangeProvider = true;
};
struct InitializeResult {
diff --git a/include/Compiler/Command.h b/include/Compiler/Command.h
index 3a27dafd..f5660810 100644
--- a/include/Compiler/Command.h
+++ b/include/Compiler/Command.h
@@ -1,8 +1,7 @@
#pragma once
#include
-#include "llvm/ADT/StringRef.h"
-#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
namespace clice {
@@ -18,4 +17,50 @@ std::expected mangleCommand(llvm::StringRef command,
llvm::SmallVectorImpl& out,
llvm::SmallVectorImpl& buffer);
+/// `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 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 moduleMap;
+};
+
} // namespace clice
diff --git a/include/Feature/DocumentSymbol.h b/include/Feature/DocumentSymbol.h
index 50ddf51b..8f198e05 100644
--- a/include/Feature/DocumentSymbol.h
+++ b/include/Feature/DocumentSymbol.h
@@ -1,3 +1,5 @@
+#pragma once
+
#include "Basic/Document.h"
#include "Basic/SourceCode.h"
#include "Index/Shared.h"
diff --git a/include/Index/FeatureIndex.h b/include/Index/FeatureIndex.h
index f066992e..62f1795b 100644
--- a/include/Index/FeatureIndex.h
+++ b/include/Index/FeatureIndex.h
@@ -31,7 +31,9 @@ public:
}
}
- llvm::ArrayRef semanticTokens() const;
+ std::vector semanticTokens() const;
+
+ std::vector foldingRanges() const;
public:
char* base;
diff --git a/include/Index/Shared2.h b/include/Index/Shared2.h
new file mode 100644
index 00000000..0b46d662
--- /dev/null
+++ b/include/Index/Shared2.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "SymbolIndex.h"
+#include "FeatureIndex.h"
+#include "Async/FileSystem.h"
+
+namespace clice::index {
+
+struct Index2 {
+ std::optional symbol;
+ llvm::XXH128_hash_t symbolHash = {0, 0};
+
+ std::optional feature;
+ llvm::XXH128_hash_t featureHash = {0, 0};
+
+ static Shared build(ASTInfo& AST);
+
+ async::Task<> write(std::string path) {
+ if(symbol) {
+ co_await async::fs::write(path + ".sidx", symbol->base, symbol->size);
+ }
+
+ if(feature) {
+ co_await async::fs::write(path + ".fidx", feature->base, feature->size);
+ }
+ }
+};
+
+} // namespace clice::index
diff --git a/include/Server/Cache.h b/include/Server/Cache.h
deleted file mode 100644
index 6166a877..00000000
--- a/include/Server/Cache.h
+++ /dev/null
@@ -1,60 +0,0 @@
-#pragma once
-
-#include
-
-#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 pchs;
-
- /// A map between source file and its PCH.
- llvm::StringMap pchMap;
-
- /// [module name] -> [PCMInfo]
- llvm::StringMap pcms;
-};
-
-} // namespace clice
diff --git a/include/Server/Config.h b/include/Server/Config.h
index b563545d..386fcfbc 100644
--- a/include/Server/Config.h
+++ b/include/Server/Config.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
#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 load(llvm::StringRef execute, llvm::StringRef filename);
/// Initialize the config, replace all predefined variables in the config file.
/// called in `Server::initialize`.
diff --git a/include/Server/Database.h b/include/Server/Database.h
deleted file mode 100644
index dfa13485..00000000
--- a/include/Server/Database.h
+++ /dev/null
@@ -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 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 moduleMap;
-};
-
-} // namespace clice
diff --git a/include/Server/IncludeGraph.h b/include/Server/IncludeGraph.h
index f7887813..912d090b 100644
--- a/include/Server/IncludeGraph.h
+++ b/include/Server/IncludeGraph.h
@@ -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 indices;
-
- /// All header contexts of this header.
- llvm::DenseMap> contexts;
-
- /// The active translation unit and the index of the context.
- std::pair 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>;
+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 indices;
+
+ /// All header contexts of this header.
+ llvm::DenseMap> contexts;
+
+ /// Given a translation unit and a include location, return its
+ /// its corresponding index.
+ std::optional 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 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 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 indices(TranslationUnit* tu = nullptr);
-
- /// Resolve the symbol at the given position.
- async::Task> resolve(const proto::TextDocumentPositionParams& params);
-
- using LookupCallback = llvm::unique_function;
-
- async::Task<> lookup(llvm::ArrayRef targets,
- llvm::ArrayRef files,
- LookupCallback callback);
-
-public:
- /// Lookup the reference information according to the given position.
- async::Task lookup(const proto::ReferenceParams& params,
- RelationKind kind);
-
- /// According to the given file and offset, resolve the symbol at the offset.
- async::Task
- prepareHierarchy(const proto::HierarchyPrepareParams& params);
-
- async::Task
- incomingCalls(const proto::HierarchyParams& params);
-
- async::Task
- outgoingCalls(const proto::HierarchyParams& params);
-
- async::Task typeHierarchy(const proto::HierarchyParams& params,
- bool super);
-
private:
std::string getIndexPath(llvm::StringRef file);
@@ -190,7 +136,8 @@ private:
uint32_t addIncludeChain(std::vector& locations,
llvm::DenseMap& 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& files);
-private:
+protected:
const config::IndexOptions& options;
llvm::StringMap headers;
llvm::StringMap tus;
diff --git a/include/Server/Indexer.h b/include/Server/Indexer.h
index 6d135ba1..9ffa7b39 100644
--- a/include/Server/Indexer.h
+++ b/include/Server/Indexer.h
@@ -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 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 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
+ 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 indices(TranslationUnit* tu = nullptr);
+
+ /// Resolve the symbol at the given position.
+ async::Task> resolve(const proto::TextDocumentPositionParams& params);
+
+ using LookupCallback = llvm::unique_function;
+
+ async::Task<> lookup(llvm::ArrayRef targets,
+ llvm::ArrayRef files,
+ LookupCallback callback);
+
+ /// Lookup the reference information according to the given position.
+ async::Task lookup(const proto::ReferenceParams& params,
+ RelationKind kind);
+
+ /// According to the given file and offset, resolve the symbol at the offset.
+ async::Task
+ prepareHierarchy(const proto::HierarchyPrepareParams& params);
+
+ async::Task
+ incomingCalls(const proto::HierarchyParams& params);
+
+ async::Task
+ outgoingCalls(const proto::HierarchyParams& params);
+
+ async::Task typeHierarchy(const proto::HierarchyParams& params,
+ bool super);
+
+public:
+ async::Task> getFeatureIndex(std::string& buffer,
+ llvm::StringRef file) const;
+
+ async::Task> semanticTokens(llvm::StringRef file) const;
+
+ async::Task> foldingRanges(llvm::StringRef file) const;
+
private:
async::Task<> index(std::string file);
diff --git a/include/Server/LSPConverter.h b/include/Server/LSPConverter.h
index 049818de..7596ac6b 100644
--- a/include/Server/LSPConverter.h
+++ b/include/Server/LSPConverter.h
@@ -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;
+ 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 tokens);
+
+ std::vector transform(llvm::StringRef content,
+ llvm::ArrayRef foldings);
+
Result convert(llvm::StringRef path, llvm::ArrayRef tokens);
Result convert(llvm::StringRef path, llvm::ArrayRef foldings);
@@ -22,7 +43,8 @@ public:
Result convert(const feature::Hover& hover);
private:
- proto::PositionEncodingKind kind;
+ proto::InitializeParams params;
+ std::string workspacePath;
};
} // namespace clice
diff --git a/include/Server/Protocol.h b/include/Server/Protocol.h
index 05c16ba6..707ecbe6 100644
--- a/include/Server/Protocol.h
+++ b/include/Server/Protocol.h
@@ -1,3 +1,77 @@
#pragma once
-#include "Basic/Lifecycle.h"
\ No newline at end of file
+#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 contexts;
+};
+
+struct HeaderContextSwitchParams {
+ /// The header file path which wants to switch context.
+ std::string header;
+
+ /// The context
+ HeaderContext context;
+};
+
+} // namespace clice::proto
diff --git a/include/Server/Scheduler.h b/include/Server/Scheduler.h
deleted file mode 100644
index ecc33df7..00000000
--- a/include/Server/Scheduler.h
+++ /dev/null
@@ -1,46 +0,0 @@
-#pragma once
-
-#include "Cache.h"
-
-namespace clice {
-
-struct Rule {
- /// The file name pattern.
- std::string pattern;
-
- /// ...
- std::vector append;
-
- /// ...
- std::vector remove;
-
- std::string readonly;
-
- std::string header;
-
- std::vector context;
-};
-
-/// This class is responsible for managing all opened files.
-class Scheduler {
-public:
- Scheduler(CompilationDatabase& database, llvm::ArrayRef 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 rules;
-
- struct File {};
-
- llvm::StringMap files;
-};
-
-} // namespace clice
diff --git a/include/Server/Server.h b/include/Server/Server.h
index a349e535..5e811143 100644
--- a/include/Server/Server.h
+++ b/include/Server/Server.h
@@ -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 onRequest(llvm::StringRef method, json::Value value);
+
+ /// Handle requests started with `textDocument/`.
+ async::Task onTextDocument(llvm::StringRef method, json::Value value);
+
+ /// Handle requests started with `context/`.
+ async::Task onContext(llvm::StringRef method, json::Value value);
+
+ /// Handle requests started with `index/`.
+ async::Task 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(json::Value, json::Value)>;
- using onNotification = llvm::unique_function(json::Value)>;
-
- template
- 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(value));
- });
- }
-
- template
- 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(value));
- });
- }
-
- llvm::StringMap requests;
- llvm::StringMap 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
diff --git a/include/Support/Enum.h b/include/Support/Enum.h
index f12ad3af..c4ae997c 100644
--- a/include/Support/Enum.h
+++ b/include/Support/Enum.h
@@ -266,21 +266,18 @@ public:
using underlying_type = underlying;
- constexpr Enum(underlying value) : m_Value(value) {
+ constexpr Enum(underlying value) {
static_assert(
requires { Derived::All; },
"Derived enum must define all possible enum values.");
- auto check = [](auto value) {
- for(auto kind: Derived::All) {
- if(kind == value) {
- return true;
- }
+ for(auto& element: Derived::All) {
+ if(element == value) {
+ m_Value = element;
}
- return false;
- };
+ }
- assert(check(value) && "Invalid enum value.");
+ assert(!m_Value.empty() && "Invalid enum value.");
}
constexpr Enum(const Enum&) = default;
diff --git a/include/Support/FileSystem.h b/include/Support/FileSystem.h
index d70eca03..f42d656a 100644
--- a/include/Support/FileSystem.h
+++ b/include/Support/FileSystem.h
@@ -38,15 +38,15 @@ using namespace llvm::sys::fs;
inline std::string resource_dir = "";
-inline llvm::Error init_resource_dir(llvm::StringRef execute) {
+inline std::expected init_resource_dir(llvm::StringRef execute) {
llvm::SmallString<128> path;
path::append(path, path::parent_path(execute), "..");
path::append(path, "lib", "clang", "20");
if(auto error = real_path(path, path)) {
- return llvm::make_error(error.message(), error);
+ return std::unexpected(error);
}
resource_dir = path.str();
- return llvm::Error::success();
+ return std::expected();
}
inline std::expected createTemporaryFile(llvm::StringRef prefix,
diff --git a/src/Async/Network.cpp b/src/Async/Network.cpp
index 1edcf859..468c1796 100644
--- a/src/Async/Network.cpp
+++ b/src/Async/Network.cpp
@@ -29,7 +29,8 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
if(nread == UV_EOF) [[unlikely]] {
uv_read_stop(stream);
uv_close(uv_cast(*stream), nullptr);
- uv_close(uv_cast(*writer), nullptr);
+ /// FIXME: Figure out why the writer is already closed.
+ ///uv_close(uv_cast(*writer), nullptr);
return;
}
diff --git a/src/Compiler/Command.cpp b/src/Compiler/Command.cpp
index cd9f6bb4..74a021e3 100644
--- a/src/Compiler/Command.cpp
+++ b/src/Compiler/Command.cpp
@@ -1,6 +1,7 @@
+#include "Support/Logger.h"
#include "Compiler/Command.h"
+#include "Compiler/Compilation.h"
#include "Support/FileSystem.h"
-#include "Support/Format.h"
namespace clice {
@@ -70,4 +71,122 @@ std::expected mangleCommand(llvm::StringRef command,
return {};
}
+/// Update the compile commands with the given file.
+void CompilationDatabase::updateCommands(llvm::StringRef filename) {
+ auto path = path::real_path(filename);
+ filename = path;
+
+ /// Read the compile commands from the file.
+ json::Value json = nullptr;
+
+ if(auto buffer = llvm::MemoryBuffer::getFile(filename)) {
+ if(auto result = json::parse(buffer->get()->getBuffer())) {
+ /// llvm::json::Value will hold on string buffer.
+ /// Do not worry about the lifetime of the buffer.
+ /// Release buffer to save memory.
+ json = std::move(result.get());
+ } else {
+ log::warn("Failed to parse json file at {0}, because {1}",
+ filename,
+ result.takeError());
+ return;
+ }
+ } else {
+ log::warn("Failed to read file {0}", filename);
+ return;
+ }
+
+ assert(json.kind() != json::Value::Null && "json is nullptr");
+
+ if(json.kind() != json::Value::Array) {
+ log::warn(
+ "Compilation CompilationDatabase requires a array of object, but get {0}, input file: {1}",
+ refl::enum_name(json.kind()),
+ filename);
+ return;
+ }
+
+ auto elements = json.getAsArray();
+ assert(elements && "json is not an array");
+
+ for(auto& element: *elements) {
+ auto object = element.getAsObject();
+ if(!object) {
+ log::warn(
+ "Compilation CompilationDatabase requires an array of object, but get a array of {0}, input file: {1}",
+ refl::enum_name(element.kind()),
+ filename);
+ continue;
+ }
+
+ /// FIXME: currently we assume all path here is absolute.
+ /// Add `directory` field in the future.
+
+ llvm::SmallString<128> path;
+
+ if(auto file = object->getString("file")) {
+ if(auto error = fs::real_path(*file, path)) {
+ log::warn("Failed to get real path of {0}, because {1}", *file, error.message());
+ continue;
+ }
+ } else {
+ log::warn("The element does not have a file field, input file: {0}", filename);
+ continue;
+ }
+
+ auto command = object->getString("command");
+ if(!command) {
+ log::warn("The key:{0} does not have a command field, input file: {1}", path, filename);
+ continue;
+ }
+
+ commands[path] = *command;
+ }
+
+ log::info("Successfully loaded compile commands from {0}, total {1} commands",
+ filename,
+ commands.size());
+
+ /// Scan all files to build module map.
+ // CompilationParams params;
+ // for(auto& [path, command]: commands) {
+ // params.srcPath = path;
+ // params.command = command;
+ //
+ // auto name = scanModuleName(params);
+ // if(!name.empty()) {
+ // moduleMap[name] = path;
+ // }
+ //}
+
+ log::info("Successfully built module map, total {0} modules", moduleMap.size());
+}
+
+void CompilationDatabase::updateCommand(llvm::StringRef file, llvm::StringRef command) {
+ commands[path::real_path(file)] = command;
+}
+
+/// Update the module map with the given file and module name.
+void CompilationDatabase::updateModule(llvm::StringRef file, llvm::StringRef name) {
+ moduleMap[path::real_path(file)] = file;
+}
+
+/// Lookup the compile commands of the given file.
+llvm::StringRef CompilationDatabase::getCommand(llvm::StringRef file) {
+ auto iter = commands.find(file);
+ if(iter == commands.end()) {
+ return "";
+ }
+ return iter->second;
+}
+
+/// Lookup the module interface unit file path of the given module name.
+llvm::StringRef CompilationDatabase::getModuleFile(llvm::StringRef name) {
+ auto iter = moduleMap.find(name);
+ if(iter == moduleMap.end()) {
+ return "";
+ }
+ return iter->second;
+}
+
} // namespace clice
diff --git a/src/Driver/clice.cc b/src/Driver/clice.cc
index 3315e71e..2bdd6590 100644
--- a/src/Driver/clice.cc
+++ b/src/Driver/clice.cc
@@ -1,61 +1,128 @@
-#include "Support/Logger.h"
#include "Server/Server.h"
+#include "Support/Logger.h"
+#include "Support/Format.h"
+
+#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/CommandLine.h"
+namespace cl = llvm::cl;
using namespace clice;
-namespace cl {
+namespace {
-llvm::cl::opt config("config",
- llvm::cl::desc("The path of the config file"),
- llvm::cl::value_desc("path"));
+static cl::OptionCategory category("clice options");
-llvm::cl::opt mode("mode", llvm::cl::desc("Use pipe mode"));
+cl::opt
+ mode("mode",
+ cl::cat(category),
+ cl::value_desc("pipe|socket|indexer"),
+ cl::desc("The mode of clice, default is pipe, socket is usually used for debugging"));
-llvm::cl::opt resource_dir("resource-dir", llvm::cl::desc("Resource dir path"));
+cl::opt config_path(
+ "config",
+ cl::cat(category),
+ cl::value_desc("path"),
+ cl::desc(
+ "The path of the clice config file, if not specified, the default config will be used"));
-} // namespace cl
+cl::opt resource_dir(
+ "resource-dir",
+ cl::cat(category),
+ cl::value_desc("path"),
+ cl::desc(R"(The path of the clang resource directory, default is "../../lib/clang/version")"));
+
+void printVersion(llvm::raw_ostream& os) {
+ os << std::format("clice version: {}\n", clice::config::version)
+ << std::format("llvm version: {}\n", clice::config::llvm_version);
+}
+
+/// Check the command line arguments and initialize the clice.
+bool checkArguments(int argc, const char** argv) {
+ /// Hide unrelated options.
+ cl::HideUnrelatedOptions(category);
+
+ // Set version printer and parse command line options
+ cl::SetVersionPrinter(printVersion);
+ cl::ParseCommandLineOptions(argc,
+ argv,
+ "clice is a new generation of language server for C/C++");
-int main(int argc, const char** argv) {
for(int i = 0; i < argc; ++i) {
- log::info("argv[{0}] = {1}", i, argv[i]);
+ log::info("argv[{}] = {}", i, argv[i]);
}
- llvm::cl::SetVersionPrinter([](llvm::raw_ostream& os) { os << "clice version: 0.0.1\n"; });
- llvm::cl::ParseCommandLineOptions(argc, argv, "clice language server");
-
- if(cl::config.empty()) {
- log::warn("No config file specified; using default configuration.");
+ // Handle configuration file loading
+ if(config_path.empty()) {
+ log::info("No configuration file specified, using default settings");
} else {
- config::load(argv[0], cl::config.getValue());
- log::info("Successfully loaded configuration file from {0}.", cl::config.getValue());
- }
-
- /// Get the resource directory.
- if(!cl::resource_dir.empty()) {
- fs::resource_dir = cl::resource_dir.getValue();
- } else {
- if(auto error = fs::init_resource_dir(argv[0])) {
- log::fatal("Failed to get resource directory, because {0}", error);
- return 1;
+ llvm::StringRef path = config_path;
+ // Try to load the configuration file and check the result
+ if(auto result = config::load(argv[0], path); result) {
+ log::info("Configuration file loaded successfully from: {}", path);
+ } else {
+ log::warn("Failed to load configuration file from: {} because {}",
+ path,
+ result.error());
+ return false;
}
}
- static Server server;
- auto loop = [](json::Value value) -> async::Task<> {
- co_await server.onReceive(value);
- };
+ // Initialize resource directory
+ if(resource_dir.empty()) {
+ log::info("No resource directory specified, using default resource directory");
+ // Try to initialize default resource directory
+ if(auto result = fs::init_resource_dir(argv[0]); !result) {
+ log::warn("Cannot find default resource directory, because {}", result.error());
+ return false;
+ }
+ } else {
+ // Set and check the specified resource directory
+ fs::resource_dir = resource_dir.getValue();
+ if(fs::exists(fs::resource_dir)) {
+ log::info("Resource directory found: {}", fs::resource_dir);
+ } else {
+ log::warn("Resource directory not found: {}", fs::resource_dir);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+int main(int argc, const char** argv) {
+ llvm::InitLLVM guard(argc, argv);
+ llvm::setBugReportMsg(
+ "Please report bugs to https://github.com/clice-project/clice/issues and include the crash backtrace");
+
+ if(!checkArguments(argc, argv)) {
+ return 1;
+ }
async::init();
- if(cl::mode == "pipe") {
+ /// The global server instance.
+ static Server instance;
+ auto loop = [&](json::Value value) -> async::Task<> {
+ co_await instance.onReceive(value);
+ };
+
+ if(mode == "pipe") {
async::net::listen(loop);
log::info("Server starts listening on stdin/stdout");
- } else if(cl::mode == "socket") {
+ } else if(mode == "socket") {
async::net::listen("127.0.0.1", 50051, loop);
log::info("Server starts listening on {}:{}", "127.0.0.1", 50051);
+ } else if(mode == "indexer") {
+ /// TODO:
+ } else {
+ log::fatal("Invalid mode: {}", mode.getValue());
+ return 1;
}
async::run();
+
+ return 0;
}
diff --git a/src/Driver/unit_tests.cc b/src/Driver/unit_tests.cc
index 3895dfd0..15010f33 100644
--- a/src/Driver/unit_tests.cc
+++ b/src/Driver/unit_tests.cc
@@ -37,8 +37,8 @@ int main(int argc, char** argv) {
if(!cl::resource_dir.empty()) {
fs::resource_dir = cl::resource_dir.getValue();
} else {
- if(auto error = fs::init_resource_dir(argv[0])) {
- llvm::outs() << std::format("Failed to get resource directory, because {}\n", error);
+ if(auto result = fs::init_resource_dir(argv[0]); !result) {
+ llvm::outs() << std::format("Failed to get resource directory, because {}\n", result.error());
return 1;
}
}
diff --git a/src/Index/FeatureIndex.cpp b/src/Index/FeatureIndex.cpp
index 4af36a89..74d8eb84 100644
--- a/src/Index/FeatureIndex.cpp
+++ b/src/Index/FeatureIndex.cpp
@@ -44,8 +44,25 @@ Shared indexFeature(ASTInfo& info) {
return result;
}
-llvm::ArrayRef FeatureIndex::semanticTokens() const {
+std::vector FeatureIndex::semanticTokens() const {
return binary::Proxy{base, base}.get<"tokens">().as_array();
}
+std::vector FeatureIndex::foldingRanges() const {
+ auto array = binary::Proxy{base, base}.get<"foldings">();
+
+ std::vector result;
+ result.reserve(array.size());
+
+ /// FIXME: Use iterator or other thing to make cast easier.
+ for(std::size_t i = 0, n = array.size(); i < n; ++i) {
+ auto&& range = array[i];
+ result.emplace_back(range.get<"range">().value(),
+ range.get<"kind">().value(),
+ range.get<"text">().as_string().str());
+ }
+
+ return result;
+}
+
} // namespace clice::index
diff --git a/src/Index/Index2.cpp b/src/Index/Index2.cpp
new file mode 100644
index 00000000..60c1f4ae
--- /dev/null
+++ b/src/Index/Index2.cpp
@@ -0,0 +1,36 @@
+#include "Index/Shared2.h"
+#include "Compiler/Compilation.h"
+
+namespace clice::index {
+
+Shared Index2::build(ASTInfo& AST) {
+ llvm::DenseMap indices;
+
+ auto symbolIndices = index::index(AST);
+ for(auto& [fid, index]: symbolIndices) {
+ indices[fid].symbol.emplace(std::move(index));
+ }
+
+ auto featureIndices = index::indexFeature(AST);
+ for(auto& [fid, index]: featureIndices) {
+ indices[fid].feature.emplace(std::move(index));
+ }
+
+ for(auto& [fid, index]: indices) {
+ if(index.symbol) {
+ auto data = llvm::ArrayRef(reinterpret_cast(index.symbol->base),
+ index.symbol->size);
+ index.symbolHash = llvm::xxh3_128bits(data);
+ }
+
+ if(index.feature) {
+ auto data = llvm::ArrayRef(reinterpret_cast(index.feature->base),
+ index.feature->size);
+ index.featureHash = llvm::xxh3_128bits(data);
+ }
+ }
+
+ return indices;
+}
+
+} // namespace clice::index
diff --git a/src/Server/Cache.cpp b/src/Server/Cache.cpp
deleted file mode 100644
index 3248824a..00000000
--- a/src/Server/Cache.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "Server/Cache.h"
-
-namespace clice {
-
-void CacheController::loadFromDisk() {
-
-}
-
-void CacheController::saveToDisk() {
-
-}
-
-async::Task<> CacheController::prepare(CompilationParams& params) {
- /// 首先要区分这个文件是不是 module file.
- bool isModule = false;
-
- if(isModule){
- /// Update dependent mods ....
- }
-
- /// 准备构建 PCH ...
-
- co_return;
-}
-
-} // namespace clice
diff --git a/src/Server/Config.cpp b/src/Server/Config.cpp
index 0de8527c..96de7048 100644
--- a/src/Server/Config.cpp
+++ b/src/Server/Config.cpp
@@ -73,19 +73,18 @@ static void parse(Object& object, auto&& value) {
}
}
-void load(llvm::StringRef execute, llvm::StringRef filename) {
+std::expected load(llvm::StringRef execute, llvm::StringRef filename) {
predefined["version"] = "0.0.1";
predefined["binary"] = execute;
predefined["llvm_version"] = "20";
auto toml = toml::parse_file(filename);
if(toml.failed()) {
- log::fatal("Failed to parse config file: {0}. Because: {1}",
- filename,
- toml.error().description());
+ return std::unexpected(toml.error().description());
}
parse(config, toml.table());
+ return {};
}
/// replace all predefined variables in the text.
diff --git a/src/Server/Database.cpp b/src/Server/Database.cpp
deleted file mode 100644
index eda0e58a..00000000
--- a/src/Server/Database.cpp
+++ /dev/null
@@ -1,125 +0,0 @@
-#include "Support/Logger.h"
-#include "Server/Database.h"
-#include "Support/FileSystem.h"
-#include "Compiler/Compilation.h"
-
-namespace clice {
-
-/// Update the compile commands with the given file.
-void CompilationDatabase::updateCommands(llvm::StringRef filename) {
- auto path = path::real_path(filename);
- filename = path;
-
- /// Read the compile commands from the file.
- json::Value json = nullptr;
-
- if(auto buffer = llvm::MemoryBuffer::getFile(filename)) {
- if(auto result = json::parse(buffer->get()->getBuffer())) {
- /// llvm::json::Value will hold on string buffer.
- /// Do not worry about the lifetime of the buffer.
- /// Release buffer to save memory.
- json = std::move(result.get());
- } else {
- log::warn("Failed to parse json file at {0}, because {1}",
- filename,
- result.takeError());
- return;
- }
- } else {
- log::warn("Failed to read file {0}", filename);
- return;
- }
-
- assert(json.kind() != json::Value::Null && "json is nullptr");
-
- if(json.kind() != json::Value::Array) {
- log::warn("Compilation Database requires a array of object, but get {0}, input file: {1}",
- refl::enum_name(json.kind()),
- filename);
- return;
- }
-
- auto elements = json.getAsArray();
- assert(elements && "json is not an array");
-
- for(auto& element: *elements) {
- auto object = element.getAsObject();
- if(!object) {
- log::warn(
- "Compilation Database requires an array of object, but get a array of {0}, input file: {1}",
- refl::enum_name(element.kind()),
- filename);
- continue;
- }
-
- /// FIXME: currently we assume all path here is absolute.
- /// Add `directory` field in the future.
-
- llvm::SmallString<128> path;
-
- if(auto file = object->getString("file")) {
- if(auto error = fs::real_path(*file, path)) {
- log::warn("Failed to get real path of {0}, because {1}", *file, error.message());
- continue;
- }
- } else {
- log::warn("The element does not have a file field, input file: {0}", filename);
- continue;
- }
-
- auto command = object->getString("command");
- if(!command) {
- log::warn("The key:{0} does not have a command field, input file: {1}", path, filename);
- continue;
- }
-
- commands[path] = *command;
- }
-
- log::info("Successfully loaded compile commands from {0}, total {1} commands",
- filename,
- commands.size());
-
- /// Scan all files to build module map.
- // CompilationParams params;
- // for(auto& [path, command]: commands) {
- // params.srcPath = path;
- // params.command = command;
- //
- // auto name = scanModuleName(params);
- // if(!name.empty()) {
- // moduleMap[name] = path;
- // }
- //}
-
- log::info("Successfully built module map, total {0} modules", moduleMap.size());
-}
-
-void CompilationDatabase::updateCommand(llvm::StringRef file, llvm::StringRef command) {
- commands[path::real_path(file)] = command;
-}
-
-/// Update the module map with the given file and module name.
-void CompilationDatabase::updateModule(llvm::StringRef file, llvm::StringRef name) {
- moduleMap[path::real_path(file)] = file;
-}
-
-/// Lookup the compile commands of the given file.
-llvm::StringRef CompilationDatabase::getCommand(llvm::StringRef file) {
- auto iter = commands.find(file);
- if(iter == commands.end()) {
- return "";
- }
- return iter->second;
-}
-
-/// Lookup the module interface unit file path of the given module name.
-llvm::StringRef CompilationDatabase::getModuleFile(llvm::StringRef name) {
- auto iter = moduleMap.find(name);
- if(iter == moduleMap.end()) {
- return "";
- }
- return iter->second;
-}
-
-} // namespace clice
diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp
deleted file mode 100644
index 66c49505..00000000
--- a/src/Server/Document.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "Basic/SourceConverter.h"
-#include "Server/Server.h"
-
-namespace clice {
-
-async::Task<> Server::onDidOpen(const proto::DidOpenTextDocumentParams& params) {
- co_return;
-}
-
-async::Task<> Server::onDidChange(const proto::DidChangeTextDocumentParams& document) {
- co_return;
-}
-
-async::Task<> Server::onDidSave(const proto::DidSaveTextDocumentParams& document) {
- co_return;
-}
-
-async::Task<> Server::onDidClose(const proto::DidCloseTextDocumentParams& document) {
- co_return;
-}
-
-} // namespace clice
diff --git a/src/Server/Extension.cpp b/src/Server/Extension.cpp
index 85bbb0e5..fccc8093 100644
--- a/src/Server/Extension.cpp
+++ b/src/Server/Extension.cpp
@@ -2,27 +2,27 @@
namespace clice {
-async::Task<> Server::onIndexCurrent(const proto::TextDocumentIdentifier& params) {
- auto path = SourceConverter::toPath(params.uri);
- /// co_await indexer.index(path);
- co_return;
-}
-
-async::Task<> Server::onIndexAll(const proto::None& params) {
- /// co_await indexer.indexAll();
- co_return;
-}
-
-async::Task<> Server::onContextCurrent(const proto::TextDocumentIdentifier& params) {
- co_return;
-}
-
-async::Task<> Server::onContextAll(const proto::TextDocumentIdentifier& params) {
- co_return;
-}
-
-async::Task<> Server::onContextSwitch(const proto::TextDocumentIdentifier& params) {
- co_return;
-}
+// async::Task<> Server::onIndexCurrent(const proto::TextDocumentIdentifier& params) {
+// auto path = SourceConverter::toPath(params.uri);
+// /// co_await indexer.index(path);
+// co_return;
+// }
+//
+// async::Task<> Server::onIndexAll(const proto::None& params) {
+// /// co_await indexer.indexAll();
+// co_return;
+// }
+//
+// async::Task<> Server::onContextCurrent(const proto::TextDocumentIdentifier& params) {
+// co_return;
+// }
+//
+// async::Task<> Server::onContextAll(const proto::TextDocumentIdentifier& params) {
+// co_return;
+// }
+//
+// async::Task<> Server::onContextSwitch(const proto::TextDocumentIdentifier& params) {
+// co_return;
+// }
} // namespace clice
diff --git a/src/Server/IncludeGraph.cpp b/src/Server/IncludeGraph.cpp
index 7960f03b..6b60b6cf 100644
--- a/src/Server/IncludeGraph.cpp
+++ b/src/Server/IncludeGraph.cpp
@@ -3,6 +3,7 @@
#include "Server/IncludeGraph.h"
#include "Index/SymbolIndex.h"
#include "Index/FeatureIndex.h"
+#include "Index/Shared2.h"
#include "Support/Compare.h"
#include "Support/Logger.h"
#include "llvm/ADT/DenseSet.h"
@@ -133,269 +134,6 @@ async::Task<> IncludeGraph::index(llvm::StringRef file, CompilationDatabase& dat
co_await updateIndices(**info, tu, files);
}
-proto::HeaderContextGroups IncludeGraph::contextAll(llvm::StringRef file) {
- llvm::StringMap> groups;
-
- if(auto iter = headers.find(file); iter != headers.end()) {
- auto header = iter->second;
- for(auto& [tu, contexts]: header->contexts) {
- for(auto& context: contexts) {
- auto& group = groups[tu->indexPath];
- if(group.size() < 10) {
- group.emplace_back(file.str(), tu->srcPath, context.index, tu->version);
- }
- }
- }
- }
-
- proto::HeaderContextGroups result;
- for(auto& [_, group]: groups) {
- result.emplace_back(std::move(group));
- }
- return result;
-}
-
-std::optional IncludeGraph::contextCurrent(llvm::StringRef file) {
- if(auto iter = headers.find(file); iter != headers.end()) {
- auto header = iter->second;
- auto [tu, index] = header->active;
- if(tu) {
- return proto::HeaderContext{
- .srcFile = file.str(),
- .contextFile = tu->srcPath,
- .index = index,
- .version = tu->version,
- };
- }
-
- /// If no active translation unit, we just return the first context.
- if(!header->contexts.empty()) {
- /// FIXME: Is it possible that a tu does not have any context?
- auto& [tu, contexts] = *header->contexts.begin();
- header->active = {tu, 0};
- return proto::HeaderContext{
- .srcFile = file.str(),
- .contextFile = tu->srcPath,
- .index = 0,
- .version = tu->version,
- };
- }
- }
-
- return std::nullopt;
-}
-
-void IncludeGraph::contextSwitch(const proto::HeaderContext& context) {
- Header* header = nullptr;
- if(auto iter = headers.find(context.srcFile); iter != headers.end()) {
- header = iter->second;
- } else {
- return;
- }
-
- TranslationUnit* tu = nullptr;
- if(auto iter = tus.find(context.contextFile); iter != tus.end()) {
- tu = iter->second;
- } else {
- return;
- }
-
- /// Check whether the context is valid.
- if(tu->version != context.version) {
- return;
- }
-
- /// Switch to the new context.
- header->active = {tu, context.index};
-}
-
-std::vector
- IncludeGraph::contextResolve(const proto::HeaderContext& context) {
- Header* header = nullptr;
- if(auto iter = headers.find(context.srcFile); iter != headers.end()) {
- header = iter->second;
- } else {
- return {};
- }
-
- TranslationUnit* tu = nullptr;
- if(auto iter = tus.find(context.contextFile); iter != tus.end()) {
- tu = iter->second;
- } else {
- return {};
- }
-
- if(tu->version != context.version) {
- return {};
- }
-
- std::vector locations;
-
- auto include = header->contexts[tu][context.index].include;
- while(include != -1) {
- auto& location = tu->locations[include];
- locations.push_back(proto::IncludeLocation{
- .line = location.line - 1,
- .filename = pathPool[location.filename],
- });
- include = location.include;
- }
-
- return locations;
-}
-
-std::vector IncludeGraph::indices(TranslationUnit* tu) {
- std::vector indices;
- if(tu) {
- indices.emplace_back(tu->indexPath);
- for(auto& header: tu->headers) {
- for(auto& context: header->contexts[tu]) {
- indices.emplace_back(header->indices[context.index].path);
- }
- }
- } else {
- for(auto& [_, tu]: tus) {
- indices.emplace_back(tu->indexPath);
- }
-
- for(auto& [_, header]: headers) {
- for(auto& index: header->indices) {
- indices.emplace_back(index.path);
- }
- }
- }
- return indices;
-}
-
-async::Task>
- IncludeGraph::resolve(const proto::TextDocumentPositionParams& params) {
- std::vector ids;
- auto path = SourceConverter::toPath(params.textDocument.uri);
-
- std::uint32_t offset = 0;
- {
- auto content = co_await async::fs::read(path);
- if(!content) {
- co_return ids;
- }
- offset = SC.toOffset(*content, params.position);
- }
-
- std::vector indices;
- if(auto iter = tus.find(path); iter != tus.end()) {
- indices.emplace_back(iter->second->indexPath);
- } else if(auto iter = headers.find(path); iter != headers.end()) {
- /// FIXME: What is the expected result of resolving a symbol that may refers different
- /// declaration in different header contexts? Currently, we just return the first one.
- for(auto& index: iter->second->indices) {
- indices.emplace_back(index.path);
- }
- }
-
- co_await async::gather(indices, [&](const std::string& path) -> async::Task {
- auto binary = co_await async::fs::read(path + ".sidx");
- if(!binary) {
- co_return true;
- }
-
- llvm::SmallVector symbols;
- co_await async::submit([&] {
- index::SymbolIndex index(binary->data(), binary->size(), false);
- index.locateSymbols(offset, symbols);
- });
-
- if(symbols.empty()) {
- co_return true;
- }
-
- /// If we found the symbol, we just return it. And break other tasks.
- for(auto& symbol: symbols) {
- ids.emplace_back(symbol.id(), symbol.name().str());
- }
- co_return false;
- });
-
- co_return ids;
-}
-
-async::Task<> IncludeGraph::lookup(llvm::ArrayRef targets,
- llvm::ArrayRef files,
- LookupCallback callback) {
- co_await async::gather(files, [&](const std::string& indexPath) -> async::Task {
- auto binary = co_await async::fs::read(indexPath + ".sidx");
- if(!binary) {
- co_return false;
- }
-
- llvm::SmallVector symbols;
- index::SymbolIndex index(binary->data(), binary->size(), false);
- co_await async::submit([&] {
- for(auto& target: targets) {
- if(auto symbol = index.locateSymbol(target.hash, target.name)) {
- symbols.emplace_back(*symbol);
- }
- }
- });
-
- if(symbols.empty()) {
- co_return true;
- }
-
- auto srcPath = index.path().str();
- auto content = co_await async::fs::read(srcPath);
- if(!content) {
- co_return true;
- }
-
- for(auto& symbol: symbols) {
- if(!callback(srcPath, *content, symbol)) {
- co_return false;
- }
- }
-
- co_return true;
- });
-}
-
-async::Task IncludeGraph::lookup(const proto::ReferenceParams& params,
- RelationKind kind) {
- auto ids = co_await resolve(params);
- /// FIXME: If the size of ids equal to one and it is not an external symbol, we should
- /// just search the symbol in the current translation unit.
-
- proto::ReferenceResult result;
-
- std::vector files = indices();
- co_await lookup(ids,
- files,
- [&](llvm::StringRef path,
- llvm::StringRef content,
- const index::SymbolIndex::Symbol& symbol) {
- /// FIXME: We may should collect and sort the ranges in one file.
- /// So that we can cut off the context to speed up the process.
- for(auto relation: symbol.relations()) {
- if(relation.kind() & kind) {
- result.emplace_back(proto::Location{
- .uri = SourceConverter::toURI(path),
- .range = SC.toRange(*relation.range(), content),
- });
- }
- }
- return true;
- });
-
- co_return result;
-}
-
-async::Task
- IncludeGraph::prepareHierarchy(const proto::HierarchyPrepareParams& params) {
- auto ids = co_await resolve(params);
-
- proto::HierarchyPrepareResult result;
- std::vector files = indices();
- co_return result;
-}
-
std::string IncludeGraph::getIndexPath(llvm::StringRef file) {
auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast(now.time_since_epoch()).count();
@@ -438,40 +176,47 @@ async::Task IncludeGraph::check(llvm::StringRef file) {
uint32_t IncludeGraph::addIncludeChain(std::vector& locations,
llvm::DenseMap& files,
clang::SourceManager& SM,
- clang::FileID fid) {
+ clang::FileID fid,
+ ASTInfo& AST) {
auto [iter, success] = files.try_emplace(fid, locations.size());
if(!success) {
return iter->second;
}
auto index = iter->second;
- locations.emplace_back();
- auto entry = SM.getFileEntryRefForID(fid);
- assert(entry && "Invalid file entry");
- {
- auto path = path::real_path(entry->getName());
- auto [iter, success] = pathIndices.try_emplace(path, pathPool.size());
- locations[index].filename = iter->second;
- }
-
- if(auto presumed = SM.getPresumedLoc(SM.getIncludeLoc(fid), false); presumed.isValid()) {
+ auto includeLoc = SM.getIncludeLoc(fid);
+ if(includeLoc.isValid()) {
+ auto presumed = SM.getPresumedLoc(includeLoc, false);
+ locations.emplace_back();
locations[index].line = presumed.getLine();
- auto include = addIncludeChain(locations, files, SM, presumed.getFileID());
+
+ auto path = AST.getFilePath(presumed.getFileID());
+ auto [iter, success] = pathIndices.try_emplace(path, pathPool.size());
+ if(success) {
+ pathPool.emplace_back(path);
+ }
+ locations[index].file = iter->second;
+
+ uint32_t include = -1;
+ if(presumed.getIncludeLoc().isValid()) {
+ include =
+ addIncludeChain(locations, files, SM, SM.getFileID(presumed.getIncludeLoc()), AST);
+ }
locations[index].include = include;
}
return index;
}
-void IncludeGraph::addContexts(ASTInfo& info,
+void IncludeGraph::addContexts(ASTInfo& AST,
TranslationUnit* tu,
llvm::DenseMap& files) {
- auto& SM = info.srcMgr();
+ auto& SM = AST.srcMgr();
std::vector locations;
- for(auto& [fid, directive]: info.directives()) {
+ for(auto& [fid, directive]: AST.directives()) {
for(auto& include: directive.includes) {
/// If the include is invalid, it indicates that the file is skipped because of
/// include guard, or `#pragma once`. Such file cannot provide header context.
@@ -481,7 +226,7 @@ void IncludeGraph::addContexts(ASTInfo& info,
}
/// Add all include chains.
- addIncludeChain(locations, files, SM, include.fid);
+ addIncludeChain(locations, files, SM, include.fid, AST);
}
}
@@ -496,17 +241,15 @@ void IncludeGraph::addContexts(ASTInfo& info,
continue;
}
- auto entry = SM.getFileEntryRefForID(fid);
- assert(entry && "Invalid file entry");
- auto name = path::real_path(entry->getName());
+ auto path = AST.getFilePath(fid);
Header* header = nullptr;
- if(auto iter = headers.find(name); iter != headers.end()) {
+ if(auto iter = headers.find(path); iter != headers.end()) {
header = iter->second;
} else {
header = new Header;
- header->srcPath = name;
- headers.try_emplace(name, header);
+ header->srcPath = path;
+ headers.try_emplace(path, header);
}
/// Add new header context.
@@ -526,43 +269,7 @@ void IncludeGraph::addContexts(ASTInfo& info,
async::Task<> IncludeGraph::updateIndices(ASTInfo& info,
TranslationUnit* tu,
llvm::DenseMap& files) {
- struct Index {
- llvm::XXH128_hash_t symbolHash = {0, 0};
- std::optional symbol;
-
- llvm::XXH128_hash_t featureHash = {0, 0};
- std::optional feature;
- };
-
- auto indices = co_await async::submit([&info] {
- llvm::DenseMap indices;
-
- auto symbolIndices = index::index(info);
- for(auto& [fid, index]: symbolIndices) {
- indices[fid].symbol.emplace(std::move(index));
- }
-
- auto featureIndices = index::indexFeature(info);
- for(auto& [fid, index]: featureIndices) {
- indices[fid].feature.emplace(std::move(index));
- }
-
- for(auto& [fid, index]: indices) {
- if(index.symbol) {
- auto data = llvm::ArrayRef(reinterpret_cast(index.symbol->base),
- index.symbol->size);
- index.symbolHash = llvm::xxh3_128bits(data);
- }
-
- if(index.feature) {
- auto data = llvm::ArrayRef(reinterpret_cast(index.feature->base),
- index.feature->size);
- index.featureHash = llvm::xxh3_128bits(data);
- }
- }
-
- return indices;
- });
+ auto indices = co_await async::submit([&info] { return index::Index2::build(info); });
auto& SM = info.srcMgr();
@@ -572,28 +279,12 @@ async::Task<> IncludeGraph::updateIndices(ASTInfo& info,
tu->indexPath = getIndexPath(tu->srcPath);
}
- if(index.symbol) {
- co_await async::fs::write(tu->indexPath + ".sidx",
- index.symbol->base,
- index.symbol->size);
- }
-
- if(index.feature) {
- co_await async::fs::write(tu->indexPath + ".fidx",
- index.feature->base,
- index.feature->size);
- }
+ co_await index.write(tu->indexPath);
continue;
}
- auto entry = SM.getFileEntryRefForID(fid);
- if(!entry) {
- log::info("Invalid file entry for file id: {}", fid.getHashValue());
- }
- assert(entry && "Invalid file entry");
-
- auto name = path::real_path(entry->getName());
+ auto name = info.getFilePath(fid);
assert(headers.contains(name) && "Invalid header name");
auto header = headers[name];
@@ -629,35 +320,7 @@ async::Task<> IncludeGraph::updateIndices(ASTInfo& info,
.featureHash = index.featureHash,
});
- // if(header->srcPath == "/home/ykiko/C++/clice/include/Support/JSON.h") {
- // if(index.symbol) {
- // auto json = index.symbol->toJSON();
- // llvm::SmallString<128> path;
- // llvm::raw_svector_ostream stream(path);
- // stream << json;
- //
- // co_await async::fs::write(indices.back().path + ".json", path.data(),
- // path.size());
- // }
- //
- // // if(index.feature) {
- // // println("{{ hash: {}, index: {} }}",
- // // json::serialize(index.featureHash),
- // // json::serialize(index.feature->semanticTokens()));
- // // }
- //}
-
- if(index.symbol) {
- co_await async::fs::write(indices.back().path + ".sidx",
- index.symbol->base,
- index.symbol->size);
- }
-
- if(index.feature) {
- co_await async::fs::write(indices.back().path + ".fidx",
- index.feature->base,
- index.feature->size);
- }
+ co_await index.write(indices.back().path);
}
}
diff --git a/src/Server/Indexer.cpp b/src/Server/Indexer.cpp
index 170aded2..140b1fe7 100644
--- a/src/Server/Indexer.cpp
+++ b/src/Server/Indexer.cpp
@@ -113,4 +113,326 @@ async::Task<> Indexer::index(std::string file) {
tasks.try_emplace(file, std::move(next));
}
+Header* Indexer::getHeader(llvm::StringRef file) const {
+ auto it = headers.find(file);
+ if(it == headers.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+TranslationUnit* Indexer::getTranslationUnit(llvm::StringRef file) const {
+ auto it = tus.find(file);
+ if(it == tus.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+std::optional Indexer::currentContext(llvm::StringRef file) const {
+ auto header = getHeader(file);
+ if(!header || !header->active.valid()) {
+ return std::nullopt;
+ }
+
+ auto& active = header->active;
+ return proto::HeaderContext{
+ .file = active.tu->srcPath,
+ .version = active.tu->version,
+ .include = active.context.include,
+ };
+}
+
+bool Indexer::switchContext(llvm::StringRef headerFile, proto::HeaderContext context) {
+ Header* header = getHeader(headerFile);
+ if(!header) {
+ return false;
+ }
+
+ TranslationUnit* tu = getTranslationUnit(context.file);
+ if(!tu || tu->version != context.version) {
+ return false;
+ }
+
+ auto index = header->getIndex(tu, context.include);
+ if(!index) {
+ return false;
+ }
+
+ header->active = HeaderContext{
+ .tu = tu,
+ .context = {.index = *index, .include = context.include},
+ };
+
+ return true;
+}
+
+std::vector Indexer::resolveContext(proto::HeaderContext context) const {
+ auto tu = getTranslationUnit(context.file);
+ if(!tu || tu->version != context.version) {
+ return {};
+ }
+
+ auto include = context.include;
+ auto& locations = tu->locations;
+ if(locations.size() <= include) [[unlikely]] {
+ /// FIXME: This should never occur, we should log.
+ return {};
+ }
+
+ std::vector result;
+ while(include != -1) {
+ auto& location = locations[include];
+ result.emplace_back(proto::IncludeLocation{
+ .line = location.line - 1,
+ .file = pathPool[location.file],
+ });
+ include = location.include;
+ }
+ return result;
+}
+
+std::vector Indexer::allContexts(llvm::StringRef headerFile,
+ uint32_t limit,
+ llvm::StringRef contextFile) const {
+ auto header = getHeader(headerFile);
+ if(!header) {
+ return {};
+ }
+
+ llvm::DenseMap> groups;
+
+ if(auto tu = getTranslationUnit(contextFile)) {
+ for(auto context: header->contexts[tu]) {
+ groups[context.index].emplace_back(proto::HeaderContext{
+ .file = tu->srcPath,
+ .version = tu->version,
+ .include = context.include,
+ });
+ }
+ } else {
+ for(auto&& [tu, contexts]: header->contexts) {
+ for(auto& context: contexts) {
+ auto& group = groups[context.index];
+ if(group.size() >= 10) {
+ continue;
+ }
+
+ group.emplace_back(proto::HeaderContext{
+ .file = tu->srcPath,
+ .version = tu->version,
+ .include = context.include,
+ });
+ }
+ }
+ }
+
+ std::vector result;
+ for(auto& [index, group]: groups) {
+ result.emplace_back(header->indices[index].path, std::move(group));
+ }
+ return result;
+}
+
+std::vector Indexer::indices(TranslationUnit* tu) {
+ std::vector indices;
+ if(tu) {
+ indices.emplace_back(tu->indexPath);
+ for(auto& header: tu->headers) {
+ for(auto& context: header->contexts[tu]) {
+ indices.emplace_back(header->indices[context.index].path);
+ }
+ }
+ } else {
+ for(auto& [_, tu]: tus) {
+ indices.emplace_back(tu->indexPath);
+ }
+
+ for(auto& [_, header]: headers) {
+ for(auto& index: header->indices) {
+ indices.emplace_back(index.path);
+ }
+ }
+ }
+ return indices;
+}
+
+async::Task>
+ Indexer::resolve(const proto::TextDocumentPositionParams& params) {
+ std::vector ids;
+ auto path = SourceConverter::toPath(params.textDocument.uri);
+
+ std::uint32_t offset = 0;
+ {
+ auto content = co_await async::fs::read(path);
+ if(!content) {
+ co_return ids;
+ }
+ offset = SC.toOffset(*content, params.position);
+ }
+
+ std::vector indices;
+ if(auto iter = tus.find(path); iter != tus.end()) {
+ indices.emplace_back(iter->second->indexPath);
+ } else if(auto iter = headers.find(path); iter != headers.end()) {
+ /// FIXME: What is the expected result of resolving a symbol that may refers different
+ /// declaration in different header contexts? Currently, we just return the first one.
+ for(auto& index: iter->second->indices) {
+ indices.emplace_back(index.path);
+ }
+ }
+
+ co_await async::gather(indices, [&](const std::string& path) -> async::Task {
+ auto binary = co_await async::fs::read(path + ".sidx");
+ if(!binary) {
+ co_return true;
+ }
+
+ llvm::SmallVector symbols;
+ co_await async::submit([&] {
+ index::SymbolIndex index(binary->data(), binary->size(), false);
+ index.locateSymbols(offset, symbols);
+ });
+
+ if(symbols.empty()) {
+ co_return true;
+ }
+
+ /// If we found the symbol, we just return it. And break other tasks.
+ for(auto& symbol: symbols) {
+ ids.emplace_back(symbol.id(), symbol.name().str());
+ }
+ co_return false;
+ });
+
+ co_return ids;
+}
+
+async::Task<> Indexer::lookup(llvm::ArrayRef targets,
+ llvm::ArrayRef files,
+ LookupCallback callback) {
+ co_await async::gather(files, [&](const std::string& indexPath) -> async::Task {
+ auto binary = co_await async::fs::read(indexPath + ".sidx");
+ if(!binary) {
+ co_return false;
+ }
+
+ llvm::SmallVector symbols;
+ index::SymbolIndex index(binary->data(), binary->size(), false);
+ co_await async::submit([&] {
+ for(auto& target: targets) {
+ if(auto symbol = index.locateSymbol(target.hash, target.name)) {
+ symbols.emplace_back(*symbol);
+ }
+ }
+ });
+
+ if(symbols.empty()) {
+ co_return true;
+ }
+
+ auto srcPath = index.path().str();
+ auto content = co_await async::fs::read(srcPath);
+ if(!content) {
+ co_return true;
+ }
+
+ for(auto& symbol: symbols) {
+ if(!callback(srcPath, *content, symbol)) {
+ co_return false;
+ }
+ }
+
+ co_return true;
+ });
+}
+
+async::Task Indexer::lookup(const proto::ReferenceParams& params,
+ RelationKind kind) {
+ auto ids = co_await resolve(params);
+ /// FIXME: If the size of ids equal to one and it is not an external symbol, we should
+ /// just search the symbol in the current translation unit.
+
+ proto::ReferenceResult result;
+
+ std::vector files = indices();
+ co_await lookup(ids,
+ files,
+ [&](llvm::StringRef path,
+ llvm::StringRef content,
+ const index::SymbolIndex::Symbol& symbol) {
+ /// FIXME: We may should collect and sort the ranges in one file.
+ /// So that we can cut off the context to speed up the process.
+ for(auto relation: symbol.relations()) {
+ if(relation.kind() & kind) {
+ result.emplace_back(proto::Location{
+ .uri = SourceConverter::toURI(path),
+ .range = SC.toRange(*relation.range(), content),
+ });
+ }
+ }
+ return true;
+ });
+
+ co_return result;
+}
+
+async::Task
+ Indexer::prepareHierarchy(const proto::HierarchyPrepareParams& params) {
+ auto ids = co_await resolve(params);
+
+ proto::HierarchyPrepareResult result;
+ std::vector files = indices();
+ co_return result;
+}
+
+async::Task>
+ Indexer::getFeatureIndex(std::string& buffer, llvm::StringRef file) const {
+ std::string path;
+ if(auto tu = tus.find(file); tu != tus.end()) {
+ path = tu->second->indexPath;
+ }
+
+ if(auto header = headers.find(file); header != headers.end()) {
+ for(auto&& index: header->second->indices) {
+ path = index.path;
+ break;
+ }
+ }
+
+ auto content = co_await async::fs::read(path + ".fidx");
+ if(!content) {
+ co_return std::nullopt;
+ }
+
+ buffer = std::move(*content);
+
+ co_return index::FeatureIndex(buffer.data(), buffer.size(), false);
+}
+
+async::Task>
+ Indexer::semanticTokens(llvm::StringRef file) const {
+ std::vector result;
+
+ std::string buffer;
+ auto index = co_await getFeatureIndex(buffer, file);
+ if(!index) {
+ co_return result;
+ }
+
+ co_return index->semanticTokens();
+}
+
+async::Task> Indexer::foldingRanges(llvm::StringRef file) const {
+ std::vector result;
+
+ std::string buffer;
+ auto index = co_await getFeatureIndex(buffer, file);
+ if(!index) {
+ co_return result;
+ }
+
+ co_return index->foldingRanges();
+}
+
} // namespace clice
diff --git a/src/Server/LSPConverter.cpp b/src/Server/LSPConverter.cpp
index bf6b5d16..d7af9bdb 100644
--- a/src/Server/LSPConverter.cpp
+++ b/src/Server/LSPConverter.cpp
@@ -1,4 +1,5 @@
#include "Server/LSPConverter.h"
+#include "Basic/SourceConverter.h"
namespace clice {
@@ -136,8 +137,8 @@ std::uint32_t remeasure(llvm::StringRef content, proto::PositionEncodingKind kin
class PositionConverter {
public:
- PositionConverter(llvm::StringRef content, proto::PositionEncodingKind kind) :
- content(content), kind(kind) {}
+ PositionConverter(llvm::StringRef content, proto::PositionEncodingKind encoding) :
+ content(content), encoding(encoding) {}
/// Convert a offset to a proto::Position with given encoding.
/// The input offset must be UTF-8 encoded and in order.
@@ -167,7 +168,7 @@ public:
auto lineContent = content.substr(lastLineOffset, lineLength);
auto position = proto::Position{
.line = line,
- .character = remeasure(lineContent, kind),
+ .character = remeasure(lineContent, encoding),
};
/// Cache the result.
@@ -187,10 +188,11 @@ public:
}
ranges::sort(offsets);
- ranges::unique(offsets);
for(auto&& offset: offsets) {
- toPosition(offset);
+ if(auto it = cache.find(offset); it == cache.end()) {
+ cache.try_emplace(offset, toPosition(offset));
+ }
}
}
@@ -212,42 +214,54 @@ private:
llvm::DenseMap cache;
llvm::StringRef content;
- proto::PositionEncodingKind kind;
+ proto::PositionEncodingKind encoding;
};
} // namespace
-LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
- llvm::ArrayRef tokens) {
- /// FIXME: Use a better way to handle file content.
- auto file = co_await async::fs::read(path.str());
- if(!file) {
- co_return json::Value(nullptr);
+proto::InitializeResult LSPConverter::initialize(json::Value value) {
+ params = json::deserialize(value);
+
+ proto::InitializeResult result = {};
+ result.serverInfo.name = "clice";
+ result.serverInfo.version = "0.0.1";
+
+ auto& semantictokens = result.capabilities.semanticTokensProvider;
+ for(auto& name: SymbolKind::all()) {
+ std::string type{name};
+ type[0] = std::tolower(type[0]);
+ semantictokens.legend.tokenTypes.emplace_back(std::move(type));
}
- llvm::StringRef content = *file;
- struct SemanticTokens {
- /// The actual tokens.
- std::vector data;
+ return result;
+}
- void add(std::uint32_t line,
- std::uint32_t character,
- std::uint32_t length,
- SymbolKind kind,
- SymbolModifiers modifiers) {
- /// FIXME: Add a map between lsp kinds and our kinds.
- /// [line, character, length, tokenType, tokenModifiers]
- data.emplace_back(line);
- data.emplace_back(character);
- data.emplace_back(length);
- data.emplace_back(kind.value());
- data.emplace_back(modifiers.value());
- }
+llvm::StringRef LSPConverter::workspace() {
+ if(workspacePath.empty()) {
+ workspacePath = SourceConverter::toPath(params.workspaceFolders[0].uri);
+ }
+ return workspacePath;
+}
+
+proto::SemanticTokens LSPConverter::transform(llvm::StringRef content,
+ llvm::ArrayRef tokens) {
+ proto::SemanticTokens result;
+
+ auto addGroup = [&](uint32_t line,
+ uint32_t character,
+ uint32_t length,
+ SymbolKind kind,
+ SymbolModifiers modifiers) {
+ result.data.emplace_back(line);
+ result.data.emplace_back(character);
+ result.data.emplace_back(length);
+ result.data.emplace_back(kind.value());
+
+ /// FIXME:
+ result.data.emplace_back(0);
};
- SemanticTokens result;
-
- PositionConverter converter(content, kind);
+ PositionConverter converter(content, encoding());
std::uint32_t lastLine = 0;
std::uint32_t lastChar = 0;
@@ -260,7 +274,7 @@ LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
std::uint32_t line = beginLine - lastLine;
std::uint32_t character = (line == 0 ? beginChar - lastChar : beginChar);
std::uint32_t length = endChar - beginChar;
- result.add(line, character, length, token.kind, token.modifiers);
+ addGroup(line, character, length, token.kind, token.modifiers);
} else {
/// If the token spans multiple lines, split it into multiple tokens.
auto subContent = content.substr(beginOffset, endOffset - beginOffset);
@@ -288,8 +302,8 @@ LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
}
std::uint32_t length =
- remeasure(subContent.substr(lastLineOffset, lineLength), kind);
- result.add(line, character, length, token.kind, token.modifiers);
+ remeasure(subContent.substr(lastLineOffset, lineLength), encoding());
+ addGroup(line, character, length, token.kind, token.modifiers);
lastLineOffset += lineLength;
lineLength = 0;
@@ -298,81 +312,24 @@ LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
/// Process the last line if it's not empty.
if(lineLength > 0) {
- std::uint32_t length = remeasure(subContent.substr(lastLineOffset), kind);
- result.add(1, 0, length, token.kind, token.modifiers);
+ std::uint32_t length = remeasure(subContent.substr(lastLineOffset), encoding());
+ addGroup(1, 0, length, token.kind, token.modifiers);
}
}
lastLine = endLine;
- lastChar = endChar;
+ lastChar = beginChar;
}
- co_return json::serialize(result);
+ return result;
}
-namespace proto {
-
-/// A set of predefined range kinds.
-enum class FoldingRangeKind {
- /// Folding range for a comment.
- Comment,
-
- /// Folding range for imports or includes.
- Imports,
-
- /// Folding range for a region.
- Region,
-};
-
-/// Represents a folding range. To be valid, start and end line must be bigger
-/// than zero and smaller than the number of lines in the document. Clients
-/// are free to ignore invalid ranges.
-struct FoldingRange {
- /// The zero-based start line of the range to fold. The folded area starts
- /// after the line's last character. To be valid, the end must be zero or
- /// larger and smaller than the number of lines in the document.
- uint32_t startLine;
-
- /// The zero-based character offset from where the folded range starts. If
- /// not defined, defaults to the length of the start line.
- std::optional startCharacter;
-
- /// The zero-based end line of the range to fold. The folded area ends with
- /// the line's last character. To be valid, the end must be zero or larger
- /// and smaller than the number of lines in the document.
- uint32_t endLine;
-
- /// The zero-based character offset before the folded range ends. If not
- /// defined, defaults to the length of the end line.
- std::optional endCharacter;
-
- /// Describes the kind of the folding range such as `comment` or `region`.
- /// The kind is used to categorize folding ranges and used by commands like
- /// 'Fold all comments'. See [FoldingRangeKind](#FoldingRangeKind) for an
- /// enumeration of standardized kinds.
- FoldingRangeKind kind;
-
- /// The text that the client should show when the specified range is
- /// collapsed. If not defined or not supported by the client, a default
- /// will be chosen by the client.
- ///
- /// @since 3.17.0 - proposed
- std::optional collapsedText;
-};
-
-} // namespace proto
-
-LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
- llvm::ArrayRef foldings) {
- auto file = co_await async::fs::read(path.str());
- if(!file) {
- co_return json::Value(nullptr);
- }
- llvm::StringRef content = *file;
-
+std::vector
+ LSPConverter::transform(llvm::StringRef content,
+ llvm::ArrayRef foldings) {
std::vector result;
- PositionConverter converter(content, kind);
+ PositionConverter converter(content, encoding());
converter.toPositions(foldings, [](auto&& folding) { return folding.range; });
for(auto&& folding: foldings) {
@@ -384,13 +341,34 @@ LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
.startLine = beginLine,
.startCharacter = beginChar,
.endLine = endLine,
- .endCharacter = endChar,
+ /// FIXME: Figure out how to handle end character.
+ .endCharacter = endChar - 1,
.kind = proto::FoldingRangeKind::Region,
.collapsedText = folding.text,
});
}
- co_return json::serialize(result);
+ return result;
+}
+
+LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
+ llvm::ArrayRef tokens) {
+ auto file = co_await async::fs::read(path.str());
+ if(!file) {
+ co_return json::Value(nullptr);
+ }
+ llvm::StringRef content = *file;
+ co_return json::serialize(transform(content, tokens));
+}
+
+LSPConverter::Result LSPConverter::convert(llvm::StringRef path,
+ llvm::ArrayRef foldings) {
+ auto file = co_await async::fs::read(path.str());
+ if(!file) {
+ co_return json::Value(nullptr);
+ }
+ llvm::StringRef content = *file;
+ co_return json::serialize(transform(content, foldings));
}
LSPConverter::Result LSPConverter::convert(const feature::Hover& hover) {
diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp
index e32066c4..3a22b2f9 100644
--- a/src/Server/Lifecycle.cpp
+++ b/src/Server/Lifecycle.cpp
@@ -4,44 +4,57 @@
namespace clice {
-async::Task<> Server::onInitialize(json::Value id, const proto::InitializeParams& params) {
+async::Task<> Server::initialize(json::Value value) {
+ converter.initialize(std::move(value));
+
proto::InitializeResult result = {};
+
+ /// Initialize the server info.
result.serverInfo.name = "clice";
result.serverInfo.version = "0.0.1";
- /// Set `SemanticTokensOptions`
- for(auto kind: SymbolKind::all()) {
- std::string name{kind};
- name[0] = std::tolower(name[0]);
- result.capabilities.semanticTokensProvider.legend.tokenTypes.emplace_back(std::move(name));
- }
-
- result.capabilities.semanticTokensProvider.legend.tokenModifiers = {
-
- };
-
co_await response(std::move(id), json::serialize(result));
-
- auto workplace = SourceConverter::toPath(params.workspaceFolders[0].uri);
- config::init(workplace);
-
- for(auto& dir: config::server.compile_commands_dirs) {
- llvm::SmallString<128> path = {dir};
- path::append(path, "compile_commands.json");
- database.updateCommands(path);
- }
-}
-
-async::Task<> Server::onInitialized(const proto::InitializedParams& params) {
co_return;
}
-async::Task<> Server::onExit(const proto::None&) {
- co_return;
-}
+// async::Task<> Server::onInitialize(json::Value id, const proto::InitializeParams& params) {
+// proto::InitializeResult result = {};
+// result.serverInfo.name = "clice";
+// result.serverInfo.version = "0.0.1";
+//
+// /// Set `SemanticTokensOptions`
+// for(auto kind: SymbolKind::all()) {
+// std::string name{kind};
+// name[0] = std::tolower(name[0]);
+// result.capabilities.semanticTokensProvider.legend.tokenTypes.emplace_back(std::move(name));
+// }
+//
+// result.capabilities.semanticTokensProvider.legend.tokenModifiers = {
+//
+// };
+//
+// co_await response(std::move(id), json::serialize(result));
+//
+// auto workplace = SourceConverter::toPath(params.workspaceFolders[0].uri);
+// config::init(workplace);
+//
+// for(auto& dir: config::server.compile_commands_dirs) {
+// llvm::SmallString<128> path = {dir};
+// path::append(path, "compile_commands.json");
+// database.updateCommands(path);
+// }
+// }
-async::Task<> Server::onShutdown(json::Value id, const proto::None&) {
- co_return;
-}
+/// async::Task<> Server::onInitialized(const proto::InitializedParams& params) {
+/// co_return;
+/// }
+///
+/// async::Task<> Server::onExit(const proto::None&) {
+/// co_return;
+/// }
+///
+/// async::Task<> Server::onShutdown(json::Value id, const proto::None&) {
+/// co_return;
+/// }
} // namespace clice
diff --git a/src/Server/Scheduler.cpp b/src/Server/Scheduler.cpp
deleted file mode 100644
index 1e840dd5..00000000
--- a/src/Server/Scheduler.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "Server/Scheduler.h"
-
-namespace clice {
-
-async::Task<> Scheduler::open(llvm::StringRef file) {
- /// 首先根据 rule 来对 file 进行判别
-
- /// 查看一下这 file 属于什么模式
-
- /// 1. 正常的源文件或者模块文件
-
- /// 预处理一遍源文件,获取一些相关的信息
- /// 是不是 module file?
- /// 获取模块依赖关系
- /// 或者计算 Preamble 的位置
-
- auto command = database.getCommand(file);
-
- /// 如果不是 readonly 模式
- /// 调用 CacheController 里面对应的函数更新这个文件的 cache
- /// PCH or PCM
-
- /// 准备 CompilationParams 进行 AST 编译
-
- /// 调度索引任务 ...
-
- /// 2. 头文件
-
- /// 先检查一下该头文件是否有选中的上下文... [一组 include 位置]
- ///
- /// 对上下文源文件进行上述的处理
-
- ///
- /// ...
-
- /// 重新索引依赖这个头文件的源文件
-
- co_return;
-}
-
-} // namespace clice
diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp
index aa930a16..78aee909 100644
--- a/src/Server/Server.cpp
+++ b/src/Server/Server.cpp
@@ -3,78 +3,128 @@
namespace clice {
-Server::Server() : indexer(database, config::index), scheduler(database, {}) {
- addMethod("initialize", &Server::onInitialize);
- addMethod("initialized", &Server::onInitialized);
- addMethod("shutdown", &Server::onShutdown);
- addMethod("exit", &Server::onExit);
-
- addMethod("textDocument/didOpen", &Server::onDidOpen);
- addMethod("textDocument/didChange", &Server::onDidChange);
- addMethod("textDocument/didSave", &Server::onDidSave);
- addMethod("textDocument/didClose", &Server::onDidClose);
-
- // addMethod("textDocument/declaration", &Server::onGotoDeclaration);
- // addMethod("textDocument/definition", &Server::onGotoDefinition);
- // addMethod("textDocument/typeDefinition", &Server::onGotoTypeDefinition);
- // addMethod("textDocument/implementation", &Server::onGotoImplementation);
- // addMethod("textDocument/references", &Server::onFindReferences);
- // addMethod("textDocument/callHierarchy/prepare", &Server::onPrepareCallHierarchy);
- // addMethod("textDocument/callHierarchy/incomingCalls", &Server::onIncomingCall);
- // addMethod("textDocument/callHierarchy/outgoingCalls", &Server::onOutgoingCall);
- // addMethod("textDocument/typeHierarchy/prepare", &Server::onPrepareTypeHierarchy);
- // addMethod("textDocument/typeHierarchy/supertypes", &Server::onSupertypes);
- // addMethod("textDocument/typeHierarchy/subtypes", &Server::onSubtypes);
- // addMethod("textDocument/documentHighlight", &Server::onDocumentHighlight);
- // addMethod("textDocument/documentLink", &Server::onDocumentLink);
- // addMethod("textDocument/hover", &Server::onHover);
- // addMethod("textDocument/codeLens", &Server::onCodeLens);
- // addMethod("textDocument/foldingRange", &Server::onFoldingRange);
- // addMethod("textDocument/documentSymbol", &Server::onDocumentSymbol);
- // addMethod("textDocument/semanticTokens/full", &Server::onSemanticTokens);
- // addMethod("textDocument/inlayHint", &Server::onInlayHint);
- // addMethod("textDocument/completion", &Server::onCodeCompletion);
- // addMethod("textDocument/signatureHelp", &Server::onSignatureHelp);
- // addMethod("textDocument/codeAction", &Server::onCodeAction);
- // addMethod("textDocument/formatting", &Server::onFormatting);
- // addMethod("textDocument/rangeFormatting", &Server::onRangeFormatting);
-
- addMethod("workspace/didChangeWatchedFiles", &Server::onDidChangeWatchedFiles);
-
- addMethod("index/current", &Server::onIndexCurrent);
- addMethod("index/all", &Server::onIndexAll);
- addMethod("context/current", &Server::onContextCurrent);
- addMethod("context/switch", &Server::onContextSwitch);
- addMethod("context/all", &Server::onContextAll);
-}
+Server::Server() : indexer(database, config::index) {}
async::Task<> Server::onReceive(json::Value value) {
- assert(value.kind() == json::Value::Object);
auto object = value.getAsObject();
- assert(object && "value is not an object");
- if(auto method = object->get("method")) {
- auto name = *method->getAsString();
- auto params = object->get("params");
- if(auto id = object->get("id")) {
- if(auto iter = requests.find(name); iter != requests.end()) {
- /// auto tracer = Tracer();
- log::info("Receive request: {0}", name);
- co_await iter->second(std::move(*id),
- params ? std::move(*params) : json::Value(nullptr));
- log::info("Request {0} is done, elapsed {1}", name, 0);
+ if(!object) [[unlikely]] {
+ log::fatal("Invalid LSP message, not an object: {}", value);
+ }
- } else {
- log::warn("Unknown request: {0}", name);
- }
- } else {
- if(auto iter = notifications.find(name); iter != notifications.end()) {
- log::info("Notification: {0}", name);
- co_await iter->second(params ? std::move(*params) : json::Value(nullptr));
- } else {
- log::warn("Unknown notification: {0}", name);
- }
+ /// If the json object has an `id`, it's a request,
+ /// which needs a response. Otherwise, it's a notification.
+ auto id = object->get("id");
+
+ llvm::StringRef method;
+ if(auto result = object->getString("method")) {
+ method = *result;
+ } else [[unlikely]] {
+ log::warn("Invalid LSP message, method not found: {}", value);
+ if(id) {
+ co_await response(std::move(*id),
+ proto::ErrorCodes::InvalidRequest,
+ "Method not found");
+ }
+ co_return;
+ }
+
+ json::Value params = json::Value(nullptr);
+ if(auto result = object->get("params")) {
+ params = std::move(*result);
+ }
+
+ /// Handle request and notification separately.
+ /// TODO: Record the time of handling request and notification.
+ if(id) {
+ log::info("Handling request: {}", method);
+ auto result = co_await onRequest(method, std::move(params));
+ co_await response(std::move(*id), std::move(result));
+ log::info("Handled request: {}", method);
+ } else {
+ log::info("Handling notification: {}", method);
+ co_await onNotification(method, std::move(params));
+ log::info("Handled notification: {}", method);
+ }
+
+ co_return;
+}
+
+async::Task Server::onRequest(llvm::StringRef method, json::Value value) {
+ if(method == "initialize") {
+ auto result = converter.initialize(std::move(value));
+ config::init(converter.workspace());
+
+ /// FIXME: Use a better way to handle compile commands.
+ for(auto&& dir: config::server.compile_commands_dirs) {
+ database.updateCommands(dir + "/compile_commands.json");
+ }
+
+ indexer.load();
+
+ co_return json::serialize(result);
+ } else if(method == "shutdown") {
+ indexer.save();
+ } else if(method.consume_front("textDocument/")) {
+ co_return co_await onTextDocument(method, std::move(value));
+ } else if(method.consume_front("context/")) {
+ co_return co_await onContext(method, std::move(value));
+ } else if(method.consume_front("index/")) {
+ co_return co_await onIndex(method, std::move(value));
+ }
+
+ co_return json::Value(nullptr);
+}
+
+async::Task Server::onTextDocument(llvm::StringRef method, json::Value value) {
+ if(method == "semanticTokens/full") {
+ auto params2 = json::deserialize(value);
+ auto path = SourceConverter::toPath(params2.textDocument.uri);
+ auto tokens = co_await indexer.semanticTokens(path);
+ co_return co_await converter.convert(path, tokens);
+ } else if(method == "foldingRange") {
+ auto params2 = json::deserialize(value);
+ auto path = SourceConverter::toPath(params2.textDocument.uri);
+ auto foldings = co_await indexer.foldingRanges(path);
+ co_return co_await converter.convert(path, foldings);
+ }
+
+ co_return json::Value(nullptr);
+}
+
+async::Task Server::onContext(llvm::StringRef method, json::Value value) {
+ if(method == "current") {
+ auto param2 = json::deserialize(value);
+ auto path = SourceConverter::toPath(param2.textDocument.uri);
+ auto result = indexer.currentContext(path);
+ co_return result ? json::serialize(*result) : json::Value(nullptr);
+ } else if(method == "switch") {
+ auto params = json::deserialize(value);
+ auto header = SourceConverter::toPath(params.header);
+ indexer.switchContext(header, params.context);
+ } else if(method == "all") {
+ auto param2 = json::deserialize(value);
+ auto path = SourceConverter::toPath(param2.textDocument.uri);
+ auto result = indexer.allContexts(path);
+ co_return json::serialize(result);
+ } else if(method == "resolve") {
+ co_return json::serialize(
+ indexer.resolveContext(json::deserialize(value)));
+ }
+
+ co_return json::Value(nullptr);
+}
+
+async::Task Server::onIndex(llvm::StringRef method, json::Value value) {
+ co_return json::Value(nullptr);
+}
+
+async::Task<> Server::onNotification(llvm::StringRef method, json::Value value) {
+ if(method.consume_front("index/")) {
+ if(method == "all") {
+ indexer.indexAll();
}
}
+
co_return;
}
@@ -98,11 +148,24 @@ async::Task<> Server::notify(llvm::StringRef method, json::Value params) {
async::Task<> Server::response(json::Value id, json::Value result) {
co_await async::net::write(json::Object{
{"jsonrpc", "2.0" },
- {"id", id },
+ {"id", std::move(id) },
{"result", std::move(result)},
});
}
+async::Task<> Server::response(json::Value id, proto::ErrorCodes code, llvm::StringRef message) {
+ json::Object error{
+ {"code", static_cast(code)},
+ {"message", message },
+ };
+
+ co_await async::net::write(json::Object{
+ {"jsonrpc", "2.0" },
+ {"id", std::move(id) },
+ {"error", std::move(error)},
+ });
+}
+
async::Task<> Server::registerCapacity(llvm::StringRef id,
llvm::StringRef method,
json::Value registerOptions) {
diff --git a/src/Server/Workspace.cpp b/src/Server/Workspace.cpp
deleted file mode 100644
index d5fca396..00000000
--- a/src/Server/Workspace.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "Basic/SourceConverter.h"
-#include "Server/Server.h"
-
-namespace clice {
-
-async::Task<> Server::onDidChangeWatchedFiles(const proto::DidChangeWatchedFilesParams& params) {
- co_return;
-}
-
-} // namespace clice
diff --git a/unittests/Compiler/Command.cpp b/unittests/Compiler/Command.cpp
index 992a349f..160be781 100644
--- a/unittests/Compiler/Command.cpp
+++ b/unittests/Compiler/Command.cpp
@@ -5,31 +5,9 @@ namespace clice::testing {
namespace {
-TEST(clice, Command) {
- llvm::SmallString<1024> buffer;
- llvm::SmallVector args;
+TEST(CompilationDatabase, Command) {}
- auto command =
- "/usr/local/bin/clang++"
- " -Dclice_core_EXPORTS"
- " -I/home/ykiko/C++/clice2/deps/llvm/build-install/include"
- " -I/home/ykiko/C++/clice2/include"
- " -I/home/ykiko/C++/clice2/deps/toml/include"
- " -I/home/ykiko/C++/clice2/deps/libuv/include"
- " -fno-rtti"
- " -fno-exceptions"
- " -g -O0 -fsanitize=address"
- " -Wno-deprecated-declarations"
- " -g -std=gnu++23 -fPIC -Winvalid-pch"
- " -Xclang -include-pch"
- " -Xclang /home/ykiko/C++/clice2/build/CMakeFiles/clice-core.dir/cmake_pch.hxx.pch"
- " -Xclang -include -Xclang /home/ykiko/C++/clice2/build/CMakeFiles/clice-core.dir/cmake_pch.hxx"
- " -o CMakeFiles/clice-core.dir/src/Basic/URI.cpp.o"
- " -c /home/ykiko/C++/clice2/src/Basic/URI.cpp";
-
- auto result = mangleCommand(command, args, buffer);
- ASSERT_FALSE(bool(!result));
-}
+TEST(CompilationDatabase, Module) {}
} // namespace
diff --git a/unittests/Server/Database.cpp b/unittests/Server/Database.cpp
deleted file mode 100644
index 727daa07..00000000
--- a/unittests/Server/Database.cpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#include "Test/Test.h"
-#include "Server/Database.h"
-
-namespace clice::testing {
-
-namespace {
-
-TEST(CompilationDatabase, Command) {}
-
-TEST(CompilationDatabase, Module) {}
-
-} // namespace
-
-} // namespace clice::testing
diff --git a/unittests/Server/LSPConverter.cpp b/unittests/Server/LSPConverter.cpp
new file mode 100644
index 00000000..0fd0b86c
--- /dev/null
+++ b/unittests/Server/LSPConverter.cpp
@@ -0,0 +1,12 @@
+#include
+#include
+
+namespace clice::testing {
+
+namespace {
+
+TEST(Server, SourceConverter) {}
+
+} // namespace
+
+} // namespace clice::testing