some update.

This commit is contained in:
ykiko
2024-07-04 22:42:29 +08:00
parent bc60ed69b4
commit d0a354ec8b
14 changed files with 471 additions and 199 deletions

View File

@@ -22,7 +22,8 @@ TODO:
FIXME:
- 为每个 Token 都提供语义高亮: https://github.com/clangd/clangd/issues/1115
- no-self-contained: https://github.com/clangd/clangd/issues/45
- 提供更好的模板代码补全需要在索引文件里面记录模板实例化https://github.com/clangd/clangd/issues/443然后补全的时候获取`.`号前面的表达式类型,之后再这里查找
- 支持模块https://github.com/clangd/clangd/issues/1293
## 性能优化
TODO: 寻找核心优化点
@@ -43,6 +44,7 @@ TODO:
- [支持离线索引](https://github.com/clangd/clangd/issues/587)
- 另外请见 https://discourse.llvm.org/t/using-background-and-static-indexes-simultaneously-for-large-codebases/3706/7
## 详细的支持功能列表
需要具体到哪些功能要做

View File

@@ -27,52 +27,52 @@
### Language Features
首先对 LSP 支持的功能进行概览LSP 3.17 currently:
首先对 LSP 支持的功能进行概览: LSP 3.17 currently:
- Goto Declaration跳转到声明
- Goto Definition跳转到定义
- Goto Type Definition跳转到类型定义
- Goto Implementation跳转到实现
- Find References查找所有引用
- Prepare Call Hierarchy没搞懂
- Call Hierarchy Incoming Calls没搞懂
- Call Hierarchy Outgoing Calls没搞懂
- Prepare Type Hierarchy没搞懂
- Type Hierarchy Supertypes没搞懂
- Type Hierarchy Subtypes没搞懂
- Document Highlights没搞懂
- Document Link:没搞懂
- Document Link Resolve没搞懂
- Hover悬停提示
- Code Lens没搞懂
- Code Lens Refresh Request没搞懂
- Folding Range把某段代码折叠起来
- Selection Range没搞懂
- Document Symbols:没搞懂
- Semantic Tokens用于语义高亮
- Inline Value没搞懂
- Inline Value Refresh没搞懂
- Inlay Hint用于内嵌提示,比如函数参数或者`auto`的类型
- Inlay Hint Resolve没搞懂
- Inlay Hint Refresh刷新内嵌提示
- Monikers没搞懂
- Completion代码补全
- Completion Item Resolve解决重载函数的代码补全
- PublishDiagnostics Notification发出诊断信息
- Pull Diagnostics没搞懂
- Signature Help Request请求函数签名信息
- Code Action重构等操作(还有那个 quick fix
- Code Action Resolve没搞懂
- Document Color没搞懂
- Color Presentation没搞懂
- Document Formatting格式化
- Document Range Formatting只格式化某个部分
- Document on Type Formatting没搞懂
- Rename重命名
- Prepare Rename解决重命名
- Linked Editing Range没搞懂
- Goto Declaration: 跳转到声明
- Goto Definition: 跳转到定义
- Goto Type Definition: 跳转到类型定义
- Goto Implementation: 跳转到实现
- Find References: 查找所有引用
- Prepare Call Hierarchy: 没搞懂
- Call Hierarchy Incoming Calls: 没搞懂
- Call Hierarchy Outgoing Calls: 没搞懂
- Prepare Type Hierarchy: 没搞懂
- Type Hierarchy Supertypes: 没搞懂
- Type Hierarchy Subtypes: 没搞懂
- Document Highlights: 没搞懂
- Document Link: 用来处理文件链接跳转,例如点击一个文件名跳转到文件
- Document Link Resolve: 没搞懂
- Hover: 悬停提示
- Code Lens: 没搞懂
- Code Lens Refresh Request: 没搞懂
- Folding Range: 把某段代码折叠起来
- Selection Range: 没搞懂
- Document Symbols: 列出文档中的所有符号,可以用于 vscode 左边那个 Outline
- Semantic Tokens: 用于语义高亮
- Inline Value: 没搞懂
- Inline Value Refresh: 没搞懂
- Inlay Hint: 用于内嵌提示,比如函数参数或者`auto`的类型
- Inlay Hint Resolve: 没搞懂
- Inlay Hint Refresh: 刷新内嵌提示
- Monikers: 没搞懂
- Completion: 代码补全
- Completion Item Resolve: 解决重载函数的代码补全
- PublishDiagnostics Notification: 发出诊断信息
- Pull Diagnostics: 没搞懂
- Signature Help Request: 请求函数签名信息
- Code Action: 重构等操作(还有那个 quick fix
- Code Action Resolve: 没搞懂
- Document Color: 没搞懂
- Color Presentation: 没搞懂
- Document Formatting: 格式化
- Document Range Formatting: 只格式化某个部分
- Document on Type Formatting: 没搞懂
- Rename: 重命名
- Prepare Rename: 解决重命名
- Linked Editing Range: 没搞懂
这些任务从最终实现的角度来说可以主要分成三种
这些任务从最终实现的角度来说可以主要分成三种:
1. CodeCompletion 这个需要利用 CodeCompletionConsumer 调用 Clang 提供的接口来实现然而我们实际上可以做一些更加复杂的分析clangd 目前没有做)。比如判断当前的是不是在 Template 语境下从而决定补全`sizeof`的时候要不要补全`...`。在补全成员的时候,似乎我们也可以获取`expr.f`中的父对象的类型,从而根据它的类型来做一些补全。有待进一步研究。
2. Semantic Tokens 等基于当前 AST 的操作,则是遍历 AST 渲染 Token 即可。
3. 剩下很多的,例如 Find References 等等等查询功能,都是在已经索引好的文件中进行查询,不需要对语法树进行什么改动。

View File

@@ -1,3 +1,5 @@
#pragma once
#include "clang/AST/RecursiveASTVisitor.h"
#include <clang/Basic/Diagnostic.h>
#include <clang/Frontend/CompilerInstance.h>
@@ -5,4 +7,25 @@
#include <clang/Frontend/TextDiagnosticPrinter.h>
#include <clang/Sema/Sema.h>
#include <clang/Tooling/CompilationDatabase.h>
#include <clang/Tooling/Syntax/Tokens.h>
#include <clang/Tooling/Syntax/Tokens.h>
namespace clice {
using clang::CompilerInstance;
using clang::CompilerInvocation;
template <typename T>
union uninitialized {
T value;
uninitialized() {}
~uninitialized() { value.~T(); }
template <typename... Args>
auto& construct(Args&&... args) {
return *new (&value) T{std::forward<Args>(args)...};
}
};
} // namespace clice

View File

@@ -0,0 +1,35 @@
#include <Clang/Clang.h>
namespace clice {
class CompileDatabase {
private:
std::unique_ptr<clang::tooling::CompilationDatabase> database;
public:
static auto& instance() {
static CompileDatabase instance;
return instance;
}
void load(std::string_view path) {
std::string error;
database = clang::tooling::CompilationDatabase::loadFromDirectory(path, error);
if(!database) {
llvm::errs() << "Failed to load compilation database. " << error << "\n";
std::terminate();
}
}
auto lookup(std::string_view path) {
auto commands = database->getCompileCommands(path);
llvm::ArrayRef command = commands[0].CommandLine;
std::vector<const char*> args = {command.front().c_str(), "-Xclang", "-no-round-trip-args"};
for(auto& arg: command.drop_front()) {
args.push_back(arg.c_str());
}
return args;
}
};
} // namespace clice

View File

@@ -0,0 +1,7 @@
#include "Clang.h"
namespace clice {
inline auto createCompilerInvocation() {}
} // namespace clice

View File

@@ -0,0 +1,9 @@
#include "Clang.h"
namespace clice {
class Diagnostic {};
class DiagnosticConsumer : clang::DiagnosticConsumer {};
} // namespace clice

View File

@@ -1,7 +1,49 @@
#include "Diagnostics.h"
#include "Preamble.h"
#include "CompileDatabase.h"
namespace clice {
class ParsedAST {
private:
using Decl = clang::Decl*;
using PathRef = llvm::StringRef;
using TokenBuffer = clang::syntax::TokenBuffer;
using ASTConsumer = std::unique_ptr<clang::ASTConsumer>;
struct FrontendAction : public clang::ASTFrontendAction {
ASTConsumer CreateASTConsumer(CompilerInstance& instance, PathRef file) override;
};
private:
/// path of translation unit
PathRef path;
/// llvm version
std::string version;
/// headers part of the tu, when a file is loaded, we will build the preamble, the reuse it.
Preamble preamble;
/// some extra info
std::vector<Decl> topLevelDecls;
std::vector<Diagnostic> diagnostics;
/// core members for clang frontend
uninitialized<TokenBuffer> tokens;
clang::SyntaxOnlyAction action;
CompilerInstance instance;
ParsedAST() = default;
public:
std::unique_ptr<ParsedAST> build(std::string_view path,
const std::shared_ptr<CompilerInvocation>& invocation,
const std::shared_ptr<Preamble>& preamble);
auto& Tokens() { return tokens.value; }
auto& Diagnostics() { return diagnostics; }
auto& ASTContext() { return instance.getASTContext(); }
auto& TranslationUnit() { return *instance.getASTContext().getTranslationUnitDecl(); }
};
} // namespace clice

View File

@@ -1,5 +1,10 @@
namespace {
#include "Clang.h"
class Preamble {};
namespace clice{
class Preamble {
public:
};
} // namespace

10
include/LSP/Scheduler.h Normal file
View File

@@ -0,0 +1,10 @@
#include <Clang/ParsedAST.h>
namespace clice {
class Scheduler {
std::mutex mutex;
llvm::StringMap<std::unique_ptr<ParsedAST>> parsedASTs;
};
} // namespace clice

View File

@@ -1,15 +1,22 @@
#include <uv.h>
#include <string_view>
namespace clice {
// global server instance
extern class Server server;
/// core class responsible for starting the server
class Server {
static uv_loop_t* loop;
static uv_pipe_t stdin_pipe;
uv_loop_t* loop;
uv_pipe_t stdin_pipe;
public:
static int Initialize();
static int Exit();
int initialize();
int exit();
public:
void handle_message(std::string_view message);
};
} // namespace clice

53
src/Clang/ParsedAST.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include <Clang/ParsedAST.h>
namespace clice {
std::unique_ptr<ParsedAST> ParsedAST::build(std::string_view path,
const std::shared_ptr<CompilerInvocation>& invocation,
const std::shared_ptr<Preamble>& preamble) {
auto AST = new ParsedAST();
AST->path = path;
// some settings for CompilerInstance
auto& instance = AST->instance;
instance.setInvocation(invocation);
instance.createDiagnostics();
if(!instance.createTarget()) {
llvm::errs() << "Failed to create target\n";
std::terminate();
}
if(auto manager = instance.createFileManager()) {
instance.createSourceManager(*manager);
} else {
llvm::errs() << "Failed to create file manager\n";
std::terminate();
}
instance.createPreprocessor(clang::TranslationUnitKind::TU_Complete);
instance.createASTContext();
// start FrontendAction
const auto& input = instance.getFrontendOpts().Inputs[0];
auto& action = AST->action;
if(!action.BeginSourceFile(instance, input)) {
llvm::errs() << "Failed to begin source file\n";
std::terminate();
}
clang::syntax::TokenCollector collector = {instance.getPreprocessor()};
if(llvm::Error error = action.Execute()) {
llvm::errs() << "Failed to execute action: " << error << "\n";
std::terminate();
}
AST->tokens.construct(std::move(collector).consume());
return std::unique_ptr<ParsedAST>(AST);
}
} // namespace clice

View File

@@ -1,176 +1,170 @@
#include <Clang/Clang.h>
namespace tooling = clang::tooling;
namespace {
std::unique_ptr<tooling::CompilationDatabase> datebase;
auto GetCommands(std::string_view path, std::string_view compile_commands_path)
-> std::vector<tooling::CompileCommand> {
if (!datebase) {
std::string error;
datebase = tooling::CompilationDatabase::loadFromDirectory(
compile_commands_path, error);
auto GetCommands(std::string_view path,
std::string_view compile_commands_path) -> std::vector<tooling::CompileCommand> {
if(!datebase) {
std::string error;
datebase = tooling::CompilationDatabase::loadFromDirectory(compile_commands_path, error);
if (!datebase) {
llvm::errs() << "Failed to load compilation database. " << error << "\n";
std::terminate();
if(!datebase) {
llvm::errs() << "Failed to load compilation database. " << error << "\n";
std::terminate();
}
}
}
return datebase->getCompileCommands(path);
return datebase->getCompileCommands(path);
}
auto createDiagnostic() {
clang::DiagnosticOptions DiagOpts;
clang::DiagnosticOptions DiagOpts;
clang::TextDiagnosticPrinter *DiagClient =
new clang::TextDiagnosticPrinter(llvm::errs(), &DiagOpts);
clang::TextDiagnosticPrinter* DiagClient =
new clang::TextDiagnosticPrinter(llvm::errs(), &DiagOpts);
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> DiagID =
new clang::DiagnosticIDs();
llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> DiagID = new clang::DiagnosticIDs();
return clang::DiagnosticsEngine(DiagID, &DiagOpts, DiagClient);
return clang::DiagnosticsEngine(DiagID, &DiagOpts, DiagClient);
}
auto createInvocation(std::string_view path,
std::string_view compile_commands) {
auto commands = GetCommands(path, compile_commands);
llvm::ArrayRef command = commands[0].CommandLine;
auto createInvocation(std::string_view path, std::string_view compile_commands) {
auto commands = GetCommands(path, compile_commands);
llvm::ArrayRef command = commands[0].CommandLine;
std::vector<const char *> args = {command.front().c_str(), "-Xclang",
"-no-round-trip-args"};
std::vector<const char*> args = {command.front().c_str(), "-Xclang", "-no-round-trip-args"};
for (auto &arg : command.drop_front()) {
args.push_back(arg.c_str());
}
for(auto& arg: command.drop_front()) {
args.push_back(arg.c_str());
}
static auto engine = createDiagnostic();
auto invocation = clang::createInvocation(args);
static auto engine = createDiagnostic();
auto invocation = clang::createInvocation(args);
// set input file
auto &inputs = invocation->getFrontendOpts().Inputs;
inputs.push_back(
clang::FrontendInputFile(path, clang::InputKind{clang::Language::CXX}));
// set input file
auto& inputs = invocation->getFrontendOpts().Inputs;
inputs.push_back(clang::FrontendInputFile(path, clang::InputKind{clang::Language::CXX}));
return invocation;
return invocation;
}
struct DiagnosticConsumer : clang::DiagnosticConsumer {
void BeginSourceFile(const clang::LangOptions &LangOpts,
const clang::Preprocessor *PP) override {}
void BeginSourceFile(const clang::LangOptions& LangOpts,
const clang::Preprocessor* PP) override {}
void EndSourceFile() override {}
void EndSourceFile() override {}
void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic &Info) override {
if (DiagLevel == clang::DiagnosticsEngine::Level::Note) {
return;
void HandleDiagnostic(clang::DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic& Info) override {
if(DiagLevel == clang::DiagnosticsEngine::Level::Note) {
return;
}
llvm::errs() << "Diagnostic: ";
llvm::SmallVector<char> buf;
Info.FormatDiagnostic(buf);
llvm::errs().write(buf.data(), buf.size());
llvm::errs() << "\n";
}
llvm::errs() << "Diagnostic: ";
llvm::SmallVector<char> buf;
Info.FormatDiagnostic(buf);
llvm::errs().write(buf.data(), buf.size());
llvm::errs() << "\n";
}
};
auto createInstance(std::string_view path, std::string_view compile_commands) {
std::unique_ptr<clang::CompilerInstance> instance =
std::make_unique<clang::CompilerInstance>();
std::unique_ptr<clang::CompilerInstance> instance = std::make_unique<clang::CompilerInstance>();
auto invocation = createInvocation(path, compile_commands);
instance->setInvocation(
std::make_shared<clang::CompilerInvocation>(*invocation));
auto invocation = createInvocation(path, compile_commands);
instance->setInvocation(std::make_shared<clang::CompilerInvocation>(*invocation));
instance->createDiagnostics(new DiagnosticConsumer(), true);
instance->createDiagnostics(new DiagnosticConsumer(), true);
if (!instance->createTarget()) {
llvm::errs() << "Failed to create target\n";
std::terminate();
}
if(!instance->createTarget()) {
llvm::errs() << "Failed to create target\n";
std::terminate();
}
if (auto manager = instance->createFileManager()) {
instance->createSourceManager(*manager);
} else {
llvm::errs() << "Failed to create file manager\n";
std::terminate();
}
if(auto manager = instance->createFileManager()) {
instance->createSourceManager(*manager);
} else {
llvm::errs() << "Failed to create file manager\n";
std::terminate();
}
instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete);
instance->createPreprocessor(clang::TranslationUnitKind::TU_Complete);
instance->createASTContext();
instance->createASTContext();
return instance;
return instance;
}
class AST {
clang::FrontendAction *action;
clang::CompilerInstance *instance;
clang::syntax::TokenBuffer *tokens;
clang::FrontendAction* action;
clang::CompilerInstance* instance;
clang::syntax::TokenBuffer* tokens;
private:
AST() = default;
AST() = default;
public:
AST(const AST &) = delete;
AST(const AST&) = delete;
AST(AST &&other) noexcept
: action(other.action), instance(other.instance), tokens(other.tokens) {
other.action = nullptr;
other.instance = nullptr;
other.tokens = nullptr;
}
~AST() {
if (action) {
action->EndSourceFile();
delete action;
delete instance;
delete tokens;
}
}
static AST create(std::string_view path, std::string_view compile_commands) {
AST ast;
ast.instance = createInstance(path, compile_commands).release();
ast.action = new clang::SyntaxOnlyAction();
const auto &input = ast.instance->getFrontendOpts().Inputs[0];
if (!ast.action->BeginSourceFile(*ast.instance, input)) {
llvm::errs() << "Failed to begin source file\n";
std::terminate();
AST(AST&& other) noexcept :
action(other.action), instance(other.instance), tokens(other.tokens) {
other.action = nullptr;
other.instance = nullptr;
other.tokens = nullptr;
}
clang::syntax::TokenCollector collector = {ast.instance->getPreprocessor()};
if (llvm::Error error = ast.action->Execute()) {
llvm::errs() << "Failed to execute action: " << error << "\n";
std::terminate();
~AST() {
if(action) {
action->EndSourceFile();
delete action;
delete instance;
delete tokens;
}
}
ast.tokens = new auto(std::move(collector).consume());
static AST create(std::string_view path, std::string_view compile_commands) {
AST ast;
return ast;
}
ast.instance = createInstance(path, compile_commands).release();
ast.action = new clang::SyntaxOnlyAction();
auto &getASTContext() { return instance->getASTContext(); }
const auto& input = ast.instance->getFrontendOpts().Inputs[0];
auto &getSourceManager() { return instance->getSourceManager(); }
if(!ast.action->BeginSourceFile(*ast.instance, input)) {
llvm::errs() << "Failed to begin source file\n";
std::terminate();
}
auto getTokens() { return tokens->expandedTokens(); }
clang::syntax::TokenCollector collector = {ast.instance->getPreprocessor()};
if(llvm::Error error = ast.action->Execute()) {
llvm::errs() << "Failed to execute action: " << error << "\n";
std::terminate();
}
ast.tokens = new auto(std::move(collector).consume());
return ast;
}
auto& getASTContext() { return instance->getASTContext(); }
auto& getSourceManager() { return instance->getSourceManager(); }
auto getTokens() { return tokens->expandedTokens(); }
};
struct Visitor : clang::RecursiveASTVisitor<Visitor> {
bool VisitTranslationUnitDecl(clang::TranslationUnitDecl *tu) {
tu->dump();
return true;
}
bool VisitTranslationUnitDecl(clang::TranslationUnitDecl* tu) {
tu->dump();
return true;
}
bool VisitCXXMethodDecl(clang::CXXMethodDecl *decl) { return true; }
bool VisitCXXMethodDecl(clang::CXXMethodDecl* decl) { return true; }
};
} // namespace
} // namespace

View File

@@ -1,44 +1,55 @@
#include <simdjson.h>
#include <LSP/Server.h>
namespace clice {
uv_loop_t* Server::loop;
uv_pipe_t Server::stdin_pipe;
Server server;
static void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
if(nread > 0) {
printf("Read: %s\n", buf->base);
} else if(nread < 0) {
if(nread != UV_EOF) {
fprintf(stderr, "Read error: %s\n", uv_err_name(nread));
}
if(!uv_is_closing((uv_handle_t*)&pipe)) {
uv_close((uv_handle_t*)&pipe, NULL);
}
}
if(buf->base) {
free(buf->base);
}
}
static void alloc_buffer(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = (char*)malloc(suggested_size);
buf->len = suggested_size;
}
int Server::Initialize() {
int Server::initialize() {
loop = uv_default_loop();
// initialize pipe and bind to stdin
uv_pipe_init(loop, &stdin_pipe, 0);
uv_pipe_open(&stdin_pipe, 0);
auto alloc_buffer = [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) {
buf->base = (char*)std::malloc(suggested_size);
buf->len = suggested_size;
};
auto on_read = [](uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
if(nread > 0) {
server.handle_message(std::string_view(buf->base, nread));
} else if(nread < 0) {
// TODO: error handling
}
std::free(buf->base);
};
uv_read_start((uv_stream_t*)&stdin_pipe, alloc_buffer, on_read);
// start the event loop
return uv_run(loop, UV_RUN_DEFAULT);
}
int Server::Exit() {
uv_close((uv_handle_t*)&stdin_pipe, NULL);
return uv_run(loop, UV_RUN_DEFAULT);
int Server::exit() {
// close the pipe
// uv_close((uv_handle_t*)&stdin_pipe, NULL);
// stop the event loop
uv_stop(loop);
return 0;
}
void Server::handle_message(std::string_view message) {
const char* json = R"({
"name": "John",
"age": 30,
"city": "New York"
})";
simdjson::dom::parser parser;
} // namespace clice
} // namespace clice

View File

@@ -1,8 +1,82 @@
#include <LSP/Server.h>
#include <coroutine>
#include <thread>
#include <iostream>
uv_loop_t* loop = nullptr;
struct Task {
struct promise_type {
Task get_return_object() { return Task{Handle::from_promise(*this)}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
struct Handle {
std::coroutine_handle<promise_type> coro;
static Handle from_promise(promise_type& p) {
return Handle{std::coroutine_handle<promise_type>::from_promise(p)};
}
~Handle() {
if(coro)
coro.destroy();
}
};
Handle h;
};
struct uv_awaitable {
uv_work_t req;
std::function<void()> work_fn;
std::coroutine_handle<> handle;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> handle) {
this->handle = handle;
req.data = this;
std::cout << "Scheduling work..." << std::endl;
uv_queue_work(
loop,
&req,
[](uv_work_t* req) {
auto* self = static_cast<uv_awaitable*>(req->data);
self->work_fn();
},
[](uv_work_t* req, int status) {
auto& handle = static_cast<uv_awaitable*>(req->data)->handle;
if(handle.done()) {
std::cout << "Work done" << std::endl;
} else {
handle.resume();
}
});
}
auto await_resume() { return 1; }
};
Task async_factorial(int n) {
long result = 1;
auto s = co_await uv_awaitable{.work_fn = [&result, n] {
for(int i = 1; i <= n; ++i) {
result *= i;
}
}};
std::cout << "Factorial of " << n << " is " << result << std::endl;
}
int main() {
using namespace clice;
Server::Initialize();
Server::Exit();
auto& server = clice::server;
server.initialize();
return 0;
}