Refactor LSPConverter (#118)

This commit is contained in:
ykiko
2025-04-05 13:44:44 +08:00
committed by GitHub
parent 3f408a8e8e
commit 3f96d45ab4
8 changed files with 281 additions and 562 deletions

View File

@@ -1,50 +1,57 @@
#pragma once
#include "Config.h"
#include "Protocol.h"
#include "Async/Async.h"
#include "Feature/Hover.h"
#include "Feature/InlayHint.h"
#include "Feature/FoldingRange.h"
#include "Feature/DocumentLink.h"
#include "Feature/DocumentSymbol.h"
#include "Feature/SemanticToken.h"
#include "Feature/DocumentLink.h"
#include "Server/Protocol.h"
namespace clice {
enum class PositionEncodingKind : std::uint8_t {
UTF8 = 0,
UTF16,
UTF32,
};
/// Responsible for converting between LSP and internal types.
class LSPConverter {
public:
using Result = async::Task<json::Value>;
json::Value initialize(json::Value value);
proto::InitializeResult initialize(json::Value value);
auto encoding() {
return params.capabilities.general.positionEncodings[0];
PositionEncodingKind encoding() {
return kind;
}
auto& capabilities() {
return params.capabilities;
llvm::StringRef workspace() {
return workspacePath;
}
/// The path of the workspace.
llvm::StringRef workspace();
public:
/// Convert a position into an offset relative to the beginning of the file.
uint32_t convert(llvm::StringRef content, proto::Position position);
std::uint32_t convert(llvm::StringRef content, proto::Position position);
proto::SemanticTokens transform(llvm::StringRef content,
llvm::ArrayRef<feature::SemanticToken> tokens);
/// Convert `TextDocumentParams` to file path.
std::string convert(proto::TextDocumentParams params);
std::vector<proto::FoldingRange> transform(llvm::StringRef content,
llvm::ArrayRef<feature::FoldingRange> foldings);
json::Value convert(llvm::StringRef content, const feature::Hover& hover);
std::vector<proto::DocumentLink> transform(llvm::StringRef content,
llvm::ArrayRef<feature::DocumentLink> links);
json::Value convert(llvm::StringRef content, const feature::InlayHints& hints);
json::Value convert(llvm::StringRef content, const feature::FoldingRanges& foldings);
json::Value convert(llvm::StringRef content, const feature::DocumentLinks& links);
json::Value convert(llvm::StringRef content, const feature::DocumentSymbols& symbols);
json::Value convert(llvm::StringRef content, const feature::SemanticTokens& tokens);
private:
proto::InitializeParams params;
PositionEncodingKind kind;
std::string workspacePath;
};

View File

@@ -25,19 +25,6 @@ using DocumentUri = std::string;
using URI = std::string;
struct None {};
/// A set of predefined position encoding kinds.
struct PositionEncodingKind : refl::Enum<PositionEncodingKind, false, std::string_view> {
using Enum::Enum;
constexpr inline static std::string_view UTF8 = "utf-8";
constexpr inline static std::string_view UTF16 = "utf-16";
constexpr inline static std::string_view UTF32 = "utf-32";
constexpr inline static std::array All = {UTF8, UTF16, UTF32};
};
struct Position {
/// Line position in a document (zero-based).
uinteger line;
@@ -46,22 +33,18 @@ struct Position {
/// The meaning of this offset is determined by the negotiated
/// `PositionEncodingKind`.
uinteger character;
constexpr friend bool operator== (const Position&, const Position&) = default;
};
constexpr bool operator== (const proto::Position& lhs, const proto::Position rhs) {
return lhs.character == rhs.character && lhs.line == rhs.line;
}
constexpr auto operator<=> (const proto::Position& lhs, const proto::Position rhs) {
return std::tie(lhs.line, lhs.character) <=> std::tie(rhs.line, rhs.character);
}
struct Range {
/// The range's start position.
Position start;
/// The range's end position.
Position end;
constexpr friend bool operator== (const Range&, const Range&) = default;
};
struct Location {
@@ -80,23 +63,6 @@ struct TextEdit {
string newText;
};
struct TextDocumentSyncKind : refl::Enum<TextDocumentSyncKind, false, std::uint8_t> {
using Enum::Enum;
enum Kind : std::uint8_t {
/// Documents should not be synced at all.
None = 0,
/// Documents are synced by always sending the full content of the document.
Full = 1,
/// Documents are synced by sending the full content on open. After that
/// only
/// incremental updates to the document are sent.
Incremental = 2,
};
};
struct TextDocumentItem {
/// The text document's URI.
DocumentUri uri;
@@ -117,48 +83,6 @@ struct TextDocumentIdentifier {
DocumentUri uri;
};
struct VersionedTextDocumentIdentifier {
/// The text document's URI.
DocumentUri uri;
/// The version number of this document.
///
/// The version number of a document will increase after each change,
/// including undo/redo. The number doesn't need to be consecutive.
integer version;
};
/// An event describing a change to a text document. If only a text is provided
/// it is considered to be the full content of the document.
struct TextDocumentContentChangeEvent {
/// The range of the document that changed.
Range range;
/// The new text for the provided range.
string text;
};
struct DidChangeTextDocumentParams {
/// The document that did change. The version number points
/// to the version after all provided content changes have
/// been applied.
VersionedTextDocumentIdentifier textDocument;
/// The actual content changes. The content changes describe single state
/// changes to the document. So if there are two content changes c1 (at
/// array index 0) and c2 (at array index 1) for a document in state S then
/// c1 moves the document from S to S' and c2 from S' to S''. So c1 is
/// computed on the state S and c2 is computed on the state S'.
///
/// To mirror the content of a document using change events use the following
/// approach:
/// - start with the same initial content
/// - apply the 'textDocument/didChange' notifications in the order you
/// receive them.
/// - apply the `TextDocumentContentChangeEvent`s in a single notification
/// in the order you receive them.
std::vector<TextDocumentContentChangeEvent> contentChanges;
};
struct TextDocumentPositionParams {
/// The text document.
TextDocumentIdentifier textDocument;
@@ -167,39 +91,12 @@ struct TextDocumentPositionParams {
Position position;
};
using MarkupKind = string;
struct MarkupContent {
/// The type of the Markup.
MarkupKind kind = "markdown";
/// The content itself.
string value;
enum class TextDocumentSyncKind {
None = 0,
Full = 1,
Incremental = 2,
};
struct DidOpenTextDocumentParams {
/// The document that was opened.
TextDocumentItem textDocument;
};
struct DidSaveTextDocumentParams {
/// The document that was saved.
TextDocumentIdentifier textDocument;
/// Optional the content when saved. Depends on the includeText value
/// when the save notifcation was requested.
string text;
};
struct DidCloseTextDocumentParams {
/// The document that was closed.
TextDocumentIdentifier textDocument;
};
} // namespace clice::proto
namespace clice::proto {
struct WorkspaceFolder {
/// The associated URI for this workspace folder.
URI uri;
@@ -209,248 +106,77 @@ struct WorkspaceFolder {
std::string name;
};
struct DidChangeWatchedFilesParams {};
enum class ErrorCodes {
// Defined by JSON-RPC
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
struct ClientCapabilities {
/// General client capabilities.
struct {
/// The position encodings supported by the client. Client and server
/// have to agree on the same position encoding to ensure that offsets
/// (e.g. character position in a line) are interpreted the same on both
/// side.
///
/// To keep the protocol backwards compatible the following applies: if
/// the value 'utf-16' is missing from the array of position encodings
/// servers can assume that the client supports UTF-16. UTF-16 is
/// therefore a mandatory encoding.
///
/// If omitted it defaults to ['utf-16'].
///
/// Implementation considerations: since the conversion from one encoding
/// into another requires the content of the file / line the conversion
/// is best done where the file is read which is usually on the server
/// side.
std::vector<PositionEncodingKind> positionEncodings = {PositionEncodingKind::UTF16};
} general;
/**
* 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,
};
struct InitializeParams {
/// Information about the client.
struct {
/// The name of the client as defined by the client.
std::string name;
/// The client's version as defined by the client.
std::string version;
} clientInfo;
/// The capabilities provided by the client (editor or tool).
ClientCapabilities capabilities;
/// The workspace folders configured in the client when the server starts.
/// This property is only available if the client supports workspace folders.
/// It can be `null` if the client supports workspace folders but none are
/// configured.
std::vector<WorkspaceFolder> workspaceFolders;
};
struct SemanticTokensOptions {
/// The legend used by the server.
struct SemanticTokensLegend {
/// The token types a server uses.
std::vector<std::string> tokenTypes;
/// The token modifiers a server uses.
std::vector<std::string> tokenModifiers;
} legend;
/// Server supports providing semantic tokens for a specific range
/// of a document.
bool range = false;
/// Server supports providing semantic tokens for a full document.
bool full = true;
};
struct SemanticTokens {
/// The actual tokens.
std::vector<std::uint32_t> 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,
};
struct DocumentLink {
Range range;
URI target;
};
/// 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<uint32_t> 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<uint32_t> 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<std::string> collapsedText;
};
/// Server Capability.
struct ServerCapabilities {
/// The position encoding the server picked from the encodings offered
/// by the client via the client capability `general.positionEncodings`.
///
/// If the client didn't provide any position encodings the only valid
/// value that a server can return is 'utf-16'.
///
/// If omitted it defaults to 'utf-16'.
PositionEncodingKind positionEncoding = PositionEncodingKind::UTF16;
/// Defines how text documents are synced. Is either a detailed structure
/// defining each notification or for backwards compatibility the
/// TextDocumentSyncKind number. If omitted it defaults to
/// `TextDocumentSyncKind.None`.
TextDocumentSyncKind textDocumentSync = TextDocumentSyncKind::None;
/// The server provides go to declaration support.
bool declarationProvider = true;
/// The server provides goto definition support.
bool definitionProvider = true;
/// The server provides goto type definition support.
bool typeDefinitionProvider = true;
/// The server provides goto implementation support.
bool implementationProvider = true;
/// The server provides find references support.
bool referencesProvider = true;
/// The server provides call hierarchy support.
bool callHierarchyProvider = true;
/// The server provides type hierarchy support.
bool typeHierarchyProvider = true;
/// The server provides semantic tokens support.
SemanticTokensOptions semanticTokensProvider;
struct DocumentLinkOptions {
/// Document links have a resolve provider as well.
bool resolveProvider = false;
};
/// The server provides document link support.
DocumentLinkOptions documentLinkProvider;
/// The server provides folding provider support.
bool foldingRangeProvider = true;
};
struct InitializeResult {
/// The capabilities the language server provides.
ServerCapabilities capabilities;
/// Information about the server.
struct {
/// The name of the server as defined by the server.
std::string name;
/// The server's version as defined by the server.
std::string version;
} serverInfo;
};
struct InitializedParams {};
} // namespace clice::proto
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;
using DocumentLinkParams = TextDocumentParams;
using DocumentSymbolParams = TextDocumentParams;
enum class SymbolKind {};
struct DocumentSymbol {
std::string name;
std::string detail;
SymbolKind kind;
Range range;
Range selectionRange;
std::vector<DocumentSymbol> children;
struct ResolveProvider {
bool resolveProvider;
};
struct SemanticTokenOptions {
struct {
std::vector<std::string> tokenTypes;
std::vector<std::string> tokenModifiers;
} legend;
bool full = true;
};
struct HeaderContext {

View File

@@ -10,63 +10,6 @@
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();
@@ -88,6 +31,9 @@ public:
/// Handle notifications, a notification doesn't require response.
async::Task<> onNotification(llvm::StringRef method, json::Value value);
/// Handle notifications `context/
async::Task<> onFileOperation(llvm::StringRef method, json::Value value);
private:
/// Send a request to the client.
async::Task<> request(llvm::StringRef method, json::Value params);
@@ -106,7 +52,6 @@ private:
json::Value registerOptions);
private:
async::Task<> initialize(json::Value value);
public:
std::uint32_t id = 0;