From c1c1930757712b0d2b004f15b997741cca75bb2e Mon Sep 17 00:00:00 2001 From: Shiyu Date: Fri, 10 Jan 2025 19:26:41 +0800 Subject: [PATCH] Implement SourceConverter (#26) --- include/Basic/Basic.h | 3 - include/Basic/SourceCode.h | 30 ----- include/Basic/SourceConverter.h | 68 ++++++++++++ include/Basic/URI.h | 52 --------- include/Feature/FoldingRange.h | 9 +- include/Server/Protocol.h | 2 - .../{SourceCode.cpp => SourceConverter.cpp} | 105 +++++++++++++++--- src/Basic/URI.cpp | 105 ------------------ src/Feature/CodeCompletion.cpp | 3 +- src/Feature/FoldingRange.cpp | 74 +++++++----- src/Feature/SignatureHelp.cpp | 1 - src/Server/Document.cpp | 9 +- src/Server/Feature.cpp | 13 +-- src/Server/Lifestyle.cpp | 3 +- src/Server/Workplace.cpp | 3 +- unittests/Basic/SourceCode.cpp | 22 ---- unittests/Basic/SourceConverter.cpp | 93 ++++++++++++++++ unittests/Basic/URI.cpp | 55 --------- unittests/Feature/FoldingRange.cpp | 39 ++++--- unittests/Index/IndexTester.h | 2 +- 20 files changed, 343 insertions(+), 348 deletions(-) delete mode 100644 include/Basic/SourceCode.h create mode 100644 include/Basic/SourceConverter.h delete mode 100644 include/Basic/URI.h rename src/Basic/{SourceCode.cpp => SourceConverter.cpp} (62%) delete mode 100644 src/Basic/URI.cpp delete mode 100644 unittests/Basic/SourceCode.cpp create mode 100644 unittests/Basic/SourceConverter.cpp delete mode 100644 unittests/Basic/URI.cpp diff --git a/include/Basic/Basic.h b/include/Basic/Basic.h index 8476104d..2288e454 100644 --- a/include/Basic/Basic.h +++ b/include/Basic/Basic.h @@ -21,7 +21,4 @@ using array = std::vector; using DocumentUri = std::string; -// TODO: figure out URI. -using URI = std::string; - } // namespace clice::proto diff --git a/include/Basic/SourceCode.h b/include/Basic/SourceCode.h deleted file mode 100644 index e19c0ded..00000000 --- a/include/Basic/SourceCode.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include -#include - -namespace clice { - -/// Measure the length of the content with the specified encoding kind. -std::size_t remeasure(llvm::StringRef content, proto::PositionEncodingKind kind); - -/// Convert a clang::SourceLocation to a proto::Position according to the -/// specified encoding kind. Note that `SourceLocation` in clang is 1-based and -/// is always encoded in UTF-8. -proto::Position toPosition(llvm::StringRef content, - clang::SourceLocation location, - proto::PositionEncodingKind kind, - const clang::SourceManager& SM); - -/// Same as above, but content is retrieved from the `SourceManager`. -proto::Position toPosition(clang::SourceLocation location, - proto::PositionEncodingKind kind, - const clang::SourceManager& SM); - -/// Convert a proto::Position to a file offset in the content with the specified -/// encoding kind. -std::size_t toOffset(llvm::StringRef content, - proto::Position position, - proto::PositionEncodingKind kind); - -} // namespace clice diff --git a/include/Basic/SourceConverter.h b/include/Basic/SourceConverter.h new file mode 100644 index 00000000..31a480eb --- /dev/null +++ b/include/Basic/SourceConverter.h @@ -0,0 +1,68 @@ +#pragma once + +#include "llvm/Support/Error.h" + +#include +#include + +namespace clice { + +/// A helper class to convert `Position, Range, Location` between 1-1 encoding based clang and 0-0 +/// encoding based LSP. The conversion of DocumentUri is also supported. +class SourceConverter { +public: + /// [(origin, new)], map origin header directory to another source directory. + using SourceDirMapping = std::vector>; + + /// Construct a `SourceConverter` with the specified encoding kind and empty source map. + explicit SourceConverter(proto::PositionEncodingKind kind) : kind(kind), sourceMap() {} + + SourceConverter(proto::PositionEncodingKind kind, SourceDirMapping sourceMap) : + kind(kind), sourceMap(std::move(sourceMap)) {} + + SourceConverter(const SourceConverter&) = delete; + + SourceConverter(SourceConverter&&) = default; + + /// Measure the length (character count) of the content with the specified encoding kind. + std::size_t remeasure(llvm::StringRef content) const; + + /// Convert a clang::SourceLocation to a proto::Position according to the + /// specified encoding kind. Note that `SourceLocation` in clang is 1-based and + /// is always encoded in UTF-8. + proto::Position toPosition(llvm::StringRef content, clang::SourceLocation location, + const clang::SourceManager& SM) const; + + /// Same as above, but content is retrieved from the `SourceManager`. + proto::Position toPosition(clang::SourceLocation location, + const clang::SourceManager& SM) const; + + /// Convert a clang::SourceRange to a proto::Range according to the specified encoding kind. + proto::Range toRange(clang::SourceRange range, const clang::SourceManager& SM) const { + return {toPosition(range.getBegin(), SM), toPosition(range.getEnd(), SM)}; + } + + /// Convert a proto::Position to a file offset in the content with the specified + /// encoding kind. + std::size_t toOffset(llvm::StringRef content, proto::Position position) const; + + /// Get the encoding kind of the content in LSP protocol. + proto::PositionEncodingKind encodingKind() const { + return kind; + } + + /// Convert a real path of a file to URI. Crash if failed. + static proto::DocumentUri toURI(llvm::StringRef fspath); + + /// Convert a file URI to real path with `clice::fs::real_path`. Crash if failed. + static std::string toPath(llvm::StringRef uri); + +private: + /// The encoding kind of the content in LSP protocol. + proto::PositionEncodingKind kind; + + /// A user-defined map from header file to its source directory. + SourceDirMapping sourceMap; +}; + +} // namespace clice diff --git a/include/Basic/URI.h b/include/Basic/URI.h deleted file mode 100644 index 25bf22b1..00000000 --- a/include/Basic/URI.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include - -#include "Support/Support.h" - -namespace clice { - -class URI { -public: - URI(llvm::StringRef scheme, llvm::StringRef authority, llvm::StringRef path) : - m_scheme(scheme), m_authority(authority), m_body(path) {} - - URI(const URI&) = default; - - bool operator== (const URI&) const = default; - - /// Construct a URI object from the given file path. - static URI from(llvm::StringRef file); - - /// Parse the given URI string to create a URI object. - static llvm::Expected parse(llvm::StringRef content); - - /// Same as `parse`, but will crash if failed. - static std::string resolve(llvm::StringRef content); - - /// Returns decoded scheme e.g. "https" - llvm::StringRef scheme() const { - return m_scheme; - } - - /// Returns decoded authority e.g. "reviews.llvm.org" - llvm::StringRef authority() const { - return m_authority; - } - - /// Returns decoded body e.g. "/D41946" - llvm::StringRef body() const { - return m_body; - } - - std::string toString() const { - return std::format("{}://{}{}", m_scheme, m_authority, m_body); - } - -private: - std::string m_scheme; - std::string m_authority; - std::string m_body; -}; - -} // namespace clice diff --git a/include/Feature/FoldingRange.h b/include/Feature/FoldingRange.h index 1abee571..068bd414 100644 --- a/include/Feature/FoldingRange.h +++ b/include/Feature/FoldingRange.h @@ -1,4 +1,5 @@ #include "Basic/Document.h" +#include "Basic/SourceConverter.h" #include "Compiler/Compiler.h" namespace clice { @@ -21,7 +22,6 @@ struct FoldingRangeClientCapabilities {}; /// ``` struct FoldingRangeParams { - /// The text document. TextDocumentIdentifier textDocument; }; @@ -68,12 +68,11 @@ using FoldingRangeResult = std::vector; namespace feature { -/// TODO: -/// use `proto::FoldingRangeClientCapabilities` instead of `json::Value`to make a proper overload. -// json::Value capability(json::Value FoldingRangeClientCapabilities); +json::Value foldingRangeCapability(json::Value foldingRangeClientCapabilities); /// Return folding range in given file. -proto::FoldingRangeResult foldingRange(FoldingRangeParams& params, ASTInfo& ast); +proto::FoldingRangeResult foldingRange(FoldingRangeParams& params, ASTInfo& ast, + const SourceConverter& converter); } // namespace feature diff --git a/include/Server/Protocol.h b/include/Server/Protocol.h index 68520f7c..5461c255 100644 --- a/include/Server/Protocol.h +++ b/include/Server/Protocol.h @@ -1,7 +1,5 @@ #pragma once -#include "Basic/URI.h" - #include "Feature/Lookup.h" #include "Feature/DocumentHighlight.h" #include "Feature/DocumentLink.h" diff --git a/src/Basic/SourceCode.cpp b/src/Basic/SourceConverter.cpp similarity index 62% rename from src/Basic/SourceCode.cpp rename to src/Basic/SourceConverter.cpp index 22ef4bf3..9a2c091c 100644 --- a/src/Basic/SourceCode.cpp +++ b/src/Basic/SourceConverter.cpp @@ -1,4 +1,5 @@ -#include "Basic/SourceCode.h" +#include "Basic/SourceConverter.h" +#include "Basic/Location.h" namespace clice { @@ -60,7 +61,7 @@ static bool iterateCodepoints(llvm::StringRef content, const Callback& callback) return false; } -std::size_t remeasure(llvm::StringRef content, proto::PositionEncodingKind kind) { +std::size_t SourceConverter::remeasure(llvm::StringRef content) const { if(kind == proto::PositionEncodingKind::UTF8) { return content.size(); } @@ -86,10 +87,8 @@ std::size_t remeasure(llvm::StringRef content, proto::PositionEncodingKind kind) std::unreachable(); } -proto::Position toPosition(llvm::StringRef content, - clang::SourceLocation location, - proto::PositionEncodingKind kind, - const clang::SourceManager& SM) { +proto::Position SourceConverter::toPosition(llvm::StringRef content, clang::SourceLocation location, + const clang::SourceManager& SM) const { assert(location.isValid() && location.isFileID() && "SourceLocation must be valid and not a macro location"); auto [fileID, offset] = SM.getDecomposedSpellingLoc(location); @@ -101,23 +100,24 @@ proto::Position toPosition(llvm::StringRef content, proto::Position position; /// Line doesn't need to be adjusted. It is encoding-dependent. position.line = line; + /// Column needs to be adjusted based on the encoding. - position.character = remeasure(content.substr(offset - column, column), kind); + if(auto word = content.substr(offset - column, column); !word.empty()) + position.character = remeasure(word); + else + position.character = column; // word is the last column of that line. return position; } -proto::Position toPosition(clang::SourceLocation location, - proto::PositionEncodingKind kind, - const clang::SourceManager& SM) { +proto::Position SourceConverter::toPosition(clang::SourceLocation location, + const clang::SourceManager& SM) const { bool isInvalid = false; llvm::StringRef content = SM.getCharacterData(location, &isInvalid); assert(!isInvalid && "Invalid SourceLocation"); - return toPosition(content, location, kind, SM); + return toPosition(content, location, SM); } -std::size_t toOffset(llvm::StringRef content, - proto::Position position, - proto::PositionEncodingKind kind) { +std::size_t SourceConverter::toOffset(llvm::StringRef content, proto::Position position) const { std::size_t offset = 0; for(auto i = 0; i < position.line; i++) { auto pos = content.find('\n'); @@ -159,4 +159,81 @@ std::size_t toOffset(llvm::StringRef content, std::unreachable(); } +namespace { + +/// decodes a string according to percent-encoding, e.g., "a%20b" -> "a b". +static std::string decodePercent(llvm::StringRef content) { + std::string result; + result.reserve(content.size()); + + for(auto iter = content.begin(), send = content.end(); iter != send; ++iter) { + auto c = *iter; + if(c == '%' && iter + 2 < send) { + auto m = *(iter + 1); + auto n = *(iter + 2); + if(llvm::isHexDigit(m) && llvm::isHexDigit(n)) { + result += llvm::hexFromNibbles(m, n); + iter += 2; + continue; + } + } + result += c; + } + return result; +} + +} // namespace + +proto::DocumentUri SourceConverter::toURI(llvm::StringRef fspath) { + if(!path::is_absolute(fspath)) + std::terminate(); + + llvm::SmallString<128> path("file://"); + + for(auto c: fspath) { + if(c == '\\') { + path.push_back('/'); + } else if(std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '/') { + path.push_back(c); + } else { + path.push_back('%'); + path.push_back(llvm::hexdigit(c >> 4)); + path.push_back(llvm::hexdigit(c & 0xF)); + } + } + + /// TODO: + /// use `sourceMap` to replace prefix with mapped path. + // for(const auto& [prefix, newPrefix]: sourceMap) { + // if(fspath.starts_with(prefix)) { + // path.append(newPrefix); // todo: newPrefix.end_with('/') ??? + // path.append(fspath.substr(prefix.size())); + // break; + // } + // } + + return path.str().str(); +}; + +std::string SourceConverter::toPath(llvm::StringRef uri) { + llvm::StringRef cloned = uri; + + auto pos = cloned.find(':'); + if(pos == llvm::StringRef::npos) + std::terminate(); + + auto scheme = cloned.substr(0, pos); + cloned = cloned.substr(pos + 1); + + if(cloned.consume_front("//")) + cloned = cloned.substr(cloned.find('/')); + + auto decoded = decodePercent(cloned); + llvm::SmallString<128> result; + if(auto err = fs::real_path(decoded, result)) + std::terminate(); + + return result.str().str(); +} + } // namespace clice diff --git a/src/Basic/URI.cpp b/src/Basic/URI.cpp deleted file mode 100644 index ff0dc912..00000000 --- a/src/Basic/URI.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include - -#include -#include - -namespace clice { - -namespace { - -/// returns true if the scheme is valid according to RFC 3986. -bool isValidScheme(llvm::StringRef scheme) { - if(scheme.empty()) { - return false; - } - - if(!llvm::isAlpha(scheme[0])) { - return false; - } - - return llvm::all_of(llvm::drop_begin(scheme), [](char C) { - return llvm::isAlnum(C) || C == '+' || C == '.' || C == '-'; - }); -} - -/// decodes a string according to percent-encoding, e.g., "a%20b" -> "a b". -static std::string decodePercent(llvm::StringRef content) { - std::string result; - for(auto iter = content.begin(), sent = content.end(); iter != sent; ++iter) { - auto c = *iter; - if(c == '%' && iter + 2 < sent) { - auto m = *(iter + 1); - auto n = *(iter + 2); - if(llvm::isHexDigit(m) && llvm::isHexDigit(n)) { - result += llvm::hexFromNibbles(m, n); - iter += 2; - continue; - } - } - result += c; - } - return result; -} - -} // namespace - -URI URI::from(llvm::StringRef file) { - if(!path::is_absolute(file)) { - std::terminate(); - } - - llvm::SmallString<128> path; - - for(auto c: file) { - if(c == '\\') { - path.push_back('/'); - } else if(std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '/') { - path.push_back(c); - } else { - path.push_back('%'); - path.push_back(llvm::hexdigit(c >> 4)); - path.push_back(llvm::hexdigit(c & 0xF)); - } - } - - return URI("file", "", path); -} - -llvm::Expected URI::parse(llvm::StringRef content) { - URI result("", "", ""); - llvm::StringRef uri = content; - auto pos = uri.find(':'); - if(pos == llvm::StringRef::npos) { - return error("scheme is missing in URI: {}", content); - } else { - result.m_scheme = uri.substr(0, pos); - if(!isValidScheme(result.m_scheme)) { - return error("invalid scheme in URI: {}", content); - } - uri = uri.substr(pos + 1); - } - - if(uri.consume_front("//")) { - pos = uri.find('/'); - result.m_authority = uri.substr(0, pos); - uri = uri.substr(pos); - } - - result.m_body = decodePercent(uri); - - return result; -} - -std::string URI::resolve(llvm::StringRef content) { - auto uri = parse(content); - if(!uri) { - std::terminate(); - } - llvm::SmallString<128> result; - if(auto err = fs::real_path(uri->body(), result)) { - std::terminate(); - } - return result.str().str(); -} - -} // namespace clice diff --git a/src/Feature/CodeCompletion.cpp b/src/Feature/CodeCompletion.cpp index 0435f8d0..6d3a040b 100644 --- a/src/Feature/CodeCompletion.cpp +++ b/src/Feature/CodeCompletion.cpp @@ -1,5 +1,4 @@ -#include -#include +#include #include #include diff --git a/src/Feature/FoldingRange.cpp b/src/Feature/FoldingRange.cpp index 14ff601d..4fb1ef1a 100644 --- a/src/Feature/FoldingRange.cpp +++ b/src/Feature/FoldingRange.cpp @@ -12,41 +12,48 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitor; + /// The converter used to adapt LSP protocol. + const SourceConverter& cvtr; + /// The source manager of given AST. - clang::SourceManager* src; + clang::SourceManager& src; /// Token buffer of given AST. - clang::syntax::TokenBuffer* tkbuf; + clang::syntax::TokenBuffer& tkbuf; /// The result of folding ranges. proto::FoldingRangeResult result; /// Do not produce folding ranges if either range ends is not within the main file. bool needFilter(clang::SourceLocation loc) { - return loc.isInvalid() || !src->isInMainFile(loc); + return loc.isInvalid() || !src.isInMainFile(loc); } /// Get last column of previous line of a location. clang::SourceLocation prevLineLastColOf(clang::SourceLocation loc) { - return src->translateLineCol(src->getMainFileID(), - src->getPresumedLineNumber(loc) - 1, - std::numeric_limits::max()); + return src.translateLineCol(src.getMainFileID(), + src.getPresumedLineNumber(loc) - 1, + std::numeric_limits::max()); } /// Collect source range as a folding range. - void collect(const clang::SourceRange sr) { - // - 1: convert 1-1 based LSP location to 0-0 based LSP location. - proto::FoldingRange Range; - Range.startLine = src->getPresumedLineNumber(sr.getBegin()) - 1; - Range.endLine = src->getPresumedLineNumber(sr.getEnd()) - 1; + void collect(const clang::SourceRange sr, + proto::FoldingRangeKind kind = proto::FoldingRangeKind::Region) { + auto startLine = src.getPresumedLineNumber(sr.getBegin()) - 1; + auto endLine = src.getPresumedLineNumber(sr.getEnd()) - 1; // Skip ranges on a single line. - if(Range.startLine >= Range.endLine) + if(startLine >= endLine) return; - Range.startCharacter = src->getPresumedColumnNumber(sr.getBegin()) - 1; - Range.endCharacter = src->getPresumedColumnNumber(sr.getEnd()) - 1; - result.push_back(Range); + auto range = cvtr.toRange(sr, src); + result.push_back({ + .startLine = range.start.line, + .endLine = range.end.line, + .startCharacter = range.start.character, + .endCharacter = range.end.character, + .kind = kind, + }); } bool TraverseNamespaceDecl(clang::NamespaceDecl* decl) { @@ -57,7 +64,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorexpandedTokens(decl->getSourceRange()); + auto tks = tkbuf.expandedTokens(decl->getSourceRange()); // Find first '{' in namespace declaration. auto shrink = tks.drop_until([](const clang::syntax::Token& tk) -> bool { @@ -70,7 +77,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorexpandedTokens(decl->getSourceRange()); + auto tks = tkbuf.expandedTokens(decl->getSourceRange()); auto shrink = tks.drop_until([](const clang::syntax::Token& tk) -> bool { return tk.kind() == clang::tok::TokenKind::l_square; @@ -110,20 +117,19 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorexpandedTokens(decl->getSourceRange()); + auto tks = tkbuf.expandedTokens(decl->getSourceRange()); auto tryCollectRegion = [this](clang::SourceLocation ll, clang::SourceLocation lr) { // Skip continous access control keywords. - if(src->getPresumedLineNumber(ll) == src->getPresumedLineNumber(lr)) + if(src.getPresumedLineNumber(ll) == src.getPresumedLineNumber(lr)) return; collect({ll, prevLineLastColOf(lr)}); }; // If there is no access control blocks, return. tks = tks.drop_until(is_accctrl); - if(tks.empty()) { + if(tks.empty()) return; - } auto [_, rb] = decl->getBraceRange(); tks = tks.drop_front(); // Move to ':' after private/public/protected @@ -150,6 +156,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorgetBraceRange(); + auto name = decl->getName(); collect({lb.getLocWithOffset(1), prevLineLastColOf(rb)}); if(auto cxd = llvm::dyn_cast(decl); @@ -161,7 +168,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorexpandedTokens({left, right}); + auto tks = tkbuf.expandedTokens({left, right}); tks = tks.drop_until([](const auto& tk) { return tk.kind() == clang::tok::l_paren; }); if(tks.empty()) @@ -239,7 +246,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorexpandedTokens(expr->getSourceRange()); + auto tks = tkbuf.expandedTokens(expr->getSourceRange()); if(tks.back().kind() != clang::tok::r_paren) return true; @@ -291,7 +298,7 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitorgetMainFileID()) + if(fileid != src.getMainFileID()) continue; collectConditionMacro(dirc.conditions); @@ -347,16 +354,23 @@ struct FoldingRangeCollector : public clang::RecursiveASTVisitor #include #include diff --git a/src/Server/Document.cpp b/src/Server/Document.cpp index dbd5f0b4..f3031bc3 100644 --- a/src/Server/Document.cpp +++ b/src/Server/Document.cpp @@ -1,27 +1,28 @@ +#include "Basic/SourceConverter.h" #include "Server/Server.h" namespace clice { async::promise Server::onDidOpen(const proto::DidOpenTextDocumentParams& params) { - auto path = URI::resolve(params.textDocument.uri); + auto path = SourceConverter::toPath(params.textDocument.uri); llvm::StringRef content = params.textDocument.text; co_await scheduler.update(path, content, synchronizer); } async::promise Server::onDidChange(const proto::DidChangeTextDocumentParams& document) { - auto path = URI::resolve(document.textDocument.uri); + auto path = SourceConverter::toPath(document.textDocument.uri); llvm::StringRef content = document.contentChanges[0].text; co_await scheduler.update(path, content, synchronizer); } async::promise Server::onDidSave(const proto::DidSaveTextDocumentParams& document) { - auto path = URI::resolve(document.textDocument.uri); + auto path = SourceConverter::toPath(document.textDocument.uri); /// co_await scheduler.save(path); co_return; } async::promise Server::onDidClose(const proto::DidCloseTextDocumentParams& document) { - auto path = URI::resolve(document.textDocument.uri); + auto path = SourceConverter::toPath(document.textDocument.uri); /// co_await scheduler.close(path); co_return; } diff --git a/src/Server/Feature.cpp b/src/Server/Feature.cpp index 06a2707a..2ada6162 100644 --- a/src/Server/Feature.cpp +++ b/src/Server/Feature.cpp @@ -1,3 +1,4 @@ +#include "Basic/SourceConverter.h" #include "Server/Server.h" namespace clice { @@ -27,9 +28,8 @@ async::promise Server::onFindReferences(json::Value id, co_return; } -async::promise - Server::onPrepareCallHierarchy(json::Value id, - const proto::CallHierarchyPrepareParams& params) { +async::promise Server::onPrepareCallHierarchy( + json::Value id, const proto::CallHierarchyPrepareParams& params) { co_return; } @@ -43,9 +43,8 @@ async::promise Server::onOutgoingCall(json::Value id, co_return; } -async::promise - Server::onPrepareTypeHierarchy(json::Value id, - const proto::TypeHierarchyPrepareParams& params) { +async::promise Server::onPrepareTypeHierarchy( + json::Value id, const proto::TypeHierarchyPrepareParams& params) { co_return; } @@ -89,7 +88,7 @@ async::promise Server::onDocumentSymbol(json::Value id, async::promise Server::onSemanticTokens(json::Value id, const proto::SemanticTokensParams& params) { - auto path = URI::resolve(params.textDocument.uri); + auto path = SourceConverter::toPath(params.textDocument.uri); proto::SemanticTokens result; co_await scheduler.execute(path, [&id, &path, &result](ASTInfo& info) { result = feature::semanticTokens(info, path); diff --git a/src/Server/Lifestyle.cpp b/src/Server/Lifestyle.cpp index 0264cbb5..d755c405 100644 --- a/src/Server/Lifestyle.cpp +++ b/src/Server/Lifestyle.cpp @@ -1,9 +1,10 @@ +#include "Basic/SourceConverter.h" #include "Server/Server.h" namespace clice { async::promise Server::onInitialize(json::Value id, const proto::InitializeParams& params) { - auto workplace = URI::resolve(params.workspaceFolders[0].uri); + auto workplace = SourceConverter::toPath(params.workspaceFolders[0].uri); config::init(workplace); if(!params.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration) { diff --git a/src/Server/Workplace.cpp b/src/Server/Workplace.cpp index d8475dc0..e10a697b 100644 --- a/src/Server/Workplace.cpp +++ b/src/Server/Workplace.cpp @@ -1,3 +1,4 @@ +#include "Basic/SourceConverter.h" #include "Server/Server.h" namespace clice { @@ -10,7 +11,7 @@ async::promise<> Server::onDidChangeWatchedFiles(const proto::DidChangeWatchedFi } case proto::FileChangeType::Changed: { - auto path = URI::resolve(event.uri); + auto path = SourceConverter::toPath(event.uri); synchronizer.sync(path); break; } diff --git a/unittests/Basic/SourceCode.cpp b/unittests/Basic/SourceCode.cpp deleted file mode 100644 index ee2151f3..00000000 --- a/unittests/Basic/SourceCode.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "../Test.h" -#include "Basic/SourceCode.h" - -namespace clice { - -namespace { - -TEST(SourceCode, Remeasure) { - EXPECT_EQ(remeasure("", proto::PositionEncodingKind::UTF8), 0); - EXPECT_EQ(remeasure("ascii", proto::PositionEncodingKind::UTF8), 5); - - EXPECT_EQ(remeasure("↓", proto::PositionEncodingKind::UTF16), 1); - EXPECT_EQ(remeasure("¥", proto::PositionEncodingKind::UTF16), 1); - - EXPECT_EQ(remeasure("😂", proto::PositionEncodingKind::UTF16), 2); - EXPECT_EQ(remeasure("😂", proto::PositionEncodingKind::UTF32), 1); -} - -} // namespace - -} // namespace clice - diff --git a/unittests/Basic/SourceConverter.cpp b/unittests/Basic/SourceConverter.cpp new file mode 100644 index 00000000..70854461 --- /dev/null +++ b/unittests/Basic/SourceConverter.cpp @@ -0,0 +1,93 @@ +#include "../Test.h" +#include "Basic/SourceConverter.h" + +namespace clice { + +namespace { + +TEST(SourceConverter, Remeasure) { + using SC = SourceConverter; + + SourceConverter utf8{proto::PositionEncodingKind::UTF8}; + + EXPECT_EQ(utf8.remeasure(""), 0); + EXPECT_EQ(utf8.remeasure("ascii"), 5); + + SourceConverter utf16{proto::PositionEncodingKind::UTF16}; + + EXPECT_EQ(utf16.remeasure("↓"), 1); + EXPECT_EQ(utf16.remeasure("¥"), 1); + + SourceConverter utf32{proto::PositionEncodingKind::UTF32}; + EXPECT_EQ(utf8.remeasure("😂"), 4); + EXPECT_EQ(utf16.remeasure("😂"), 2); + EXPECT_EQ(utf32.remeasure("😂"), 1); +} + +TEST(SourceConverter, Position) { + const char* main = "int a /*😂*/ = 1;$(eof)"; + + Tester txs("main.cpp", main); + txs.run("-std=c++11"); + + auto& src = txs.info.srcMgr(); + auto& tks = txs.info.tokBuf(); + + auto mainid = src.getMainFileID(); + auto tokens = + tks.expandedTokens({src.getLocForStartOfFile(mainid), src.getLocForEndOfFile(mainid)}); + + auto eof = tokens.back().endLocation(); + txs.expect("eof", eof); + + { + SourceConverter cvtr{proto::PositionEncodingKind::UTF8}; + auto pos = cvtr.toPosition(eof, src); + EXPECT_EQ(pos.line, 0); + EXPECT_EQ(pos.character, 19); + } + + { + SourceConverter cvtr{proto::PositionEncodingKind::UTF16}; + auto pos = cvtr.toPosition(eof, src); + EXPECT_EQ(pos.line, 0); + EXPECT_EQ(pos.character, 19); + } + + { + SourceConverter cvtr{proto::PositionEncodingKind::UTF32}; + auto pos = cvtr.toPosition(eof, src); + EXPECT_EQ(pos.line, 0); + EXPECT_EQ(pos.character, 19); + } +} + +TEST(SourceConverter, UriAndFsPath) { + using SC = SourceConverter; + + // It must be a existed file. +#ifdef unix + const char* fspath[] = {"/dev/null"}; + const char* uri[] = {"file:///dev/null"}; +#else + const char* fspath[] = {}; + const char* uri[] = {}; +#endif + EXPECT_EQ(std::size(fspath), std::size(uri)); + + for(int i = 0; i < std::size(fspath); ++i) { + auto fspath_ = fspath[i]; + auto uri_ = uri[i]; + + auto uri1 = SC::toURI(fspath_); + EXPECT_EQ(uri1, uri_); + + auto fspath2 = SC::toPath(uri_); + EXPECT_EQ(fspath2, fspath_); + } +} + +} // namespace + +} // namespace clice + diff --git a/unittests/Basic/URI.cpp b/unittests/Basic/URI.cpp deleted file mode 100644 index 7b47ccf5..00000000 --- a/unittests/Basic/URI.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include - -namespace clice { - -namespace { - -TEST(URI, Basic) { - URI uri("https", "reviews.llvm.org", "/D41946"); - - EXPECT_EQ(uri.scheme(), "https"); - EXPECT_EQ(uri.authority(), "reviews.llvm.org"); - EXPECT_EQ(uri.body(), "/D41946"); -} - -TEST(URI, Copy) { - URI uri1("https", "reviews.llvm.org", "/D41946"); - URI uri2(uri1); - - EXPECT_EQ(uri2.scheme(), "https"); - EXPECT_EQ(uri2.authority(), "reviews.llvm.org"); - EXPECT_EQ(uri2.body(), "/D41946"); -} - -TEST(URI, Eq) { - URI uri1("https", "reviews.llvm.org", "/D41946"); - URI uri2("https", "reviews.llvm.org", "/D41946"); - URI uri3("http", "example.com", "/index.html"); - - EXPECT_TRUE(uri1 == uri2); - EXPECT_FALSE(uri1 == uri3); -} - -TEST(URI, File) { - auto uri = URI::from("/home/user/file.txt"); - EXPECT_EQ(uri.scheme(), "file"); - EXPECT_EQ(uri.authority(), ""); - EXPECT_EQ(uri.body(), "/home/user/file.txt"); - EXPECT_EQ(uri.toString(), "file:///home/user/file.txt"); -} - -TEST(URI, Parse) { - auto expectedUri = URI::parse("https://reviews.llvm.org/D41946"); - ASSERT_TRUE(bool(expectedUri)); - - URI& uri = expectedUri.get(); - EXPECT_EQ(uri.scheme(), "https"); - EXPECT_EQ(uri.authority(), "reviews.llvm.org"); - EXPECT_EQ(uri.body(), "/D41946"); -} - -} // namespace - -} // namespace clice - diff --git a/unittests/Feature/FoldingRange.cpp b/unittests/Feature/FoldingRange.cpp index 2851ecab..a5a172d5 100644 --- a/unittests/Feature/FoldingRange.cpp +++ b/unittests/Feature/FoldingRange.cpp @@ -56,8 +56,9 @@ namespace ugly auto& info = txs.info; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -105,8 +106,9 @@ enum class _2 {$(3) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -161,8 +163,9 @@ void f() {$(9) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -222,8 +225,9 @@ struct _4; return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -270,8 +274,9 @@ auto s = [$(5) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -324,8 +329,9 @@ auto _3 = []($(5) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -379,8 +385,9 @@ void d($(5) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -428,8 +435,9 @@ void n() {$(5) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -474,8 +482,9 @@ int main() {$(1) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -526,8 +535,9 @@ int main () {$(1) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -575,8 +585,9 @@ L l2 = {$(3) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -628,8 +639,9 @@ public:$(12) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); @@ -679,8 +691,9 @@ $(2) return fromLspLocation(src, fr); }; + SourceConverter converter{proto::PositionEncodingKind::UTF8}; FoldingRangeParams param; - auto res = feature::foldingRange(param, info); + auto res = feature::foldingRange(param, info, converter); // dbg(res); diff --git a/unittests/Index/IndexTester.h b/unittests/Index/IndexTester.h index 71065615..4343120d 100644 --- a/unittests/Index/IndexTester.h +++ b/unittests/Index/IndexTester.h @@ -1,6 +1,6 @@ #include "../Test.h" #include "Index/SymbolIndex.h" -#include "Basic/SourceCode.h" +#include "Basic/SourceConverter.h" namespace clice {