Files
clice/tests/smoke/rapid_edit.jsonl
ykiko e239b0d32c feat: smart PCH rebuild, #include/import completion, rapid-edit robustness (#394)
## Summary

### Preamble completeness check
- `is_preamble_complete()` in `scan.cpp`: checks whether
`#include`/`import`/`export module` directives in the preamble region
are syntactically complete (have closing `>`/`"`/`;`)
- `ensure_pch` defers PCH rebuild when preamble is incomplete (user
still typing), reuses old PCH instead of failing

### #include / import completion
- Master intercepts completion requests in `#include "..."` / `#include
<...>` / `import ...` contexts before forwarding to worker
- `complete_include()`: searches include paths (from compile args via
`SearchConfig`) using `DirListingCache`, supports
quoted/angled/multi-level paths
- `complete_import()`: filters `path_to_module` map by prefix
- Word boundary checks prevent false matches (e.g. `important` not
treated as `import`)

### Detached compile task (rapid-edit fix)
- Compile operations (`ensure_deps` + `send_stateful` +
`publish_diagnostics`) run as detached tasks via `loop.schedule()`,
independent of the LSP request coroutine chain
- LSP `$/cancelRequest` can no longer kill in-flight compilations —
previously, cancellation would destroy the `ensure_compiled` coroutine
frame, leaving `doc.compiling` permanently set and hanging all
subsequent requests
- `CompileGuard` RAII ensures `doc.compiling` is always cleaned up even
if the detached task fails
- Stale feature requests (where `ast_dirty` became true after compile
finished) are dropped before forwarding to worker

### Other fixes
- `signal(SIGPIPE, SIG_IGN)` on POSIX: prevents server crash when LSP
client disconnects mid-write
- `CompilationUnitRef::file_path()` / `deps()`: null-check
`FileEntryRef` to prevent segfault on invalid FileID
- `stateless_worker.cpp`: log BuildPCH diagnostic errors for
debuggability
- Default worker counts changed to 2 stateful + 3 stateless
- `logging_dir` default changed to `.clice/logs` in config

### Tests
- 19 unit tests for `is_preamble_complete` (incomplete `#include`,
`import`, `export module`, mixed cases)
- Integration tests: `test_include_completion.py` (5 tests),
`test_import_completion.py` (4 tests), `test_rapid_edit.py` (2 tests),
`test_pch.py` (4 new tests)
- Smoke test: `rapid_edit.jsonl` — recorded VSCode session with 40 rapid
edits + 61 cancel requests

## Test plan
- [x] Unit tests: 463 passed
- [x] Integration tests: 104 passed
- [x] Smoke test (rapid_edit.jsonl): PASS
- [x] Manual VSCode testing with `#include <iostream>` project

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 14:49:09 +08:00

