✨ 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:
@@ -1,7 +1,5 @@
|
||||
module;
|
||||
|
||||
#include <format>
|
||||
|
||||
export module lsp.manager.bootstrap;
|
||||
import spdlog;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user