♻️ 重构

This commit is contained in:
csh
2025-12-05 21:03:18 +08:00
parent 4c2e242920
commit 549f1d1b0a
161 changed files with 26415 additions and 28009 deletions
+41
View File
@@ -0,0 +1,41 @@
#include "bootstrap.hpp"
#include <spdlog/spdlog.h>
#include "../utils/args_parser.hpp"
namespace lsp::manager::bootstrap
{
void InitializeManagerHub(
ManagerHub& hub,
scheduler::AsyncExecutor& async_executor,
const std::vector<std::string>& system_lib_paths)
{
spdlog::info("Initializing manager hub...");
for (const auto& path : system_lib_paths)
{
std::string task_name = fmt::format("Load system library: {}", path);
async_executor.Submit(
task_name,
[&hub, path]() -> std::optional<std::string> {
try
{
hub.symbols().LoadSystemLibrary(path);
return fmt::format("Loaded system library: {}", path);
}
catch (const std::exception& e)
{
spdlog::error("Failed to load system library {}: {}", path, e.what());
throw;
}
},
[path](const std::optional<std::string>& result, bool cancelled) {
if (cancelled)
spdlog::info("System library load task cancelled: {}", path);
else if (result)
spdlog::info("{}", *result);
});
}
spdlog::info("Manager hub initialized, system library loading in background");
}
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include "../manager/manager_hub.hpp"
#include "../scheduler/async_executor.hpp"
namespace lsp::manager::bootstrap
{
void InitializeManagerHub(
ManagerHub& hub,
scheduler::AsyncExecutor& async_executor,
const std::vector<std::string>& system_lib_paths);
}
@@ -0,0 +1,60 @@
#include "text_document.hpp"
#include <spdlog/spdlog.h>
#include "../../utils/text_coordinates.hpp"
namespace lsp::manager::detail
{
TextDocument::TextDocument(const protocol::TextDocumentItem& item) : item_(item) {}
void TextDocument::ApplyChange(const protocol::TextDocumentContentChangeEvent& change)
{
if (IsFullDocumentUpdate(change, item_.text))
{
item_.text = change.text;
spdlog::debug("Full Document update for: {}", item_.uri);
}
else
{
protocol::uinteger start_offset = utils::text_coordinates::ToOffset(change.range.start, item_.text);
protocol::uinteger end_offset = utils::text_coordinates::ToOffset(change.range.end, item_.text);
item_.text.replace(start_offset, end_offset - start_offset, change.text);
spdlog::debug("Incremental update for: {}", item_.uri);
}
}
void TextDocument::SetVersion(protocol::integer version)
{
item_.version = version;
}
const protocol::string& TextDocument::GetContent() const
{
return item_.text;
}
const protocol::DocumentUri& TextDocument::GetUri() const
{
return item_.uri;
}
protocol::integer TextDocument::GetVersion() const
{
return item_.version;
}
bool TextDocument::IsFullDocumentUpdate(const protocol::TextDocumentContentChangeEvent& change, const protocol::string& current_content)
{
if (change.range.start.line == 0 && change.range.start.character == 0)
{
protocol::uinteger line_count = 0;
for (size_t i = 0; i < current_content.length(); ++i)
{
if (current_content[i] == '\n')
line_count++;
}
if (change.range.end.line >= line_count)
return true;
}
return false;
}
}
@@ -0,0 +1,27 @@
#pragma once
#include "../../protocol/protocol.hpp"
extern "C" {
#include <tree_sitter/api.h>
}
namespace lsp::manager::detail
{
class TextDocument
{
public:
explicit TextDocument(const protocol::TextDocumentItem& item);
void ApplyChange(const protocol::TextDocumentContentChangeEvent& change);
void SetVersion(protocol::integer version);
const protocol::string& GetContent() const;
const protocol::DocumentUri& GetUri() const;
protocol::integer GetVersion() const;
private:
static bool IsFullDocumentUpdate(const protocol::TextDocumentContentChangeEvent& change, const protocol::string& current_content);
protocol::TextDocumentItem item_;
};
}
+92
View File
@@ -0,0 +1,92 @@
#include "document.hpp"
#include <spdlog/spdlog.h>
#include "./events.hpp"
namespace lsp::manager
{
Document::Document(EventBus& event_bus) : event_bus_(event_bus) {}
Document::~Document() = default;
void Document::OpenDocument(const protocol::DidOpenTextDocumentParams& params)
{
{
std::unique_lock<std::shared_mutex> lock(mutex_);
documents_.emplace(params.textDocument.uri, detail::TextDocument(params.textDocument));
}
event_bus_.Publish(events::DocumentOpened{ params });
spdlog::debug("Document opened: {}", params.textDocument.uri);
}
void Document::UpdateDocument(const protocol::DidChangeTextDocumentParams& params)
{
protocol::string content;
{
std::unique_lock<std::shared_mutex> lock(mutex_);
auto it = documents_.find(params.textDocument.uri);
if (it == documents_.end())
{
spdlog::warn("Attempting to update non-existent document: {}", params.textDocument.uri);
return;
}
for (const auto& change : params.contentChanges)
it->second.ApplyChange(change);
if (params.textDocument.version)
it->second.SetVersion(params.textDocument.version);
content = it->second.GetContent();
}
event_bus_.Publish(events::DocumentChanged{
.uri = params.textDocument.uri,
.version = params.textDocument.version,
.changes = params.contentChanges,
.content = std::move(content) });
spdlog::debug("Document updated: {}", params.textDocument.uri);
}
void Document::CloseDocument(const protocol::DidCloseTextDocumentParams& params)
{
{
std::unique_lock<std::shared_mutex> lock(mutex_);
documents_.erase(params.textDocument.uri);
}
event_bus_.Publish(events::DocumentClosed{ params });
spdlog::debug("Document closed: {}", params.textDocument.uri);
}
std::optional<protocol::string> Document::GetContent(const protocol::DocumentUri& uri) const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
auto it = documents_.find(uri);
if (it != documents_.end())
return it->second.GetContent();
return std::nullopt;
}
std::optional<protocol::integer> Document::GetVersion(const protocol::DocumentUri& uri) const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
auto it = documents_.find(uri);
if (it != documents_.end())
return it->second.GetVersion();
return std::nullopt;
}
std::vector<protocol::DocumentUri> Document::GetAllDocumentUris() const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
std::vector<protocol::DocumentUri> uris;
uris.reserve(documents_.size());
for (const auto& [uri, _] : documents_)
uris.push_back(uri);
return uris;
}
void Document::Clear()
{
std::unique_lock<std::shared_mutex> lock(mutex_);
documents_.clear();
}
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <shared_mutex>
#include "./event_bus.hpp"
#include "./detail/text_document.hpp"
#include "../protocol/protocol.hpp"
namespace lsp::manager
{
class Document
{
public:
explicit Document(EventBus& event_bus);
~Document();
void OpenDocument(const protocol::DidOpenTextDocumentParams& params);
void UpdateDocument(const protocol::DidChangeTextDocumentParams& params);
void CloseDocument(const protocol::DidCloseTextDocumentParams& params);
std::optional<protocol::string> GetContent(const protocol::DocumentUri& uri) const;
std::optional<protocol::integer> GetVersion(const protocol::DocumentUri& uri) const;
std::vector<protocol::DocumentUri> GetAllDocumentUris() const;
void Clear();
private:
mutable std::shared_mutex mutex_;
std::unordered_map<protocol::DocumentUri, detail::TextDocument> documents_;
EventBus& event_bus_;
};
}
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include <functional>
#include <mutex>
#include <shared_mutex>
#include <typeindex>
#include <unordered_map>
#include <vector>
namespace lsp::manager
{
class EventBus
{
public:
template<typename EventType>
using Handler = std::function<void(const EventType&)>;
template<typename EventType>
void Subscribe(Handler<EventType> handler)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
handlers_[std::type_index(typeid(EventType))].push_back(
[handler](const void* event) {
handler(*static_cast<const EventType*>(event));
});
}
template<typename EventType>
void Publish(const EventType& event) const
{
std::vector<std::function<void(const void*)>> local_handlers;
{
std::shared_lock<std::shared_mutex> lock(mutex_);
auto it = handlers_.find(std::type_index(typeid(EventType)));
if (it != handlers_.end())
local_handlers = it->second;
}
for (const auto& handler : local_handlers)
handler(&event);
}
template<typename EventType>
void Unsubscribe(std::type_index type)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
handlers_.erase(type);
}
size_t GetSubscriberCount() const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
size_t count = 0;
for (const auto& [type, handlers] : handlers_)
count += handlers.size();
return count;
}
private:
mutable std::shared_mutex mutex_;
std::unordered_map<std::type_index, std::vector<std::function<void(const void*)>>> handlers_;
};
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "../protocol/protocol.hpp"
extern "C" {
#include <tree_sitter/api.h>
}
namespace lsp::manager::events
{
using DocumentOpened = protocol::DidOpenTextDocumentParams;
using DocumentClosed = protocol::DidCloseTextDocumentParams;
struct DocumentChanged
{
protocol::DocumentUri uri;
protocol::integer version;
std::vector<protocol::TextDocumentContentChangeEvent> changes;
protocol::string content;
};
struct DocumentParsed
{
protocol::TextDocumentItem item;
TSTree* tree;
};
struct DocumentReparsed
{
protocol::TextDocumentItem item;
TSTree* tree;
};
}
+29
View File
@@ -0,0 +1,29 @@
#include "manager_hub.hpp"
#include <algorithm>
#include "../utils/text_coordinates.hpp"
namespace lsp::manager
{
ManagerHub::ManagerHub() : event_bus_(),
documents_(event_bus_),
parser_(event_bus_),
symbols_(event_bus_)
{
}
ManagerHub::~ManagerHub() = default;
void ManagerHub::Initialize()
{
documents_.Clear();
parser_.Clear();
}
void ManagerHub::Shutdown()
{
documents_.Clear();
parser_.Clear();
}
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <optional>
#include <string>
#include "../manager/event_bus.hpp"
#include "../manager/document.hpp"
#include "../manager/parser.hpp"
#include "../manager/symbol.hpp"
#include "../protocol/protocol.hpp"
namespace lsp::manager
{
class ManagerHub
{
public:
ManagerHub();
~ManagerHub();
void Initialize();
void Shutdown();
Document& documents() { return documents_; }
Parser& parser() { return parser_; }
Symbol& symbols() { return symbols_; }
private:
EventBus event_bus_;
Document documents_;
Parser parser_;
Symbol symbols_;
};
}
+190
View File
@@ -0,0 +1,190 @@
#include "parser.hpp"
#include <spdlog/spdlog.h>
#include "../utils/text_coordinates.hpp"
namespace lsp::manager
{
TreeSitter::TreeSitter()
{
parser_ = ts_parser_new();
if (!parser_)
throw std::runtime_error("Failed to create tree-sitter parser");
}
TreeSitter::~TreeSitter()
{
if (parser_)
ts_parser_delete(parser_);
}
TreeSitter::TreeSitter(TreeSitter&& other) noexcept : parser_(other.parser_)
{
other.parser_ = nullptr;
}
bool TreeSitter::SetLanguage(const TSLanguage* language)
{
if (!parser_)
return false;
return ts_parser_set_language(parser_, language);
}
TSTree* TreeSitter::Parse(const char* content, size_t length, TSTree* old_tree)
{
if (!parser_)
return nullptr;
return ts_parser_parse_string(parser_, old_tree, content, length);
}
TSParser* TreeSitter::GetRawParser() const
{
return parser_;
}
SyntaxTree::SyntaxTree(TSTree* tree) : tree_(tree, ts_tree_delete) {}
SyntaxTree::~SyntaxTree() = default;
TSTree* SyntaxTree::Get() const
{
return tree_.get();
}
void SyntaxTree::ApplyEdit(
const protocol::TextDocumentContentChangeEvent& change,
const protocol::string& content)
{
if (!tree_)
return;
protocol::uinteger start_offset = utils::text_coordinates::ToOffset(change.range.start, content);
protocol::uinteger end_offset = utils::text_coordinates::ToOffset(change.range.end, content);
TSInputEdit edit{};
edit.start_byte = start_offset;
edit.old_end_byte = end_offset;
edit.new_end_byte = start_offset + change.text.length();
edit.start_point = utils::text_coordinates::ToPoint(change.range.start);
edit.old_end_point = utils::text_coordinates::ToPoint(change.range.end);
edit.new_end_point = utils::text_coordinates::CalculateEndPoint(change.text, edit.start_point);
ts_tree_edit(tree_.get(), &edit);
}
TSNode SyntaxTree::GetRootNode() const
{
return tree_ ? ts_tree_root_node(tree_.get()) : TSNode{};
}
Parser::Parser(EventBus& event_bus) : event_bus_(event_bus)
{
if (parser_.SetLanguage(tree_sitter_tsf()))
spdlog::info("Set tree-sitter-tsf successfully");
else
spdlog::error("Failed to set tree-sitter language");
event_bus_.Subscribe<events::DocumentOpened>(
[this](const auto& e) { OnDocumentOpened(e); });
event_bus_.Subscribe<events::DocumentChanged>(
[this](const auto& e) { OnDocumentChanged(e); });
event_bus_.Subscribe<events::DocumentClosed>(
[this](const auto& e) { OnDocumentClosed(e); });
}
Parser::~Parser() = default;
TSParser* Parser::GetRawParser() const
{
return parser_.GetRawParser();
}
TSTree* Parser::GetTree(const protocol::DocumentUri& uri) const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
auto it = trees_.find(uri);
if (it != trees_.end() && it->second)
return it->second->Get();
return nullptr;
}
void Parser::Clear()
{
std::unique_lock<std::shared_mutex> lock(mutex_);
trees_.clear();
}
void Parser::OnDocumentOpened(const events::DocumentOpened& event)
{
TSTree* tree = parser_.Parse(
event.textDocument.text.c_str(),
event.textDocument.text.length());
if (tree)
{
{
std::unique_lock<std::shared_mutex> lock(mutex_);
trees_[event.textDocument.uri] = std::make_unique<SyntaxTree>(tree);
}
event_bus_.Publish(events::DocumentParsed{
.item = event.textDocument,
.tree = tree });
spdlog::debug("Successfully parsed document: {}", event.textDocument.uri);
}
else
{
spdlog::error("Failed to parse document: {}", event.textDocument.uri);
}
}
void Parser::OnDocumentChanged(const events::DocumentChanged& event)
{
TSTree* old_tree = nullptr;
{
std::shared_lock<std::shared_mutex> lock(mutex_);
auto it = trees_.find(event.uri);
if (it != trees_.end() && it->second)
{
for (const auto& change : event.changes)
it->second->ApplyEdit(change, event.content);
old_tree = it->second->Get();
}
}
TSTree* tree = parser_.Parse(
event.content.c_str(),
event.content.length(),
old_tree);
if (tree)
{
{
std::unique_lock<std::shared_mutex> lock(mutex_);
trees_[event.uri] = std::make_unique<SyntaxTree>(tree);
}
event_bus_.Publish(events::DocumentReparsed{
.item{
.uri = event.uri,
.languageId = "",
.version = event.version,
.text = event.content },
.tree = tree });
spdlog::debug("Document reparsed successfully: {}", event.uri);
}
else
{
spdlog::error("Failed to reparse document: {}", event.uri);
}
}
void Parser::OnDocumentClosed(const events::DocumentClosed& event)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
trees_.erase(event.textDocument.uri);
spdlog::debug("Removed syntax tree for: {}", event.textDocument.uri);
}
}
+70
View File
@@ -0,0 +1,70 @@
#pragma once
#include <memory>
#include <optional>
#include <shared_mutex>
#include <unordered_map>
#include "./events.hpp"
#include "./event_bus.hpp"
#include "../protocol/protocol.hpp"
extern "C" {
#include <tree_sitter/api.h>
}
extern "C" const TSLanguage* tree_sitter_tsf(void);
namespace lsp::manager
{
class TreeSitter
{
public:
TreeSitter();
TreeSitter(const TreeSitter&) = delete;
TreeSitter& operator=(const TreeSitter&) = delete;
TreeSitter(TreeSitter&& other) noexcept;
~TreeSitter();
bool SetLanguage(const TSLanguage* language);
TSTree* Parse(const char* content, size_t length, TSTree* old_tree = nullptr);
TSParser* GetRawParser() const;
private:
TSParser* parser_;
};
class SyntaxTree
{
public:
explicit SyntaxTree(TSTree* tree);
~SyntaxTree();
void ApplyEdit(const protocol::TextDocumentContentChangeEvent& change, const protocol::string& content);
TSTree* Get() const;
TSNode GetRootNode() const;
private:
std::unique_ptr<TSTree, void (*)(TSTree*)> tree_;
};
class Parser
{
public:
explicit Parser(EventBus& event_bus);
~Parser();
TSTree* GetTree(const protocol::DocumentUri& uri) const;
TSParser* GetRawParser() const;
void Clear();
private:
void OnDocumentOpened(const events::DocumentOpened& event);
void OnDocumentChanged(const events::DocumentChanged& event);
void OnDocumentClosed(const events::DocumentClosed& event);
TreeSitter parser_;
std::unordered_map<protocol::DocumentUri, std::unique_ptr<SyntaxTree>> trees_;
EventBus& event_bus_;
mutable std::shared_mutex mutex_;
};
}
+519
View File
@@ -0,0 +1,519 @@
#include "symbol.hpp"
#include <spdlog/spdlog.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <sstream>
#include <cctype>
#include "../utils/string.hpp"
#include "../language/ast/deserializer.hpp"
#include "../language/symbol/builder.hpp"
namespace lsp::manager
{
extern "C" const TSLanguage* tree_sitter_tsf(void);
namespace
{
std::string PathToUri(const std::filesystem::path& path)
{
auto absolute = std::filesystem::absolute(path).generic_string();
#ifdef _WIN32
std::replace(absolute.begin(), absolute.end(), '\\', '/');
#endif
if (!absolute.starts_with("/"))
absolute = "/" + absolute;
return "file://" + absolute;
}
std::string UriToPath(const std::string& uri)
{
std::string path = uri;
if (path.starts_with("file://"))
path = path.substr(7);
#ifdef _WIN32
if (!path.empty() && path[0] == '/')
path = path.substr(1);
std::replace(path.begin(), path.end(), '/', '\\');
#endif
// Percent-decoding
std::string decoded;
decoded.reserve(path.size());
for (size_t i = 0; i < path.size(); ++i)
{
if (path[i] == '%' && i + 2 < path.size())
{
std::string hex = path.substr(i + 1, 2);
char ch = static_cast<char>(std::stoi(hex, nullptr, 16));
decoded.push_back(ch);
i += 2;
}
else if (path[i] == '+')
{
decoded.push_back(' ');
}
else
{
decoded.push_back(path[i]);
}
}
return decoded;
}
bool IsTsfFile(const std::filesystem::path& path)
{
if (!path.has_extension())
return false;
std::string ext = path.extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
return ext == ".tsf" || ext == ".tsl";
}
std::unique_ptr<language::symbol::SymbolTable> BuildSymbolTableFromFile(
const std::filesystem::path& file_path)
{
if (!IsTsfFile(file_path))
return nullptr;
std::ifstream file(file_path, std::ios::binary);
if (!file.is_open())
{
spdlog::warn("Failed to open symbol file: {}", file_path.string());
return nullptr;
}
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
auto parser_deleter = [](TSParser* parser) {
if (parser)
ts_parser_delete(parser);
};
std::unique_ptr<TSParser, decltype(parser_deleter)> parser(ts_parser_new(), parser_deleter);
if (!parser || !ts_parser_set_language(parser.get(), tree_sitter_tsf()))
{
spdlog::error("Failed to create tree-sitter parser for file: {}", file_path.string());
return nullptr;
}
TSTree* tree_handle = ts_parser_parse_string(parser.get(), nullptr, content.c_str(), content.length());
if (!tree_handle)
{
spdlog::warn("tree-sitter failed to parse file: {}", file_path.string());
return nullptr;
}
auto tree_deleter = [](TSTree* tree) {
if (tree)
ts_tree_delete(tree);
};
std::unique_ptr<TSTree, decltype(tree_deleter)> tree(tree_handle, tree_deleter);
language::ast::Deserializer deserializer;
auto ast_result = deserializer.Parse(ts_tree_root_node(tree.get()), content);
if (!ast_result.root)
{
spdlog::warn("Failed to deserialize AST for file: {}", file_path.string());
return nullptr;
}
auto symbol_table = std::make_unique<language::symbol::SymbolTable>();
try
{
language::symbol::Builder builder(*symbol_table);
builder.Build(*ast_result.root);
}
catch (const std::exception& e)
{
spdlog::error("Exception building symbol table for {}: {}", file_path.string(), e.what());
return nullptr;
}
return symbol_table;
}
bool HasMatchingTopLevelSymbol(const language::symbol::SymbolTable& table, const std::string& stem)
{
for (const auto& wrapper : table.all_definitions())
{
const auto& symbol = wrapper.get();
switch (symbol.kind())
{
case protocol::SymbolKind::Function:
case protocol::SymbolKind::Class:
case protocol::SymbolKind::Module:
if (utils::IEquals(symbol.name(), stem))
return true;
break;
default:
break;
}
}
return false;
}
}
Symbol::Symbol(EventBus& event_bus) : event_bus_(event_bus)
{
event_bus_.Subscribe<events::DocumentParsed>(
[this](const auto& e) { OnDocumentParsed(e); });
event_bus_.Subscribe<events::DocumentReparsed>(
[this](const auto& e) { OnDocumentReparsed(e); });
event_bus_.Subscribe<events::DocumentClosed>(
[this](const auto& e) { OnDocumentClosed(e); });
}
Symbol::~Symbol() = default;
void Symbol::LoadSystemLibrary(const std::string& lib_path)
{
spdlog::info("Loading system library from: {}", lib_path);
auto start = std::chrono::steady_clock::now();
if (!std::filesystem::exists(lib_path))
{
spdlog::warn("System library path does not exist: {}", lib_path);
return;
}
size_t loaded = 0;
size_t failed = 0;
std::unordered_map<std::string, StoredSymbolEntry> new_symbols;
auto options = std::filesystem::directory_options::follow_directory_symlink |
std::filesystem::directory_options::skip_permission_denied;
for (const auto& entry : std::filesystem::recursive_directory_iterator(lib_path, options))
{
if (!entry.is_regular_file())
continue;
auto table = BuildSymbolTableFromFile(entry.path());
if (!table)
{
++failed;
continue;
}
auto stem = entry.path().stem().string();
if (!HasMatchingTopLevelSymbol(*table, stem))
{
spdlog::warn("Skipping system file {}: top-level symbol does not match file name", entry.path().string());
++failed;
continue;
}
StoredSymbolEntry stored;
stored.symbol_table = std::move(table);
stored.semantic_model = std::make_unique<language::semantic::SemanticModel>(*stored.symbol_table);
new_symbols[PathToUri(entry.path())] = std::move(stored);
++loaded;
}
{
std::unique_lock<std::shared_mutex> lock(mutex_);
system_symbols_ = std::move(new_symbols);
RebuildIndex();
}
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start)
.count();
spdlog::info("System library loaded: {} files, {} failed, {}ms",
loaded,
failed,
duration);
}
void Symbol::LoadWorkspace(const protocol::DocumentUri& workspace_uri)
{
auto workspace_path = UriToPath(workspace_uri);
spdlog::info("Loading workspace from: {}", workspace_path);
auto start = std::chrono::steady_clock::now();
if (!std::filesystem::exists(workspace_path))
{
spdlog::warn("Workspace path does not exist: {}", workspace_path);
return;
}
size_t loaded = 0;
size_t failed = 0;
std::unordered_map<std::string, StoredSymbolEntry> new_symbols;
auto options = std::filesystem::directory_options::follow_directory_symlink |
std::filesystem::directory_options::skip_permission_denied;
for (const auto& entry : std::filesystem::recursive_directory_iterator(workspace_path, options))
{
if (!entry.is_regular_file())
continue;
if (!IsTsfFile(entry.path()))
continue;
auto table = BuildSymbolTableFromFile(entry.path());
if (!table)
{
++failed;
continue;
}
auto stem = entry.path().stem().string();
if (!HasMatchingTopLevelSymbol(*table, stem))
{
spdlog::warn("Skipping system file {}: top-level symbol does not match file name", entry.path().string());
++failed;
continue;
}
StoredSymbolEntry stored;
stored.symbol_table = std::move(table);
stored.semantic_model = std::make_unique<language::semantic::SemanticModel>(*stored.symbol_table);
new_symbols[PathToUri(entry.path())] = std::move(stored);
++loaded;
}
{
std::unique_lock<std::shared_mutex> lock(mutex_);
workspace_symbols_ = std::move(new_symbols);
RebuildIndex();
}
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - start)
.count();
spdlog::info("Workspace loaded: {} files, {} failed, {}ms",
loaded,
failed,
duration);
}
const language::symbol::SymbolTable* Symbol::GetSymbolTable(
const protocol::DocumentUri& uri) const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
if (auto it = editing_symbols_.find(uri); it != editing_symbols_.end())
{
return it->second.symbol_table.get();
}
if (auto it = workspace_symbols_.find(uri); it != workspace_symbols_.end())
{
return it->second.symbol_table.get();
}
if (auto it = system_symbols_.find(uri); it != system_symbols_.end())
{
return it->second.symbol_table.get();
}
return nullptr;
}
const language::semantic::SemanticModel* Symbol::GetSemanticModel(
const protocol::DocumentUri& uri) const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
if (auto it = editing_symbols_.find(uri); it != editing_symbols_.end())
{
return it->second.semantic_model.get();
}
if (auto it = workspace_symbols_.find(uri); it != workspace_symbols_.end())
{
return it->second.semantic_model.get();
}
if (auto it = system_symbols_.find(uri); it != system_symbols_.end())
{
return it->second.semantic_model.get();
}
return nullptr;
}
std::vector<const language::symbol::SymbolTable*> Symbol::GetWorkspaceSymbolTables() const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
std::vector<const language::symbol::SymbolTable*> result;
result.reserve(workspace_symbols_.size());
for (const auto& [uri, entry] : workspace_symbols_)
{
(void)uri;
result.push_back(entry.symbol_table.get());
}
return result;
}
std::vector<const language::symbol::SymbolTable*> Symbol::GetSystemSymbolTables() const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
std::vector<const language::symbol::SymbolTable*> result;
result.reserve(system_symbols_.size());
for (const auto& [uri, entry] : system_symbols_)
{
(void)uri;
result.push_back(entry.symbol_table.get());
}
return result;
}
std::vector<Symbol::IndexedSymbol> Symbol::QueryIndexedSymbols(protocol::SymbolKind kind, std::optional<SymbolSource> source) const
{
std::shared_lock<std::shared_mutex> lock(mutex_);
std::vector<IndexedSymbol> result;
for (const auto& [_, symbols] : index_by_name_)
{
for (const auto& item : symbols)
{
if (item.kind != kind)
continue;
if (source.has_value() && item.source != *source)
continue;
result.push_back(item);
}
}
return result;
}
void Symbol::OnDocumentParsed(const events::DocumentParsed& event)
{
if (!event.tree)
{
spdlog::warn("Received null tree for document: {}", event.item.uri);
return;
}
try
{
DocumentAnalysis analysis;
analysis.uri = event.item.uri;
analysis.version = event.item.version;
analysis.deserializer = std::make_unique<language::ast::Deserializer>();
auto ast_result = analysis.deserializer->Parse(
ts_tree_root_node(event.tree),
event.item.text);
if (!ast_result.IsSuccess())
{
spdlog::error("Failed to deserialize AST for: {}", event.item.uri);
return;
}
analysis.ast = std::move(ast_result.root);
analysis.symbol_table = std::make_unique<language::symbol::SymbolTable>();
language::symbol::Builder builder(*analysis.symbol_table);
builder.Build(*analysis.ast);
analysis.semantic_model = std::make_unique<language::semantic::SemanticModel>(*analysis.symbol_table);
{
std::unique_lock<std::shared_mutex> lock(mutex_);
editing_symbols_[event.item.uri] = std::move(analysis);
RebuildIndex();
}
spdlog::debug("Document parsed and symbols built: {}", event.item.uri);
}
catch (const std::exception& e)
{
spdlog::error("Exception building symbols for {}: {}",
event.item.uri,
e.what());
}
}
void Symbol::OnDocumentReparsed(const events::DocumentReparsed& event)
{
OnDocumentParsed(events::DocumentParsed{
.item = event.item,
.tree = event.tree });
}
void Symbol::OnDocumentClosed(const events::DocumentClosed& event)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
editing_symbols_.erase(event.textDocument.uri);
RebuildIndex();
spdlog::debug("Document closed and symbols removed: {}",
event.textDocument.uri);
}
void Symbol::RebuildIndex()
{
index_by_name_.clear();
auto add_container = [this](const auto& container, SymbolSource source) {
for (const auto& [uri, entry] : container)
{
if (entry.symbol_table)
AddTableToIndex(*entry.symbol_table, uri, source);
}
};
add_container(system_symbols_, SymbolSource::kSystem);
add_container(workspace_symbols_, SymbolSource::kWorkspace);
for (const auto& [uri, analysis] : editing_symbols_)
{
if (analysis.symbol_table)
AddTableToIndex(*analysis.symbol_table, uri, SymbolSource::kEditing);
}
}
bool Symbol::IsTopLevelSymbol(const language::symbol::SymbolTable& table, language::symbol::SymbolId id) const
{
const auto& scopes = table.scopes().all_scopes();
auto global_id = table.scopes().global_scope();
auto it = scopes.find(global_id);
if (it == scopes.end())
return false;
const auto& symbols = it->second.symbols;
for (const auto& [_, ids] : symbols)
{
if (std::find(ids.begin(), ids.end(), id) != ids.end())
return true;
}
return false;
}
void Symbol::AddTableToIndex(const language::symbol::SymbolTable& table, const protocol::DocumentUri& uri, SymbolSource source)
{
for (const auto& wrapper : table.all_definitions())
{
const auto& symbol = wrapper.get();
if (symbol.kind() != protocol::SymbolKind::Function &&
symbol.kind() != protocol::SymbolKind::Class &&
symbol.kind() != protocol::SymbolKind::Module)
{
continue;
}
if (!IsTopLevelSymbol(table, symbol.id()))
continue;
IndexedSymbol item{
.uri = uri,
.name = symbol.name(),
.kind = symbol.kind(),
.source = source,
.id = symbol.id() };
auto key = utils::ToLower(symbol.name());
index_by_name_[key].push_back(std::move(item));
}
}
}
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <shared_mutex>
#include <vector>
#include "../protocol/protocol.hpp"
#include "../language/symbol/table.hpp"
#include "../language/ast/deserializer.hpp"
#include "../language/semantic/semantic_model.hpp"
#include "./event_bus.hpp"
#include "./events.hpp"
#include "../utils/string.hpp"
namespace lsp::manager
{
class Symbol
{
public:
enum class SymbolSource
{
kEditing,
kWorkspace,
kSystem
};
struct IndexedSymbol
{
protocol::DocumentUri uri;
std::string name;
protocol::SymbolKind kind;
SymbolSource source;
language::symbol::SymbolId id;
};
explicit Symbol(EventBus& event_bus);
~Symbol();
void LoadSystemLibrary(const std::string& lib_path);
void LoadWorkspace(const protocol::DocumentUri& workspace_uri);
const language::symbol::SymbolTable* GetSymbolTable(const protocol::DocumentUri& uri) const;
const language::semantic::SemanticModel* GetSemanticModel(const protocol::DocumentUri& uri) const;
std::vector<const language::symbol::SymbolTable*> GetWorkspaceSymbolTables() const;
std::vector<const language::symbol::SymbolTable*> GetSystemSymbolTables() const;
std::vector<IndexedSymbol> QueryIndexedSymbols(protocol::SymbolKind kind, std::optional<SymbolSource> source = std::nullopt) const;
private:
void OnDocumentParsed(const events::DocumentParsed& event);
void OnDocumentReparsed(const events::DocumentReparsed& event);
void OnDocumentClosed(const events::DocumentClosed& event);
struct DocumentAnalysis
{
protocol::DocumentUri uri;
protocol::integer version;
std::unique_ptr<language::ast::Deserializer> deserializer;
std::unique_ptr<language::ast::Program> ast;
std::unique_ptr<language::symbol::SymbolTable> symbol_table;
std::unique_ptr<language::semantic::SemanticModel> semantic_model;
};
struct StoredSymbolEntry
{
std::unique_ptr<language::symbol::SymbolTable> symbol_table;
std::unique_ptr<language::semantic::SemanticModel> semantic_model;
};
void RebuildIndex();
void AddTableToIndex(const language::symbol::SymbolTable& table, const protocol::DocumentUri& uri, SymbolSource source);
bool IsTopLevelSymbol(const language::symbol::SymbolTable& table, language::symbol::SymbolId id) const;
std::unordered_map<std::string, StoredSymbolEntry> system_symbols_;
std::unordered_map<std::string, StoredSymbolEntry> workspace_symbols_;
std::unordered_map<protocol::DocumentUri, DocumentAnalysis> editing_symbols_;
std::unordered_map<std::string, std::vector<IndexedSymbol>, utils::IHasher, utils::IEqualTo> index_by_name_;
EventBus& event_bus_;
mutable std::shared_mutex mutex_;
};
}