feat(lsp_server): implement missing providers and json coverage

Implement workspace configuration/folders and apply WorkspaceEdit.changes.

Strengthen provider JSON coverage (all methods require params, no errors).

Verified: Release test_provider
This commit is contained in:
csh
2025-12-24 10:42:13 +08:00
parent f6943e69ef
commit 09e65224fe
93 changed files with 14203 additions and 856 deletions
-2
View File
@@ -1,7 +1,5 @@
module;
#include <format>
export module lsp.manager.bootstrap;
import spdlog;
+90
View File
@@ -22,16 +22,33 @@ export namespace lsp::manager
void Initialize();
void Shutdown();
EventBus& event_bus() { return event_bus_; }
const EventBus& event_bus() const { return event_bus_; }
Document& documents() { return documents_; }
Parser& parser() { return parser_; }
Symbol& symbols() { return symbols_; }
void SetConfiguration(protocol::LSPAny settings);
protocol::LSPAny GetConfiguration() const;
void SetWorkspaceFolders(std::vector<protocol::WorkspaceFolder> folders);
void AddWorkspaceFolders(const std::vector<protocol::WorkspaceFolder>& folders);
void RemoveWorkspaceFolders(const std::vector<protocol::WorkspaceFolder>& folders);
std::vector<protocol::WorkspaceFolder> GetWorkspaceFolders() const;
private:
void ClearWorkspaceState();
EventBus event_bus_;
Document documents_;
Parser parser_;
Symbol symbols_;
mutable std::shared_mutex workspace_mutex_;
protocol::LSPAny configuration_settings_{ std::nullptr_t{} };
std::vector<protocol::WorkspaceFolder> workspace_folders_;
};
}
@@ -50,11 +67,84 @@ namespace lsp::manager
{
documents_.Clear();
parser_.Clear();
ClearWorkspaceState();
}
void ManagerHub::Shutdown()
{
documents_.Clear();
parser_.Clear();
ClearWorkspaceState();
}
void ManagerHub::SetConfiguration(protocol::LSPAny settings)
{
std::unique_lock<std::shared_mutex> lock(workspace_mutex_);
configuration_settings_ = std::move(settings);
}
protocol::LSPAny ManagerHub::GetConfiguration() const
{
std::shared_lock<std::shared_mutex> lock(workspace_mutex_);
return configuration_settings_;
}
void ManagerHub::SetWorkspaceFolders(std::vector<protocol::WorkspaceFolder> folders)
{
std::unique_lock<std::shared_mutex> lock(workspace_mutex_);
workspace_folders_ = std::move(folders);
}
void ManagerHub::AddWorkspaceFolders(const std::vector<protocol::WorkspaceFolder>& folders)
{
if (folders.empty())
{
return;
}
std::unique_lock<std::shared_mutex> lock(workspace_mutex_);
for (const auto& folder : folders)
{
auto existing = std::find_if(workspace_folders_.begin(), workspace_folders_.end(), [&folder](const protocol::WorkspaceFolder& item) {
return item.uri == folder.uri;
});
if (existing == workspace_folders_.end())
{
workspace_folders_.push_back(folder);
continue;
}
existing->name = folder.name;
}
}
void ManagerHub::RemoveWorkspaceFolders(const std::vector<protocol::WorkspaceFolder>& folders)
{
if (folders.empty())
{
return;
}
std::unique_lock<std::shared_mutex> lock(workspace_mutex_);
for (const auto& folder : folders)
{
std::erase_if(workspace_folders_, [&folder](const protocol::WorkspaceFolder& item) {
return item.uri == folder.uri;
});
}
}
std::vector<protocol::WorkspaceFolder> ManagerHub::GetWorkspaceFolders() const
{
std::shared_lock<std::shared_mutex> lock(workspace_mutex_);
return workspace_folders_;
}
void ManagerHub::ClearWorkspaceState()
{
std::unique_lock<std::shared_mutex> lock(workspace_mutex_);
configuration_settings_ = protocol::LSPAny(std::nullptr_t{});
workspace_folders_.clear();
}
}
+183
View File
@@ -40,6 +40,10 @@ export namespace lsp::manager
void LoadSystemLibrary(const std::string& lib_path);
void LoadWorkspace(const protocol::DocumentUri& workspace_uri);
void IndexWorkspaceFiles(const std::vector<protocol::DocumentUri>& uris);
void RemoveWorkspaceFiles(const std::vector<protocol::DocumentUri>& uris);
void RenameWorkspaceFiles(const std::vector<std::pair<protocol::DocumentUri, protocol::DocumentUri>>& files);
const language::symbol::SymbolTable* GetSymbolTable(const protocol::DocumentUri& uri) const;
const language::semantic::SemanticModel* GetSemanticModel(const protocol::DocumentUri& uri) const;
@@ -445,6 +449,185 @@ namespace lsp::manager
duration);
}
void Symbol::IndexWorkspaceFiles(const std::vector<protocol::DocumentUri>& uris)
{
std::unordered_map<std::string, StoredSymbolEntry> updates;
std::vector<std::string> removals;
updates.reserve(uris.size());
removals.reserve(uris.size());
for (const auto& uri : uris)
{
auto file_path = std::filesystem::path(UriToPath(uri));
auto kind = GetTslFileKind(file_path);
if (kind == TslFileKind::kOther)
{
continue;
}
auto normalized_uri = PathToUri(file_path);
if (!std::filesystem::exists(file_path))
{
removals.push_back(std::move(normalized_uri));
continue;
}
auto table = BuildSymbolTableFromFile(file_path);
if (!table)
{
removals.push_back(std::move(normalized_uri));
continue;
}
auto stem = file_path.stem().string();
if (kind == TslFileKind::kLibraryTsf && !HasMatchingTopLevelSymbol(*table, stem))
{
spdlog::warn("Skipping workspace file {}: top-level symbol does not match file name", file_path.string());
removals.push_back(std::move(normalized_uri));
continue;
}
StoredSymbolEntry stored;
stored.symbol_table = std::move(table);
stored.semantic_model = std::make_unique<language::semantic::SemanticModel>(*stored.symbol_table);
updates[normalized_uri] = std::move(stored);
}
if (updates.empty() && removals.empty())
{
return;
}
{
std::unique_lock<std::shared_mutex> lock(mutex_);
for (auto& [uri, entry] : updates)
{
workspace_symbols_[uri] = std::move(entry);
}
for (auto& uri : removals)
{
workspace_symbols_.erase(uri);
}
RebuildIndex();
}
}
void Symbol::RemoveWorkspaceFiles(const std::vector<protocol::DocumentUri>& uris)
{
if (uris.empty())
{
return;
}
std::vector<std::string> removals;
removals.reserve(uris.size());
for (const auto& uri : uris)
{
auto file_path = std::filesystem::path(UriToPath(uri));
auto kind = GetTslFileKind(file_path);
if (kind == TslFileKind::kOther)
{
continue;
}
removals.push_back(PathToUri(file_path));
}
if (removals.empty())
{
return;
}
{
std::unique_lock<std::shared_mutex> lock(mutex_);
for (auto& uri : removals)
{
workspace_symbols_.erase(uri);
}
RebuildIndex();
}
}
void Symbol::RenameWorkspaceFiles(const std::vector<std::pair<protocol::DocumentUri, protocol::DocumentUri>>& files)
{
if (files.empty())
{
return;
}
std::unordered_map<std::string, StoredSymbolEntry> updates;
std::vector<std::string> removals;
updates.reserve(files.size());
removals.reserve(files.size());
for (const auto& [old_uri, new_uri] : files)
{
auto old_path = std::filesystem::path(UriToPath(old_uri));
if (GetTslFileKind(old_path) != TslFileKind::kOther)
{
removals.push_back(PathToUri(old_path));
}
auto new_path = std::filesystem::path(UriToPath(new_uri));
auto kind = GetTslFileKind(new_path);
if (kind == TslFileKind::kOther)
{
continue;
}
auto normalized_uri = PathToUri(new_path);
if (!std::filesystem::exists(new_path))
{
removals.push_back(std::move(normalized_uri));
continue;
}
auto table = BuildSymbolTableFromFile(new_path);
if (!table)
{
removals.push_back(std::move(normalized_uri));
continue;
}
auto stem = new_path.stem().string();
if (kind == TslFileKind::kLibraryTsf && !HasMatchingTopLevelSymbol(*table, stem))
{
spdlog::warn("Skipping workspace file {}: top-level symbol does not match file name", new_path.string());
removals.push_back(std::move(normalized_uri));
continue;
}
StoredSymbolEntry stored;
stored.symbol_table = std::move(table);
stored.semantic_model = std::make_unique<language::semantic::SemanticModel>(*stored.symbol_table);
updates[normalized_uri] = std::move(stored);
}
if (updates.empty() && removals.empty())
{
return;
}
{
std::unique_lock<std::shared_mutex> lock(mutex_);
for (auto& uri : removals)
{
workspace_symbols_.erase(uri);
}
for (auto& [uri, entry] : updates)
{
workspace_symbols_[uri] = std::move(entry);
}
RebuildIndex();
}
}
const language::symbol::SymbolTable* Symbol::GetSymbolTable(
const protocol::DocumentUri& uri) const
{