251 lines
251 KiB
JSON
Generated
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"ts":8,"msg":"{\"jsonrpc\":\"2.0\",\"id\":0,\"method\":\"initialize\",\"params\":{\"processId\":1707078,\"clientInfo\":{\"name\":\"Visual Studio Code\",\"version\":\"1.114.0\"},\"locale\":\"en\",\"rootPath\":\"/home/ykiko/C++/clice/tests/data/hello_world\",\"rootUri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world\",\"capabilities\":{\"workspace\":{\"applyEdit\":true,\"workspaceEdit\":{\"documentChanges\":true,\"resourceOperations\":[\"create\",\"rename\",\"delete\"],\"failureHandling\":\"textOnlyTransactional\",\"normalizesLineEndings\":true,\"changeAnnotationSupport\":{\"groupsOnLabel\":true}},\"configuration\":true,\"didChangeWatchedFiles\":{\"dynamicRegistration\":true,\"relativePatternSupport\":true},\"symbol\":{\"dynamicRegistration\":true,\"symbolKind\":{\"valueSet\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},\"tagSupport\":{\"valueSet\":[1]},\"resolveSupport\":{\"properties\":[\"location.range\"]}},\"codeLens\":{\"refreshSupport\":true},\"executeCommand\":{\"dynamicRegistration\":true},\"didChangeConfiguration\":{\"dynamicRegistration\":true},\"workspaceFolders\":true,\"foldingRange\":{\"refreshSupport\":true},\"semanticTokens\":{\"refreshSupport\":true},\"fileOperations\":{\"dynamicRegistration\":true,\"didCreate\":true,\"didRename\":true,\"didDelete\":true,\"willCreate\":true,\"willRename\":true,\"willDelete\":true},\"inlineValue\":{\"refreshSupport\":true},\"inlayHint\":{\"refreshSupport\":true},\"diagnostics\":{\"refreshSupport\":true}},\"textDocument\":{\"publishDiagnostics\":{\"relatedInformation\":true,\"versionSupport\":false,\"tagSupport\":{\"valueSet\":[1,2]},\"codeDescriptionSupport\":true,\"dataSupport\":true},\"synchronization\":{\"dynamicRegistration\":true,\"willSave\":true,\"willSaveWaitUntil\":true,\"didSave\":true},\"completion\":{\"dynamicRegistration\":true,\"contextSupport\":true,\"completionItem\":{\"snippetSupport\":true,\"commitCharactersSupport\":true,\"documentationFormat\":[\"markdown\",\"plaintext\"],\"deprecatedSupport\":true,\"preselectSupport\":true,\"tagSupport\":{\"valueSet\":[1]},\"insertReplaceSupport\":true,\"resolveSupport\":{\"properties\":[\"documentation\",\"detail\",\"additionalTextEdits\"]},\"insertTextModeSupport\":{\"valueSet\":[1,2]},\"labelDetailsSupport\":true},\"insertTextMode\":2,\"completionItemKind\":{\"valueSet\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]},\"completionList\":{\"itemDefaults\":[\"commitCharacters\",\"editRange\",\"insertTextFormat\",\"insertTextMode\",\"data\"]}},\"hover\":{\"dynamicRegistration\":true,\"contentFormat\":[\"markdown\",\"plaintext\"]},\"signatureHelp\":{\"dynamicRegistration\":true,\"signatureInformation\":{\"documentationFormat\":[\"markdown\",\"plaintext\"],\"parameterInformation\":{\"labelOffsetSupport\":true},\"activeParameterSupport\":true},\"contextSupport\":true},\"definition\":{\"dynamicRegistration\":true,\"linkSupport\":true},\"references\":{\"dynamicRegistration\":true},\"documentHighlight\":{\"dynamicRegistration\":true},\"documentSymbol\":{\"dynamicRegistration\":true,\"symbolKind\":{\"valueSet\":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},\"hierarchicalDocumentSymbolSupport\":true,\"tagSupport\":{\"valueSet\":[1]},\"labelSupport\":true},\"codeAction\":{\"dynamicRegistration\":true,\"isPreferredSupport\":true,\"disabledSupport\":true,\"dataSupport\":true,\"resolveSupport\":{\"properties\":[\"edit\"]},\"codeActionLiteralSupport\":{\"codeActionKind\":{\"valueSet\":[\"\",\"quickfix\",\"refactor\",\"refactor.extract\",\"refactor.inline\",\"refactor.rewrite\",\"source\",\"source.organizeImports\"]}},\"honorsChangeAnnotations\":true},\"codeLens\":{\"dynamicRegistration\":true},\"formatting\":{\"dynamicRegistration\":true},\"rangeFormatting\":{\"dynamicRegistration\":true,\"rangesSupport\":true},\"onTypeFormatting\":{\"dynamicRegistration\":true},\"rename\":{\"dynamicRegistration\":true,\"prepareSupport\":true,\"prepareSupportDefaultBehavior\":1,\"honorsChangeAnnotations\":true},\"documentLink\":{\"dynamicRegistration\":true,\"tooltipSupport\":true},\"typeDefinition\":{\"dynamicRegistration\":true,\"linkSupport\":true},\"implementation\":{\"dynamicRegistration\":true,\"linkSupport\":true},\"colorProvider\":{\"dynamicRegistration\":true},\"foldingRange\":{\"dynamicRegistration\":true,\"rangeLimit\":5000,\"lineFoldingOnly\":true,\"foldingRangeKind\":{\"valueSet\":[\"comment\",\"imports\",\"region\"]},\"foldingRange\":{\"collapsedText\":false}},\"declaration\":{\"dynamicRegistration\":true,\"linkSupport\":true},\"selectionRange\":{\"dynamicRegistration\":true},\"callHierarchy\":{\"dynamicRegistration\":true},\"semanticTokens\":{\"dynamicRegistration\":true,\"tokenTypes\":[\"namespace\",\"type\",\"class\",\"enum\",\"interface\",\"struct\",\"typeParameter\",\"parameter\",\"variable\",\"property\",\"enumMember\",\"event\",\"function\",\"method\",\"macro\",\"keyword\",\"modifier\",\"comment\",\"string\",\"number\",\"regexp\",\"operator\",\"decorator\"],\"tokenModifiers\":[\"declaration\",\"definition\",\"readonly\",\"static\",\"deprecated\",\"abstract\",\"async\",\"modification\",\"documentation\",\"defaultLibrary\"],\"formats\":[\"relative\"],\"requests\":{\"range\":true,\"full\":{\"delta\":true}},\"multilineTokenSupport\":false,\"overlappingTokenSupport\":false,\"serverCancelSupport\":true,\"augmentsSyntaxTokens\":true},\"linkedEditingRange\":{\"dynamicRegistration\":true},\"typeHierarchy\":{\"dynamicRegistration\":true},\"inlineValue\":{\"dynamicRegistration\":true},\"inlayHint\":{\"dynamicRegistration\":true,\"resolveSupport\":{\"properties\":[\"tooltip\",\"textEdits\",\"label.tooltip\",\"label.location\",\"label.command\"]}},\"diagnostic\":{\"dynamicRegistration\":true,\"relatedDocumentSupport\":false}},\"window\":{\"showMessage\":{\"messageActionItem\":{\"additionalPropertiesSupport\":true}},\"showDocument\":{\"support\":true},\"workDoneProgress\":true},\"general\":{\"staleRequestSupport\":{\"cancel\":true,\"retryOnContentModified\":[\"textDocument/semanticTokens/full\",\"textDocument/semanticTokens/range\",\"textDocument/semanticTokens/full/delta\"]},\"regularExpressions\":{\"engine\":\"ECMAScript\",\"version\":\"ES2020\"},\"markdown\":{\"parser\":\"marked\",\"version\":\"1.1.0\"},\"positionEncodings\":[\"utf-16\"]},\"notebookDocument\":{\"synchronization\":{\"dynamicRegistration\":true,\"executionSummarySupport\":true}}},\"trace\":\"off\",\"workspaceFolders\":[{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world\",\"name\":\"hello_world\"}]}}"}
{"ts":82,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"initialized\",\"params\":{}}"}
{"ts":87,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include <iostream>\\n\\nint add(int a, int b) {\\n return a + b;\\n}\\n\\nint main() {\\n std::cout << \\\"hello world\\\" << std::endl;\\n\\n int result = add(1, 2);\\n return result;\\n}\\n\"}}}"}
{"ts":144,"msg":"{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":144,"msg":"{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":9,\"character\":27},\"end\":{\"line\":9,\"character\":27}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":144,"msg":"{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"textDocument/documentLink\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":144,"msg":"{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":12,\"character\":0}}}}"}
{"ts":226,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":4}}"}
{"ts":226,"msg":"{\"jsonrpc\":\"2.0\",\"id\":5,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":12,\"character\":0}}}}"}
{"ts":342,"msg":"{\"jsonrpc\":\"2.0\",\"id\":6,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":12,\"character\":0}}}}"}
{"ts":405,"msg":"{\"jsonrpc\":\"2.0\",\"id\":7,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":406,"msg":"{\"jsonrpc\":\"2.0\",\"id\":8,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":438,"msg":"{\"jsonrpc\":\"2.0\",\"id\":9,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":15}}}"}
{"ts":578,"msg":"{\"jsonrpc\":\"2.0\",\"id\":10,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":842,"msg":"{\"jsonrpc\":\"2.0\",\"id\":11,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":19}}}"}
{"ts":1478,"msg":"{\"jsonrpc\":\"2.0\",\"id\":12,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":19}}}"}
{"ts":2350,"msg":"{\"jsonrpc\":\"2.0\",\"id\":13,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":25}}}"}
{"ts":2639,"msg":"{\"jsonrpc\":\"2.0\",\"id\":14,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":12}}}"}
{"ts":3694,"msg":"{\"jsonrpc\":\"2.0\",\"id\":15,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":18},\"end\":{\"line\":10,\"character\":18}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":4741,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/compile/compilation_unit.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"compile/implement.h\\\"\\n#include \\\"index/usr.h\\\"\\n#include \\\"semantic/ast_utility.h\\\"\\n\\nnamespace clice {\\n\\nCompilationKind CompilationUnitRef::kind() {\\n return self->kind;\\n}\\n\\nCompilationStatus CompilationUnitRef::status() {\\n return self->status;\\n}\\n\\nauto CompilationUnitRef::file_id(clang::FileEntryRef entry) -> clang::FileID {\\n return self->SM().translateFile(entry);\\n}\\n\\nauto CompilationUnitRef::file_id(llvm::StringRef file) -> clang::FileID {\\n auto entry = self->SM().getFileManager().getFileRef(file);\\n if(entry) {\\n return file_id(*entry);\\n }\\n\\n return clang::FileID();\\n}\\n\\nauto CompilationUnitRef::decompose_location(clang::SourceLocation location)\\n -> std::pair<clang::FileID, std::uint32_t> {\\n assert(location.isFileID() && \\\"Decompose macro location is meaningless!\\\");\\n return self->SM().getDecomposedLoc(location);\\n}\\n\\nauto CompilationUnitRef::decompose_range(clang::SourceRange range)\\n -> std::pair<clang::FileID, LocalSourceRange> {\\n auto [begin, end] = range;\\n assert(begin.isValid() && end.isValid() && \\\"Invalid source range\\\");\\n assert(begin.isFileID() && end.isValid() && \\\"Input source range should be a file range\\\");\\n\\n if(begin == end) {\\n auto [fid, offset] = decompose_location(begin);\\n return {\\n fid,\\n {offset, offset + token_length(end)}\\n };\\n } else {\\n auto [begin_fid, begin_offset] = decompose_location(begin);\\n auto [end_fid, end_offset] = decompose_location(end);\\n\\n if(begin_fid == end_fid) {\\n end_offset += token_length(end);\\n } else {\\n auto content = file_content(begin_fid);\\n end_offset = content.size();\\n }\\n\\n return {\\n begin_fid,\\n {begin_offset, end_offset}\\n };\\n }\\n}\\n\\nauto CompilationUnitRef::decompose_expansion_range(clang::SourceRange range)\\n -> std::pair<clang::FileID, LocalSourceRange> {\\n auto [begin, end] = range;\\n if(begin == end) {\\n return decompose_range(expansion_location(begin));\\n } else {\\n return decompose_range(\\n clang::SourceRange(expansion_location(begin), expansion_location(end)));\\n }\\n}\\n\\nauto CompilationUnitRef::file_id(clang::SourceLocation location) -> clang::FileID {\\n return self->SM().getFileID(location);\\n}\\n\\nauto CompilationUnitRef::file_offset(clang::SourceLocation location) -> std::uint32_t {\\n return self->SM().getFileOffset(location);\\n}\\n\\nauto CompilationUnitRef::file_path(clang::FileID fid) -> llvm::StringRef {\\n assert(fid.isValid() && \\\"Invalid fid\\\");\\n if(auto it = self->path_cache.find(fid); it != self->path_cache.end()) {\\n return it->second;\\n }\\n\\n auto entry = self->SM().getFileEntryRefForID(fid);\\n if(!entry) {\\n return {};\\n }\\n\\n llvm::SmallString<128> path;\\n\\n /// Try to get the real path of the file.\\n auto name = entry->getName();\\n if(auto error = llvm::sys::fs::real_path(name, path)) {\\n /// If failed, use the virtual path.\\n path = name;\\n }\\n assert(!path.empty() && \\\"Invalid file path\\\");\\n\\n /// Allocate the path in the storage.\\n auto size = path.size();\\n auto data = self->path_storage.Allocate<char>(size + 1);\\n memcpy(data, path.data(), size);\\n data[size] = '\\\\0';\\n\\n auto [it, inserted] = self->path_cache.try_emplace(fid, llvm::StringRef(data, size));\\n assert(inserted && \\\"File path already exists\\\");\\n return it->second;\\n}\\n\\nauto CompilationUnitRef::file_content(clang::FileID fid) -> llvm::StringRef {\\n return self->SM().getBufferData(fid);\\n}\\n\\nauto CompilationUnitRef::interested_file() -> clang::FileID {\\n return self->SM().getMainFileID();\\n}\\n\\nauto CompilationUnitRef::interested_content() -> llvm::StringRef {\\n return file_content(interested_file());\\n}\\n\\nbool CompilationUnitRef::is_builtin_file(clang::FileID fid) {\\n // No FileEntryRef => built-in/command line/scratch.\\n if(!self->SM().getFileEntryRefForID(fid)) {\\n if(auto buffer = self->SM().getBufferOrNone(fid)) {\\n auto name = buffer->getBufferIdentifier();\\n return name == \\\"<built-in>\\\" || name == \\\"<command line>\\\" || name == \\\"<scratch space>\\\";\\n }\\n }\\n\\n return false;\\n}\\n\\nauto CompilationUnitRef::start_location(clang::FileID fid) -> clang::SourceLocation {\\n return self->SM().getLocForStartOfFile(fid);\\n}\\n\\nauto CompilationUnitRef::end_location(clang::FileID fid) -> clang::SourceLocation {\\n return self->SM().getLocForEndOfFile(fid);\\n}\\n\\nauto CompilationUnitRef::spelling_location(clang::SourceLocation loc) -> clang::SourceLocation {\\n return self->SM().getSpellingLoc(loc);\\n}\\n\\nauto CompilationUnitRef::expansion_location(clang::SourceLocation location)\\n -> clang::SourceLocation {\\n return self->SM().getExpansionLoc(location);\\n}\\n\\nauto CompilationUnitRef::file_location(clang::SourceLocation location) -> clang::SourceLocation {\\n return self->SM().getFileLoc(location);\\n}\\n\\nauto CompilationUnitRef::include_location(clang::FileID fid) -> clang::SourceLocation {\\n return self->SM().getIncludeLoc(fid);\\n}\\n\\nauto CompilationUnitRef::presumed_location(clang::SourceLocation location) -> clang::PresumedLoc {\\n return self->SM().getPresumedLoc(location, false);\\n}\\n\\nauto CompilationUnitRef::create_location(clang::FileID fid, std::uint32_t offset)\\n -> clang::SourceLocation {\\n return self->SM().getComposedLoc(fid, offset);\\n}\\n\\nauto CompilationUnitRef::spelled_tokens(clang::FileID fid) -> TokenRange {\\n return self->buffer->spelledTokens(fid);\\n}\\n\\nauto CompilationUnitRef::spelled_tokens(clang::SourceRange range) -> TokenRange {\\n auto tokens = self->buffer->spelledForExpanded(self->buffer->expandedTokens(range));\\n if(!tokens) {\\n return {};\\n }\\n\\n return *tokens;\\n}\\n\\nauto CompilationUnitRef::spelled_tokens_touch(clang::SourceLocation location) -> TokenRange {\\n return clang::syntax::spelledTokensTouching(location, *self->buffer);\\n}\\n\\nauto CompilationUnitRef::expanded_tokens() -> TokenRange {\\n return self->buffer->expandedTokens();\\n}\\n\\nauto CompilationUnitRef::expanded_tokens(clang::SourceRange range) -> TokenRange {\\n return self->buffer->expandedTokens(range);\\n}\\n\\nauto CompilationUnitRef::expansions_overlapping(TokenRange spelled_tokens)\\n -> std::vector<clang::syntax::TokenBuffer::Expansion> {\\n return self->buffer->expansionsOverlapping(spelled_tokens);\\n}\\n\\nauto CompilationUnitRef::token_length(clang::SourceLocation location) -> std::uint32_t {\\n return clang::Lexer::MeasureTokenLength(location, self->SM(), self->instance->getLangOpts());\\n}\\n\\nauto CompilationUnitRef::token_spelling(clang::SourceLocation location) -> llvm::StringRef {\\n return llvm::StringRef(self->SM().getCharacterData(location), token_length(location));\\n}\\n\\nauto CompilationUnitRef::module_name() -> llvm::StringRef {\\n return self->instance->getPreprocessor().getNamedModuleName();\\n}\\n\\nbool CompilationUnitRef::is_module_interface_unit() {\\n return self->instance->getPreprocessor().isInNamedInterfaceUnit();\\n}\\n\\nauto CompilationUnitRef::diagnostics() -> std::vector<Diagnostic>& {\\n return self->diagnostics;\\n}\\n\\nauto CompilationUnitRef::top_level_decls() -> llvm::ArrayRef<clang::Decl*> {\\n return self->top_level_decls;\\n}\\n\\nstd::chrono::milliseconds CompilationUnitRef::build_at() {\\n return self->build_at;\\n}\\n\\nstd::chrono::milliseconds CompilationUnitRef::build_duration() {\\n return self->build_duration;\\n}\\n\\nclang::LangOptions& CompilationUnitRef::lang_options() {\\n return self->instance->getLangOpts();\\n}\\n\\nstd::vector<std::string> CompilationUnitRef::deps() {\\n llvm::StringSet<> deps;\\n\\n /// FIXME: consider `#embed` and `__has_embed`.\\n\\n for(auto& [fid, directive]: directives()) {\\n for(auto& include: directive.includes) {\\n if(!include.skipped) {\\n auto path = file_path(include.fid);\\n if(!path.empty()) {\\n deps.try_emplace(path);\\n }\\n }\\n }\\n\\n for(auto& has_include: directive.has_includes) {\\n if(has_include.fid.isValid()) {\\n auto path = file_path(has_include.fid);\\n if(!path.empty()) {\\n deps.try_emplace(path);\\n }\\n }\\n }\\n }\\n\\n std::vector<std::string> result;\\n\\n for(auto& deps: deps) {\\n result.emplace_back(deps.getKey().str());\\n }\\n\\n return result;\\n}\\n\\nindex::SymbolID CompilationUnitRef::getSymbolID(const clang::NamedDecl* decl) {\\n uint64_t hash;\\n auto iter = self->symbol_hash_cache.find(decl);\\n if(iter != self->symbol_hash_cache.end()) {\\n hash = iter->second;\\n } else {\\n llvm::SmallString<128> usr;\\n index::generateUSRForDecl(decl, usr);\\n hash = llvm::xxh3_64bits(usr);\\n self->symbol_hash_cache.try_emplace(decl, hash);\\n }\\n return index::SymbolID{hash, ast::name_of(decl)};\\n}\\n\\nindex::SymbolID CompilationUnitRef::getSymbolID(const clang::MacroInfo* macro) {\\n std::uint64_t hash;\\n auto name = token_spelling(macro->getDefinitionLoc());\\n auto iter = self->symbol_hash_cache.find(macro);\\n if(iter != self->symbol_hash_cache.end()) {\\n hash = iter->second;\\n } else {\\n llvm::SmallString<128> usr;\\n index::generateUSRForMacro(name, macro->getDefinitionLoc(), self->SM(), usr);\\n hash = llvm::xxh3_64bits(usr);\\n self->symbol_hash_cache.try_emplace(macro, hash);\\n }\\n return index::SymbolID{hash, name.str()};\\n}\\n\\nconst llvm::DenseSet<clang::FileID>& CompilationUnitRef::files() {\\n if(self->all_files.empty()) {\\n /// FIXME: handle preamble and embed file id.\\n for(auto& [fid, directive]: directives()) {\\n for(auto& include: directive.includes) {\\n if(!include.skipped && include.fid.isValid()) {\\n self->all_files.insert(include.fid);\\n }\\n }\\n }\\n self->all_files.insert(self->SM().getMainFileID());\\n }\\n return self->all_files;\\n}\\n\\nclang::TranslationUnitDecl* CompilationUnitRef::tu() {\\n return self->instance->getASTContext().getTranslationUnitDecl();\\n}\\n\\nllvm::DenseMap<clang::FileID, Directive>& CompilationUnitRef::directives() {\\n return self->directives;\\n}\\n\\nTemplateResolver& CompilationUnitRef::resolver() {\\n assert(self->resolver && \\\"Template resolver is not available\\\");\\n return *self->resolver;\\n}\\n\\nclang::ASTContext& CompilationUnitRef::context() {\\n return self->instance->getASTContext();\\n}\\n\\nclang::syntax::TokenBuffer& CompilationUnitRef::token_buffer() {\\n return *self->buffer;\\n}\\n\\nCompilationUnit::~CompilationUnit() {\\n delete self;\\n}\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4741,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/server/config.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"server/config.h\\\"\\n\\n#include <algorithm>\\n#include <thread>\\n\\n#include \\\"eventide/serde/toml.h\\\"\\n#include \\\"support/filesystem.h\\\"\\n#include \\\"support/logging.h\\\"\\n\\nnamespace clice {\\n\\n/// Replace all occurrences of ${workspace} with the workspace root.\\nstatic void substitute_workspace(std::string& value, const std::string& workspace_root) {\\n constexpr std::string_view placeholder = \\\"${workspace}\\\";\\n std::string::size_type pos = 0;\\n while((pos = value.find(placeholder, pos)) != std::string::npos) {\\n value.replace(pos, placeholder.size(), workspace_root);\\n pos += workspace_root.size();\\n }\\n}\\n\\nvoid CliceConfig::apply_defaults(const std::string& workspace_root) {\\n auto cpu_count = std::thread::hardware_concurrency();\\n if(cpu_count == 0)\\n cpu_count = 4;\\n\\n if(stateful_worker_count == 0) {\\n stateful_worker_count = 2;\\n }\\n if(stateless_worker_count == 0) {\\n stateless_worker_count = 3;\\n }\\n if(worker_memory_limit == 0) {\\n worker_memory_limit = 4ULL * 1024 * 1024 * 1024; // 4GB default\\n }\\n if(cache_dir.empty() && !workspace_root.empty()) {\\n cache_dir = path::join(workspace_root, \\\".clice\\\");\\n }\\n\\n if(index_dir.empty() && !cache_dir.empty()) {\\n index_dir = path::join(cache_dir, \\\"index\\\");\\n }\\n\\n if(logging_dir.empty() && !cache_dir.empty()) {\\n logging_dir = path::join(cache_dir, \\\"logs\\\");\\n }\\n\\n // Apply variable substitution to string fields\\n substitute_workspace(compile_commands_path, workspace_root);\\n substitute_workspace(cache_dir, workspace_root);\\n substitute_workspace(index_dir, workspace_root);\\n substitute_workspace(logging_dir, workspace_root);\\n}\\n\\nstd::optional<CliceConfig> CliceConfig::load(const std::string& path,\\n const std::string& workspace_root) {\\n auto content = fs::read(path);\\n if(!content) {\\n return std::nullopt;\\n }\\n\\n auto result = eventide::serde::toml::parse<CliceConfig>(*content);\\n if(!result) {\\n LOG_WARN(\\\"Failed to parse config file {}\\\", path);\\n return std::nullopt;\\n }\\n\\n auto config = std::move(*result);\\n config.apply_defaults(workspace_root);\\n\\n LOG_INFO(\\\"Loaded config from {}\\\", path);\\n return config;\\n}\\n\\nCliceConfig CliceConfig::load_from_workspace(const std::string& workspace_root) {\\n if(!workspace_root.empty()) {\\n // Try standard config file locations\\n for(auto* name: {\\\"clice.toml\\\", \\\".clice/config.toml\\\"}) {\\n auto config_path = path::join(workspace_root, name);\\n if(llvm::sys::fs::exists(config_path)) {\\n auto config = load(config_path, workspace_root);\\n if(config)\\n return std::move(*config);\\n }\\n }\\n }\\n\\n // No config file found; use defaults\\n CliceConfig config;\\n config.apply_defaults(workspace_root);\\n LOG_INFO(\\n \\\"No clice.toml found, using default configuration \\\" \\\"(stateful={}, stateless={}, memory_limit={}MB)\\\",\\n config.stateful_worker_count,\\n config.stateless_worker_count,\\n config.worker_memory_limit / (1024 * 1024));\\n return config;\\n}\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4744,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/server/master_server.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"server/master_server.h\\\"\\n\\n#include <algorithm>\\n#include <chrono>\\n#include <format>\\n#include <optional>\\n#include <string>\\n#include <type_traits>\\n#include <variant>\\n#include <vector>\\n\\n#include \\\"command/search_config.h\\\"\\n#include \\\"eventide/ipc/json_codec.h\\\"\\n#include \\\"eventide/ipc/lsp/position.h\\\"\\n#include \\\"eventide/ipc/lsp/uri.h\\\"\\n#include \\\"eventide/reflection/enum.h\\\"\\n#include \\\"eventide/serde/json/json.h\\\"\\n#include \\\"eventide/serde/serde/raw_value.h\\\"\\n#include \\\"index/tu_index.h\\\"\\n#include \\\"semantic/symbol_kind.h\\\"\\n#include \\\"server/protocol.h\\\"\\n#include \\\"support/filesystem.h\\\"\\n#include \\\"support/logging.h\\\"\\n#include \\\"syntax/dependency_graph.h\\\"\\n#include \\\"syntax/include_resolver.h\\\"\\n#include \\\"syntax/scan.h\\\"\\n\\n#include \\\"llvm/Support/Chrono.h\\\"\\n#include \\\"llvm/Support/FileSystem.h\\\"\\n#include \\\"llvm/Support/Path.h\\\"\\n#include \\\"llvm/Support/Process.h\\\"\\n#include \\\"llvm/Support/raw_ostream.h\\\"\\n#include \\\"llvm/Support/xxhash.h\\\"\\n\\nnamespace clice {\\n\\nnamespace protocol = eventide::ipc::protocol;\\nnamespace lsp = eventide::ipc::lsp;\\nnamespace refl = eventide::refl;\\nusing et::ipc::RequestResult;\\nusing RequestContext = et::ipc::JsonPeer::RequestContext;\\n\\n/// Hash a file's content using xxh3_64bits. Returns 0 on read failure.\\nstatic std::uint64_t hash_file(llvm::StringRef path) {\\n auto buf = llvm::MemoryBuffer::getFile(path);\\n if(!buf)\\n return 0;\\n return llvm::xxh3_64bits((*buf)->getBuffer());\\n}\\n\\n/// Capture a two-layer staleness snapshot after a successful compilation.\\n/// Interns dependency paths into the PathPool and hashes each file's content.\\nstatic DepsSnapshot capture_deps_snapshot(PathPool& pool, llvm::ArrayRef<std::string> deps) {\\n DepsSnapshot snap;\\n // Capture timestamp BEFORE hashing to avoid TOCTOU: if a file is modified\\n // during hashing, its mtime will be > build_at, triggering Layer 2 re-hash.\\n snap.build_at = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\\n snap.path_ids.reserve(deps.size());\\n snap.hashes.reserve(deps.size());\\n for(const auto& file: deps) {\\n snap.path_ids.push_back(pool.intern(file));\\n snap.hashes.push_back(hash_file(file));\\n }\\n return snap;\\n}\\n\\n/// Two-layer staleness check.\\n///\\n/// Layer 1 (fast): stat each dep file, compare mtime against build_at.\\n/// If all mtimes <= build_at → nothing changed, return false immediately.\\n///\\n/// Layer 2 (precise): for files with mtime > build_at, re-hash their content.\\n/// If the hash matches the stored hash → file was touched but not modified.\\n/// If any hash differs → truly changed, return true.\\nstatic bool deps_changed(const PathPool& pool, const DepsSnapshot& snap) {\\n for(std::size_t i = 0; i < snap.path_ids.size(); ++i) {\\n auto path = pool.resolve(snap.path_ids[i]);\\n llvm::sys::fs::file_status status;\\n if(auto ec = llvm::sys::fs::status(path, status)) {\\n // File disappeared — definitely changed.\\n if(snap.hashes[i] != 0)\\n return true;\\n continue;\\n }\\n\\n // Layer 1: mtime check (cheap, stat only).\\n auto current_mtime = llvm::sys::toTimeT(status.getLastModificationTime());\\n if(current_mtime <= snap.build_at)\\n continue;\\n\\n // Layer 2: mtime is newer — re-hash content to confirm actual change.\\n auto current_hash = hash_file(path);\\n if(current_hash != snap.hashes[i])\\n return true;\\n }\\n return false;\\n}\\n\\nMasterServer::MasterServer(et::event_loop& loop, et::ipc::JsonPeer& peer, std::string self_path) :\\n loop(loop), peer(peer), pool(loop), self_path(std::move(self_path)) {}\\n\\nMasterServer::~MasterServer() {\\n if(compile_graph) {\\n compile_graph->cancel_all();\\n }\\n}\\n\\nstd::string MasterServer::uri_to_path(const std::string& uri) {\\n auto parsed = lsp::URI::parse(uri);\\n if(parsed.has_value()) {\\n auto path = parsed->file_path();\\n if(path.has_value()) {\\n return std::move(*path);\\n }\\n }\\n return uri;\\n}\\n\\nvoid MasterServer::publish_diagnostics(const std::string& uri,\\n int version,\\n const et::serde::RawValue& diagnostics_json) {\\n std::vector<protocol::Diagnostic> diagnostics;\\n if(!diagnostics_json.empty()) {\\n auto status = et::serde::json::from_json(diagnostics_json.data, diagnostics);\\n if(!status) {\\n LOG_WARN(\\\"Failed to deserialize diagnostics JSON for {}\\\", uri);\\n }\\n }\\n protocol::PublishDiagnosticsParams params;\\n params.uri = uri;\\n params.version = version;\\n params.diagnostics = std::move(diagnostics);\\n peer.send_notification(params);\\n}\\n\\nvoid MasterServer::clear_diagnostics(const std::string& uri) {\\n protocol::PublishDiagnosticsParams params;\\n params.uri = uri;\\n params.diagnostics = {};\\n peer.send_notification(params);\\n}\\n\\n/// Serializable cache structures for cache.json persistence.\\n/// Paths are stored in a shared table and referenced by index to avoid\\n/// redundant storage (a single file can depend on thousands of headers,\\n/// many of which are shared across entries).\\nnamespace {\\n\\nstruct CacheDepEntry {\\n std::uint32_t path; // index into CacheData::paths\\n std::uint64_t hash;\\n};\\n\\nstruct CachePCHEntry {\\n std::string filename;\\n std::uint32_t source_file; // index into CacheData::paths\\n std::uint64_t hash;\\n std::uint32_t bound;\\n std::int64_t build_at;\\n std::vector<CacheDepEntry> deps;\\n};\\n\\nstruct CachePCMEntry {\\n std::string filename;\\n std::uint32_t source_file; // index into CacheData::paths\\n std::string module_name;\\n std::int64_t build_at;\\n std::vector<CacheDepEntry> deps;\\n};\\n\\nstruct CacheData {\\n std::vector<std::string> paths;\\n std::vector<CachePCHEntry> pch;\\n std::vector<CachePCMEntry> pcm;\\n};\\n\\n} // namespace\\n\\nvoid MasterServer::load_cache() {\\n if(config.cache_dir.empty())\\n return;\\n\\n auto cache_path = path::join(config.cache_dir, \\\"cache\\\", \\\"cache.json\\\");\\n auto content = fs::read(cache_path);\\n if(!content) {\\n LOG_DEBUG(\\\"No cache.json found at {}\\\", cache_path);\\n return;\\n }\\n\\n CacheData data;\\n auto status = et::serde::json::from_json(*content, data);\\n if(!status) {\\n LOG_WARN(\\\"Failed to parse cache.json\\\");\\n return;\\n }\\n\\n auto resolve = [&](std::uint32_t idx) -> llvm::StringRef {\\n return idx < data.paths.size() ? llvm::StringRef(data.paths[idx]) : \\\"\\\";\\n };\\n\\n for(auto& entry: data.pch) {\\n auto pch_path = path::join(config.cache_dir, \\\"cache\\\", \\\"pch\\\", entry.filename);\\n auto source = resolve(entry.source_file);\\n if(!llvm::sys::fs::exists(pch_path) || source.empty())\\n continue;\\n\\n DepsSnapshot deps;\\n deps.build_at = entry.build_at;\\n for(auto& dep: entry.deps) {\\n auto dep_path = resolve(dep.path);\\n if(dep_path.empty())\\n continue;\\n deps.path_ids.push_back(path_pool.intern(dep_path));\\n deps.hashes.push_back(dep.hash);\\n }\\n\\n auto path_id = path_pool.intern(source);\\n auto& st = pch_states[path_id];\\n st.path = pch_path;\\n st.hash = entry.hash;\\n st.bound = entry.bound;\\n st.deps = std::move(deps);\\n\\n LOG_DEBUG(\\\"Loaded cached PCH: {} -> {}\\\", source, pch_path);\\n }\\n\\n for(auto& entry: data.pcm) {\\n auto pcm_path = path::join(config.cache_dir, \\\"cache\\\", \\\"pcm\\\", entry.filename);\\n auto source = resolve(entry.source_file);\\n if(!llvm::sys::fs::exists(pcm_path) || source.empty())\\n continue;\\n\\n DepsSnapshot deps;\\n deps.build_at = entry.build_at;\\n for(auto& dep: entry.deps) {\\n auto dep_path = resolve(dep.path);\\n if(dep_path.empty())\\n continue;\\n deps.path_ids.push_back(path_pool.intern(dep_path));\\n deps.hashes.push_back(dep.hash);\\n }\\n\\n auto path_id = path_pool.intern(source);\\n pcm_states[path_id] = {pcm_path, std::move(deps)};\\n pcm_paths[path_id] = pcm_path;\\n\\n LOG_DEBUG(\\\"Loaded cached PCM: {} (module {}) -> {}\\\", source, entry.module_name, pcm_path);\\n }\\n\\n LOG_INFO(\\\"Loaded cache.json: {} PCH entries, {} PCM entries\\\",\\n pch_states.size(),\\n pcm_states.size());\\n}\\n\\nvoid MasterServer::save_cache() {\\n if(config.cache_dir.empty())\\n return;\\n\\n CacheData data;\\n std::unordered_map<std::string, std::uint32_t> index_map;\\n\\n auto intern = [&](std::uint32_t runtime_path_id) -> std::uint32_t {\\n auto path = std::string(path_pool.resolve(runtime_path_id));\\n auto [it, inserted] =\\n index_map.try_emplace(path, static_cast<std::uint32_t>(data.paths.size()));\\n if(inserted) {\\n data.paths.push_back(path);\\n }\\n return it->second;\\n };\\n\\n for(auto& [path_id, st]: pch_states) {\\n if(st.path.empty())\\n continue;\\n\\n CachePCHEntry entry;\\n entry.filename = std::string(path::filename(st.path));\\n entry.source_file = intern(path_id);\\n entry.hash = st.hash;\\n entry.bound = st.bound;\\n entry.build_at = st.deps.build_at;\\n for(std::size_t i = 0; i < st.deps.path_ids.size(); ++i) {\\n entry.deps.push_back({intern(st.deps.path_ids[i]), st.deps.hashes[i]});\\n }\\n data.pch.push_back(std::move(entry));\\n }\\n\\n for(auto& [path_id, st]: pcm_states) {\\n if(st.path.empty())\\n continue;\\n\\n CachePCMEntry entry;\\n entry.filename = std::string(path::filename(st.path));\\n entry.source_file = intern(path_id);\\n auto mod_it = path_to_module.find(path_id);\\n entry.module_name = mod_it != path_to_module.end() ? mod_it->second : \\\"\\\";\\n entry.build_at = st.deps.build_at;\\n for(std::size_t i = 0; i < st.deps.path_ids.size(); ++i) {\\n entry.deps.push_back({intern(st.deps.path_ids[i]), st.deps.hashes[i]});\\n }\\n data.pcm.push_back(std::move(entry));\\n }\\n\\n auto json_str = et::serde::json::to_json(data);\\n if(!json_str) {\\n LOG_WARN(\\\"Failed to serialize cache.json\\\");\\n return;\\n }\\n\\n auto cache_path = path::join(config.cache_dir, \\\"cache\\\", \\\"cache.json\\\");\\n auto tmp_path = cache_path + \\\".tmp\\\";\\n auto write_result = fs::write(tmp_path, *json_str);\\n if(!write_result) {\\n LOG_WARN(\\\"Failed to write cache.json.tmp: {}\\\", write_result.error().message());\\n return;\\n }\\n auto rename_result = fs::rename(tmp_path, cache_path);\\n if(!rename_result) {\\n LOG_WARN(\\\"Failed to rename cache.json.tmp to cache.json: {}\\\",\\n rename_result.error().message());\\n }\\n}\\n\\nvoid MasterServer::cleanup_cache(int max_age_days) {\\n if(config.cache_dir.empty())\\n return;\\n\\n auto now = std::chrono::system_clock::now();\\n auto max_age = std::chrono::hours(max_age_days * 24);\\n\\n for(auto* subdir: {\\\"cache/pch\\\", \\\"cache/pcm\\\"}) {\\n auto dir = path::join(config.cache_dir, subdir);\\n std::error_code ec;\\n for(auto it = llvm::sys::fs::directory_iterator(dir, ec);\\n !ec && it != llvm::sys::fs::directory_iterator();\\n it.increment(ec)) {\\n llvm::sys::fs::file_status status;\\n if(auto stat_ec = llvm::sys::fs::status(it->path(), status))\\n continue;\\n\\n auto mtime = status.getLastModificationTime();\\n auto age = now - mtime;\\n if(age > max_age) {\\n llvm::sys::fs::remove(it->path());\\n LOG_DEBUG(\\\"Cleaned up stale cache file: {}\\\", it->path());\\n }\\n }\\n }\\n}\\n\\net::task<> MasterServer::load_workspace() {\\n if(workspace_root.empty())\\n co_return;\\n\\n // Create cache directory if configured\\n if(!config.cache_dir.empty()) {\\n auto ec = llvm::sys::fs::create_directories(config.cache_dir);\\n if(ec) {\\n LOG_WARN(\\\"Failed to create cache directory {}: {}\\\", config.cache_dir, ec.message());\\n } else {\\n LOG_INFO(\\\"Cache directory: {}\\\", config.cache_dir);\\n }\\n\\n // Create cache/pch/ and cache/pcm/ subdirectories\\n for(auto* subdir: {\\\"cache/pch\\\", \\\"cache/pcm\\\"}) {\\n auto dir = path::join(config.cache_dir, subdir);\\n auto ec2 = llvm::sys::fs::create_directories(dir);\\n if(ec2) {\\n LOG_WARN(\\\"Failed to create {}: {}\\\", dir, ec2.message());\\n }\\n }\\n\\n // Clean up stale files first, then load — load_cache() only restores\\n // entries still listed in cache.json, so cleanup won't delete live files.\\n cleanup_cache();\\n load_cache();\\n }\\n\\n // Search for compile_commands.json\\n std::string cdb_path;\\n\\n // If the config specifies a CDB path, use it\\n if(!config.compile_commands_path.empty()) {\\n if(llvm::sys::fs::exists(config.compile_commands_path)) {\\n cdb_path = config.compile_commands_path;\\n } else {\\n LOG_WARN(\\\"Configured compile_commands_path not found: {}\\\",\\n config.compile_commands_path);\\n }\\n }\\n\\n // Otherwise auto-detect in common locations\\n if(cdb_path.empty()) {\\n for(auto* subdir: {\\\"build\\\", \\\"cmake-build-debug\\\", \\\"cmake-build-release\\\", \\\"out\\\", \\\".\\\"}) {\\n auto candidate = path::join(workspace_root, subdir, \\\"compile_commands.json\\\");\\n if(llvm::sys::fs::exists(candidate)) {\\n cdb_path = std::move(candidate);\\n break;\\n }\\n }\\n }\\n\\n if(cdb_path.empty()) {\\n LOG_WARN(\\\"No compile_commands.json found in workspace {}\\\", workspace_root);\\n co_return;\\n }\\n\\n auto count = cdb.load(cdb_path);\\n LOG_INFO(\\\"Loaded CDB from {} with {} entries\\\", cdb_path, count);\\n\\n auto report = scan_dependency_graph(cdb, path_pool, dependency_graph);\\n\\n auto unresolved = report.includes_found - report.includes_resolved;\\n double accuracy =\\n report.includes_found > 0\\n ? 100.0 * static_cast<double>(report.includes_resolved) / report.includes_found\\n : 100.0;\\n LOG_INFO(\\n \\\"Dependency scan: {}ms, {} files ({} source + {} header), \\\" \\\"{} edges, {}/{} resolved ({:.1f}%), {} waves\\\",\\n report.elapsed_ms,\\n report.total_files,\\n report.source_files,\\n report.header_files,\\n report.total_edges,\\n report.includes_resolved,\\n report.includes_found,\\n accuracy,\\n report.waves);\\n if(unresolved > 0) {\\n LOG_WARN(\\\"{} unresolved includes\\\", unresolved);\\n }\\n\\n // Build reverse mapping: path_id -> module name.\\n for(auto& [module_name, path_ids]: dependency_graph.modules()) {\\n for(auto path_id: path_ids) {\\n path_to_module[path_id] = module_name.str();\\n }\\n }\\n\\n // Load persisted index from disk.\\n load_index();\\n\\n // Build index queue from CDB entries (all source files).\\n // CDB entries use the CDB's internal path_ids; convert to server path_ids.\\n if(config.enable_indexing) {\\n for(auto& entry: cdb.get_entries()) {\\n auto file = cdb.resolve_path(entry.file);\\n auto server_id = path_pool.intern(file);\\n index_queue.push_back(server_id);\\n }\\n if(!index_queue.empty()) {\\n LOG_INFO(\\\"Queued {} files for background indexing\\\", index_queue.size());\\n for(auto sid: index_queue) {\\n LOG_INFO(\\\" queue entry: server_path_id={} path='{}'\\\", sid, path_pool.resolve(sid));\\n }\\n schedule_indexing();\\n }\\n }\\n\\n if(path_to_module.empty()) {\\n LOG_INFO(\\\"No C++20 modules detected, skipping CompileGraph\\\");\\n co_return;\\n }\\n\\n // Lazy dependency resolver: scans a module file on demand to discover imports.\\n auto resolve = [this](std::uint32_t path_id) -> llvm::SmallVector<std::uint32_t> {\\n auto file_path = path_pool.resolve(path_id);\\n auto results = cdb.lookup(file_path, {.query_toolchain = true, .suppress_logging = true});\\n if(results.empty()) {\\n return {};\\n }\\n\\n auto& ctx = results[0];\\n auto scan_result = scan_precise(ctx.arguments, ctx.directory);\\n\\n llvm::SmallVector<std::uint32_t> deps;\\n for(auto& mod_name: scan_result.modules) {\\n auto mod_ids = dependency_graph.lookup_module(mod_name);\\n if(!mod_ids.empty()) {\\n deps.push_back(mod_ids[0]);\\n }\\n }\\n\\n // Module implementation units implicitly depend on their interface unit.\\n if(!scan_result.module_name.empty() && !scan_result.is_interface_unit) {\\n auto mod_ids = dependency_graph.lookup_module(scan_result.module_name);\\n if(!mod_ids.empty()) {\\n deps.push_back(mod_ids[0]);\\n }\\n }\\n\\n return deps;\\n };\\n\\n // Dispatch: sends BuildPCM request to a stateless worker.\\n auto dispatch = [this](std::uint32_t path_id) -> et::task<bool> {\\n auto mod_it = path_to_module.find(path_id);\\n if(mod_it == path_to_module.end()) {\\n co_return false;\\n }\\n\\n auto file_path = std::string(path_pool.resolve(path_id));\\n\\n worker::BuildPCMParams pcm_params;\\n pcm_params.file = file_path;\\n if(!fill_compile_args(file_path, pcm_params.directory, pcm_params.arguments)) {\\n co_return false;\\n }\\n\\n // Compute deterministic content-addressed PCM path.\\n // Replace ':' with '-' in module name for filesystem safety.\\n // Hash includes file path AND compile arguments so that argument\\n // changes (e.g. -DFOO) invalidate the cached PCM.\\n auto safe_module_name = mod_it->second;\\n std::ranges::replace(safe_module_name, ':', '-');\\n std::string hash_input = file_path;\\n for(auto& arg: pcm_params.arguments) {\\n hash_input += arg;\\n }\\n auto args_hash = llvm::xxh3_64bits(llvm::StringRef(hash_input));\\n auto pcm_filename = std::format(\\\"{}-{:016x}.pcm\\\", safe_module_name, args_hash);\\n auto pcm_path = path::join(config.cache_dir, \\\"cache\\\", \\\"pcm\\\", pcm_filename);\\n\\n // Check if cached PCM is still valid.\\n if(auto pcm_it = pcm_states.find(path_id); pcm_it != pcm_states.end()) {\\n if(!pcm_it->second.path.empty() && llvm::sys::fs::exists(pcm_it->second.path) &&\\n !deps_changed(path_pool, pcm_it->second.deps)) {\\n pcm_paths[path_id] = pcm_it->second.path;\\n co_return true;\\n }\\n }\\n\\n pcm_params.module_name = mod_it->second;\\n pcm_params.output_path = pcm_path;\\n\\n // Clang needs ALL transitive PCM deps, not just direct imports.\\n for(auto& [pid, existing_pcm_path]: pcm_paths) {\\n auto dep_mod_it = path_to_module.find(pid);\\n if(dep_mod_it != path_to_module.end()) {\\n pcm_params.pcms[dep_mod_it->second] = existing_pcm_path;\\n }\\n }\\n\\n auto result = co_await pool.send_stateless(pcm_params);\\n if(!result.has_value() || !result.value().success) {\\n LOG_WARN(\\\"BuildPCM failed for module {}: {}\\\",\\n mod_it->second,\\n result.has_value() ? result.value().error : result.error().message);\\n co_return false;\\n }\\n\\n pcm_paths[path_id] = result.value().pcm_path;\\n pcm_states[path_id] = {result.value().pcm_path,\\n capture_deps_snapshot(path_pool, result.value().deps)};\\n LOG_INFO(\\\"Built PCM for module {}: {}\\\", mod_it->second, result.value().pcm_path);\\n\\n // Persist cache metadata after successful build.\\n save_cache();\\n\\n co_return true;\\n };\\n\\n compile_graph = std::make_unique<CompileGraph>(std::move(dispatch), std::move(resolve));\\n LOG_INFO(\\\"CompileGraph initialized with {} module(s)\\\", path_to_module.size());\\n}\\n\\nbool MasterServer::fill_compile_args(llvm::StringRef path,\\n std::string& directory,\\n std::vector<std::string>& arguments) {\\n auto results = cdb.lookup(path, {.query_toolchain = true});\\n if(results.empty()) {\\n LOG_WARN(\\\"No CDB entry for {}\\\", path);\\n return false;\\n }\\n auto& ctx = results.front();\\n directory = ctx.directory.str();\\n arguments.clear();\\n for(auto* arg: ctx.arguments) {\\n arguments.emplace_back(arg);\\n }\\n return true;\\n}\\n\\net::task<bool> MasterServer::ensure_pch(std::uint32_t path_id,\\n llvm::StringRef path,\\n const std::string& text,\\n const std::string& directory,\\n const std::vector<std::string>& arguments) {\\n auto bound = compute_preamble_bound(text);\\n if(bound == 0) {\\n // No preamble directives — PCH would be empty. Clear any stale entry.\\n pch_states.erase(path_id);\\n co_return true;\\n }\\n\\n auto preamble_hash = llvm::xxh3_64bits(llvm::StringRef(text).substr(0, bound));\\n\\n // Deterministic content-addressed PCH path.\\n auto pch_path =\\n path::join(config.cache_dir, \\\"cache\\\", \\\"pch\\\", std::format(\\\"{:016x}.pch\\\", preamble_hash));\\n\\n // Reuse existing PCH if preamble content and deps haven't changed.\\n if(auto it = pch_states.find(path_id); it != pch_states.end()) {\\n auto& st = it->second;\\n if(st.hash == preamble_hash && !st.path.empty() && !deps_changed(path_pool, st.deps)) {\\n st.bound = bound;\\n co_return true;\\n }\\n }\\n\\n // Preamble incomplete (user still typing) — defer rebuild, reuse old PCH if available.\\n if(!is_preamble_complete(text, bound)) {\\n LOG_DEBUG(\\\"Preamble incomplete for {}, deferring PCH rebuild\\\", path);\\n co_return pch_states.count(path_id) && !pch_states[path_id].path.empty();\\n }\\n\\n // If another coroutine is already building PCH for this file, wait for it.\\n if(auto it = pch_states.find(path_id); it != pch_states.end() && it->second.building) {\\n co_await it->second.building->wait();\\n co_return !pch_states[path_id].path.empty();\\n }\\n\\n // Register in-flight build so concurrent requests wait on us.\\n auto completion = std::make_shared<et::event>();\\n pch_states[path_id].building = completion;\\n\\n // Build a new PCH via stateless worker.\\n worker::BuildPCHParams pch_params;\\n pch_params.file = std::string(path);\\n pch_params.directory = directory;\\n pch_params.arguments = arguments;\\n pch_params.content = text;\\n pch_params.preamble_bound = bound;\\n pch_params.output_path = pch_path;\\n\\n LOG_DEBUG(\\\"Building PCH for {}, bound={}, output={}\\\", path, bound, pch_path);\\n\\n auto result = co_await pool.send_stateless(pch_params);\\n\\n if(!result.has_value() || !result.value().success) {\\n LOG_WARN(\\\"PCH build failed for {}: {}\\\",\\n path,\\n result.has_value() ? result.value().error : result.error().message);\\n pch_states[path_id].building.reset();\\n completion->set();\\n co_return false;\\n }\\n\\n // Update state — no need to delete old file; content-addressed names differ\\n // when content differs, and the 7-day cleanup handles orphaned files.\\n auto& st = pch_states[path_id];\\n st.path = result.value().pch_path;\\n st.bound = bound;\\n st.hash = preamble_hash;\\n st.deps = capture_deps_snapshot(path_pool, result.value().deps);\\n st.building.reset();\\n\\n LOG_INFO(\\\"PCH built for {}: {}\\\", path, result.value().pch_path);\\n\\n // Persist cache metadata after successful build.\\n save_cache();\\n\\n completion->set();\\n co_return true;\\n}\\n\\n/// Compile module dependencies, build/reuse PCH, and fill PCM paths.\\n/// Shared preparation step used by both ensure_compiled() (stateful path)\\n/// and forward_stateless() (completion/signatureHelp path).\\net::task<bool> MasterServer::ensure_deps(std::uint32_t path_id,\\n llvm::StringRef path,\\n const std::string& text,\\n const std::string& directory,\\n const std::vector<std::string>& arguments,\\n std::pair<std::string, uint32_t>& pch,\\n std::unordered_map<std::string, std::string>& pcms) {\\n // Compile C++20 module dependencies (PCMs).\\n if(compile_graph && !co_await compile_graph->compile_deps(path_id)) {\\n co_return false;\\n }\\n\\n // Scan buffer text for module imports that might not be in compile_graph yet.\\n // When a user adds `import std;` without saving, the compile_graph (disk-based)\\n // doesn't know about the new dependency. Scan the in-memory text to find them.\\n {\\n auto scan_result = scan(text);\\n for(auto& mod_name: scan_result.modules) {\\n if(mod_name.empty()) {\\n continue;\\n }\\n bool found = false;\\n for(auto& [pid, name]: path_to_module) {\\n if(name == mod_name) {\\n // If PCM not already built, try to build it.\\n if(pcm_paths.find(pid) == pcm_paths.end()) {\\n if(compile_graph && compile_graph->has_unit(pid)) {\\n co_await compile_graph->compile_deps(pid);\\n }\\n }\\n found = true;\\n break;\\n }\\n }\\n if(!found) {\\n LOG_DEBUG(\\\"Buffer imports unknown module '{}', skipping\\\", mod_name);\\n }\\n }\\n }\\n\\n // Build or reuse PCH.\\n auto pch_ok = co_await ensure_pch(path_id, path, text, directory, arguments);\\n if(pch_ok) {\\n if(auto pch_it = pch_states.find(path_id); pch_it != pch_states.end()) {\\n pch = {pch_it->second.path, pch_it->second.bound};\\n }\\n }\\n\\n // Fill all available PCM paths so clang can resolve transitive imports.\\n // Exclude the file's own PCM to avoid \\\"multiple module declarations\\\".\\n for(auto& [pid, pcm_path]: pcm_paths) {\\n if(pid == path_id)\\n continue;\\n auto mod_it = path_to_module.find(pid);\\n if(mod_it != path_to_module.end()) {\\n pcms[mod_it->second] = pcm_path;\\n }\\n }\\n\\n co_return true;\\n}\\n\\n/// Pull-based compilation entry point for user-opened files.\\n///\\n/// Called lazily by forward_stateful() / forward_stateless() before every\\n/// feature request (hover, semantic tokens, etc.). Guarantees that when it\\n/// returns true the stateful worker assigned to `path_id` holds an up-to-date\\n/// AST and diagnostics have been published to the client.\\n///\\n/// Lifecycle overview (pull-based model):\\n///\\n/// didOpen / didChange only update DocumentState, mark ast_dirty\\n/// didSave mark dependents dirty, queue indexing\\n/// feature request arrives calls ensure_compiled() first\\n/// 1. Fast-path exit if AST is already clean (!ast_dirty).\\n/// 2. Compile any C++20 module dependencies (PCMs) via CompileGraph.\\n/// 3. Build / reuse the precompiled header (PCH) via ensure_pch().\\n/// 4. Send CompileParams to the stateful worker, which builds the AST.\\n/// 5. On success: publish diagnostics, clear ast_dirty, schedule indexing.\\n/// 6. On generation mismatch (user edited during compile): keep dirty,\\n/// the next feature request will trigger another compile cycle.\\n///\\n/// Only the opened file itself is remapped (its in-memory text is sent to the\\n/// worker); every other file is read from disk by the compiler.\\n///\\n/// Concurrency: multiple concurrent feature requests for the same file will\\n/// each call ensure_compiled(). The first one triggers the actual compilation;\\n/// subsequent ones observe ast_dirty == false after the first completes and\\n/// take the fast path. This is safe because forward_stateful serialises\\n/// requests per worker slot.\\net::task<bool> MasterServer::ensure_compiled(std::uint32_t path_id) {\\n auto it = documents.find(path_id);\\n if(it == documents.end()) {\\n LOG_DEBUG(\\\"ensure_compiled: doc not found for path_id={}\\\", path_id);\\n co_return false;\\n }\\n\\n auto& doc = it->second;\\n LOG_DEBUG(\\\"ensure_compiled: path_id={} version={} gen={} ast_dirty={}\\\",\\n path_id,\\n doc.version,\\n doc.generation,\\n doc.ast_dirty);\\n\\n if(!doc.ast_dirty) {\\n bool changed = false;\\n auto ast_deps_it = ast_deps.find(path_id);\\n if(ast_deps_it != ast_deps.end() && deps_changed(path_pool, ast_deps_it->second)) {\\n changed = true;\\n }\\n if(!changed) {\\n auto pch_it = pch_states.find(path_id);\\n if(pch_it != pch_states.end() && deps_changed(path_pool, pch_it->second.deps)) {\\n changed = true;\\n }\\n }\\n if(!changed) {\\n LOG_INFO(\\\"ensure_compiled: fast path, ast clean path_id={}\\\", path_id);\\n co_return true;\\n }\\n LOG_INFO(\\\"ensure_compiled: deps changed, marking dirty path_id={}\\\", path_id);\\n doc.ast_dirty = true;\\n }\\n LOG_INFO(\\\"ensure_compiled: ast_dirty=true, compiling={} path_id={}\\\",\\n (bool)doc.compiling,\\n path_id);\\n\\n // After co_await suspension points the iterator may be invalidated (the\\n // documents map could have been modified by didClose on another file).\\n auto recheck = [&]() -> bool {\\n it = documents.find(path_id);\\n return it != documents.end();\\n };\\n\\n // If another ensure_compiled() is already in flight for this file,\\n // wait for it to finish instead of sending a duplicate compile request.\\n // Loop because multiple waiters wake up simultaneously — only the first\\n // one should start a new compile; the rest must wait again.\\n while(it->second.compiling) {\\n LOG_INFO(\\\"ensure_compiled: waiting on in-flight compile for path_id={}\\\", path_id);\\n co_await it->second.compiling->wait();\\n LOG_INFO(\\\"ensure_compiled: in-flight compile done for path_id={}\\\", path_id);\\n if(!recheck())\\n co_return false;\\n if(!it->second.ast_dirty) {\\n LOG_INFO(\\\"ensure_compiled: ast clean after wait, path_id={}\\\", path_id);\\n co_return true;\\n }\\n // ast still dirty — loop back to check if someone else already\\n // started a new compile; only fall through if no one has.\\n }\\n\\n // Snapshot the generation counter *before* any co_await. After compilation\\n // we compare it with the current value to detect edits that arrived while\\n // we were suspended — if they differ, the result is stale and we must not\\n // mark the AST as clean.\\n auto gen = doc.generation;\\n\\n // Register in-flight compile so concurrent callers wait on us.\\n auto completion = std::make_shared<et::event>();\\n doc.compiling = completion;\\n LOG_INFO(\\\"ensure_compiled: starting compile path_id={} gen={}\\\", path_id, gen);\\n\\n // Signal waiters and clear compiling flag on any return path.\\n auto finish_compile = [&]() {\\n LOG_INFO(\\\"ensure_compiled: finish_compile path_id={} gen={}\\\", path_id, gen);\\n if(auto doc_it = documents.find(path_id); doc_it != documents.end()) {\\n doc_it->second.compiling.reset();\\n }\\n completion->set();\\n };\\n\\n auto file_path = std::string(path_pool.resolve(path_id));\\n auto uri = lsp::URI::from_file_path(file_path);\\n std::string uri_str = uri.has_value() ? uri->str() : file_path;\\n\\n // ── Phase 13: Module deps, PCH, PCM paths ─────────────────────────\\n worker::CompileParams params;\\n params.path = file_path;\\n if(!recheck()) {\\n finish_compile();\\n co_return false;\\n }\\n params.version = it->second.version;\\n params.text = it->second.text;\\n if(!fill_compile_args(path_pool.resolve(path_id), params.directory, params.arguments)) {\\n finish_compile();\\n co_return false;\\n }\\n\\n LOG_INFO(\\\"ensure_compiled: ensure_deps start path_id={} gen={}\\\", path_id, gen);\\n if(!co_await ensure_deps(path_id,\\n params.path,\\n params.text,\\n params.directory,\\n params.arguments,\\n params.pch,\\n params.pcms)) {\\n LOG_WARN(\\\"Dependency preparation failed for {}, skipping compile\\\", uri_str);\\n finish_compile();\\n co_return false;\\n }\\n\\n if(!recheck()) {\\n finish_compile();\\n co_return false;\\n }\\n\\n // ── Phase 4: Dispatch to stateful worker ────────────────────────────\\n //\\n // The stateful worker receives the full document text and compile args,\\n // builds the AST, and caches it for subsequent feature requests. The\\n // response carries diagnostics collected during compilation.\\n LOG_INFO(\\\"ensure_compiled: ensure_deps done, sending compile path_id={} gen={}\\\", path_id, gen);\\n auto result = co_await pool.send_stateful(path_id, params);\\n LOG_INFO(\\\"ensure_compiled: compile response received path_id={} gen={}\\\", path_id, gen);\\n\\n // Re-lookup: the document may have been closed while we were compiling.\\n it = documents.find(path_id);\\n if(it == documents.end()) {\\n finish_compile();\\n co_return false;\\n }\\n\\n auto& doc2 = it->second;\\n\\n // ── Phase 5: Handle result ──────────────────────────────────────────\\n //\\n // Generation mismatch means the user edited the file while we compiled.\\n // The AST we just built corresponds to an older version of the text, so\\n // we discard the diagnostics and leave ast_dirty == true. The next\\n // feature request will trigger another compile cycle with the latest text.\\n if(doc2.generation != gen) {\\n LOG_INFO(\\\"ensure_compiled: generation mismatch ({} vs {}) for {}\\\",\\n doc2.generation,\\n gen,\\n uri_str);\\n finish_compile();\\n co_return false;\\n }\\n\\n if(!result.has_value()) {\\n LOG_WARN(\\\"Compile failed for {}: {}\\\", uri_str, result.error().message);\\n // Clear stale diagnostics so the editor doesn't show errors from a\\n // previous successful compilation that no longer apply.\\n clear_diagnostics(uri_str);\\n finish_compile();\\n co_return false;\\n }\\n\\n LOG_INFO(\\\"ensure_compiled: success path_id={} gen={}, publishing diagnostics\\\", path_id, gen);\\n doc2.ast_dirty = false;\\n ast_deps[path_id] = capture_deps_snapshot(path_pool, result.value().deps);\\n finish_compile();\\n\\n // Publish diagnostics AFTER marking compile as done, so that concurrent\\n // forward_stateful() calls can proceed immediately instead of waiting\\n // for the (potentially large) diagnostics to be written to the client pipe.\\n publish_diagnostics(uri_str, doc2.version, result.value().diagnostics);\\n schedule_indexing();\\n co_return true;\\n}\\n\\n// =========================================================================\\n// Index integration\\n// =========================================================================\\n\\nvoid MasterServer::merge_index_result(const void* tu_index_data, std::size_t size) {\\n auto tu_index = index::TUIndex::from(tu_index_data);\\n\\n // Merge symbols into ProjectIndex, get TU-local path_id -> global path_id mapping.\\n auto file_ids_map = project_index.merge(tu_index);\\n\\n auto main_tu_path_id = static_cast<std::uint32_t>(tu_index.graph.paths.size() - 1);\\n\\n // Merge a single file's index into the corresponding MergedIndex shard.\\n auto merge_file_index = [&](std::uint32_t tu_path_id, index::FileIndex& file_idx) {\\n auto global_path_id = file_ids_map[tu_path_id];\\n auto& merged = merged_indices[global_path_id];\\n\\n if(tu_path_id == main_tu_path_id) {\\n // Main file (source file) gets a compilation context with include locations.\\n // Collect ALL include locations with path_ids remapped to project-level ids.\\n std::vector<index::IncludeLocation> include_locs;\\n for(auto& loc: tu_index.graph.locations) {\\n index::IncludeLocation remapped = loc;\\n remapped.path_id = file_ids_map[loc.path_id];\\n include_locs.push_back(remapped);\\n }\\n // Read the file content from disk for position mapping in queries.\\n auto file_path = project_index.path_pool.path(global_path_id);\\n llvm::StringRef file_content;\\n std::string file_content_storage;\\n auto buf = llvm::MemoryBuffer::getFile(file_path);\\n if(buf) {\\n file_content_storage = (*buf)->getBuffer().str();\\n file_content = file_content_storage;\\n }\\n merged.merge(global_path_id,\\n tu_index.built_at,\\n std::move(include_locs),\\n file_idx,\\n file_content);\\n } else {\\n // Header files get a header context keyed by include location.\\n std::uint32_t include_id = 0;\\n for(std::uint32_t i = 0; i < tu_index.graph.locations.size(); ++i) {\\n if(tu_index.graph.locations[i].path_id == tu_path_id) {\\n include_id = i;\\n break;\\n }\\n }\\n // Read header file content for position mapping in queries.\\n auto header_path = project_index.path_pool.path(global_path_id);\\n llvm::StringRef header_content;\\n std::string header_content_storage;\\n auto header_buf = llvm::MemoryBuffer::getFile(header_path);\\n if(header_buf) {\\n header_content_storage = (*header_buf)->getBuffer().str();\\n header_content = header_content_storage;\\n }\\n merged.merge(global_path_id, include_id, file_idx, header_content);\\n }\\n };\\n\\n // Merge from path_file_indices (deserialized TUIndex from IPC).\\n for(auto& [tu_path_id, file_idx]: tu_index.path_file_indices) {\\n merge_file_index(tu_path_id, file_idx);\\n }\\n\\n // Merge main file index.\\n merge_file_index(main_tu_path_id, tu_index.main_file_index);\\n\\n LOG_INFO(\\\"Merged TUIndex: {} paths, {} symbols, {} merged_shards\\\",\\n tu_index.graph.paths.size(),\\n tu_index.symbols.size(),\\n merged_indices.size());\\n for(auto& [pid, _]: merged_indices) {\\n LOG_INFO(\\\" shard proj_path_id={} path='{}'\\\", pid, project_index.path_pool.path(pid));\\n }\\n}\\n\\nvoid MasterServer::save_index() {\\n if(config.index_dir.empty())\\n return;\\n\\n auto ec = llvm::sys::fs::create_directories(config.index_dir);\\n if(ec) {\\n LOG_WARN(\\\"Failed to create index directory {}: {}\\\", config.index_dir, ec.message());\\n return;\\n }\\n\\n // Save ProjectIndex.\\n auto project_path = path::join(config.index_dir, \\\"project.idx\\\");\\n {\\n std::error_code write_ec;\\n llvm::raw_fd_ostream os(project_path, write_ec);\\n if(!write_ec) {\\n project_index.serialize(os);\\n LOG_INFO(\\\"Saved ProjectIndex to {}\\\", project_path);\\n } else {\\n LOG_WARN(\\\"Failed to save ProjectIndex: {}\\\", write_ec.message());\\n }\\n }\\n\\n // Save MergedIndex shards.\\n auto shards_dir = path::join(config.index_dir, \\\"shards\\\");\\n ec = llvm::sys::fs::create_directories(shards_dir);\\n if(ec) {\\n LOG_WARN(\\\"Failed to create shards directory: {}\\\", ec.message());\\n return;\\n }\\n\\n std::size_t saved = 0;\\n for(auto& [path_id, merged]: merged_indices) {\\n if(!merged.need_rewrite())\\n continue;\\n auto shard_path = path::join(shards_dir, std::to_string(path_id) + \\\".idx\\\");\\n std::error_code write_ec;\\n llvm::raw_fd_ostream os(shard_path, write_ec);\\n if(!write_ec) {\\n merged.serialize(os);\\n ++saved;\\n }\\n }\\n LOG_INFO(\\\"Saved {} MergedIndex shards (of {} total)\\\", saved, merged_indices.size());\\n}\\n\\nvoid MasterServer::load_index() {\\n if(config.index_dir.empty())\\n return;\\n\\n // Load ProjectIndex.\\n auto project_path = path::join(config.index_dir, \\\"project.idx\\\");\\n auto buf = llvm::MemoryBuffer::getFile(project_path);\\n if(buf) {\\n project_index = index::ProjectIndex::from((*buf)->getBufferStart());\\n LOG_INFO(\\\"Loaded ProjectIndex: {} symbols\\\", project_index.symbols.size());\\n }\\n\\n // Load MergedIndex shards.\\n auto shards_dir = path::join(config.index_dir, \\\"shards\\\");\\n std::error_code ec;\\n for(auto it = llvm::sys::fs::directory_iterator(shards_dir, ec);\\n !ec && it != llvm::sys::fs::directory_iterator();\\n it.increment(ec)) {\\n auto filename = llvm::sys::path::filename(it->path());\\n if(!filename.ends_with(\\\".idx\\\"))\\n continue;\\n\\n auto stem = filename.drop_back(4); // remove \\\".idx\\\"\\n std::uint32_t path_id = 0;\\n if(stem.getAsInteger(10, path_id))\\n continue;\\n\\n merged_indices[path_id] = index::MergedIndex::load(it->path());\\n }\\n\\n if(!merged_indices.empty()) {\\n LOG_INFO(\\\"Loaded {} MergedIndex shards\\\", merged_indices.size());\\n }\\n}\\n\\nvoid MasterServer::schedule_indexing() {\\n LOG_INFO(\\n \\\"schedule_indexing called: enable={} active={} scheduled={} queue_size={} queue_pos={}\\\",\\n config.enable_indexing,\\n indexing_active,\\n indexing_scheduled,\\n index_queue.size(),\\n index_queue_pos);\\n if(!config.enable_indexing || indexing_active || indexing_scheduled)\\n return;\\n indexing_scheduled = true;\\n\\n // Create or reset idle timer.\\n if(!index_idle_timer) {\\n index_idle_timer = std::make_shared<et::timer>(et::timer::create(loop));\\n }\\n index_idle_timer->start(std::chrono::milliseconds(config.idle_timeout_ms));\\n loop.schedule(run_background_indexing());\\n}\\n\\net::task<> MasterServer::run_background_indexing() {\\n // Wait for idle timeout before starting.\\n if(index_idle_timer) {\\n co_await index_idle_timer->wait();\\n }\\n indexing_scheduled = false;\\n\\n if(index_queue_pos >= index_queue.size()) {\\n LOG_DEBUG(\\\"Background indexing: queue exhausted\\\");\\n co_return;\\n }\\n\\n indexing_active = true;\\n std::size_t processed = 0;\\n\\n while(index_queue_pos < index_queue.size()) {\\n auto server_path_id = index_queue[index_queue_pos];\\n index_queue_pos++;\\n\\n auto file_path = std::string(path_pool.resolve(server_path_id));\\n\\n // Note: we do NOT skip open documents here. Index data is needed for\\n // cross-file features (references, call hierarchy, type hierarchy, etc.)\\n // regardless of whether the file is open.\\n\\n // Check if the index needs update by checking mtime against existing shard.\\n // If the file is not yet in the project_index path pool, it has never been\\n // indexed — always proceed. Only skip when we already have a shard that is\\n // still fresh.\\n auto cache_it = project_index.path_pool.find(file_path);\\n if(cache_it != project_index.path_pool.cache.end()) {\\n auto proj_path_id = cache_it->second;\\n auto merged_it = merged_indices.find(proj_path_id);\\n if(merged_it != merged_indices.end()) {\\n // Build path mapping for need_update check.\\n llvm::SmallVector<llvm::StringRef> path_mapping;\\n for(auto& p: project_index.path_pool.paths) {\\n path_mapping.push_back(p);\\n }\\n if(!merged_it->second.need_update(path_mapping))\\n continue;\\n }\\n }\\n\\n // Prepare IndexParams for the stateless worker.\\n worker::IndexParams params;\\n params.file = file_path;\\n if(!fill_compile_args(file_path, params.directory, params.arguments))\\n continue;\\n\\n // Fill PCM deps for module-aware indexing.\\n for(auto& [pid, pcm_path]: pcm_paths) {\\n auto mod_it = path_to_module.find(pid);\\n if(mod_it != path_to_module.end()) {\\n params.pcms[mod_it->second] = pcm_path;\\n }\\n }\\n\\n LOG_INFO(\\\"Background indexing: {}\\\", file_path);\\n\\n auto result = co_await pool.send_stateless(params);\\n if(result.has_value() && result.value().success && !result.value().tu_index_data.empty()) {\\n LOG_INFO(\\\"Background indexing got TUIndex for {}: {} bytes\\\",\\n file_path,\\n result.value().tu_index_data.size());\\n merge_index_result(result.value().tu_index_data.data(),\\n result.value().tu_index_data.size());\\n ++processed;\\n } else if(result.has_value() && !result.value().success) {\\n LOG_WARN(\\\"Background index failed for {}: {}\\\", file_path, result.value().error);\\n } else if(result.has_value() && result.value().tu_index_data.empty()) {\\n LOG_WARN(\\\"Background index returned empty TUIndex for {}\\\", file_path);\\n } else {\\n LOG_WARN(\\\"Background index IPC error for {}: {}\\\", file_path, result.error().message);\\n }\\n }\\n\\n indexing_active = false;\\n LOG_INFO(\\\"Background indexing complete: {} files processed\\\", processed);\\n\\n // Persist index to disk after a full pass.\\n save_index();\\n}\\n\\n// =========================================================================\\n// Include/import completion (handled in master)\\n// =========================================================================\\n\\nPreambleCompletionContext MasterServer::detect_completion_context(const std::string& text,\\n uint32_t offset) {\\n // Find the start of the line containing offset.\\n auto line_start = text.rfind('\\\\n', offset > 0 ? offset - 1 : 0);\\n line_start = (line_start == std::string::npos) ? 0 : line_start + 1;\\n\\n // Find the end of the line.\\n auto line_end = text.find('\\\\n', offset);\\n if(line_end == std::string::npos)\\n line_end = text.size();\\n\\n // Extract the line up to the cursor position.\\n auto line = llvm::StringRef(text).slice(line_start, offset);\\n\\n // Strip leading whitespace.\\n auto trimmed = line.ltrim();\\n\\n // Check for #include \\\"prefix or #include <prefix\\n if(trimmed.starts_with(\\\"#\\\")) {\\n auto directive = trimmed.drop_front(1).ltrim();\\n if(directive.consume_front(\\\"include\\\")) {\\n directive = directive.ltrim();\\n if(directive.consume_front(\\\"\\\\\\\"\\\")) {\\n return {CompletionContext::IncludeQuoted, directive.str()};\\n }\\n if(directive.consume_front(\\\"<\\\")) {\\n return {CompletionContext::IncludeAngled, directive.str()};\\n }\\n }\\n // Line starts with # but isn't #include — not a completion context.\\n return {};\\n }\\n\\n // Check for [export] import prefix (without trailing semicolon).\\n auto import_check = trimmed;\\n if(import_check.consume_front(\\\"export\\\")) {\\n import_check = import_check.ltrim();\\n }\\n if(import_check.consume_front(\\\"import\\\")) {\\n import_check = import_check.ltrim();\\n // Only treat as import if there's no semicolon in what follows.\\n auto rest_of_line = llvm::StringRef(text).slice(line_start, line_end);\\n if(!rest_of_line.contains(';')) {\\n return {CompletionContext::Import, import_check.str()};\\n }\\n }\\n\\n return {};\\n}\\n\\net::serde::RawValue MasterServer::complete_include(const PreambleCompletionContext& ctx,\\n llvm::StringRef path) {\\n std::string directory;\\n std::vector<std::string> arguments;\\n if(!fill_compile_args(path, directory, arguments))\\n return et::serde::RawValue{\\\"[]\\\"};\\n\\n // Convert arguments to const char* array.\\n std::vector<const char*> args_ptrs;\\n args_ptrs.reserve(arguments.size());\\n for(auto& arg: arguments) {\\n args_ptrs.push_back(arg.c_str());\\n }\\n\\n auto config = extract_search_config(args_ptrs, directory);\\n DirListingCache dir_cache;\\n auto resolved = resolve_search_config(config, dir_cache);\\n\\n // Determine search range based on context.\\n unsigned start_idx = 0;\\n if(ctx.kind == CompletionContext::IncludeAngled) {\\n start_idx = resolved.angled_start_idx;\\n }\\n\\n // Split prefix into dir_prefix and file_prefix if it contains '/'.\\n llvm::StringRef prefix_ref(ctx.prefix);\\n llvm::StringRef dir_prefix;\\n llvm::StringRef file_prefix = prefix_ref;\\n auto slash_pos = prefix_ref.rfind('/');\\n if(slash_pos != llvm::StringRef::npos) {\\n dir_prefix = prefix_ref.slice(0, slash_pos);\\n file_prefix = prefix_ref.slice(slash_pos + 1, llvm::StringRef::npos);\\n }\\n\\n std::vector<protocol::CompletionItem> items;\\n llvm::StringSet<> seen; // Deduplicate entries across search dirs.\\n\\n for(unsigned i = start_idx; i < resolved.dirs.size(); ++i) {\\n auto& search_dir = resolved.dirs[i];\\n\\n // If there's a dir_prefix, resolve the subdirectory.\\n const llvm::StringSet<>* entries = nullptr;\\n if(!dir_prefix.empty()) {\\n llvm::SmallString<256> sub_path(search_dir.path);\\n llvm::sys::path::append(sub_path, dir_prefix);\\n entries = resolve_dir(sub_path, dir_cache);\\n } else {\\n entries = search_dir.entries;\\n }\\n\\n if(!entries)\\n continue;\\n\\n for(auto& entry: *entries) {\\n auto name = entry.getKey();\\n if(!name.starts_with(file_prefix))\\n continue;\\n if(!seen.insert(name).second)\\n continue;\\n\\n // Check if this entry is a directory.\\n llvm::SmallString<256> full_path(search_dir.path);\\n if(!dir_prefix.empty()) {\\n llvm::sys::path::append(full_path, dir_prefix);\\n }\\n llvm::sys::path::append(full_path, name);\\n\\n bool is_dir = false;\\n llvm::sys::fs::is_directory(llvm::Twine(full_path), is_dir);\\n\\n protocol::CompletionItem item;\\n if(is_dir) {\\n item.label = (name + \\\"/\\\").str();\\n } else {\\n item.label = name.str();\\n }\\n item.kind = protocol::CompletionItemKind::File;\\n items.push_back(std::move(item));\\n }\\n }\\n\\n auto json = et::serde::json::to_json<et::ipc::lsp_config>(items);\\n return et::serde::RawValue{json ? std::move(*json) : \\\"[]\\\"};\\n}\\n\\net::serde::RawValue MasterServer::complete_import(const PreambleCompletionContext& ctx) {\\n std::vector<protocol::CompletionItem> items;\\n llvm::StringRef prefix_ref(ctx.prefix);\\n\\n for(auto& [path_id, module_name]: path_to_module) {\\n llvm::StringRef name_ref(module_name);\\n if(!name_ref.starts_with(prefix_ref))\\n continue;\\n\\n protocol::CompletionItem item;\\n item.label = module_name;\\n item.kind = protocol::CompletionItemKind::Module;\\n item.insert_text = module_name + \\\";\\\";\\n items.push_back(std::move(item));\\n }\\n\\n auto json = et::serde::json::to_json<et::ipc::lsp_config>(items);\\n return et::serde::RawValue{json ? std::move(*json) : \\\"[]\\\"};\\n}\\n\\n// =========================================================================\\n// Forwarding helpers\\n// =========================================================================\\n\\nusing serde_raw = et::serde::RawValue;\\n\\ntemplate <typename WorkerParams>\\nMasterServer::RawResult MasterServer::forward_stateful(const std::string& uri) {\\n auto path = uri_to_path(uri);\\n auto path_id = path_pool.intern(path);\\n\\n LOG_INFO(\\\"forward_stateful: request path={}\\\", path);\\n\\n if(!co_await ensure_compiled(path_id)) {\\n LOG_DEBUG(\\\"forward_stateful: ensure_compiled failed for {}\\\", path);\\n co_return serde_raw{\\\"null\\\"};\\n }\\n\\n WorkerParams wp;\\n wp.path = path;\\n\\n LOG_INFO(\\\"forward_stateful: sending feature to worker path={}\\\", path);\\n auto result = co_await pool.send_stateful(path_id, wp);\\n LOG_INFO(\\\"forward_stateful: feature response path={} ok={}\\\", path, result.has_value());\\n if(!result.has_value()) {\\n co_return serde_raw{};\\n }\\n co_return std::move(result.value());\\n}\\n\\ntemplate <typename WorkerParams>\\nMasterServer::RawResult MasterServer::forward_stateful(const std::string& uri,\\n const protocol::Position& position) {\\n auto path = uri_to_path(uri);\\n auto path_id = path_pool.intern(path);\\n\\n LOG_INFO(\\\"forward_stateful(pos): request path={} pos={}:{}\\\",\\n path,\\n position.line,\\n position.character);\\n\\n if(!co_await ensure_compiled(path_id)) {\\n LOG_INFO(\\\"forward_stateful(pos): ensure_compiled failed for {}\\\", path);\\n co_return serde_raw{\\\"null\\\"};\\n }\\n\\n WorkerParams wp;\\n wp.path = path;\\n\\n auto doc_it = documents.find(path_id);\\n if(doc_it != documents.end()) {\\n lsp::PositionMapper mapper(doc_it->second.text, lsp::PositionEncoding::UTF16);\\n auto offset = mapper.to_offset(position);\\n if(!offset)\\n co_return serde_raw{\\\"null\\\"};\\n wp.offset = *offset;\\n }\\n\\n LOG_INFO(\\\"forward_stateful(pos): sending feature to worker path={}\\\", path);\\n auto result = co_await pool.send_stateful(path_id, wp);\\n LOG_INFO(\\\"forward_stateful(pos): feature response path={} ok={}\\\", path, result.has_value());\\n if(!result.has_value()) {\\n co_return serde_raw{};\\n }\\n co_return std::move(result.value());\\n}\\n\\ntemplate <typename WorkerParams>\\nMasterServer::RawResult MasterServer::forward_stateless(const std::string& uri,\\n const protocol::Position& position) {\\n auto path = uri_to_path(uri);\\n auto path_id = path_pool.intern(path);\\n\\n LOG_DEBUG(\\\"forward_stateless: {} path={} pos={}:{}\\\",\\n \\\"request\\\",\\n path,\\n position.line,\\n position.character);\\n\\n auto doc_it = documents.find(path_id);\\n if(doc_it == documents.end()) {\\n LOG_DEBUG(\\\"forward_stateless: doc not found for {}\\\", path);\\n co_return serde_raw{};\\n }\\n\\n auto& doc = doc_it->second;\\n\\n WorkerParams wp;\\n wp.path = path;\\n wp.version = doc.version;\\n wp.text = doc.text;\\n if(!fill_compile_args(path, wp.directory, wp.arguments)) {\\n LOG_DEBUG(\\\"forward_stateless: no CDB for {}\\\", path);\\n co_return serde_raw{};\\n }\\n\\n // Ensure module deps, PCH, and PCM paths are ready for stateless compilation.\\n if(!co_await ensure_deps(path_id, path, wp.text, wp.directory, wp.arguments, wp.pch, wp.pcms)) {\\n LOG_DEBUG(\\\"forward_stateless: ensure_deps failed for {}\\\", path);\\n co_return serde_raw{};\\n }\\n\\n lsp::PositionMapper mapper(wp.text, lsp::PositionEncoding::UTF16);\\n auto offset = mapper.to_offset(position);\\n if(!offset)\\n co_return serde_raw{\\\"null\\\"};\\n wp.offset = *offset;\\n\\n auto result = co_await pool.send_stateless(wp);\\n if(!result.has_value()) {\\n LOG_DEBUG(\\\"forward_stateless: worker error for {}: {}\\\", path, result.error().message);\\n co_return serde_raw{};\\n }\\n LOG_DEBUG(\\\"forward_stateless: done {}\\\", path);\\n co_return std::move(result.value());\\n}\\n\\n// Serialize a value to a JSON RawValue using LSP config.\\ntemplate <typename T>\\nstatic serde_raw to_raw(const T& value) {\\n auto json = et::serde::json::to_json<et::ipc::lsp_config>(value);\\n return serde_raw{json ? std::move(*json) : \\\"null\\\"};\\n}\\n\\nMasterServer::RawResult MasterServer::query_index_relations(const std::string& uri,\\n const protocol::Position& position,\\n RelationKind kind) {\\n auto path = uri_to_path(uri);\\n auto server_path_id = path_pool.intern(path);\\n\\n // Need document text for position-to-offset conversion.\\n auto doc_it = documents.find(server_path_id);\\n if(doc_it == documents.end())\\n co_return serde_raw{\\\"null\\\"};\\n\\n lsp::PositionMapper mapper(doc_it->second.text, lsp::PositionEncoding::UTF16);\\n auto offset_opt = mapper.to_offset(position);\\n if(!offset_opt)\\n co_return serde_raw{\\\"null\\\"};\\n auto offset = *offset_opt;\\n\\n // Find the project-level path_id for this file.\\n auto proj_cache_it = project_index.path_pool.find(path);\\n if(proj_cache_it == project_index.path_pool.cache.end()) {\\n LOG_DEBUG(\\\"query_index_relations: path '{}' not in project_index\\\", path);\\n co_return serde_raw{\\\"null\\\"};\\n }\\n auto proj_path_id = proj_cache_it->second;\\n\\n // Lookup occurrence at offset in this file's MergedIndex.\\n auto merged_it = merged_indices.find(proj_path_id);\\n if(merged_it == merged_indices.end()) {\\n LOG_DEBUG(\\\"query_index_relations: no MergedIndex for proj_path_id={}\\\", proj_path_id);\\n co_return serde_raw{\\\"null\\\"};\\n }\\n\\n index::SymbolHash symbol_hash = 0;\\n merged_it->second.lookup(offset, [&](const index::Occurrence& o) {\\n symbol_hash = o.target;\\n return false; // stop after first match\\n });\\n\\n if(symbol_hash == 0) {\\n LOG_DEBUG(\\\"query_index_relations: no occurrence at offset {} in '{}'\\\", offset, path);\\n co_return serde_raw{\\\"null\\\"};\\n }\\n\\n // Get reference files from ProjectIndex.\\n auto sym_it = project_index.symbols.find(symbol_hash);\\n if(sym_it == project_index.symbols.end()) {\\n LOG_DEBUG(\\\"query_index_relations: symbol {} not in project_index\\\", symbol_hash);\\n co_return serde_raw{\\\"null\\\"};\\n }\\n\\n // Query each referenced file's MergedIndex for relations of the requested kind.\\n std::vector<protocol::Location> locations;\\n\\n for(auto file_id: sym_it->second.reference_files) {\\n auto file_merged_it = merged_indices.find(file_id);\\n if(file_merged_it == merged_indices.end())\\n continue;\\n\\n auto file_path = project_index.path_pool.path(file_id);\\n auto file_uri = lsp::URI::from_file_path(file_path);\\n if(!file_uri)\\n continue;\\n auto file_uri_str = file_uri->str();\\n\\n // Use stored content from MergedIndex for offset-to-position conversion.\\n auto file_content = file_merged_it->second.content();\\n if(file_content.empty())\\n continue;\\n\\n lsp::PositionMapper file_mapper(file_content, lsp::PositionEncoding::UTF16);\\n\\n file_merged_it->second.lookup(symbol_hash, kind, [&](const index::Relation& r) {\\n auto start = file_mapper.to_position(r.range.begin);\\n auto end = file_mapper.to_position(r.range.end);\\n if(start && end) {\\n protocol::Location loc;\\n loc.uri = file_uri_str;\\n loc.range = protocol::Range{*start, *end};\\n locations.push_back(std::move(loc));\\n }\\n return true; // continue collecting\\n });\\n }\\n\\n if(locations.empty())\\n co_return serde_raw{\\\"null\\\"};\\n\\n co_return to_raw(locations);\\n}\\n\\nprotocol::SymbolKind MasterServer::to_lsp_symbol_kind(SymbolKind kind) {\\n switch(kind) {\\n case SymbolKind::Namespace: return protocol::SymbolKind::Namespace;\\n case SymbolKind::Class: return protocol::SymbolKind::Class;\\n case SymbolKind::Struct: return protocol::SymbolKind::Struct;\\n case SymbolKind::Union: return protocol::SymbolKind::Class;\\n case SymbolKind::Enum: return protocol::SymbolKind::Enum;\\n case SymbolKind::Type: return protocol::SymbolKind::TypeParameter;\\n case SymbolKind::Field: return protocol::SymbolKind::Field;\\n case SymbolKind::EnumMember: return protocol::SymbolKind::EnumMember;\\n case SymbolKind::Function: return protocol::SymbolKind::Function;\\n case SymbolKind::Method: return protocol::SymbolKind::Method;\\n case SymbolKind::Variable: return protocol::SymbolKind::Variable;\\n case SymbolKind::Parameter: return protocol::SymbolKind::Variable;\\n case SymbolKind::Macro: return protocol::SymbolKind::Function;\\n case SymbolKind::Concept: return protocol::SymbolKind::Interface;\\n case SymbolKind::Module: return protocol::SymbolKind::Module;\\n case SymbolKind::Operator: return protocol::SymbolKind::Operator;\\n default: return protocol::SymbolKind::Variable;\\n }\\n}\\n\\net::task<std::optional<SymbolInfo>>\\n MasterServer::lookup_symbol_at_position(const std::string& uri,\\n const protocol::Position& position) {\\n auto path = uri_to_path(uri);\\n auto server_path_id = path_pool.intern(path);\\n\\n // Need document text for position-to-offset conversion.\\n auto doc_it = documents.find(server_path_id);\\n if(doc_it == documents.end())\\n co_return std::nullopt;\\n\\n lsp::PositionMapper mapper(doc_it->second.text, lsp::PositionEncoding::UTF16);\\n auto offset_opt = mapper.to_offset(position);\\n if(!offset_opt)\\n co_return std::nullopt;\\n auto offset = *offset_opt;\\n\\n // Find the project-level path_id for this file.\\n auto proj_cache_it = project_index.path_pool.find(path);\\n if(proj_cache_it == project_index.path_pool.cache.end()) {\\n LOG_WARN(\\\"lookup_symbol: path '{}' not in project_index (pool has {} entries)\\\",\\n path,\\n project_index.path_pool.paths.size());\\n co_return std::nullopt;\\n }\\n auto proj_path_id = proj_cache_it->second;\\n\\n // Lookup occurrence at offset in this file's MergedIndex.\\n auto merged_it = merged_indices.find(proj_path_id);\\n if(merged_it == merged_indices.end()) {\\n LOG_WARN(\\\"lookup_symbol: no MergedIndex for proj_path_id={} (have {} shards)\\\",\\n proj_path_id,\\n merged_indices.size());\\n co_return std::nullopt;\\n }\\n\\n index::SymbolHash symbol_hash = 0;\\n index::Range occ_range{};\\n merged_it->second.lookup(offset, [&](const index::Occurrence& o) {\\n symbol_hash = o.target;\\n occ_range = o.range;\\n return false; // stop after first match\\n });\\n\\n if(symbol_hash == 0) {\\n LOG_WARN(\\\"lookup_symbol: no occurrence at offset {} in '{}'\\\", offset, path);\\n co_return std::nullopt;\\n }\\n\\n // Get symbol info from ProjectIndex.\\n auto sym_it = project_index.symbols.find(symbol_hash);\\n if(sym_it == project_index.symbols.end())\\n co_return std::nullopt;\\n\\n // Convert occurrence range to LSP Range.\\n auto start = mapper.to_position(occ_range.begin);\\n auto end = mapper.to_position(occ_range.end);\\n if(!start || !end)\\n co_return std::nullopt;\\n\\n SymbolInfo info;\\n info.hash = symbol_hash;\\n info.name = sym_it->second.name;\\n info.kind = sym_it->second.kind;\\n info.uri = uri;\\n info.range = protocol::Range{*start, *end};\\n co_return info;\\n}\\n\\nstd::optional<protocol::Location>\\n MasterServer::find_symbol_definition_location(index::SymbolHash hash) {\\n auto sym_it = project_index.symbols.find(hash);\\n if(sym_it == project_index.symbols.end())\\n return std::nullopt;\\n\\n // Search reference files for a Definition relation.\\n for(auto file_id: sym_it->second.reference_files) {\\n auto file_merged_it = merged_indices.find(file_id);\\n if(file_merged_it == merged_indices.end())\\n continue;\\n\\n auto file_path = project_index.path_pool.path(file_id);\\n auto file_uri = lsp::URI::from_file_path(file_path);\\n if(!file_uri)\\n continue;\\n\\n // Use stored content from MergedIndex for offset-to-position conversion.\\n auto file_content = file_merged_it->second.content();\\n if(file_content.empty())\\n continue;\\n lsp::PositionMapper file_mapper(file_content, lsp::PositionEncoding::UTF16);\\n\\n std::optional<protocol::Location> result;\\n file_merged_it->second.lookup(hash,\\n RelationKind::Definition,\\n [&](const index::Relation& r) {\\n auto start = file_mapper.to_position(r.range.begin);\\n auto end = file_mapper.to_position(r.range.end);\\n if(start && end) {\\n protocol::Location loc;\\n loc.uri = file_uri->str();\\n loc.range = protocol::Range{*start, *end};\\n result = std::move(loc);\\n return false; // found it, stop\\n }\\n return true;\\n });\\n\\n if(result)\\n return result;\\n }\\n\\n return std::nullopt;\\n}\\n\\nprotocol::CallHierarchyItem MasterServer::build_call_hierarchy_item(const SymbolInfo& info) {\\n protocol::CallHierarchyItem item;\\n item.name = info.name;\\n item.kind = to_lsp_symbol_kind(info.kind);\\n item.uri = info.uri;\\n item.range = info.range;\\n item.selection_range = info.range;\\n // Store the symbol hash in data for later use in incoming/outgoing calls.\\n item.data = protocol::LSPAny(static_cast<std::int64_t>(info.hash));\\n return item;\\n}\\n\\nprotocol::TypeHierarchyItem MasterServer::build_type_hierarchy_item(const SymbolInfo& info) {\\n protocol::TypeHierarchyItem item;\\n item.name = info.name;\\n item.kind = to_lsp_symbol_kind(info.kind);\\n item.uri = info.uri;\\n item.range = info.range;\\n item.selection_range = info.range;\\n item.data = protocol::LSPAny(static_cast<std::int64_t>(info.hash));\\n return item;\\n}\\n\\net::task<std::optional<SymbolInfo>>\\n MasterServer::resolve_hierarchy_item(const std::string& uri,\\n const protocol::Range& range,\\n const std::optional<protocol::LSPAny>& data) {\\n // Try to extract symbol hash from the stored data field first.\\n if(data) {\\n if(auto* int_val = std::get_if<std::int64_t>(&*data)) {\\n auto hash = static_cast<index::SymbolHash>(*int_val);\\n auto sym_it = project_index.symbols.find(hash);\\n if(sym_it != project_index.symbols.end()) {\\n SymbolInfo info;\\n info.hash = hash;\\n info.name = sym_it->second.name;\\n info.kind = sym_it->second.kind;\\n info.uri = uri;\\n info.range = range;\\n co_return info;\\n }\\n }\\n }\\n\\n // Fallback: re-lookup from position (requires document to be open).\\n co_return co_await lookup_symbol_at_position(uri, range.start);\\n}\\n\\nvoid MasterServer::register_handlers() {\\n // === initialize ===\\n peer.on_request([this](RequestContext& ctx, const protocol::InitializeParams& params)\\n -> RequestResult<protocol::InitializeParams> {\\n if(lifecycle != ServerLifecycle::Uninitialized) {\\n co_return et::outcome_error(protocol::Error{\\\"Server already initialized\\\"});\\n }\\n\\n // Extract workspace root\\n auto& init = params.lsp__initialize_params;\\n if(init.root_uri.has_value()) {\\n workspace_root = uri_to_path(*init.root_uri);\\n }\\n\\n lifecycle = ServerLifecycle::Initialized;\\n\\n LOG_INFO(\\\"Initialized with workspace: {}\\\", workspace_root);\\n\\n // Build capabilities\\n protocol::InitializeResult result;\\n\\n // Text document sync: incremental\\n protocol::TextDocumentSyncOptions sync_opts;\\n sync_opts.open_close = true;\\n sync_opts.change = protocol::TextDocumentSyncKind::Incremental;\\n sync_opts.save = protocol::variant<protocol::boolean, protocol::SaveOptions>{true};\\n result.capabilities.text_document_sync = std::move(sync_opts);\\n\\n // Feature capabilities\\n result.capabilities.hover_provider = true;\\n result.capabilities.completion_provider = protocol::CompletionOptions{};\\n result.capabilities.signature_help_provider = protocol::SignatureHelpOptions{};\\n result.capabilities.definition_provider = true;\\n result.capabilities.references_provider = true;\\n result.capabilities.document_symbol_provider = true;\\n result.capabilities.document_link_provider = protocol::DocumentLinkOptions{};\\n result.capabilities.code_action_provider = true;\\n result.capabilities.folding_range_provider = true;\\n result.capabilities.inlay_hint_provider = true;\\n result.capabilities.call_hierarchy_provider = true;\\n result.capabilities.type_hierarchy_provider = true;\\n result.capabilities.workspace_symbol_provider = true;\\n\\n // Semantic tokens\\n protocol::SemanticTokensOptions sem_opts;\\n {\\n auto lower_first = [](std::string_view name) -> std::string {\\n std::string s(name);\\n if(!s.empty()) {\\n s[0] = static_cast<char>(std::tolower(static_cast<unsigned char>(s[0])));\\n }\\n return s;\\n };\\n\\n auto to_names = [&](auto names) {\\n return std::ranges::to<std::vector>(names | std::views::transform(lower_first));\\n };\\n\\n sem_opts.legend = protocol::SemanticTokensLegend{\\n to_names(refl::reflection<SymbolKind::Kind>::member_names),\\n to_names(refl::reflection<SymbolModifiers::Kind>::member_names),\\n };\\n }\\n sem_opts.full = true;\\n result.capabilities.semantic_tokens_provider = std::move(sem_opts);\\n\\n // Server info\\n protocol::ServerInfo info;\\n info.name = \\\"clice\\\";\\n info.version = \\\"0.1.0\\\";\\n result.server_info = std::move(info);\\n\\n co_return result;\\n });\\n\\n // === initialized ===\\n peer.on_notification([this](const protocol::InitializedParams& params) {\\n // Load configuration from workspace\\n config = CliceConfig::load_from_workspace(workspace_root);\\n\\n // Switch master to file logging under a session-timestamped directory\\n if(!config.logging_dir.empty()) {\\n auto now = std::chrono::system_clock::now();\\n auto pid = llvm::sys::Process::getProcessId();\\n auto session_dir =\\n path::join(config.logging_dir, std::format(\\\"{:%Y-%m-%d_%H-%M-%S}_{}\\\", now, pid));\\n logging::file_logger(\\\"master\\\", session_dir, logging::options);\\n session_log_dir = session_dir;\\n }\\n\\n LOG_INFO(\\\"Server ready (stateful={}, stateless={}, idle={}ms)\\\",\\n config.stateful_worker_count,\\n config.stateless_worker_count,\\n config.idle_timeout_ms);\\n\\n // Start worker pool\\n WorkerPoolOptions pool_opts;\\n pool_opts.self_path = self_path;\\n pool_opts.stateful_count = config.stateful_worker_count;\\n pool_opts.stateless_count = config.stateless_worker_count;\\n pool_opts.worker_memory_limit = config.worker_memory_limit;\\n pool_opts.log_dir = session_log_dir;\\n if(!pool.start(pool_opts)) {\\n LOG_ERROR(\\\"Failed to start worker pool\\\");\\n return;\\n }\\n\\n lifecycle = ServerLifecycle::Ready;\\n\\n // Load CDB in background\\n loop.schedule(load_workspace());\\n });\\n\\n // === shutdown ===\\n peer.on_request(\\n [this](RequestContext& ctx,\\n const protocol::ShutdownParams& params) -> RequestResult<protocol::ShutdownParams> {\\n lifecycle = ServerLifecycle::ShuttingDown;\\n LOG_INFO(\\\"Shutdown requested\\\");\\n co_return nullptr;\\n });\\n\\n // === exit ===\\n peer.on_notification([this](const protocol::ExitParams& params) {\\n lifecycle = ServerLifecycle::Exited;\\n LOG_INFO(\\\"Exit notification received\\\");\\n\\n // Persist index and cache state before stopping.\\n save_index();\\n save_cache();\\n\\n // Graceful shutdown: cancel compilations, stop workers, then stop loop\\n loop.schedule([this]() -> et::task<> {\\n co_await pool.stop();\\n loop.stop();\\n }());\\n });\\n\\n // === textDocument/didOpen ===\\n peer.on_notification([this](const protocol::DidOpenTextDocumentParams& params) {\\n if(lifecycle != ServerLifecycle::Ready)\\n return;\\n\\n auto& td = params.text_document;\\n auto path = uri_to_path(td.uri);\\n auto path_id = path_pool.intern(path);\\n\\n auto& doc = documents[path_id];\\n doc.version = td.version;\\n doc.text = td.text;\\n doc.generation++;\\n\\n LOG_DEBUG(\\\"didOpen: {} (v{})\\\", path, td.version);\\n });\\n\\n // === textDocument/didChange ===\\n peer.on_notification([this](const protocol::DidChangeTextDocumentParams& params) {\\n if(lifecycle != ServerLifecycle::Ready)\\n return;\\n\\n auto path = uri_to_path(params.text_document.uri);\\n auto path_id = path_pool.intern(path);\\n\\n auto it = documents.find(path_id);\\n if(it == documents.end())\\n return;\\n\\n auto& doc = it->second;\\n doc.version = params.text_document.version;\\n\\n // Apply content changes.\\n for(auto& change: params.content_changes) {\\n std::visit(\\n [&](auto& c) {\\n using T = std::remove_cvref_t<decltype(c)>;\\n if constexpr(std::is_same_v<T,\\n protocol::TextDocumentContentChangeWholeDocument>) {\\n doc.text = c.text;\\n } else {\\n // Incremental change: replace range\\n auto& range = c.range;\\n lsp::PositionMapper mapper(doc.text, lsp::PositionEncoding::UTF16);\\n auto start = mapper.to_offset(range.start);\\n auto end = mapper.to_offset(range.end);\\n if(start && end && *start <= *end) {\\n doc.text.replace(*start, *end - *start, c.text);\\n }\\n }\\n },\\n change);\\n }\\n\\n doc.generation++;\\n doc.ast_dirty = true;\\n\\n LOG_DEBUG(\\\"didChange: path={} version={} gen={}\\\", path, doc.version, doc.generation);\\n\\n // Notify the owning stateful worker so it marks the document dirty\\n worker::DocumentUpdateParams update;\\n update.path = path;\\n update.version = doc.version;\\n pool.notify_stateful(path_id, update);\\n });\\n\\n // === textDocument/didClose ===\\n peer.on_notification([this](const protocol::DidCloseTextDocumentParams& params) {\\n if(lifecycle != ServerLifecycle::Ready)\\n return;\\n\\n auto path = uri_to_path(params.text_document.uri);\\n auto path_id = path_pool.intern(path);\\n\\n // Cancel in-flight module compilations for this file.\\n if(compile_graph && compile_graph->has_unit(path_id)) {\\n compile_graph->update(path_id);\\n }\\n\\n documents.erase(path_id);\\n pch_states.erase(path_id);\\n ast_deps.erase(path_id);\\n\\n // Clear diagnostics for closed file\\n clear_diagnostics(params.text_document.uri);\\n\\n LOG_DEBUG(\\\"didClose: {}\\\", path);\\n });\\n\\n // === textDocument/didSave ===\\n peer.on_notification([this](const protocol::DidSaveTextDocumentParams& params) {\\n if(lifecycle != ServerLifecycle::Ready)\\n return;\\n\\n auto path = uri_to_path(params.text_document.uri);\\n auto path_id = path_pool.intern(path);\\n\\n // Invalidate this file and cascade to dependents in the compile graph.\\n if(compile_graph) {\\n auto dirtied = compile_graph->update(path_id);\\n // Remove stale PCMs for all invalidated units.\\n for(auto dirty_id: dirtied) {\\n pcm_paths.erase(dirty_id);\\n pcm_states.erase(dirty_id);\\n }\\n // Mark ast_dirty for open documents that depend on the saved file.\\n for(auto dirty_id: dirtied) {\\n auto doc_it = documents.find(dirty_id);\\n if(doc_it != documents.end()) {\\n doc_it->second.ast_dirty = true;\\n }\\n }\\n }\\n\\n // Trigger background indexing after save.\\n schedule_indexing();\\n\\n LOG_DEBUG(\\\"didSave: {}\\\", params.text_document.uri);\\n });\\n\\n // =========================================================================\\n // Feature requests routed to stateful workers (RawValue passthrough)\\n // =========================================================================\\n\\n // --- textDocument/hover ---\\n peer.on_request([this](RequestContext& ctx, const protocol::HoverParams& params) -> RawResult {\\n co_return co_await forward_stateful<worker::HoverParams>(\\n params.text_document_position_params.text_document.uri,\\n params.text_document_position_params.position);\\n });\\n\\n // --- textDocument/semanticTokens/full ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::SemanticTokensParams& params) -> RawResult {\\n co_return co_await forward_stateful<worker::SemanticTokensParams>(params.text_document.uri);\\n });\\n\\n // --- textDocument/inlayHint ---\\n peer.on_request(\\n [this](RequestContext& ctx, const protocol::InlayHintParams& params) -> RawResult {\\n co_return co_await forward_stateful<worker::InlayHintsParams>(params.text_document.uri);\\n });\\n\\n // --- textDocument/foldingRange ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::FoldingRangeParams& params) -> RawResult {\\n co_return co_await forward_stateful<worker::FoldingRangeParams>(params.text_document.uri);\\n });\\n\\n // --- textDocument/documentSymbol ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::DocumentSymbolParams& params) -> RawResult {\\n co_return co_await forward_stateful<worker::DocumentSymbolParams>(params.text_document.uri);\\n });\\n\\n // --- textDocument/documentLink ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::DocumentLinkParams& params) -> RawResult {\\n co_return co_await forward_stateful<worker::DocumentLinkParams>(params.text_document.uri);\\n });\\n\\n // --- textDocument/codeAction ---\\n peer.on_request(\\n [this](RequestContext& ctx, const protocol::CodeActionParams& params) -> RawResult {\\n co_return co_await forward_stateful<worker::CodeActionParams>(params.text_document.uri);\\n });\\n\\n // --- textDocument/definition ---\\n peer.on_request(\\n [this](RequestContext& ctx, const protocol::DefinitionParams& params) -> RawResult {\\n auto& uri = params.text_document_position_params.text_document.uri;\\n auto& pos = params.text_document_position_params.position;\\n\\n // Try index-based lookup first.\\n auto result = co_await query_index_relations(uri, pos, RelationKind::Definition);\\n if(result.has_value() && !result.value().empty() && result.value().data != \\\"null\\\") {\\n co_return std::move(result).value();\\n }\\n\\n // Fall back to stateful worker AST query.\\n co_return co_await forward_stateful<worker::GoToDefinitionParams>(uri, pos);\\n });\\n\\n // --- textDocument/references ---\\n peer.on_request(\\n [this](RequestContext& ctx, const protocol::ReferenceParams& params) -> RawResult {\\n auto& uri = params.text_document_position_params.text_document.uri;\\n auto& pos = params.text_document_position_params.position;\\n\\n auto refs = co_await query_index_relations(uri, pos, RelationKind::Reference);\\n\\n if(params.context.include_declaration) {\\n // Also include Definition locations when the client requests the declaration.\\n auto defs = co_await query_index_relations(uri, pos, RelationKind::Definition);\\n if(defs.has_value() && !defs.value().empty() && defs.value().data != \\\"null\\\") {\\n if(!refs.has_value() || refs.value().empty() || refs.value().data == \\\"null\\\") {\\n co_return std::move(defs).value();\\n }\\n // Merge: parse both JSON arrays and concatenate.\\n auto& ref_json = refs.value().data;\\n auto& def_json = defs.value().data;\\n // Both are JSON arrays like \\\"[...]\\\". Merge them.\\n if(ref_json.size() > 2 && def_json.size() > 2) {\\n // Remove trailing ']' from refs, add comma, add def content without leading\\n // '['\\n std::string merged = ref_json.substr(0, ref_json.size() - 1);\\n merged += ',';\\n merged += def_json.substr(1);\\n co_return serde_raw{std::move(merged)};\\n }\\n }\\n }\\n\\n co_return refs;\\n });\\n\\n // =========================================================================\\n // Feature requests routed to stateless workers\\n // =========================================================================\\n\\n // --- textDocument/completion ---\\n peer.on_request(\\n [this](RequestContext& ctx, const protocol::CompletionParams& params) -> RawResult {\\n auto uri = params.text_document_position_params.text_document.uri;\\n auto position = params.text_document_position_params.position;\\n\\n // Check if cursor is on an #include or import line.\\n auto path = uri_to_path(uri);\\n auto path_id = path_pool.intern(path);\\n auto doc_it = documents.find(path_id);\\n if(doc_it != documents.end()) {\\n lsp::PositionMapper mapper(doc_it->second.text, lsp::PositionEncoding::UTF16);\\n auto offset = mapper.to_offset(position);\\n if(offset) {\\n auto pctx = detect_completion_context(doc_it->second.text, *offset);\\n if(pctx.kind == CompletionContext::IncludeQuoted ||\\n pctx.kind == CompletionContext::IncludeAngled) {\\n co_return complete_include(pctx, path);\\n }\\n if(pctx.kind == CompletionContext::Import) {\\n co_return complete_import(pctx);\\n }\\n }\\n }\\n\\n // Default: forward to stateless worker.\\n co_return co_await forward_stateless<worker::CompletionParams>(uri, position);\\n });\\n\\n // --- textDocument/signatureHelp ---\\n peer.on_request(\\n [this](RequestContext& ctx, const protocol::SignatureHelpParams& params) -> RawResult {\\n co_return co_await forward_stateless<worker::SignatureHelpParams>(\\n params.text_document_position_params.text_document.uri,\\n params.text_document_position_params.position);\\n });\\n\\n // =========================================================================\\n // Hierarchy and workspace symbol handlers (index-based)\\n // =========================================================================\\n\\n // --- textDocument/prepareCallHierarchy ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::CallHierarchyPrepareParams& params) -> RawResult {\\n auto info = co_await lookup_symbol_at_position(\\n params.text_document_position_params.text_document.uri,\\n params.text_document_position_params.position);\\n if(!info)\\n co_return serde_raw{\\\"null\\\"};\\n\\n // Only functions/methods are valid for call hierarchy.\\n if(!(info->kind == SymbolKind::Function || info->kind == SymbolKind::Method))\\n co_return serde_raw{\\\"null\\\"};\\n\\n std::vector<protocol::CallHierarchyItem> items;\\n items.push_back(build_call_hierarchy_item(*info));\\n co_return to_raw(items);\\n });\\n\\n // --- callHierarchy/incomingCalls ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::CallHierarchyIncomingCallsParams& params) -> RawResult {\\n // Re-lookup the symbol from the item.\\n auto info =\\n co_await resolve_hierarchy_item(params.item.uri, params.item.range, params.item.data);\\n if(!info)\\n co_return serde_raw{\\\"null\\\"};\\n\\n auto sym_it = project_index.symbols.find(info->hash);\\n if(sym_it == project_index.symbols.end())\\n co_return serde_raw{\\\"null\\\"};\\n\\n // Collect all Caller relations across reference files.\\n // Caller relation: on the callee symbol, target_symbol is the caller's hash.\\n std::vector<protocol::CallHierarchyIncomingCall> results;\\n\\n // Group call sites by caller symbol hash.\\n llvm::DenseMap<index::SymbolHash, std::vector<protocol::Range>> caller_ranges;\\n\\n for(auto file_id: sym_it->second.reference_files) {\\n auto file_merged_it = merged_indices.find(file_id);\\n if(file_merged_it == merged_indices.end())\\n continue;\\n\\n // Use stored content from MergedIndex for offset-to-position conversion.\\n auto file_content = file_merged_it->second.content();\\n if(file_content.empty())\\n continue;\\n\\n lsp::PositionMapper file_mapper(file_content, lsp::PositionEncoding::UTF16);\\n\\n file_merged_it->second.lookup(info->hash,\\n RelationKind::Caller,\\n [&](const index::Relation& r) {\\n auto start = file_mapper.to_position(r.range.begin);\\n auto end = file_mapper.to_position(r.range.end);\\n if(start && end) {\\n caller_ranges[r.target_symbol].push_back(\\n protocol::Range{*start, *end});\\n }\\n return true;\\n });\\n }\\n\\n // Build incoming call items from grouped caller symbols.\\n for(auto& [caller_hash, ranges]: caller_ranges) {\\n auto def_loc = find_symbol_definition_location(caller_hash);\\n auto caller_sym_it = project_index.symbols.find(caller_hash);\\n if(caller_sym_it == project_index.symbols.end())\\n continue;\\n\\n if(!def_loc)\\n continue;\\n\\n protocol::CallHierarchyItem caller_item;\\n caller_item.name = caller_sym_it->second.name;\\n caller_item.kind = to_lsp_symbol_kind(caller_sym_it->second.kind);\\n caller_item.uri = def_loc->uri;\\n caller_item.range = def_loc->range;\\n caller_item.selection_range = def_loc->range;\\n caller_item.data = protocol::LSPAny(static_cast<std::int64_t>(caller_hash));\\n\\n protocol::CallHierarchyIncomingCall call;\\n call.from = std::move(caller_item);\\n call.from_ranges = std::move(ranges);\\n results.push_back(std::move(call));\\n }\\n\\n if(results.empty())\\n co_return serde_raw{\\\"null\\\"};\\n co_return to_raw(results);\\n });\\n\\n // --- callHierarchy/outgoingCalls ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::CallHierarchyOutgoingCallsParams& params) -> RawResult {\\n auto info =\\n co_await resolve_hierarchy_item(params.item.uri, params.item.range, params.item.data);\\n if(!info)\\n co_return serde_raw{\\\"null\\\"};\\n\\n auto sym_it = project_index.symbols.find(info->hash);\\n if(sym_it == project_index.symbols.end())\\n co_return serde_raw{\\\"null\\\"};\\n\\n // Collect Callee relations (outgoing calls from this function).\\n // Group call sites by callee symbol hash.\\n llvm::DenseMap<index::SymbolHash, std::vector<protocol::Range>> callee_ranges;\\n\\n for(auto file_id: sym_it->second.reference_files) {\\n auto file_merged_it = merged_indices.find(file_id);\\n if(file_merged_it == merged_indices.end())\\n continue;\\n\\n // Use stored content from MergedIndex for offset-to-position conversion.\\n auto file_content = file_merged_it->second.content();\\n if(file_content.empty())\\n continue;\\n\\n lsp::PositionMapper file_mapper(file_content, lsp::PositionEncoding::UTF16);\\n\\n file_merged_it->second.lookup(info->hash,\\n RelationKind::Callee,\\n [&](const index::Relation& r) {\\n auto start = file_mapper.to_position(r.range.begin);\\n auto end = file_mapper.to_position(r.range.end);\\n if(start && end) {\\n callee_ranges[r.target_symbol].push_back(\\n protocol::Range{*start, *end});\\n }\\n return true;\\n });\\n }\\n\\n std::vector<protocol::CallHierarchyOutgoingCall> results;\\n for(auto& [callee_hash, ranges]: callee_ranges) {\\n auto def_loc = find_symbol_definition_location(callee_hash);\\n auto callee_sym_it = project_index.symbols.find(callee_hash);\\n if(callee_sym_it == project_index.symbols.end())\\n continue;\\n\\n if(!def_loc)\\n continue;\\n\\n protocol::CallHierarchyItem callee_item;\\n callee_item.name = callee_sym_it->second.name;\\n callee_item.kind = to_lsp_symbol_kind(callee_sym_it->second.kind);\\n callee_item.uri = def_loc->uri;\\n callee_item.range = def_loc->range;\\n callee_item.selection_range = def_loc->range;\\n callee_item.data = protocol::LSPAny(static_cast<std::int64_t>(callee_hash));\\n\\n protocol::CallHierarchyOutgoingCall call;\\n call.to = std::move(callee_item);\\n call.from_ranges = std::move(ranges);\\n results.push_back(std::move(call));\\n }\\n\\n if(results.empty())\\n co_return serde_raw{\\\"null\\\"};\\n co_return to_raw(results);\\n });\\n\\n // --- textDocument/prepareTypeHierarchy ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::TypeHierarchyPrepareParams& params) -> RawResult {\\n auto info = co_await lookup_symbol_at_position(\\n params.text_document_position_params.text_document.uri,\\n params.text_document_position_params.position);\\n if(!info)\\n co_return serde_raw{\\\"null\\\"};\\n\\n // Only class-like types are valid for type hierarchy.\\n if(!(info->kind == SymbolKind::Class || info->kind == SymbolKind::Struct ||\\n info->kind == SymbolKind::Enum || info->kind == SymbolKind::Union))\\n co_return serde_raw{\\\"null\\\"};\\n\\n std::vector<protocol::TypeHierarchyItem> items;\\n items.push_back(build_type_hierarchy_item(*info));\\n co_return to_raw(items);\\n });\\n\\n // --- typeHierarchy/supertypes ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::TypeHierarchySupertypesParams& params) -> RawResult {\\n auto info =\\n co_await resolve_hierarchy_item(params.item.uri, params.item.range, params.item.data);\\n if(!info)\\n co_return serde_raw{\\\"null\\\"};\\n\\n auto sym_it = project_index.symbols.find(info->hash);\\n if(sym_it == project_index.symbols.end())\\n co_return serde_raw{\\\"null\\\"};\\n\\n // Find Base relations: supertypes of this class.\\n std::vector<protocol::TypeHierarchyItem> results;\\n\\n for(auto file_id: sym_it->second.reference_files) {\\n auto file_merged_it = merged_indices.find(file_id);\\n if(file_merged_it == merged_indices.end())\\n continue;\\n\\n file_merged_it->second.lookup(\\n info->hash,\\n RelationKind::Base,\\n [&](const index::Relation& r) {\\n auto base_hash = r.target_symbol;\\n auto base_sym_it = project_index.symbols.find(base_hash);\\n if(base_sym_it == project_index.symbols.end())\\n return true;\\n\\n // Find the definition location of the base class.\\n auto def_loc = find_symbol_definition_location(base_hash);\\n if(!def_loc)\\n return true;\\n\\n protocol::TypeHierarchyItem item;\\n item.name = base_sym_it->second.name;\\n item.kind = to_lsp_symbol_kind(base_sym_it->second.kind);\\n item.uri = def_loc->uri;\\n item.range = def_loc->range;\\n item.selection_range = def_loc->range;\\n item.data = protocol::LSPAny(static_cast<std::int64_t>(base_hash));\\n results.push_back(std::move(item));\\n return true;\\n });\\n }\\n\\n if(results.empty())\\n co_return serde_raw{\\\"null\\\"};\\n co_return to_raw(results);\\n });\\n\\n // --- typeHierarchy/subtypes ---\\n peer.on_request([this](RequestContext& ctx,\\n const protocol::TypeHierarchySubtypesParams& params) -> RawResult {\\n auto info =\\n co_await resolve_hierarchy_item(params.item.uri, params.item.range, params.item.data);\\n if(!info)\\n co_return serde_raw{\\\"null\\\"};\\n\\n auto sym_it = project_index.symbols.find(info->hash);\\n if(sym_it == project_index.symbols.end())\\n co_return serde_raw{\\\"null\\\"};\\n\\n // Find Derived relations across all reference files: subtypes of this class.\\n std::vector<protocol::TypeHierarchyItem> results;\\n\\n for(auto file_id: sym_it->second.reference_files) {\\n auto file_merged_it = merged_indices.find(file_id);\\n if(file_merged_it == merged_indices.end())\\n continue;\\n\\n file_merged_it->second.lookup(\\n info->hash,\\n RelationKind::Derived,\\n [&](const index::Relation& r) {\\n auto derived_hash = r.target_symbol;\\n auto derived_sym_it = project_index.symbols.find(derived_hash);\\n if(derived_sym_it == project_index.symbols.end())\\n return true;\\n\\n auto def_loc = find_symbol_definition_location(derived_hash);\\n if(!def_loc)\\n return true;\\n\\n protocol::TypeHierarchyItem item;\\n item.name = derived_sym_it->second.name;\\n item.kind = to_lsp_symbol_kind(derived_sym_it->second.kind);\\n item.uri = def_loc->uri;\\n item.range = def_loc->range;\\n item.selection_range = def_loc->range;\\n item.data = protocol::LSPAny(static_cast<std::int64_t>(derived_hash));\\n results.push_back(std::move(item));\\n return true;\\n });\\n }\\n\\n if(results.empty())\\n co_return serde_raw{\\\"null\\\"};\\n co_return to_raw(results);\\n });\\n\\n // --- workspace/symbol ---\\n peer.on_request(\\n [this](RequestContext& ctx, const protocol::WorkspaceSymbolParams& params) -> RawResult {\\n auto query = llvm::StringRef(params.query);\\n std::vector<protocol::SymbolInformation> results;\\n\\n // Case-insensitive substring match on symbol names.\\n std::string query_lower = query.lower();\\n\\n for(auto& [hash, symbol]: project_index.symbols) {\\n if(results.size() >= 100)\\n break;\\n\\n // Skip non-indexable symbol kinds (punctuation, literals, etc.)\\n // Skip non-indexable symbol kinds using a positive check instead.\\n auto sk = symbol.kind;\\n if(!(sk == SymbolKind::Namespace || sk == SymbolKind::Class ||\\n sk == SymbolKind::Struct || sk == SymbolKind::Union ||\\n sk == SymbolKind::Enum || sk == SymbolKind::Type || sk == SymbolKind::Field ||\\n sk == SymbolKind::EnumMember || sk == SymbolKind::Function ||\\n sk == SymbolKind::Method || sk == SymbolKind::Variable ||\\n sk == SymbolKind::Parameter || sk == SymbolKind::Macro ||\\n sk == SymbolKind::Concept || sk == SymbolKind::Module ||\\n sk == SymbolKind::Operator || sk == SymbolKind::MacroParameter ||\\n sk == SymbolKind::Label || sk == SymbolKind::Attribute))\\n continue;\\n\\n if(symbol.name.empty())\\n continue;\\n\\n // Check case-insensitive substring match.\\n std::string name_lower = llvm::StringRef(symbol.name).lower();\\n if(!query_lower.empty() && name_lower.find(query_lower) == std::string::npos)\\n continue;\\n\\n auto def_loc = find_symbol_definition_location(hash);\\n if(!def_loc)\\n continue;\\n\\n protocol::SymbolInformation info;\\n info.name = symbol.name;\\n info.kind = to_lsp_symbol_kind(symbol.kind);\\n info.location = std::move(*def_loc);\\n results.push_back(std::move(info));\\n }\\n\\n if(results.empty())\\n co_return serde_raw{\\\"null\\\"};\\n co_return to_raw(results);\\n });\\n}\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4744,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/server/master_server.h\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#pragma once\\n\\n#include <cstdint>\\n#include <memory>\\n#include <string>\\n\\n#include \\\"command/command.h\\\"\\n#include \\\"eventide/async/async.h\\\"\\n#include \\\"eventide/ipc/lsp/protocol.h\\\"\\n#include \\\"eventide/ipc/peer.h\\\"\\n#include \\\"eventide/serde/serde/raw_value.h\\\"\\n#include \\\"index/merged_index.h\\\"\\n#include \\\"index/project_index.h\\\"\\n#include \\\"semantic/relation_kind.h\\\"\\n#include \\\"server/compile_graph.h\\\"\\n#include \\\"server/config.h\\\"\\n#include \\\"server/worker_pool.h\\\"\\n#include \\\"support/path_pool.h\\\"\\n#include \\\"syntax/dependency_graph.h\\\"\\n\\n#include \\\"llvm/ADT/DenseMap.h\\\"\\n#include \\\"llvm/ADT/SmallVector.h\\\"\\n#include \\\"llvm/ADT/StringMap.h\\\"\\n#include \\\"llvm/ADT/StringRef.h\\\"\\n\\nnamespace clice {\\n\\nnamespace et = eventide;\\nnamespace protocol = et::ipc::protocol;\\n\\nenum class CompletionContext { None, IncludeQuoted, IncludeAngled, Import };\\n\\nstruct PreambleCompletionContext {\\n CompletionContext kind = CompletionContext::None;\\n std::string prefix;\\n};\\n\\nstruct DocumentState {\\n int version = 0;\\n\\n std::string text;\\n\\n std::uint64_t generation = 0;\\n\\n bool ast_dirty = true;\\n\\n /// Non-null while a compile is in flight. Other ensure_compiled() calls\\n /// wait on this instead of sending duplicate compile requests.\\n std::shared_ptr<et::event> compiling;\\n};\\n\\n/// Two-layer staleness snapshot for compilation artifacts (PCH, AST, etc.).\\n///\\n/// Layer 1 (fast): compare each file's current mtime against build_at.\\n/// If all mtimes <= build_at, the artifact is fresh (zero I/O beyond stat).\\n///\\n/// Layer 2 (precise): for files whose mtime changed, re-hash their content\\n/// and compare against the stored hash. If the hash matches, the file was\\n/// \\\"touched\\\" but not actually modified — skip the rebuild.\\n///\\n/// This avoids unnecessary recompilation from timestamp-only changes (e.g.\\n/// git checkout, touch, backup restore) while remaining cheap in the common\\n/// case where nothing changed.\\nstruct DepsSnapshot {\\n /// File path IDs interned via PathPool.\\n llvm::SmallVector<std::uint32_t> path_ids;\\n\\n /// xxh3_64bits of file content at build time.\\n llvm::SmallVector<std::uint64_t> hashes;\\n\\n /// time_t when this snapshot was captured.\\n std::int64_t build_at = 0;\\n};\\n\\n/// Cached PCH state for a single source file.\\nstruct PCHState {\\n /// Built PCH file path.\\n std::string path;\\n\\n /// Preamble byte offset used when building.\\n std::uint32_t bound = 0;\\n\\n /// xxh3 hash of preamble content.\\n std::uint64_t hash = 0;\\n\\n /// Dependency snapshot for staleness detection.\\n DepsSnapshot deps;\\n\\n /// Non-null while a build is in flight.\\n std::shared_ptr<et::event> building;\\n};\\n\\n/// Cached PCM state for a single module file.\\nstruct PCMState {\\n /// Built PCM file path.\\n std::string path;\\n\\n /// Dependency snapshot for staleness detection.\\n DepsSnapshot deps;\\n};\\n\\n/// Information about a symbol at a given position.\\nstruct SymbolInfo {\\n /// Unique hash identifying this symbol across the project.\\n index::SymbolHash hash = 0;\\n\\n /// Human-readable symbol name.\\n std::string name;\\n\\n /// Symbol kind (function, class, variable, etc.).\\n SymbolKind kind;\\n\\n /// URI of the file containing this symbol.\\n std::string uri;\\n\\n /// Source range of the symbol's identifier.\\n protocol::Range range;\\n};\\n\\nenum class ServerLifecycle : std::uint8_t {\\n Uninitialized,\\n Initialized,\\n Ready,\\n ShuttingDown,\\n Exited,\\n};\\n\\nclass MasterServer {\\npublic:\\n MasterServer(et::event_loop& loop, et::ipc::JsonPeer& peer, std::string self_path);\\n ~MasterServer();\\n\\n void register_handlers();\\n\\nprivate:\\n /// Event loop for scheduling async tasks.\\n et::event_loop& loop;\\n\\n /// JSON-RPC peer for LSP communication.\\n et::ipc::JsonPeer& peer;\\n\\n /// Pool of stateful/stateless worker processes.\\n WorkerPool pool;\\n\\n /// Interning pool for file paths (path string -> uint32_t ID).\\n PathPool path_pool;\\n\\n /// Current server lifecycle state.\\n ServerLifecycle lifecycle = ServerLifecycle::Uninitialized;\\n\\n /// Path to the clice binary itself.\\n std::string self_path;\\n\\n /// Root directory of the opened workspace.\\n std::string workspace_root;\\n\\n /// User/project configuration.\\n CliceConfig config;\\n\\n /// Session-specific log directory (e.g. .clice/logs/2026-04-05_10-30-00/).\\n std::string session_log_dir;\\n\\n /// Compilation database (compile_commands.json).\\n CompilationDatabase cdb;\\n\\n /// Include/module dependency graph built from fast lexer scanning.\\n DependencyGraph dependency_graph;\\n\\n /// Module compilation graph (lazy dependency resolution).\\n std::unique_ptr<CompileGraph> compile_graph;\\n\\n /// path_id -> built PCM output path (set after successful module build).\\n llvm::DenseMap<std::uint32_t, std::string> pcm_paths;\\n\\n /// path_id -> module name (for files that provide a module interface).\\n llvm::DenseMap<std::uint32_t, std::string> path_to_module;\\n\\n /// path_id -> cached PCH state (path, preamble hash, deps, build event).\\n llvm::DenseMap<std::uint32_t, PCHState> pch_states;\\n\\n /// path_id -> cached PCM state (path, deps).\\n llvm::DenseMap<std::uint32_t, PCMState> pcm_states;\\n\\n // === Index state ===\\n\\n /// Global symbol table and path mapping for the project.\\n index::ProjectIndex project_index;\\n\\n /// Per-file merged index shards (keyed by project-level path_id).\\n llvm::DenseMap<std::uint32_t, index::MergedIndex> merged_indices;\\n\\n /// Files queued for background indexing (server-level path_ids from CDB).\\n std::vector<std::uint32_t> index_queue;\\n\\n /// Index of next file to process in index_queue.\\n std::size_t index_queue_pos = 0;\\n\\n /// Whether background indexing is currently in progress.\\n bool indexing_active = false;\\n\\n /// Whether a background indexing coroutine has been scheduled (waiting on timer).\\n bool indexing_scheduled = false;\\n\\n /// Timer for idle-triggered background indexing.\\n std::shared_ptr<et::timer> index_idle_timer;\\n\\n /// path_id -> open document state (text, version, generation, dirty flag).\\n llvm::DenseMap<std::uint32_t, DocumentState> documents;\\n\\n /// Per-file dependency snapshots from last successful AST compilation.\\n llvm::DenseMap<std::uint32_t, DepsSnapshot> ast_deps;\\n\\n // === Helpers ===\\n\\n /// Convert a file:// URI to a local file path.\\n std::string uri_to_path(const std::string& uri);\\n\\n /// Publish diagnostics to the LSP client.\\n void publish_diagnostics(const std::string& uri,\\n int version,\\n const eventide::serde::RawValue& diagnostics_json);\\n\\n /// Clear diagnostics for a file (publish empty array).\\n void clear_diagnostics(const std::string& uri);\\n\\n /// Pull-based compilation entry point. Ensures AST is up-to-date.\\n et::task<bool> ensure_compiled(std::uint32_t path_id);\\n\\n /// Load CDB and build initial include/module dependency graph.\\n et::task<> load_workspace();\\n\\n /// Fill compile arguments from CDB for a given file.\\n bool fill_compile_args(llvm::StringRef path,\\n std::string& directory,\\n std::vector<std::string>& arguments);\\n\\n /// Build or reuse PCH for a source file. Returns true if PCH is available.\\n et::task<bool> ensure_pch(std::uint32_t path_id,\\n llvm::StringRef path,\\n const std::string& text,\\n const std::string& directory,\\n const std::vector<std::string>& arguments);\\n\\n /// Compile module dependencies, build/reuse PCH, and fill PCM paths.\\n /// Shared preparation step for ensure_compiled() and forward_stateless().\\n et::task<bool> ensure_deps(std::uint32_t path_id,\\n llvm::StringRef path,\\n const std::string& text,\\n const std::string& directory,\\n const std::vector<std::string>& arguments,\\n std::pair<std::string, uint32_t>& pch,\\n std::unordered_map<std::string, std::string>& pcms);\\n\\n /// Schedule background indexing when idle.\\n void schedule_indexing();\\n\\n /// Background indexing coroutine: picks files from queue and dispatches to workers.\\n et::task<> run_background_indexing();\\n\\n /// Merge a TUIndex result into ProjectIndex and MergedIndex shards.\\n void merge_index_result(const void* tu_index_data, std::size_t size);\\n\\n /// Persist index state to disk.\\n void save_index();\\n\\n /// Load index state from disk.\\n void load_index();\\n\\n /// Load PCH/PCM cache metadata from cache.json.\\n void load_cache();\\n\\n /// Save PCH/PCM cache metadata to cache.json.\\n void save_cache();\\n\\n /// Clean up stale cache files older than max_age_days.\\n void cleanup_cache(int max_age_days = 7);\\n\\n // === Include/import completion (handled in master) ===\\n\\n /// Detect whether the cursor is on an #include or import line.\\n PreambleCompletionContext detect_completion_context(const std::string& text, uint32_t offset);\\n\\n /// Complete #include paths by enumerating search directories.\\n et::serde::RawValue complete_include(const PreambleCompletionContext& ctx,\\n llvm::StringRef path);\\n\\n /// Complete import module names from the known module map.\\n et::serde::RawValue complete_import(const PreambleCompletionContext& ctx);\\n\\n // === Feature request forwarding ===\\n\\n using RawResult = et::task<et::serde::RawValue, et::ipc::Error>;\\n\\n /// Forward a simple stateful request (path-only worker params).\\n template <typename WorkerParams>\\n RawResult forward_stateful(const std::string& uri);\\n\\n /// Forward a stateful request with position-to-offset conversion.\\n template <typename WorkerParams>\\n RawResult forward_stateful(const std::string& uri, const protocol::Position& position);\\n\\n /// Forward a stateless request with document content and compile args.\\n template <typename WorkerParams>\\n RawResult forward_stateless(const std::string& uri, const protocol::Position& position);\\n\\n // === Index query helpers ===\\n\\n /// Query index for symbol relations (GoToDefinition, FindReferences, etc.).\\n RawResult query_index_relations(const std::string& uri,\\n const protocol::Position& position,\\n RelationKind kind);\\n\\n /// Look up a symbol at a position, returning its hash, name, kind, and range.\\n et::task<std::optional<SymbolInfo>>\\n lookup_symbol_at_position(const std::string& uri, const protocol::Position& position);\\n\\n /// Find the definition location (uri + range) of a symbol by its hash.\\n std::optional<protocol::Location> find_symbol_definition_location(index::SymbolHash hash);\\n\\n /// Convert clice::SymbolKind to LSP protocol::SymbolKind.\\n static protocol::SymbolKind to_lsp_symbol_kind(SymbolKind kind);\\n\\n /// Build a CallHierarchyItem from a SymbolInfo.\\n protocol::CallHierarchyItem build_call_hierarchy_item(const SymbolInfo& info);\\n\\n /// Build a TypeHierarchyItem from a SymbolInfo.\\n protocol::TypeHierarchyItem build_type_hierarchy_item(const SymbolInfo& info);\\n\\n /// Resolve SymbolInfo from a hierarchy item's stored data (symbol hash).\\n et::task<std::optional<SymbolInfo>>\\n resolve_hierarchy_item(const std::string& uri,\\n const protocol::Range& range,\\n const std::optional<protocol::LSPAny>& data);\\n};\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4744,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/server/stateless_worker.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"server/stateless_worker.h\\\"\\n\\n#include <chrono>\\n\\n#include \\\"compile/compilation.h\\\"\\n#include \\\"eventide/async/async.h\\\"\\n#include \\\"eventide/ipc/json_codec.h\\\"\\n#include \\\"eventide/ipc/peer.h\\\"\\n#include \\\"eventide/ipc/transport.h\\\"\\n#include \\\"eventide/serde/json/serializer.h\\\"\\n#include \\\"eventide/serde/serde/raw_value.h\\\"\\n#include \\\"feature/feature.h\\\"\\n#include \\\"index/tu_index.h\\\"\\n#include \\\"server/protocol.h\\\"\\n#include \\\"support/logging.h\\\"\\n\\n#include \\\"llvm/Support/raw_ostream.h\\\"\\n\\nnamespace clice {\\n\\nnamespace et = eventide;\\nusing et::ipc::RequestResult;\\nusing RequestContext = et::ipc::BincodePeer::RequestContext;\\n\\nstruct ScopedTimer {\\n std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();\\n\\n long long ms() const {\\n return std::chrono::duration_cast<std::chrono::milliseconds>(\\n std::chrono::steady_clock::now() - start)\\n .count();\\n }\\n};\\n\\nstatic void fill_args(CompilationParams& cp,\\n const std::string& directory,\\n const std::vector<std::string>& arguments) {\\n cp.directory = directory;\\n for(auto& arg: arguments) {\\n cp.arguments.push_back(arg.c_str());\\n }\\n}\\n\\ntemplate <typename T>\\nstatic et::serde::RawValue to_raw(const T& value) {\\n auto json = et::serde::json::to_json<et::ipc::lsp_config>(value);\\n return et::serde::RawValue{json ? std::move(*json) : \\\"null\\\"};\\n}\\n\\nint run_stateless_worker_mode(const std::string& worker_name, const std::string& log_dir) {\\n logging::stderr_logger(worker_name, logging::options);\\n if(!log_dir.empty()) {\\n logging::file_logger(worker_name, log_dir, logging::options);\\n }\\n\\n LOG_INFO(\\\"Starting stateless worker\\\");\\n\\n et::event_loop loop;\\n\\n auto transport_result = et::ipc::StreamTransport::open_stdio(loop);\\n if(!transport_result) {\\n LOG_ERROR(\\\"Failed to open stdio transport\\\");\\n return 1;\\n }\\n\\n et::ipc::BincodePeer peer(loop, std::move(*transport_result));\\n\\n // === BuildPCH ===\\n peer.on_request(\\n [&](RequestContext& ctx,\\n const worker::BuildPCHParams& params) -> RequestResult<worker::BuildPCHParams> {\\n LOG_INFO(\\\"BuildPCH request: file={}\\\", params.file);\\n\\n auto result = co_await et::queue([&]() -> worker::BuildPCHResult {\\n ScopedTimer timer;\\n\\n CompilationParams cp;\\n cp.kind = CompilationKind::Preamble;\\n fill_args(cp, params.directory, params.arguments);\\n cp.add_remapped_file(params.file, params.content, params.preamble_bound);\\n\\n // When output_path is set, write to .tmp then rename — avoids\\n // Windows file-lock failures when the target is held by another\\n // process. When empty, fall back to a temporary file.\\n std::string tmp_path;\\n bool has_output = !params.output_path.empty();\\n if(has_output) {\\n tmp_path = params.output_path + \\\".tmp\\\";\\n } else {\\n auto tmp = fs::createTemporaryFile(\\\"clice-pch\\\", \\\"pch\\\");\\n if(!tmp) {\\n LOG_ERROR(\\\"BuildPCH: failed to create temp file\\\");\\n return {false, \\\"Failed to create temporary PCH file\\\", \\\"\\\"};\\n }\\n tmp_path = *tmp;\\n }\\n cp.output_file = tmp_path;\\n\\n PCHInfo pch_info;\\n auto unit = compile(cp, pch_info);\\n bool success = unit.completed();\\n\\n // Extract error messages before destroying the unit.\\n std::string errors;\\n if(!success) {\\n for(auto& diag: unit.diagnostics()) {\\n if(diag.id.level >= DiagnosticLevel::Error) {\\n if(!errors.empty())\\n errors += \\\"; \\\";\\n errors += diag.message;\\n }\\n }\\n }\\n\\n // Destroy CompilationUnit to flush PCH file to disk\\n // (EndSourceFile serializes the AST on destruction).\\n unit = CompilationUnit(nullptr);\\n\\n if(success) {\\n std::string final_path;\\n if(has_output) {\\n auto ec = fs::rename(tmp_path, params.output_path);\\n if(ec) {\\n final_path = params.output_path;\\n } else {\\n LOG_WARN(\\\"BuildPCH: rename {} -> {} failed: {}\\\",\\n tmp_path,\\n params.output_path,\\n ec.error().message());\\n final_path = tmp_path;\\n }\\n } else {\\n final_path = tmp_path;\\n }\\n LOG_INFO(\\\"BuildPCH done: file={}, output={}, {}ms\\\",\\n params.file,\\n final_path,\\n timer.ms());\\n worker::BuildPCHResult pch_result{true, \\\"\\\", std::move(final_path)};\\n pch_result.deps = pch_info.deps;\\n return pch_result;\\n } else {\\n LOG_WARN(\\\"BuildPCH failed: file={}, {}ms, errors=[{}]\\\",\\n params.file,\\n timer.ms(),\\n errors);\\n fs::remove(tmp_path);\\n return {false, errors.empty() ? \\\"PCH compilation failed\\\" : errors, \\\"\\\"};\\n }\\n });\\n co_return result.value();\\n });\\n\\n // === BuildPCM ===\\n peer.on_request(\\n [&](RequestContext& ctx,\\n const worker::BuildPCMParams& params) -> RequestResult<worker::BuildPCMParams> {\\n LOG_INFO(\\\"BuildPCM request: file={}, module={}\\\", params.file, params.module_name);\\n\\n auto result = co_await et::queue([&]() -> worker::BuildPCMResult {\\n ScopedTimer timer;\\n\\n CompilationParams cp;\\n cp.kind = CompilationKind::ModuleInterface;\\n fill_args(cp, params.directory, params.arguments);\\n for(auto& [name, path]: params.pcms) {\\n cp.pcms.try_emplace(name, path);\\n }\\n\\n std::string tmp_path;\\n bool has_output = !params.output_path.empty();\\n if(has_output) {\\n tmp_path = params.output_path + \\\".tmp\\\";\\n } else {\\n auto tmp = fs::createTemporaryFile(\\\"clice-pcm\\\", \\\"pcm\\\");\\n if(!tmp) {\\n LOG_ERROR(\\\"BuildPCM: failed to create temp file\\\");\\n return {false, \\\"Failed to create temporary PCM file\\\", \\\"\\\"};\\n }\\n tmp_path = *tmp;\\n }\\n cp.output_file = tmp_path;\\n\\n PCMInfo pcm_info;\\n auto unit = compile(cp, pcm_info);\\n bool success = unit.completed();\\n\\n std::string errors;\\n if(!success) {\\n for(auto& diag: unit.diagnostics()) {\\n if(diag.id.level >= DiagnosticLevel::Error) {\\n if(!errors.empty())\\n errors += \\\"; \\\";\\n errors += diag.message;\\n }\\n }\\n }\\n\\n unit = CompilationUnit(nullptr);\\n\\n if(success) {\\n std::string final_path = tmp_path;\\n if(has_output) {\\n auto ec = fs::rename(tmp_path, params.output_path);\\n if(ec) {\\n final_path = params.output_path;\\n } else {\\n LOG_WARN(\\\"BuildPCM: rename {} -> {} failed: {}\\\",\\n tmp_path,\\n params.output_path,\\n ec.error().message());\\n }\\n }\\n LOG_INFO(\\\"BuildPCM done: module={}, {}ms\\\", params.module_name, timer.ms());\\n worker::BuildPCMResult pcm_result{true, \\\"\\\", std::move(final_path)};\\n pcm_result.deps = pcm_info.deps;\\n return pcm_result;\\n } else {\\n LOG_WARN(\\\"BuildPCM failed: module={}, {}ms, errors=[{}]\\\",\\n params.module_name,\\n timer.ms(),\\n errors);\\n fs::remove(tmp_path);\\n return {false, errors.empty() ? \\\"PCM compilation failed\\\" : errors, \\\"\\\"};\\n }\\n });\\n co_return result.value();\\n });\\n\\n // === Completion ===\\n peer.on_request(\\n [&](RequestContext& ctx,\\n const worker::CompletionParams& params) -> RequestResult<worker::CompletionParams> {\\n LOG_DEBUG(\\\"Completion request: path={}, offset={}\\\", params.path, params.offset);\\n\\n auto result = co_await et::queue([&]() -> et::serde::RawValue {\\n ScopedTimer timer;\\n\\n CompilationParams cp;\\n cp.kind = CompilationKind::Completion;\\n fill_args(cp, params.directory, params.arguments);\\n if(!params.pch.first.empty()) {\\n cp.pch = params.pch;\\n }\\n for(auto& [name, path]: params.pcms) {\\n cp.pcms.try_emplace(name, path);\\n }\\n cp.add_remapped_file(params.path, params.text);\\n cp.completion = {params.path, params.offset};\\n\\n auto items = feature::code_complete(cp);\\n LOG_DEBUG(\\\"Completion done: {} items, {}ms\\\", items.size(), timer.ms());\\n return to_raw(items);\\n });\\n co_return result.value();\\n });\\n\\n // === SignatureHelp ===\\n peer.on_request([&](RequestContext& ctx, const worker::SignatureHelpParams& params)\\n -> RequestResult<worker::SignatureHelpParams> {\\n LOG_DEBUG(\\\"SignatureHelp request: path={}, offset={}\\\", params.path, params.offset);\\n\\n auto result = co_await et::queue([&]() -> et::serde::RawValue {\\n ScopedTimer timer;\\n\\n CompilationParams cp;\\n cp.kind = CompilationKind::Completion;\\n fill_args(cp, params.directory, params.arguments);\\n if(!params.pch.first.empty()) {\\n cp.pch = params.pch;\\n }\\n for(auto& [name, path]: params.pcms) {\\n cp.pcms.try_emplace(name, path);\\n }\\n cp.add_remapped_file(params.path, params.text);\\n cp.completion = {params.path, params.offset};\\n\\n auto help = feature::signature_help(cp);\\n LOG_DEBUG(\\\"SignatureHelp done: {}ms\\\", timer.ms());\\n return to_raw(help);\\n });\\n co_return result.value();\\n });\\n\\n // === Index ===\\n peer.on_request([&](RequestContext& ctx,\\n const worker::IndexParams& params) -> RequestResult<worker::IndexParams> {\\n LOG_INFO(\\\"Index request: file={}\\\", params.file);\\n\\n auto result = co_await et::queue([&]() -> worker::IndexResult {\\n ScopedTimer timer;\\n\\n CompilationParams cp;\\n cp.kind = CompilationKind::Indexing;\\n fill_args(cp, params.directory, params.arguments);\\n for(auto& [name, path]: params.pcms) {\\n cp.pcms.try_emplace(name, path);\\n }\\n\\n auto unit = compile(cp);\\n\\n if(!unit.completed()) {\\n LOG_WARN(\\\"Index failed: file={}, {}ms\\\", params.file, timer.ms());\\n return {false, \\\"Index compilation failed\\\", \\\"\\\"};\\n }\\n\\n auto tu_index = index::TUIndex::build(unit);\\n\\n std::string serialized;\\n llvm::raw_string_ostream os(serialized);\\n tu_index.serialize(os);\\n\\n LOG_INFO(\\\"Index done: file={}, {} symbols, {}ms\\\",\\n params.file,\\n tu_index.symbols.size(),\\n timer.ms());\\n return {true, \\\"\\\", std::move(serialized)};\\n });\\n co_return result.value();\\n });\\n\\n LOG_INFO(\\\"Stateless worker ready, waiting for requests\\\");\\n loop.schedule(peer.run());\\n auto ret = loop.run();\\n LOG_INFO(\\\"Stateless worker exiting with code {}\\\", ret);\\n return ret;\\n}\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4745,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/server/worker_pool.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"server/worker_pool.h\\\"\\n\\n#include <csignal>\\n#include <string>\\n\\n#include \\\"eventide/ipc/transport.h\\\"\\n#include \\\"support/logging.h\\\"\\n\\nnamespace clice {\\n\\nnamespace {\\n\\n/// Coroutine that drains a worker's stderr pipe.\\n/// Workers write their own log files, so this only captures unexpected output\\n/// (crash stacktraces, assertion failures, etc.) that bypasses spdlog.\\net::task<> drain_stderr(et::pipe stderr_pipe, std::string prefix) {\\n std::string buffer;\\n while(true) {\\n auto result = co_await stderr_pipe.read();\\n if(!result.has_value()) {\\n break;\\n }\\n auto& chunk = result.value();\\n if(chunk.empty())\\n break;\\n\\n buffer += chunk;\\n\\n std::size_t pos = 0;\\n while(true) {\\n auto nl = buffer.find('\\\\n', pos);\\n if(nl == std::string::npos)\\n break;\\n auto line = buffer.substr(pos, nl - pos);\\n if(!line.empty()) {\\n LOG_DEBUG(\\\"{} {}\\\", prefix, line);\\n }\\n pos = nl + 1;\\n }\\n buffer.erase(0, pos);\\n }\\n\\n if(!buffer.empty()) {\\n LOG_DEBUG(\\\"{} {}\\\", prefix, buffer);\\n }\\n}\\n\\n} // namespace\\n\\nbool WorkerPool::spawn_worker(const std::string& self_path,\\n bool stateful,\\n std::uint64_t memory_limit) {\\n auto& workers = stateful ? stateful_workers : stateless_workers;\\n auto worker_index = workers.size();\\n std::string worker_name = std::string(stateful ? \\\"SF-\\\" : \\\"SL-\\\") + std::to_string(worker_index);\\n\\n et::process::options opts;\\n opts.file = self_path;\\n if(stateful) {\\n opts.args = {self_path,\\n \\\"--mode\\\",\\n \\\"stateful-worker\\\",\\n \\\"--worker-memory-limit\\\",\\n std::to_string(memory_limit)};\\n } else {\\n opts.args = {self_path, \\\"--mode\\\", \\\"stateless-worker\\\"};\\n }\\n\\n opts.args.push_back(\\\"--worker-name\\\");\\n opts.args.push_back(worker_name);\\n\\n if(!log_dir_.empty()) {\\n opts.args.push_back(\\\"--log-dir\\\");\\n opts.args.push_back(log_dir_);\\n }\\n\\n opts.streams = {\\n et::process::stdio::pipe(true, false), // stdin: child reads\\n et::process::stdio::pipe(false, true), // stdout: child writes\\n et::process::stdio::pipe(false, true), // stderr: child writes\\n };\\n\\n auto result = et::process::spawn(opts, loop);\\n if(!result) {\\n LOG_ERROR(\\\"Failed to spawn {} worker: {}\\\",\\n stateful ? \\\"stateful\\\" : \\\"stateless\\\",\\n result.error().message());\\n return false;\\n }\\n\\n auto& spawn = *result;\\n\\n // StreamTransport: input = child's stdout (parent reads), output = child's stdin (parent\\n // writes)\\n auto transport = std::make_unique<et::ipc::StreamTransport>(std::move(spawn.stdout_pipe),\\n std::move(spawn.stdin_pipe));\\n auto peer = std::make_unique<et::ipc::BincodePeer>(loop, std::move(transport));\\n\\n // Schedule stderr log collection\\n std::string prefix = \\\"[\\\" + worker_name + \\\"]\\\";\\n loop.schedule(drain_stderr(std::move(spawn.stderr_pipe), prefix));\\n\\n workers.push_back(WorkerProcess{\\n .proc = std::move(spawn.proc),\\n .peer = std::move(peer),\\n .owned_documents = 0,\\n });\\n\\n auto& w = workers.back();\\n w.peer->set_logger(\\n [prefix](et::ipc::LogLevel, std::string_view msg) {\\n LOG_INFO(\\\"{} ipc: {}\\\", prefix, msg);\\n },\\n et::ipc::LogLevel::debug);\\n loop.schedule(w.peer->run());\\n\\n return true;\\n}\\n\\nbool WorkerPool::start(const WorkerPoolOptions& options) {\\n log_dir_ = options.log_dir;\\n\\n for(std::uint32_t i = 0; i < options.stateless_count; ++i) {\\n if(!spawn_worker(options.self_path, false, 0)) {\\n return false;\\n }\\n }\\n\\n for(std::uint32_t i = 0; i < options.stateful_count; ++i) {\\n if(!spawn_worker(options.self_path, true, options.worker_memory_limit)) {\\n return false;\\n }\\n }\\n\\n // Register evicted notification handler for each stateful worker\\n for(std::size_t i = 0; i < stateful_workers.size(); ++i) {\\n stateful_workers[i].peer->on_notification([this](const worker::EvictedParams& params) {\\n if(on_evicted) {\\n on_evicted(params.path);\\n }\\n });\\n }\\n\\n LOG_INFO(\\\"WorkerPool started: {} stateless, {} stateful workers\\\",\\n stateless_workers.size(),\\n stateful_workers.size());\\n return true;\\n}\\n\\net::task<> WorkerPool::stop() {\\n LOG_INFO(\\\"WorkerPool stopping...\\\");\\n\\n // Close output pipes to signal workers to exit gracefully\\n for(auto& w: stateless_workers) {\\n w.peer->close_output();\\n }\\n for(auto& w: stateful_workers) {\\n w.peer->close_output();\\n }\\n\\n // Send SIGTERM to all workers\\n for(auto& w: stateless_workers) {\\n w.proc.kill(SIGTERM);\\n }\\n for(auto& w: stateful_workers) {\\n w.proc.kill(SIGTERM);\\n }\\n\\n // Wait for all worker processes to exit\\n for(auto& w: stateless_workers) {\\n co_await w.proc.wait();\\n }\\n for(auto& w: stateful_workers) {\\n co_await w.proc.wait();\\n }\\n\\n LOG_INFO(\\\"WorkerPool stopped\\\");\\n}\\n\\nstd::size_t WorkerPool::assign_worker(std::uint32_t path_id) {\\n auto it = owner.find(path_id);\\n if(it != owner.end()) {\\n // Already assigned; touch LRU\\n auto lru_it = owner_lru_index.find(path_id);\\n if(lru_it != owner_lru_index.end()) {\\n owner_lru.erase(lru_it->second);\\n }\\n owner_lru.push_front(path_id);\\n owner_lru_index[path_id] = owner_lru.begin();\\n return it->second;\\n }\\n\\n // New assignment: pick the least-loaded worker\\n auto selected = pick_least_loaded();\\n owner[path_id] = selected;\\n stateful_workers[selected].owned_documents++;\\n owner_lru.push_front(path_id);\\n owner_lru_index[path_id] = owner_lru.begin();\\n return selected;\\n}\\n\\nstd::size_t WorkerPool::pick_least_loaded() {\\n std::size_t best = 0;\\n for(std::size_t i = 1; i < stateful_workers.size(); ++i) {\\n if(stateful_workers[i].owned_documents < stateful_workers[best].owned_documents) {\\n best = i;\\n }\\n }\\n return best;\\n}\\n\\nvoid WorkerPool::remove_owner(std::uint32_t path_id) {\\n auto it = owner.find(path_id);\\n if(it == owner.end())\\n return;\\n\\n auto worker_idx = it->second;\\n stateful_workers[worker_idx].owned_documents--;\\n owner.erase(it);\\n\\n auto lru_it = owner_lru_index.find(path_id);\\n if(lru_it != owner_lru_index.end()) {\\n owner_lru.erase(lru_it->second);\\n owner_lru_index.erase(lru_it);\\n }\\n}\\n\\nvoid WorkerPool::clear_owner(std::size_t worker_index) {\\n llvm::SmallVector<std::uint32_t> to_remove;\\n for(auto& [pid, widx]: owner) {\\n if(widx == worker_index) {\\n to_remove.push_back(pid);\\n }\\n }\\n for(auto pid: to_remove) {\\n remove_owner(pid);\\n }\\n}\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4745,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/syntax/scan.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"syntax/scan.h\\\"\\n\\n#include <deque>\\n\\n#include \\\"syntax/lexer.h\\\"\\n\\n#include \\\"llvm/ADT/StringSet.h\\\"\\n#include \\\"llvm/Support/MemoryBuffer.h\\\"\\n#include \\\"clang/Basic/DiagnosticOptions.h\\\"\\n#include \\\"clang/Basic/FileEntry.h\\\"\\n#include \\\"clang/Basic/FileManager.h\\\"\\n#include \\\"clang/Basic/SourceManager.h\\\"\\n#include \\\"clang/Frontend/CompilerInstance.h\\\"\\n#include \\\"clang/Frontend/FrontendActions.h\\\"\\n#include \\\"clang/Lex/PPCallbacks.h\\\"\\n#include \\\"clang/Lex/Preprocessor.h\\\"\\n#include \\\"clang/Lex/PreprocessorOptions.h\\\"\\n#include \\\"clang/Tooling/CompilationDatabase.h\\\"\\n\\nnamespace clice {\\n\\nScanResult scan(llvm::StringRef content) {\\n namespace dds = clang::dependency_directives_scan;\\n\\n ScanResult result;\\n\\n llvm::SmallVector<dds::Token> tokens;\\n llvm::SmallVector<dds::Directive> directives;\\n\\n if(clang::scanSourceForDependencyDirectives(content, tokens, directives)) {\\n return result;\\n }\\n\\n // Most source files have 10-30 includes; pre-allocate to avoid reallocs.\\n result.includes.reserve(std::min<std::size_t>(directives.size(), 32));\\n\\n int conditional_depth = 0;\\n\\n for(auto& dir: directives) {\\n switch(dir.Kind) {\\n case dds::pp_if:\\n case dds::pp_ifdef:\\n case dds::pp_ifndef: {\\n conditional_depth++;\\n break;\\n }\\n case dds::pp_endif: {\\n if(conditional_depth > 0) {\\n conditional_depth--;\\n }\\n break;\\n }\\n case dds::pp_elif:\\n case dds::pp_elifdef:\\n case dds::pp_elifndef:\\n case dds::pp_else: {\\n break;\\n }\\n case dds::pp_include:\\n case dds::pp_include_next:\\n case dds::pp___include_macros: {\\n // Find the header token (string_literal or header_name).\\n for(auto& tok: dir.Tokens) {\\n if(tok.is(clang::tok::header_name) || tok.is(clang::tok::string_literal)) {\\n auto name = content.substr(tok.Offset, tok.Length);\\n // Strip <> or \\\"\\\" delimiters.\\n if(name.size() >= 2) {\\n bool angled = name.front() == '<';\\n ScanResult::IncludeInfo info;\\n info.path = std::string(name.substr(1, name.size() - 2));\\n info.conditional = conditional_depth > 0;\\n info.is_angled = angled;\\n info.is_include_next = dir.Kind == dds::pp_include_next;\\n result.includes.push_back(std::move(info));\\n }\\n break;\\n }\\n }\\n break;\\n }\\n case dds::cxx_module_decl:\\n case dds::cxx_export_module_decl: {\\n if(conditional_depth > 0) {\\n result.need_preprocess = true;\\n return result;\\n }\\n\\n // Collect module name from tokens: skip keywords, then\\n // collect identifiers, '.', ':'.\\n std::string module_name;\\n bool seen_module_keyword = false;\\n for(auto& tok: dir.Tokens) {\\n if(!seen_module_keyword) {\\n if(tok.is(clang::tok::raw_identifier)) {\\n auto spelling = content.substr(tok.Offset, tok.Length);\\n if(spelling == \\\"module\\\") {\\n seen_module_keyword = true;\\n }\\n }\\n continue;\\n }\\n if(tok.is(clang::tok::raw_identifier)) {\\n module_name += content.substr(tok.Offset, tok.Length);\\n } else if(tok.is(clang::tok::period)) {\\n module_name += '.';\\n } else if(tok.is(clang::tok::colon)) {\\n module_name += ':';\\n }\\n }\\n\\n result.module_name = std::move(module_name);\\n result.is_interface_unit = (dir.Kind == dds::cxx_export_module_decl);\\n break;\\n }\\n default: {\\n break;\\n }\\n }\\n }\\n\\n return result;\\n}\\n\\nnamespace {\\n\\nclass ScanDirectivesGetter : public clang::DependencyDirectivesGetter {\\npublic:\\n ScanDirectivesGetter(SharedScanCache* cache, clang::FileManager& file_mgr) :\\n cache(cache), file_mgr(&file_mgr) {}\\n\\n std::unique_ptr<clang::DependencyDirectivesGetter>\\n cloneFor(clang::FileManager& new_file_mgr) override {\\n return std::make_unique<ScanDirectivesGetter>(cache, new_file_mgr);\\n }\\n\\n std::optional<llvm::ArrayRef<clang::dependency_directives_scan::Directive>>\\n operator()(clang::FileEntryRef file) override {\\n auto path = file.getFileEntry().tryGetRealPathName();\\n if(path.empty()) {\\n path = file.getName();\\n }\\n\\n // Check cache first.\\n if(cache) {\\n auto it = cache->entries.find(path);\\n if(it != cache->entries.end()) {\\n return llvm::ArrayRef(it->second.directives);\\n }\\n }\\n\\n // Read the file content.\\n auto buffer = file_mgr->getBufferForFile(file);\\n if(!buffer) {\\n return std::nullopt;\\n }\\n\\n auto source = (*buffer)->getBuffer().str();\\n\\n // Create entry in its final location first, then scan into it.\\n // Directive::Tokens are ArrayRefs pointing into the tokens SmallVector,\\n // so the entry must not be moved after scanning.\\n SharedScanCache::CachedEntry* entry_ptr;\\n if(cache) {\\n auto [it, _] = cache->entries.try_emplace(path);\\n entry_ptr = &it->second;\\n } else {\\n local_entries.emplace_back();\\n entry_ptr = &local_entries.back();\\n }\\n\\n entry_ptr->source = std::move(source);\\n\\n if(clang::scanSourceForDependencyDirectives(entry_ptr->source,\\n entry_ptr->tokens,\\n entry_ptr->directives)) {\\n // Scan failed — remove the entry.\\n if(cache) {\\n cache->entries.erase(path);\\n } else {\\n local_entries.pop_back();\\n }\\n return std::nullopt;\\n }\\n\\n return llvm::ArrayRef(entry_ptr->directives);\\n }\\n\\nprivate:\\n SharedScanCache* cache;\\n clang::FileManager* file_mgr;\\n std::deque<SharedScanCache::CachedEntry> local_entries;\\n};\\n\\n/// PPCallbacks for precise mode: single ScanResult with accurate\\n/// conditional tracking via preprocessor callbacks.\\nclass PreciseScanPPCallbacks : public clang::PPCallbacks {\\npublic:\\n explicit PreciseScanPPCallbacks(ScanResult& result) : result(result) {}\\n\\n void InclusionDirective(clang::SourceLocation,\\n const clang::Token& include_tok,\\n llvm::StringRef file_name,\\n bool is_angled,\\n clang::CharSourceRange,\\n clang::OptionalFileEntryRef file,\\n llvm::StringRef,\\n llvm::StringRef,\\n const clang::Module*,\\n bool,\\n clang::SrcMgr::CharacteristicKind) override {\\n bool not_found = !file.has_value();\\n std::string resolved_path;\\n if(file) {\\n resolved_path = file->getFileEntry().tryGetRealPathName().str();\\n } else {\\n resolved_path = file_name.str();\\n }\\n\\n ScanResult::IncludeInfo info;\\n info.path = std::move(resolved_path);\\n info.conditional = conditional_depth > 0;\\n info.not_found = not_found;\\n info.is_angled = is_angled;\\n info.is_include_next =\\n include_tok.getIdentifierInfo() &&\\n include_tok.getIdentifierInfo()->getPPKeywordID() == clang::tok::pp_include_next;\\n result.includes.push_back(std::move(info));\\n }\\n\\n void If(clang::SourceLocation, clang::SourceRange, ConditionValueKind) override {\\n conditional_depth++;\\n }\\n\\n void Ifdef(clang::SourceLocation, const clang::Token&, const clang::MacroDefinition&) override {\\n conditional_depth++;\\n }\\n\\n void Ifndef(clang::SourceLocation,\\n const clang::Token&,\\n const clang::MacroDefinition&) override {\\n conditional_depth++;\\n }\\n\\n void Endif(clang::SourceLocation, clang::SourceLocation) override {\\n if(conditional_depth > 0) {\\n conditional_depth--;\\n }\\n }\\n\\n void moduleImport(clang::SourceLocation,\\n clang::ModuleIdPath names,\\n const clang::Module*) override {\\n std::string name;\\n for(auto& part: names) {\\n if(!name.empty()) {\\n name += '.';\\n }\\n name += part.getIdentifierInfo()->getName();\\n }\\n result.modules.emplace_back(std::move(name));\\n }\\n\\nprivate:\\n ScanResult& result;\\n int conditional_depth = 0;\\n};\\n\\n/// Create and configure a CompilerInstance for scanning.\\n/// If content is non-empty, it is used as remapped source for the main file.\\nstd::unique_ptr<clang::CompilerInstance>\\n create_scan_instance(llvm::ArrayRef<const char*> arguments,\\n llvm::StringRef directory,\\n llvm::StringRef content,\\n llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs) {\\n clang::DiagnosticOptions diag_opts;\\n auto diag_engine = clang::CompilerInstance::createDiagnostics(*vfs,\\n diag_opts,\\n new clang::IgnoringDiagConsumer(),\\n true);\\n\\n std::unique_ptr<clang::CompilerInvocation> invocation;\\n\\n bool is_cc1 = arguments.size() >= 2 && llvm::StringRef(arguments[1]) == \\\"-cc1\\\";\\n if(is_cc1) {\\n invocation = std::make_unique<clang::CompilerInvocation>();\\n if(!clang::CompilerInvocation::CreateFromArgs(*invocation,\\n llvm::ArrayRef(arguments).drop_front(2),\\n *diag_engine,\\n arguments[0])) {\\n return nullptr;\\n }\\n } else {\\n clang::CreateInvocationOptions options = {\\n .Diags = diag_engine,\\n .VFS = vfs,\\n .ProbePrecompiled = false,\\n };\\n invocation = clang::createInvocation(arguments, options);\\n if(!invocation) {\\n return nullptr;\\n }\\n }\\n\\n invocation->getFrontendOpts().DisableFree = false;\\n invocation->getFileSystemOpts().WorkingDir = directory.str();\\n\\n if(!content.empty()) {\\n auto& inputs = invocation->getFrontendOpts().Inputs;\\n if(!inputs.empty()) {\\n auto main_file = inputs[0].getFile();\\n // Use an overlay VFS to inject the remapped content. This ensures\\n // both the preprocessor and the DependencyDirectivesGetter see it.\\n auto overlay = llvm::makeIntrusiveRefCnt<llvm::vfs::OverlayFileSystem>(vfs);\\n auto mem_fs = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();\\n mem_fs->addFile(main_file, 0, llvm::MemoryBuffer::getMemBufferCopy(content, main_file));\\n overlay->pushOverlay(std::move(mem_fs));\\n vfs = std::move(overlay);\\n }\\n }\\n\\n auto instance = std::make_unique<clang::CompilerInstance>(std::move(invocation));\\n instance->createDiagnostics(*vfs, new clang::IgnoringDiagConsumer(), true);\\n instance->getDiagnostics().setSuppressAllDiagnostics(true);\\n instance->createFileManager(vfs);\\n\\n return instance;\\n}\\n\\n} // namespace\\n\\nScanResult scan_precise(llvm::ArrayRef<const char*> arguments,\\n llvm::StringRef directory,\\n llvm::StringRef content,\\n SharedScanCache* cache,\\n llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs) {\\n ScanResult result;\\n\\n if(!vfs) {\\n vfs = llvm::vfs::createPhysicalFileSystem();\\n }\\n\\n auto instance = create_scan_instance(arguments, directory, content, vfs);\\n if(!instance) {\\n return result;\\n }\\n\\n auto getter = std::make_unique<ScanDirectivesGetter>(cache, instance->getFileManager());\\n instance->setDependencyDirectivesGetter(std::move(getter));\\n\\n if(!instance->createTarget()) {\\n return result;\\n }\\n\\n auto action = std::make_unique<clang::PreprocessOnlyAction>();\\n\\n if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) {\\n return result;\\n }\\n\\n instance->getPreprocessor().addPPCallbacks(std::make_unique<PreciseScanPPCallbacks>(result));\\n\\n if(auto error = action->Execute()) {\\n llvm::consumeError(std::move(error));\\n }\\n\\n action->EndSourceFile();\\n\\n // Get module name from preprocessor.\\n auto& pp = instance->getPreprocessor();\\n if(pp.isInNamedModule()) {\\n result.module_name = pp.getNamedModuleName();\\n result.is_interface_unit = pp.isInNamedInterfaceUnit();\\n }\\n\\n return result;\\n}\\n\\nScanResult scan_module_decl(llvm::ArrayRef<const char*> arguments,\\n llvm::StringRef directory,\\n llvm::StringRef content,\\n SharedScanCache* cache,\\n llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs) {\\n ScanResult result;\\n\\n if(!vfs) {\\n vfs = llvm::vfs::createPhysicalFileSystem();\\n }\\n\\n auto instance = create_scan_instance(arguments, directory, content, vfs);\\n if(!instance) {\\n return result;\\n }\\n\\n auto getter = std::make_unique<ScanDirectivesGetter>(cache, instance->getFileManager());\\n instance->setDependencyDirectivesGetter(std::move(getter));\\n\\n if(!instance->createTarget()) {\\n return result;\\n }\\n\\n auto action = std::make_unique<clang::PreprocessOnlyAction>();\\n\\n if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) {\\n return result;\\n }\\n\\n // Instead of action->Execute() which processes the entire file,\\n // manually lex tokens and stop as soon as the module declaration is found.\\n auto& pp = instance->getPreprocessor();\\n pp.EnterMainSourceFile();\\n\\n clang::Token tok;\\n do {\\n pp.Lex(tok);\\n if(pp.isInNamedModule()) {\\n result.module_name = pp.getNamedModuleName();\\n result.is_interface_unit = pp.isInNamedInterfaceUnit();\\n break;\\n }\\n } while(tok.isNot(clang::tok::eof));\\n\\n action->EndSourceFile();\\n\\n return result;\\n}\\n\\nstd::uint32_t compute_preamble_bound(llvm::StringRef content) {\\n auto result = compute_preamble_bounds(content);\\n if(result.empty()) {\\n return 0;\\n } else {\\n return result.back();\\n }\\n}\\n\\nstd::vector<std::uint32_t> compute_preamble_bounds(llvm::StringRef content) {\\n std::vector<std::uint32_t> result;\\n\\n Lexer lexer(content, true, nullptr, false);\\n\\n while(true) {\\n auto token = lexer.advance();\\n if(token.is_eof()) {\\n break;\\n }\\n\\n if(token.is_at_start_of_line) {\\n if(token.kind == clang::tok::hash) {\\n /// For preprocessor directive, consume the whole directive.\\n lexer.advance_until(clang::tok::eod);\\n auto last = lexer.last();\\n\\n /// Append the token before the eod.\\n result.push_back(last.range.end);\\n } else if(token.is_identifier() && token.text(content) == \\\"module\\\") {\\n /// If we encounter a module keyword at the start of a line, it may be\\n /// a module declaration or global module fragment.\\n auto next = lexer.next();\\n\\n if(next.kind == clang::tok::semi) {\\n /// If next token is `;`, it is a global module fragment.\\n /// we just continue.\\n lexer.advance();\\n\\n /// Append it to bounds.\\n result.push_back(next.range.end);\\n } else {\\n break;\\n }\\n } else {\\n break;\\n }\\n }\\n }\\n\\n return result;\\n}\\n\\n/// Check if a preprocessor #include/#import directive line is complete.\\nstatic bool is_include_directive_complete(llvm::StringRef directive) {\\n if(directive.contains('\\\"')) {\\n auto after_keyword = directive.drop_front(directive.starts_with(\\\"import\\\") ? 6 : 7);\\n return after_keyword.count('\\\"') >= 2;\\n }\\n if(directive.contains('<')) {\\n return directive.contains('>');\\n }\\n // No \\\" or < — might be a macro (#include FOO) or just incomplete (#include ).\\n auto after_keyword = directive.drop_front(directive.starts_with(\\\"import\\\") ? 6 : 7).ltrim();\\n return !after_keyword.empty();\\n}\\n\\n/// Check if a C++20 module statement line (import/export module) is complete.\\n/// A complete statement must end with ';'.\\nstatic bool is_module_statement_complete(llvm::StringRef trimmed) {\\n return trimmed.rtrim().ends_with(\\\";\\\");\\n}\\n\\nbool is_preamble_complete(llvm::StringRef content, std::uint32_t bound) {\\n auto preamble = content.substr(0, bound);\\n\\n while(!preamble.empty()) {\\n auto [line, rest] = preamble.split('\\\\n');\\n preamble = rest;\\n\\n auto trimmed = line.ltrim();\\n\\n // Preprocessor directive: #include or #import\\n if(trimmed.starts_with(\\\"#\\\")) {\\n auto directive = trimmed.drop_front(1).ltrim();\\n if(directive.starts_with(\\\"include\\\") || directive.starts_with(\\\"import\\\")) {\\n if(!is_include_directive_complete(directive)) {\\n return false;\\n }\\n }\\n continue;\\n }\\n\\n // C++20 module statements: import, export module, export import\\n // Check word boundary to avoid matching identifiers like \\\"important\\\".\\n auto is_keyword = [](llvm::StringRef s, llvm::StringRef keyword) {\\n return s.starts_with(keyword) &&\\n (s.size() == keyword.size() || !llvm::isAlnum(s[keyword.size()]));\\n };\\n if(is_keyword(trimmed, \\\"import\\\") || is_keyword(trimmed, \\\"export\\\")) {\\n if(!is_module_statement_complete(trimmed)) {\\n return false;\\n }\\n }\\n }\\n\\n return true;\\n}\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4745,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/src/syntax/scan.h\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#pragma once\\n\\n#include <cstdint>\\n#include <string>\\n#include <vector>\\n\\n#include \\\"llvm/ADT/ArrayRef.h\\\"\\n#include \\\"llvm/ADT/IntrusiveRefCntPtr.h\\\"\\n#include \\\"llvm/ADT/StringMap.h\\\"\\n#include \\\"llvm/ADT/StringRef.h\\\"\\n#include \\\"llvm/Support/VirtualFileSystem.h\\\"\\n#include \\\"clang/Lex/DependencyDirectivesScanner.h\\\"\\n\\nnamespace clice {\\n\\nstruct ScanResult {\\n /// Module name (empty if not a module unit).\\n std::string module_name;\\n\\n /// Whether this is an interface unit (has `export module`).\\n bool is_interface_unit = false;\\n\\n /// Whether module declaration is inside conditional directive,\\n /// signaling caller should fall back to preprocessor-based scan.\\n bool need_preprocess = false;\\n\\n struct IncludeInfo {\\n /// Resolved file path (fuzzy/precise scan) or raw header name (lexer scan).\\n std::string path;\\n\\n /// Whether this include is inside a conditional directive context.\\n bool conditional = false;\\n\\n /// Whether the included file was not found during resolution.\\n bool not_found = false;\\n\\n /// Whether this is an angled include (<...>) vs quoted (\\\"...\\\").\\n bool is_angled = false;\\n\\n /// Whether this is an #include_next directive.\\n bool is_include_next = false;\\n };\\n\\n /// Include file names.\\n /// From lexer scan these are the raw header names;\\n /// from preprocessor scan these are resolved file paths.\\n std::vector<IncludeInfo> includes;\\n\\n /// Dependent module names.\\n std::vector<std::string> modules;\\n};\\n\\n/// Shared cache for dependency directives across multiple scan invocations.\\nstruct SharedScanCache {\\n struct CachedEntry {\\n /// The source content of the file (kept alive for token references).\\n std::string source;\\n\\n /// Scanned tokens.\\n llvm::SmallVector<clang::dependency_directives_scan::Token> tokens;\\n\\n /// Scanned directives (referencing tokens above).\\n llvm::SmallVector<clang::dependency_directives_scan::Directive> directives;\\n };\\n\\n /// path -> cached scan result.\\n llvm::StringMap<CachedEntry> entries;\\n};\\n\\n/// Quick lexer-based scan for module name and include file names.\\n/// If module declaration is inside #if/#ifdef, sets need_preprocess=true\\n/// and module_name will be empty.\\nScanResult scan(llvm::StringRef content);\\n\\n/// Precise preprocessing-based scan. Keeps all directives including #define\\n/// and conditionals. Used for lazy module dependency resolution.\\nScanResult scan_precise(llvm::ArrayRef<const char*> arguments,\\n llvm::StringRef directory,\\n llvm::StringRef content = {},\\n SharedScanCache* cache = nullptr,\\n llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs = nullptr);\\n\\n/// Lightweight preprocessor-based fallback for resolving module declarations\\n/// inside conditional directives (#if/#ifdef). When the quick `scan()` detects\\n/// `need_preprocess=true`, this function runs clang's preprocessor to evaluate\\n/// the conditions and extract the actual module name.\\n///\\n/// Much cheaper than `scan_precise()`: stops lexing as soon as the module\\n/// declaration is found, so it only processes the file preamble (global module\\n/// fragment + conditionals around the module declaration). Only populates\\n/// `module_name` and `is_interface_unit` in the returned ScanResult.\\nScanResult scan_module_decl(llvm::ArrayRef<const char*> arguments,\\n llvm::StringRef directory,\\n llvm::StringRef content = {},\\n SharedScanCache* cache = nullptr,\\n llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs = nullptr);\\n\\n/// Compute preamble bound (moved from compile/preamble).\\nstd::uint32_t compute_preamble_bound(llvm::StringRef content);\\n\\nstd::vector<std::uint32_t> compute_preamble_bounds(llvm::StringRef content);\\n\\n/// Check if the preamble region contains only syntactically complete directives.\\n/// Returns false if any #include/#import has an unclosed \\\"\\\" or <>, or any\\n/// C++20 module statement (import/export) is missing a trailing ';',\\n/// indicating the user is still typing and PCH/PCM rebuild should be deferred.\\nbool is_preamble_complete(llvm::StringRef content, std::uint32_t bound);\\n\\n} // namespace clice\\n\"}}}"}
{"ts":4745,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/unit/syntax/scan_tests.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"test/test.h\\\"\\n#include \\\"syntax/scan.h\\\"\\n\\nnamespace clice::testing {\\nnamespace {\\n\\nTEST_SUITE(Scan) {\\n\\n// === scan() tests ===\\n\\nTEST_CASE(BasicIncludes) {\\n auto result = scan(R\\\"(\\n#include <vector>\\n#include \\\"foo/bar.h\\\"\\nint x = 1;\\n)\\\");\\n\\n ASSERT_EQ(result.includes.size(), 2u);\\n EXPECT_EQ(result.includes[0].path, \\\"vector\\\");\\n EXPECT_TRUE(result.includes[0].is_angled);\\n EXPECT_FALSE(result.includes[0].conditional);\\n EXPECT_EQ(result.includes[1].path, \\\"foo/bar.h\\\");\\n EXPECT_FALSE(result.includes[1].is_angled);\\n EXPECT_FALSE(result.includes[1].conditional);\\n EXPECT_TRUE(result.module_name.empty());\\n}\\n\\nTEST_CASE(ConditionalIncludes) {\\n auto result = scan(R\\\"(\\n#include <always.h>\\n#ifdef FOO\\n#include <conditional.h>\\n#endif\\n#include <after.h>\\n)\\\");\\n\\n ASSERT_EQ(result.includes.size(), 3u);\\n EXPECT_EQ(result.includes[0].path, \\\"always.h\\\");\\n EXPECT_FALSE(result.includes[0].conditional);\\n EXPECT_EQ(result.includes[1].path, \\\"conditional.h\\\");\\n EXPECT_TRUE(result.includes[1].conditional);\\n EXPECT_EQ(result.includes[2].path, \\\"after.h\\\");\\n EXPECT_FALSE(result.includes[2].conditional);\\n}\\n\\nTEST_CASE(NestedConditionals) {\\n auto result = scan(R\\\"(\\n#ifdef A\\n#ifdef B\\n#include <nested.h>\\n#endif\\n#include <outer.h>\\n#endif\\n#include <top.h>\\n)\\\");\\n\\n ASSERT_EQ(result.includes.size(), 3u);\\n EXPECT_EQ(result.includes[0].path, \\\"nested.h\\\");\\n EXPECT_TRUE(result.includes[0].conditional);\\n EXPECT_EQ(result.includes[1].path, \\\"outer.h\\\");\\n EXPECT_TRUE(result.includes[1].conditional);\\n EXPECT_EQ(result.includes[2].path, \\\"top.h\\\");\\n EXPECT_FALSE(result.includes[2].conditional);\\n}\\n\\nTEST_CASE(ModuleDeclaration) {\\n auto result = scan(R\\\"(\\nmodule;\\n#include <header.h>\\nexport module my.module;\\n)\\\");\\n\\n EXPECT_EQ(result.module_name, \\\"my.module\\\");\\n EXPECT_TRUE(result.is_interface_unit);\\n EXPECT_FALSE(result.need_preprocess);\\n ASSERT_EQ(result.includes.size(), 1u);\\n EXPECT_EQ(result.includes[0].path, \\\"header.h\\\");\\n EXPECT_TRUE(result.includes[0].is_angled);\\n}\\n\\nTEST_CASE(ModulePartition) {\\n auto result = scan(R\\\"(\\nmodule my.module:part;\\n)\\\");\\n\\n EXPECT_EQ(result.module_name, \\\"my.module:part\\\");\\n EXPECT_FALSE(result.is_interface_unit);\\n}\\n\\nTEST_CASE(ModuleImplementation) {\\n auto result = scan(R\\\"(\\nmodule my.module;\\n)\\\");\\n\\n EXPECT_EQ(result.module_name, \\\"my.module\\\");\\n EXPECT_FALSE(result.is_interface_unit);\\n}\\n\\nTEST_CASE(ConditionalModule) {\\n auto result = scan(R\\\"(\\n#ifdef USE_MODULES\\nexport module foo;\\n#endif\\n)\\\");\\n\\n EXPECT_TRUE(result.module_name.empty());\\n EXPECT_TRUE(result.need_preprocess);\\n}\\n\\nTEST_CASE(GlobalModuleFragment) {\\n auto result = scan(R\\\"(\\nmodule;\\nexport module test;\\n)\\\");\\n\\n EXPECT_EQ(result.module_name, \\\"test\\\");\\n EXPECT_TRUE(result.is_interface_unit);\\n}\\n\\nTEST_CASE(EmptyContent) {\\n auto result = scan(\\\"\\\");\\n EXPECT_TRUE(result.includes.empty());\\n EXPECT_TRUE(result.module_name.empty());\\n EXPECT_FALSE(result.need_preprocess);\\n}\\n\\nTEST_CASE(NoDirectives) {\\n auto result = scan(R\\\"(\\nint main() {\\n return 0;\\n}\\n)\\\");\\n\\n EXPECT_TRUE(result.includes.empty());\\n EXPECT_TRUE(result.module_name.empty());\\n EXPECT_FALSE(result.is_interface_unit);\\n EXPECT_FALSE(result.need_preprocess);\\n}\\n\\n// === scan_precise() tests ===\\n\\nTEST_CASE(PreciseBasic) {\\n auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();\\n auto main_path = TestVFS::path(\\\"main.cpp\\\");\\n vfs->add(\\\"main.cpp\\\", R\\\"(\\n#include \\\"header.h\\\"\\nint main() {}\\n)\\\");\\n vfs->add(\\\"header.h\\\", R\\\"(\\n#pragma once\\nint x = 1;\\n)\\\");\\n\\n auto args = std::vector<const char*>{\\\"clang++\\\", \\\"-std=c++20\\\", main_path.c_str()};\\n auto result = scan_precise(args, TestVFS::root(), {}, nullptr, vfs);\\n\\n ASSERT_EQ(result.includes.size(), 1u);\\n EXPECT_FALSE(result.includes[0].not_found);\\n EXPECT_FALSE(result.includes[0].conditional);\\n}\\n\\nTEST_CASE(PreciseConditionalWithDefine) {\\n auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();\\n auto main_path = TestVFS::path(\\\"main.cpp\\\");\\n vfs->add(\\\"main.cpp\\\", R\\\"(\\n#define USE_FOO\\n#ifdef USE_FOO\\n#include \\\"foo.h\\\"\\n#endif\\n#ifndef USE_FOO\\n#include \\\"bar.h\\\"\\n#endif\\n)\\\");\\n vfs->add(\\\"foo.h\\\");\\n vfs->add(\\\"bar.h\\\");\\n\\n auto args = std::vector<const char*>{\\\"clang++\\\", \\\"-std=c++20\\\", main_path.c_str()};\\n auto result = scan_precise(args, TestVFS::root(), {}, nullptr, vfs);\\n\\n // Precise mode evaluates conditionals: only foo.h should be included.\\n ASSERT_EQ(result.includes.size(), 1u);\\n EXPECT_TRUE(result.includes[0].conditional);\\n EXPECT_TRUE(result.includes[0].path.find(\\\"foo.h\\\") != std::string::npos);\\n}\\n\\nTEST_CASE(PreciseWithContent) {\\n auto vfs = llvm::makeIntrusiveRefCnt<TestVFS>();\\n auto main_path = TestVFS::path(\\\"main.cpp\\\");\\n vfs->add(\\\"main.cpp\\\");\\n vfs->add(\\\"header.h\\\");\\n\\n auto args = std::vector<const char*>{\\\"clang++\\\", \\\"-std=c++20\\\", main_path.c_str()};\\n auto result = scan_precise(args, TestVFS::root(), R\\\"(#include \\\"header.h\\\")\\\", nullptr, vfs);\\n\\n ASSERT_EQ(result.includes.size(), 1u);\\n EXPECT_FALSE(result.includes[0].not_found);\\n}\\n\\n}; // TEST_SUITE(Scan)\\n\\nTEST_SUITE(PreambleBound) {\\n\\nTEST_CASE(Empty) {\\n EXPECT_EQ(compute_preamble_bound(\\\"\\\"), 0u);\\n}\\n\\nTEST_CASE(NoDirectives) {\\n EXPECT_EQ(compute_preamble_bound(\\\"int x = 1;\\\"), 0u);\\n}\\n\\nTEST_CASE(SingleInclude) {\\n llvm::StringRef src = R\\\"(\\n#include <vector>\\nint x;\\n)\\\";\\n auto bound = compute_preamble_bound(src);\\n EXPECT_TRUE(bound > 0u);\\n EXPECT_TRUE(bound <= src.find(\\\"int\\\"));\\n}\\n\\nTEST_CASE(MultipleDirectives) {\\n llvm::StringRef src = R\\\"(\\n#include <vector>\\n#include <string>\\n#define FOO 1\\nint x;\\n)\\\";\\n auto bound = compute_preamble_bound(src);\\n EXPECT_TRUE(bound > src.find(\\\"#define\\\"));\\n}\\n\\nTEST_CASE(GlobalModuleFragment) {\\n llvm::StringRef src = R\\\"(\\nmodule;\\n#include <vector>\\nexport module foo;\\n)\\\";\\n auto bound = compute_preamble_bound(src);\\n EXPECT_TRUE(bound > 0u);\\n EXPECT_TRUE(bound < src.size());\\n}\\n\\nTEST_CASE(BoundsVector) {\\n llvm::StringRef src = R\\\"(\\n#include <a>\\n#include <b>\\nint x;\\n)\\\";\\n auto bounds = compute_preamble_bounds(src);\\n ASSERT_EQ(bounds.size(), 2u);\\n EXPECT_TRUE(bounds[0] < bounds[1]);\\n}\\n\\nTEST_CASE(BoundsWithModuleFragment) {\\n llvm::StringRef src = R\\\"(\\nmodule;\\n#include <a>\\n#include <b>\\nexport module foo;\\n)\\\";\\n auto bounds = compute_preamble_bounds(src);\\n // module; + two #include = 3 bounds.\\n ASSERT_EQ(bounds.size(), 3u);\\n EXPECT_TRUE(bounds[0] < bounds[1]);\\n EXPECT_TRUE(bounds[1] < bounds[2]);\\n}\\n\\nTEST_CASE(StopsAtCode) {\\n llvm::StringRef src = R\\\"(\\n#include <a>\\nint x;\\n#include <b>\\n)\\\";\\n auto bounds = compute_preamble_bounds(src);\\n ASSERT_EQ(bounds.size(), 1u);\\n}\\n\\nTEST_CASE(ConditionalDirectives) {\\n llvm::StringRef src = R\\\"(\\n#ifndef GUARD\\n#define GUARD\\n#include <a>\\n#endif\\nint x;\\n)\\\";\\n auto bound = compute_preamble_bound(src);\\n EXPECT_TRUE(bound > src.find(\\\"#endif\\\"));\\n}\\n\\n}; // TEST_SUITE(PreambleBound)\\n\\nTEST_SUITE(PreambleComplete) {\\n\\n// --- #include completeness ---\\n\\nTEST_CASE(CompleteQuotedInclude) {\\n llvm::StringRef content = \\\"#include \\\\\\\"foo.h\\\\\\\"\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_TRUE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(CompleteAngledInclude) {\\n llvm::StringRef content = \\\"#include <vector>\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_TRUE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(IncompleteQuotedInclude) {\\n llvm::StringRef content = \\\"#include \\\\\\\"foo\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_FALSE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(IncompleteAngledInclude) {\\n llvm::StringRef content = \\\"#include <sys/\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_FALSE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(IncludeWithNoPath) {\\n llvm::StringRef content = \\\"#include \\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_FALSE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(IncludeMacroUsage) {\\n llvm::StringRef content = \\\"#include FOO\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_TRUE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(MultipleIncludesAllComplete) {\\n llvm::StringRef content = \\\"#include <vector>\\\\n#include \\\\\\\"foo.h\\\\\\\"\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_TRUE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(MultipleIncludesLastIncomplete) {\\n llvm::StringRef content = \\\"#include <vector>\\\\n#include \\\\\\\"foo\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_FALSE(is_preamble_complete(content, bound));\\n}\\n\\n// --- C++20 module statements ---\\n// Note: compute_preamble_bound does not include import/export lines in its\\n// bound, so we pass manual bounds covering the relevant lines.\\n\\nTEST_CASE(CompleteImport) {\\n llvm::StringRef content = \\\"import std;\\\\nint x;\\\";\\n // Bound covers \\\"import std;\\\\n\\\".\\n EXPECT_TRUE(is_preamble_complete(content, 12));\\n}\\n\\nTEST_CASE(ImportMissingSemicolon) {\\n llvm::StringRef content = \\\"import std\\\\nint x;\\\";\\n // Bound covers \\\"import std\\\\n\\\".\\n EXPECT_FALSE(is_preamble_complete(content, 11));\\n}\\n\\nTEST_CASE(ImportWithNothing) {\\n llvm::StringRef content = \\\"import \\\\nint x;\\\";\\n // Bound covers \\\"import \\\\n\\\".\\n EXPECT_FALSE(is_preamble_complete(content, 8));\\n}\\n\\nTEST_CASE(CompleteExportModule) {\\n llvm::StringRef content = \\\"export module foo;\\\\nint x;\\\";\\n // Bound covers \\\"export module foo;\\\\n\\\".\\n EXPECT_TRUE(is_preamble_complete(content, 19));\\n}\\n\\nTEST_CASE(ExportModuleMissingSemicolon) {\\n llvm::StringRef content = \\\"export module foo\\\\nint x;\\\";\\n // Bound covers \\\"export module foo\\\\n\\\".\\n EXPECT_FALSE(is_preamble_complete(content, 18));\\n}\\n\\nTEST_CASE(CompleteExportImport) {\\n llvm::StringRef content = \\\"export import std;\\\\nint x;\\\";\\n // Bound covers \\\"export import std;\\\\n\\\".\\n EXPECT_TRUE(is_preamble_complete(content, 19));\\n}\\n\\n// --- Edge cases ---\\n\\nTEST_CASE(EmptyPreamble) {\\n llvm::StringRef content = \\\"int x;\\\";\\n EXPECT_TRUE(is_preamble_complete(content, 0));\\n}\\n\\nTEST_CASE(NonImportIncludeLinesIgnored) {\\n llvm::StringRef content = \\\"#define FOO 1\\\\n#ifdef BAR\\\\n#endif\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_TRUE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(ImportantDoesNotMatchImport) {\\n // \\\"important\\\" starts with \\\"import\\\" but should NOT be treated as an import.\\n llvm::StringRef content = \\\"#include <vector>\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n // Manually test with content that has \\\"important\\\" within the preamble region.\\n // Since compute_preamble_bound won't include non-directive lines, we test\\n // is_preamble_complete directly with a crafted bound.\\n llvm::StringRef crafted = \\\"important = 1;\\\\n\\\";\\n EXPECT_TRUE(is_preamble_complete(crafted, crafted.size()));\\n}\\n\\nTEST_CASE(PreprocessorDirectivesIgnored) {\\n llvm::StringRef content = \\\"#ifdef FOO\\\\n#define BAR 1\\\\n#endif\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_TRUE(is_preamble_complete(content, bound));\\n}\\n\\nTEST_CASE(MixedIncludeAndImportAllComplete) {\\n llvm::StringRef content = \\\"#include <vector>\\\\nimport std;\\\\nint x;\\\";\\n auto bound = compute_preamble_bound(content);\\n EXPECT_TRUE(is_preamble_complete(content, bound));\\n}\\n\\n}; // TEST_SUITE(PreambleComplete)\\n\\n} // namespace\\n} // namespace clice::testing\\n\"}}}"}
{"ts":4746,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/include_completion/main.cpp\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#include \\\"\\n\"}}}"}
{"ts":4746,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/include_completion/myheader.h\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#pragma once\\nint my_func();\\n\"}}}"}
{"ts":4746,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didOpen\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/include_completion/subdir/nested.h\",\"languageId\":\"cpp\",\"version\":1,\"text\":\"#pragma once\\nint nested_func();\\n\"}}}"}
{"ts":4886,"msg":"{\"jsonrpc\":\"2.0\",\"id\":16,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":9,\"character\":27},\"end\":{\"line\":9,\"character\":27}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":4978,"msg":"{\"jsonrpc\":\"2.0\",\"id\":17,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":12,\"character\":0}}}}"}
{"ts":5179,"msg":"{\"jsonrpc\":\"2.0\",\"id\":18,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":12,\"character\":0}}}}"}
{"ts":5181,"msg":"{\"jsonrpc\":\"2.0\",\"id\":19,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":9,\"character\":27},\"end\":{\"line\":9,\"character\":27}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":5278,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":2},\"contentChanges\":[{\"range\":{\"start\":{\"line\":9,\"character\":27},\"end\":{\"line\":9,\"character\":27}},\"rangeLength\":0,\"text\":\"\\n \"}]}}"}
{"ts":5278,"msg":"{\"jsonrpc\":\"2.0\",\"id\":20,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":5517,"msg":"{\"jsonrpc\":\"2.0\",\"id\":21,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":5606,"msg":"{\"jsonrpc\":\"2.0\",\"id\":22,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":5666,"msg":"{\"jsonrpc\":\"2.0\",\"id\":23,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":5666,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":3},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":4},\"end\":{\"line\":10,\"character\":4}},\"rangeLength\":0,\"text\":\"s\"}]}}"}
{"ts":5666,"msg":"{\"jsonrpc\":\"2.0\",\"id\":24,\"method\":\"textDocument/completion\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":10,\"character\":5},\"context\":{\"triggerKind\":1}}}"}
{"ts":5666,"msg":"{\"jsonrpc\":\"2.0\",\"id\":25,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":5680,"msg":"{\"jsonrpc\":\"2.0\",\"id\":26,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":5775,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":4},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":5},\"end\":{\"line\":10,\"character\":5}},\"rangeLength\":0,\"text\":\"t\"}]}}"}
{"ts":5818,"msg":"{\"jsonrpc\":\"2.0\",\"id\":27,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":5954,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":5},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":6},\"end\":{\"line\":10,\"character\":6}},\"rangeLength\":0,\"text\":\"d\"}]}}"}
{"ts":5964,"msg":"{\"jsonrpc\":\"2.0\",\"id\":28,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":6202,"msg":"{\"jsonrpc\":\"2.0\",\"id\":29,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":6202,"msg":"{\"jsonrpc\":\"2.0\",\"id\":30,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":6293,"msg":"{\"jsonrpc\":\"2.0\",\"id\":31,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":6294,"msg":"{\"jsonrpc\":\"2.0\",\"id\":32,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"context\":{\"diagnostics\":[{\"range\":{\"start\":{\"line\":10,\"character\":4},\"end\":{\"line\":10,\"character\":7}},\"message\":\"unexpected namespace name 'std': expected expression\",\"code\":\"err_unexpected_namespace\",\"severity\":1,\"source\":\"clang\"},{\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"message\":\"expected ';' after expression\",\"code\":\"err_expected_semi_after_expr\",\"severity\":1,\"source\":\"clang\"}],\"triggerKind\":2}}}"}
{"ts":6294,"msg":"{\"jsonrpc\":\"2.0\",\"id\":33,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":6473,"msg":"{\"jsonrpc\":\"2.0\",\"id\":34,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"context\":{\"diagnostics\":[{\"range\":{\"start\":{\"line\":10,\"character\":4},\"end\":{\"line\":10,\"character\":7}},\"message\":\"unexpected namespace name 'std': expected expression\",\"code\":\"err_unexpected_namespace\",\"severity\":1,\"source\":\"clang\"},{\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"message\":\"expected ';' after expression\",\"code\":\"err_expected_semi_after_expr\",\"severity\":1,\"source\":\"clang\"}],\"triggerKind\":2}}}"}
{"ts":6473,"msg":"{\"jsonrpc\":\"2.0\",\"id\":35,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"context\":{\"diagnostics\":[{\"range\":{\"start\":{\"line\":10,\"character\":4},\"end\":{\"line\":10,\"character\":7}},\"message\":\"unexpected namespace name 'std': expected expression\",\"code\":\"err_unexpected_namespace\",\"severity\":1,\"source\":\"clang\"},{\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"message\":\"expected ';' after expression\",\"code\":\"err_expected_semi_after_expr\",\"severity\":1,\"source\":\"clang\"}],\"triggerKind\":2}}}"}
{"ts":6474,"msg":"{\"jsonrpc\":\"2.0\",\"id\":36,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"context\":{\"diagnostics\":[{\"range\":{\"start\":{\"line\":10,\"character\":4},\"end\":{\"line\":10,\"character\":7}},\"message\":\"unexpected namespace name 'std': expected expression\",\"code\":\"err_unexpected_namespace\",\"severity\":1,\"source\":\"clang\"},{\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"message\":\"expected ';' after expression\",\"code\":\"err_expected_semi_after_expr\",\"severity\":1,\"source\":\"clang\"}],\"triggerKind\":2}}}"}
{"ts":6480,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":34}}"}
{"ts":6480,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":35}}"}
{"ts":6522,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":36}}"}
{"ts":6522,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":6},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":7},\"end\":{\"line\":10,\"character\":7}},\"rangeLength\":0,\"text\":\":\"}]}}"}
{"ts":6533,"msg":"{\"jsonrpc\":\"2.0\",\"id\":37,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":6649,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":7},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":8},\"end\":{\"line\":10,\"character\":8}},\"rangeLength\":0,\"text\":\":\"}]}}"}
{"ts":6677,"msg":"{\"jsonrpc\":\"2.0\",\"id\":38,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":6802,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":8},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":9},\"end\":{\"line\":10,\"character\":9}},\"rangeLength\":0,\"text\":\"c\"}]}}"}
{"ts":6811,"msg":"{\"jsonrpc\":\"2.0\",\"id\":39,\"method\":\"textDocument/completion\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":10,\"character\":10},\"context\":{\"triggerKind\":1}}}"}
{"ts":6841,"msg":"{\"jsonrpc\":\"2.0\",\"id\":40,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":6966,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":9},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":10},\"end\":{\"line\":10,\"character\":10}},\"rangeLength\":0,\"text\":\"o\"}]}}"}
{"ts":7013,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":40}}"}
{"ts":7013,"msg":"{\"jsonrpc\":\"2.0\",\"id\":41,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":7014,"msg":"{\"jsonrpc\":\"2.0\",\"id\":42,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":7095,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":10},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":11},\"end\":{\"line\":10,\"character\":11}},\"rangeLength\":0,\"text\":\"u\"}]}}"}
{"ts":7138,"msg":"{\"jsonrpc\":\"2.0\",\"id\":43,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":7240,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":11},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":12},\"end\":{\"line\":10,\"character\":12}},\"rangeLength\":0,\"text\":\"t\"}]}}"}
{"ts":7282,"msg":"{\"jsonrpc\":\"2.0\",\"id\":44,\"method\":\"textDocument/completion\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":10,\"character\":13},\"context\":{\"triggerKind\":1}}}"}
{"ts":7282,"msg":"{\"jsonrpc\":\"2.0\",\"id\":45,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":7282,"msg":"{\"jsonrpc\":\"2.0\",\"id\":46,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":13},\"end\":{\"line\":10,\"character\":13}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":7282,"msg":"{\"jsonrpc\":\"2.0\",\"id\":47,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":7598,"msg":"{\"jsonrpc\":\"2.0\",\"id\":48,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":7598,"msg":"{\"jsonrpc\":\"2.0\",\"id\":49,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":7598,"msg":"{\"jsonrpc\":\"2.0\",\"id\":50,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":13},\"end\":{\"line\":10,\"character\":13}},\"context\":{\"diagnostics\":[{\"range\":{\"start\":{\"line\":10,\"character\":13},\"end\":{\"line\":10,\"character\":13}},\"message\":\"expected ';' after expression\",\"code\":\"err_expected_semi_after_expr\",\"severity\":1,\"source\":\"clang\"}],\"triggerKind\":2}}}"}
{"ts":7670,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":12},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":13},\"end\":{\"line\":10,\"character\":13}},\"rangeLength\":0,\"text\":\" \"}]}}"}
{"ts":7677,"msg":"{\"jsonrpc\":\"2.0\",\"id\":51,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":7710,"msg":"{\"jsonrpc\":\"2.0\",\"id\":52,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":7966,"msg":"{\"jsonrpc\":\"2.0\",\"id\":53,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":8014,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":53}}"}
{"ts":8014,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":13},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":14},\"end\":{\"line\":10,\"character\":14}},\"rangeLength\":0,\"text\":\"<\"}]}}"}
{"ts":8014,"msg":"{\"jsonrpc\":\"2.0\",\"id\":54,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":8014,"msg":"{\"jsonrpc\":\"2.0\",\"id\":55,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":8014,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":54}}"}
{"ts":8182,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":14},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":15},\"end\":{\"line\":10,\"character\":15}},\"rangeLength\":0,\"text\":\"<\"}]}}"}
{"ts":8184,"msg":"{\"jsonrpc\":\"2.0\",\"id\":56,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":8411,"msg":"{\"jsonrpc\":\"2.0\",\"id\":57,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":8478,"msg":"{\"jsonrpc\":\"2.0\",\"id\":58,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":8526,"msg":"{\"jsonrpc\":\"2.0\",\"id\":59,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":8526,"msg":"{\"jsonrpc\":\"2.0\",\"id\":60,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":16},\"end\":{\"line\":10,\"character\":16}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":8630,"msg":"{\"jsonrpc\":\"2.0\",\"id\":61,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":8838,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":15},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":16},\"end\":{\"line\":10,\"character\":16}},\"rangeLength\":0,\"text\":\" \"}]}}"}
{"ts":8838,"msg":"{\"jsonrpc\":\"2.0\",\"id\":62,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":9101,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":16},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":17},\"end\":{\"line\":10,\"character\":17}},\"rangeLength\":0,\"text\":\"\\\"\\\"\"}]}}"}
{"ts":9158,"msg":"{\"jsonrpc\":\"2.0\",\"id\":63,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":9258,"msg":"{\"jsonrpc\":\"2.0\",\"id\":64,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":9434,"msg":"{\"jsonrpc\":\"2.0\",\"id\":65,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":9482,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":65}}"}
{"ts":9482,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":17},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":18},\"end\":{\"line\":10,\"character\":18}},\"rangeLength\":0,\"text\":\"H\"}]}}"}
{"ts":9482,"msg":"{\"jsonrpc\":\"2.0\",\"id\":66,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":9482,"msg":"{\"jsonrpc\":\"2.0\",\"id\":67,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":9482,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":66}}"}
{"ts":9778,"msg":"{\"jsonrpc\":\"2.0\",\"id\":68,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":9830,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":68}}"}
{"ts":9830,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":18},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":19},\"end\":{\"line\":10,\"character\":19}},\"rangeLength\":0,\"text\":\"e\"}]}}"}
{"ts":9830,"msg":"{\"jsonrpc\":\"2.0\",\"id\":69,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":9837,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":69}}"}
{"ts":9838,"msg":"{\"jsonrpc\":\"2.0\",\"id\":70,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":9910,"msg":"{\"jsonrpc\":\"2.0\",\"id\":71,\"method\":\"textDocument/semanticTokens/full\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":10016,"msg":"{\"jsonrpc\":\"2.0\",\"id\":72,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":20},\"end\":{\"line\":10,\"character\":20}},\"context\":{\"diagnostics\":[{\"range\":{\"start\":{\"line\":10,\"character\":20},\"end\":{\"line\":10,\"character\":20}},\"message\":\"expected ';' after expression\",\"code\":\"err_expected_semi_after_expr\",\"severity\":1,\"source\":\"clang\"}],\"triggerKind\":2}}}"}
{"ts":10083,"msg":"{\"jsonrpc\":\"2.0\",\"id\":73,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":10131,"msg":"{\"jsonrpc\":\"2.0\",\"id\":74,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":10153,"msg":"{\"jsonrpc\":\"2.0\",\"id\":75,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":10428,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":72}}"}
{"ts":10429,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":73}}"}
{"ts":10429,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":19},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":20},\"end\":{\"line\":10,\"character\":20}},\"rangeLength\":0,\"text\":\"l\"}]}}"}
{"ts":10476,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":70}}"}
{"ts":10476,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":74}}"}
{"ts":10476,"msg":"{\"jsonrpc\":\"2.0\",\"id\":76,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":10603,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":20},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":21},\"end\":{\"line\":10,\"character\":21}},\"rangeLength\":0,\"text\":\"l\"}]}}"}
{"ts":10640,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":76}}"}
{"ts":10640,"msg":"{\"jsonrpc\":\"2.0\",\"id\":77,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":10890,"msg":"{\"jsonrpc\":\"2.0\",\"id\":78,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":22},\"end\":{\"line\":10,\"character\":22}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":10938,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":78}}"}
{"ts":10938,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":21},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":22},\"end\":{\"line\":10,\"character\":22}},\"rangeLength\":0,\"text\":\"o\"}]}}"}
{"ts":10986,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":77}}"}
{"ts":10986,"msg":"{\"jsonrpc\":\"2.0\",\"id\":79,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":10986,"msg":"{\"jsonrpc\":\"2.0\",\"id\":80,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":10986,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":80}}"}
{"ts":11158,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":22},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":23},\"end\":{\"line\":10,\"character\":23}},\"rangeLength\":0,\"text\":\" \"}]}}"}
{"ts":11158,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":79}}"}
{"ts":11158,"msg":"{\"jsonrpc\":\"2.0\",\"id\":81,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":11330,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":23},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":24},\"end\":{\"line\":10,\"character\":24}},\"rangeLength\":0,\"text\":\"w\"}]}}"}
{"ts":11345,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":81}}"}
{"ts":11345,"msg":"{\"jsonrpc\":\"2.0\",\"id\":82,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":11542,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":24},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":25},\"end\":{\"line\":10,\"character\":25}},\"rangeLength\":0,\"text\":\"o\"}]}}"}
{"ts":11542,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":82}}"}
{"ts":11542,"msg":"{\"jsonrpc\":\"2.0\",\"id\":83,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":11646,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":25},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":26},\"end\":{\"line\":10,\"character\":26}},\"rangeLength\":0,\"text\":\"r\"}]}}"}
{"ts":11649,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":83}}"}
{"ts":11649,"msg":"{\"jsonrpc\":\"2.0\",\"id\":84,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":11715,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":26},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":27},\"end\":{\"line\":10,\"character\":27}},\"rangeLength\":0,\"text\":\"l\"}]}}"}
{"ts":11759,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":84}}"}
{"ts":11759,"msg":"{\"jsonrpc\":\"2.0\",\"id\":85,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":11901,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":27},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":28},\"end\":{\"line\":10,\"character\":28}},\"rangeLength\":0,\"text\":\"d\"}]}}"}
{"ts":11902,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":85}}"}
{"ts":11902,"msg":"{\"jsonrpc\":\"2.0\",\"id\":86,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":12158,"msg":"{\"jsonrpc\":\"2.0\",\"id\":87,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":29},\"end\":{\"line\":10,\"character\":29}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":12158,"msg":"{\"jsonrpc\":\"2.0\",\"id\":88,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":12222,"msg":"{\"jsonrpc\":\"2.0\",\"id\":89,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":12267,"msg":"{\"jsonrpc\":\"2.0\",\"id\":90,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":12466,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":87}}"}
{"ts":12466,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":88}}"}
{"ts":12466,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":28},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":30},\"end\":{\"line\":10,\"character\":30}},\"rangeLength\":0,\"text\":\" \"}]}}"}
{"ts":12558,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":86}}"}
{"ts":12558,"msg":"{\"jsonrpc\":\"2.0\",\"id\":91,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":12558,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":89}}"}
{"ts":12814,"msg":"{\"jsonrpc\":\"2.0\",\"id\":92,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":12845,"msg":"{\"jsonrpc\":\"2.0\",\"id\":93,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":12852,"msg":"{\"jsonrpc\":\"2.0\",\"id\":94,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":12931,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":92}}"}
{"ts":12931,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":29},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":31},\"end\":{\"line\":10,\"character\":31}},\"rangeLength\":0,\"text\":\"<\"}]}}"}
{"ts":13022,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":91}}"}
{"ts":13022,"msg":"{\"jsonrpc\":\"2.0\",\"id\":95,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":13022,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":93}}"}
{"ts":13130,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":30},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":32},\"end\":{\"line\":10,\"character\":32}},\"rangeLength\":0,\"text\":\"<\"}]}}"}
{"ts":13130,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":95}}"}
{"ts":13130,"msg":"{\"jsonrpc\":\"2.0\",\"id\":96,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":13450,"msg":"{\"jsonrpc\":\"2.0\",\"id\":97,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":33},\"end\":{\"line\":10,\"character\":33}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":13525,"msg":"{\"jsonrpc\":\"2.0\",\"id\":98,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":13559,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":97}}"}
{"ts":13560,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":98}}"}
{"ts":13560,"msg":"{\"jsonrpc\":\"2.0\",\"id\":99,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":13560,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":31},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":33},\"end\":{\"line\":10,\"character\":33}},\"rangeLength\":0,\"text\":\" \"}]}}"}
{"ts":13614,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":96}}"}
{"ts":13614,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":99}}"}
{"ts":13614,"msg":"{\"jsonrpc\":\"2.0\",\"id\":100,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":13614,"msg":"{\"jsonrpc\":\"2.0\",\"id\":101,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":13710,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":32},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":34},\"end\":{\"line\":10,\"character\":34}},\"rangeLength\":0,\"text\":\"s\"}]}}"}
{"ts":13710,"msg":"{\"jsonrpc\":\"2.0\",\"id\":102,\"method\":\"textDocument/completion\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":10,\"character\":35},\"context\":{\"triggerKind\":1}}}"}
{"ts":13710,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":100}}"}
{"ts":13710,"msg":"{\"jsonrpc\":\"2.0\",\"id\":103,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":13846,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":33},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":35},\"end\":{\"line\":10,\"character\":35}},\"rangeLength\":0,\"text\":\"t\"}]}}"}
{"ts":13846,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":103}}"}
{"ts":13846,"msg":"{\"jsonrpc\":\"2.0\",\"id\":104,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":13998,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":34},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":36},\"end\":{\"line\":10,\"character\":36}},\"rangeLength\":0,\"text\":\"d\"}]}}"}
{"ts":13998,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":104}}"}
{"ts":13998,"msg":"{\"jsonrpc\":\"2.0\",\"id\":105,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":14242,"msg":"{\"jsonrpc\":\"2.0\",\"id\":106,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":37},\"end\":{\"line\":10,\"character\":37}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":14262,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":106}}"}
{"ts":14262,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":35},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":37},\"end\":{\"line\":10,\"character\":37}},\"rangeLength\":0,\"text\":\":\"}]}}"}
{"ts":14305,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":105}}"}
{"ts":14306,"msg":"{\"jsonrpc\":\"2.0\",\"id\":107,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":14446,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":36},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":38},\"end\":{\"line\":10,\"character\":38}},\"rangeLength\":0,\"text\":\":\"}]}}"}
{"ts":14446,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":107}}"}
{"ts":14446,"msg":"{\"jsonrpc\":\"2.0\",\"id\":108,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":14598,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":37},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":39},\"end\":{\"line\":10,\"character\":39}},\"rangeLength\":0,\"text\":\"e\"}]}}"}
{"ts":14598,"msg":"{\"jsonrpc\":\"2.0\",\"id\":109,\"method\":\"textDocument/completion\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":10,\"character\":40},\"context\":{\"triggerKind\":1}}}"}
{"ts":14598,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":108}}"}
{"ts":14598,"msg":"{\"jsonrpc\":\"2.0\",\"id\":110,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":14718,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":38},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":40},\"end\":{\"line\":10,\"character\":40}},\"rangeLength\":0,\"text\":\"n\"}]}}"}
{"ts":14718,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":110}}"}
{"ts":14718,"msg":"{\"jsonrpc\":\"2.0\",\"id\":111,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":14858,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":39},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":41},\"end\":{\"line\":10,\"character\":41}},\"rangeLength\":0,\"text\":\"d\"}]}}"}
{"ts":14858,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":111}}"}
{"ts":14858,"msg":"{\"jsonrpc\":\"2.0\",\"id\":112,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":14989,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":40},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":42},\"end\":{\"line\":10,\"character\":42}},\"rangeLength\":0,\"text\":\"l\"}]}}"}
{"ts":14990,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":112}}"}
{"ts":14991,"msg":"{\"jsonrpc\":\"2.0\",\"id\":113,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":15174,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/didChange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\",\"version\":41},\"contentChanges\":[{\"range\":{\"start\":{\"line\":10,\"character\":43},\"end\":{\"line\":10,\"character\":43}},\"rangeLength\":0,\"text\":\";\"}]}}"}
{"ts":15195,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":113}}"}
{"ts":15195,"msg":"{\"jsonrpc\":\"2.0\",\"id\":114,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":15454,"msg":"{\"jsonrpc\":\"2.0\",\"id\":115,\"method\":\"textDocument/codeAction\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":10,\"character\":44},\"end\":{\"line\":10,\"character\":44}},\"context\":{\"diagnostics\":[],\"triggerKind\":2}}}"}
{"ts":15466,"msg":"{\"jsonrpc\":\"2.0\",\"id\":116,\"method\":\"textDocument/foldingRange\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":15515,"msg":"{\"jsonrpc\":\"2.0\",\"id\":117,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":15566,"msg":"{\"jsonrpc\":\"2.0\",\"id\":118,\"method\":\"textDocument/documentSymbol\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":15966,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":114}}"}
{"ts":15966,"msg":"{\"jsonrpc\":\"2.0\",\"id\":119,\"method\":\"textDocument/inlayHint\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":{\"line\":13,\"character\":0}}}}"}
{"ts":16677,"msg":"{\"jsonrpc\":\"2.0\",\"id\":120,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":11,\"character\":12}}}"}
{"ts":16677,"msg":"{\"jsonrpc\":\"2.0\",\"id\":121,\"method\":\"textDocument/documentLink\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"}}}"}
{"ts":18279,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":120}}"}
{"ts":18510,"msg":"{\"jsonrpc\":\"2.0\",\"id\":122,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":7,\"character\":9}}}"}
{"ts":19831,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":122}}"}
{"ts":20030,"msg":"{\"jsonrpc\":\"2.0\",\"id\":123,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":12}}}"}
{"ts":21092,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":123}}"}
{"ts":21285,"msg":"{\"jsonrpc\":\"2.0\",\"id\":124,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":20}}}"}
{"ts":21404,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":124}}"}
{"ts":21662,"msg":"{\"jsonrpc\":\"2.0\",\"id\":125,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":7,\"character\":39}}}"}
{"ts":23066,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":125}}"}
{"ts":23422,"msg":"{\"jsonrpc\":\"2.0\",\"id\":126,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":18}}}"}
{"ts":24724,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":126}}"}
{"ts":24922,"msg":"{\"jsonrpc\":\"2.0\",\"id\":127,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":10}}}"}
{"ts":26768,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":127}}"}
{"ts":26966,"msg":"{\"jsonrpc\":\"2.0\",\"id\":128,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":7,\"character\":9}}}"}
{"ts":28269,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":128}}"}
{"ts":28606,"msg":"{\"jsonrpc\":\"2.0\",\"id\":129,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":7,\"character\":41}}}"}
{"ts":30260,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":129}}"}
{"ts":30462,"msg":"{\"jsonrpc\":\"2.0\",\"id\":130,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":13}}}"}
{"ts":30565,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":130}}"}
{"ts":30758,"msg":"{\"jsonrpc\":\"2.0\",\"id\":131,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":9,\"character\":12}}}"}
{"ts":30866,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":131}}"}
{"ts":31066,"msg":"{\"jsonrpc\":\"2.0\",\"id\":132,\"method\":\"textDocument/hover\",\"params\":{\"textDocument\":{\"uri\":\"file:///home/ykiko/C%2B%2B/clice/tests/data/hello_world/main.cpp\"},\"position\":{\"line\":11,\"character\":14}}}"}
{"ts":31251,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"$/cancelRequest\",\"params\":{\"id\":132}}"}
{"ts":33730,"msg":"{\"jsonrpc\":\"2.0\",\"id\":133,\"method\":\"shutdown\"}"}
{"ts":33774,"msg":"{\"jsonrpc\":\"2.0\",\"method\":\"exit\"}"}