## Summary Introduces a two-layer state model that cleanly separates disk-based project state from per-open-file editing state, and redistributes responsibilities across server components so each has a single, clear role. ## New types **Workspace** — all persistent, project-wide shared state: - CompilationDatabase, PathPool, DependencyGraph, CompileGraph - path_to_module mapping, PCH cache, PCM cache, PCM paths - ProjectIndex, MergedIndex shards - CliceConfig - Methods: on_file_saved(), on_file_closed(), load/save/cleanup_cache(), build_module_map(), fill_pcm_deps(), cancel_all() **Session** — volatile per-open-file editing state: - text, version, generation, ast_dirty - pch_ref (references Workspace.pch_cache), ast_deps, header_context - file_index (OpenFileIndex for unsaved buffer) - path_id member for self-identification ## Component responsibilities after refactor | Component | Role | Owns state? | |-----------|------|-------------| | **Workspace** | Disk truth + shared caches | Yes (all project state) | | **Session** | One open file editing state | Yes (per-file only) | | **Compiler** | Compilation pipeline, worker communication | No (references only) | | **Indexer** | Index queries + background indexing scheduling | Scheduling state only | | **MasterServer** | LSP protocol dispatch + lifecycle coordination | sessions map | ## What moved where **Into Workspace** (from Compiler/MasterServer): - PCH/PCM cache management (load_cache, save_cache, cleanup_cache) - Module map building (build_module_map, fill_pcm_deps) - File lifecycle hooks (on_file_saved, on_file_closed) - cancel_all, OpenFileIndex/MergedIndexShard type definitions **Into Session** (from Compiler documents map): - Document text, version, generation, ast_dirty - PCH reference, dependency snapshot, header context **Into Indexer** (from MasterServer): - Background indexing queue, scheduling state, idle timer - schedule(), enqueue(), run_background_indexing() **Into syntax/completion.h** (from Compiler): - detect_completion_context() — pure text parsing - complete_module_import() — prefix match on module names - complete_include_path() — directory listing against search paths **Inlined into MasterServer** (from Compiler): - didOpen/didChange/didClose/didSave handlers - switchContext/currentContext - publish_diagnostics/clear_diagnostics **Deleted from Compiler** (9 methods): - open_document, apply_changes, close_document, on_save - switch_context, get_active_context, invalidate_host_contexts - on_file_closed, on_file_saved, complete_include, complete_import ## Tests - 481 tests pass (465 existing + 16 new completion tests) - New: tests/unit/syntax/completion_tests.cpp ## Diff stats 15 files changed, +1857, -1555 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Enhanced completion support for include paths and module imports with improved context detection. * Added background indexing system for automatic project symbol indexing. * **Bug Fixes** * Improved reliability of document change tracking and compilation state management. * Better handling of header file compilation contexts. * **Tests** * Added unit tests for completion context detection and module/include path completion. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
126 lines
3.4 KiB
C++
126 lines
3.4 KiB
C++
#include "test/test.h"
|
|
#include "syntax/completion.h"
|
|
|
|
#include "llvm/ADT/DenseMap.h"
|
|
|
|
namespace clice::testing {
|
|
namespace {
|
|
|
|
TEST_SUITE(DetectCompletionContext) {
|
|
|
|
TEST_CASE(IncludeAngled) {
|
|
auto ctx = detect_completion_context("#include <vec", 13);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::IncludeAngled);
|
|
EXPECT_EQ(ctx.prefix, "vec");
|
|
}
|
|
|
|
TEST_CASE(IncludeQuoted) {
|
|
auto ctx = detect_completion_context("#include \"my_header", 19);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::IncludeQuoted);
|
|
EXPECT_EQ(ctx.prefix, "my_header");
|
|
}
|
|
|
|
TEST_CASE(IncludeAngledWithSpaces) {
|
|
auto ctx = detect_completion_context(" # include <sys/", 19);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::IncludeAngled);
|
|
EXPECT_EQ(ctx.prefix, "sys/");
|
|
}
|
|
|
|
TEST_CASE(IncludeEmpty) {
|
|
auto ctx = detect_completion_context("#include <", 10);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::IncludeAngled);
|
|
EXPECT_EQ(ctx.prefix, "");
|
|
}
|
|
|
|
TEST_CASE(ImportSimple) {
|
|
auto ctx = detect_completion_context("import std", 10);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::Import);
|
|
EXPECT_EQ(ctx.prefix, "std");
|
|
}
|
|
|
|
TEST_CASE(ExportImport) {
|
|
auto ctx = detect_completion_context("export import my_mod", 20);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::Import);
|
|
EXPECT_EQ(ctx.prefix, "my_mod");
|
|
}
|
|
|
|
TEST_CASE(ImportWithSemicolon) {
|
|
auto ctx = detect_completion_context("import std;\n", 7);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::None);
|
|
}
|
|
|
|
TEST_CASE(ImportEmpty) {
|
|
auto ctx = detect_completion_context("import ", 7);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::Import);
|
|
EXPECT_EQ(ctx.prefix, "");
|
|
}
|
|
|
|
TEST_CASE(NormalCode) {
|
|
auto ctx = detect_completion_context("int main() {", 12);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::None);
|
|
}
|
|
|
|
TEST_CASE(MultilineAtSecondLine) {
|
|
std::string text = "#include <vector>\n#include <str";
|
|
auto ctx = detect_completion_context(text, text.size());
|
|
EXPECT_EQ(ctx.kind, CompletionContext::IncludeAngled);
|
|
EXPECT_EQ(ctx.prefix, "str");
|
|
}
|
|
|
|
TEST_CASE(NotImportKeyword) {
|
|
auto ctx = detect_completion_context("importlib foo", 13);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::None);
|
|
}
|
|
|
|
TEST_CASE(HashOnly) {
|
|
auto ctx = detect_completion_context("#", 1);
|
|
EXPECT_EQ(ctx.kind, CompletionContext::None);
|
|
}
|
|
|
|
}; // TEST_SUITE(DetectCompletionContext)
|
|
|
|
TEST_SUITE(CompleteModuleImport) {
|
|
|
|
TEST_CASE(PrefixMatch) {
|
|
llvm::DenseMap<std::uint32_t, std::string> modules;
|
|
modules[1] = "std";
|
|
modules[2] = "std.io";
|
|
modules[3] = "std.net";
|
|
modules[4] = "my_lib";
|
|
|
|
auto results = complete_module_import(modules, "std");
|
|
EXPECT_EQ(results.size(), 3u);
|
|
for(auto& name: results) {
|
|
EXPECT_TRUE(name.starts_with("std"));
|
|
}
|
|
}
|
|
|
|
TEST_CASE(EmptyPrefix) {
|
|
llvm::DenseMap<std::uint32_t, std::string> modules;
|
|
modules[1] = "std";
|
|
modules[2] = "my_lib";
|
|
|
|
auto results = complete_module_import(modules, "");
|
|
EXPECT_EQ(results.size(), 2u);
|
|
}
|
|
|
|
TEST_CASE(NoMatch) {
|
|
llvm::DenseMap<std::uint32_t, std::string> modules;
|
|
modules[1] = "std";
|
|
modules[2] = "my_lib";
|
|
|
|
auto results = complete_module_import(modules, "xyz");
|
|
EXPECT_TRUE(results.empty());
|
|
}
|
|
|
|
TEST_CASE(EmptyModules) {
|
|
llvm::DenseMap<std::uint32_t, std::string> modules;
|
|
auto results = complete_module_import(modules, "std");
|
|
EXPECT_TRUE(results.empty());
|
|
}
|
|
|
|
}; // TEST_SUITE(CompleteModuleImport)
|
|
|
|
} // namespace
|
|
} // namespace clice::testing
|