Skip to content

Commit bb0b160

Browse files
16bit-ykikoclaude
andauthored
refactor(server): extract Indexer and Compiler from MasterServer (#403)
## Summary - **Extract `Indexer` class** — owns all index state (ProjectIndex, MergedIndex shards, OpenFileIndex) and query methods (definition, references, call/type hierarchy, workspace symbol search) - **Extract `Compiler` class** — owns document state, PCH/PCM cache, compile argument resolution, header context, `ensure_compiled`, and worker forwarding - **MasterServer is now a pure LSP handler registration layer** (~700 lines, down from ~3200) - **`MergedIndexShard`** wraps `index::MergedIndex` with a lazily-cached PositionMapper; `OpenFileIndex` gains matching `find_occurrence()`/`find_relations()` APIs — callers get pre-converted LSP ranges directly - **Indexer returns typed values** (`vector<Location>`, `vector<CallHierarchyIncomingCall>`, etc.) instead of pre-serialized JSON, fixing the references handler from JSON string surgery to simple vector concatenation - **Fix**: duplicate `workspace/symbol` loop in the original code ## Test plan - [x] 465 unit tests pass - [x] 113 integration tests pass - [x] 2/2 smoke tests pass - [x] `clang-format` applied 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Server-side C++ compilation orchestration (module & precompiled header builds) with LSP-integrated document handling. * **Improvements** * Deterministic, persistent, dependency-aware caching to avoid redundant rebuilds and speed up incremental work. * Better cross-file indexing and navigation, improved diagnostics and more reliable include/import-aware completions. * **Tests** * Unit tests updated to the unified worker query/build request shapes. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ada202e commit bb0b160

14 files changed

+3365
-3618
lines changed

src/server/compiler.cpp

Lines changed: 1471 additions & 0 deletions
Large diffs are not rendered by default.

src/server/compiler.h

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <functional>
5+
#include <memory>
6+
#include <optional>
7+
#include <string>
8+
#include <unordered_map>
9+
#include <vector>
10+
11+
#include "command/command.h"
12+
#include "eventide/async/async.h"
13+
#include "eventide/ipc/lsp/protocol.h"
14+
#include "eventide/ipc/peer.h"
15+
#include "eventide/serde/serde/raw_value.h"
16+
#include "server/compile_graph.h"
17+
#include "server/config.h"
18+
#include "server/indexer.h"
19+
#include "server/worker_pool.h"
20+
#include "support/path_pool.h"
21+
#include "syntax/dependency_graph.h"
22+
23+
#include "llvm/ADT/ArrayRef.h"
24+
#include "llvm/ADT/DenseMap.h"
25+
#include "llvm/ADT/SmallVector.h"
26+
#include "llvm/ADT/StringRef.h"
27+
28+
namespace clice {
29+
30+
namespace et = eventide;
31+
namespace protocol = et::ipc::protocol;
32+
33+
/// State of a document opened by the client.
34+
struct DocumentState {
35+
int version = 0;
36+
std::string text;
37+
std::uint64_t generation = 0;
38+
bool ast_dirty = true;
39+
40+
/// Non-null while a compile is in flight. Callers wait on the event;
41+
/// the compile task runs independently and cannot be cancelled by LSP
42+
/// $/cancelRequest.
43+
struct PendingCompile {
44+
et::event done;
45+
bool succeeded = false;
46+
};
47+
48+
std::shared_ptr<PendingCompile> compiling;
49+
};
50+
51+
/// Context for compiling a header file that lacks its own CDB entry.
52+
struct HeaderFileContext {
53+
std::uint32_t host_path_id; /// Source file acting as host
54+
std::string preamble_path; /// Path to generated preamble file on disk
55+
std::uint64_t preamble_hash; /// Hash of preamble content for staleness
56+
};
57+
58+
/// Two-layer staleness snapshot for compilation artifacts (PCH, AST, etc.).
59+
///
60+
/// Layer 1 (fast): compare each file's current mtime against build_at.
61+
/// If all mtimes <= build_at, the artifact is fresh (zero I/O beyond stat).
62+
///
63+
/// Layer 2 (precise): for files whose mtime changed, re-hash their content
64+
/// and compare against the stored hash. If the hash matches, the file was
65+
/// "touched" but not actually modified — skip the rebuild.
66+
struct DepsSnapshot {
67+
llvm::SmallVector<std::uint32_t> path_ids;
68+
llvm::SmallVector<std::uint64_t> hashes;
69+
std::int64_t build_at = 0;
70+
};
71+
72+
/// Cached PCH state for a single source file.
73+
struct PCHState {
74+
std::string path;
75+
std::uint32_t bound = 0;
76+
std::uint64_t hash = 0;
77+
DepsSnapshot deps;
78+
std::shared_ptr<et::event> building;
79+
};
80+
81+
/// Cached PCM state for a single module file.
82+
struct PCMState {
83+
std::string path;
84+
DepsSnapshot deps;
85+
};
86+
87+
enum class CompletionContext { None, IncludeQuoted, IncludeAngled, Import };
88+
89+
struct PreambleCompletionContext {
90+
CompletionContext kind = CompletionContext::None;
91+
std::string prefix;
92+
};
93+
94+
/// Manages the full compilation lifecycle: document state, compilation
95+
/// artifacts (PCH/PCM cache), compile argument resolution, header context,
96+
/// and feature request forwarding to workers.
97+
///
98+
/// MasterServer delegates all document and compilation operations here,
99+
/// keeping itself as a pure LSP handler registration layer.
100+
class Compiler {
101+
public:
102+
Compiler(et::event_loop& loop,
103+
et::ipc::JsonPeer& peer,
104+
PathPool& path_pool,
105+
WorkerPool& pool,
106+
Indexer& indexer,
107+
const CliceConfig& config,
108+
CompilationDatabase& cdb,
109+
DependencyGraph& dep_graph);
110+
111+
~Compiler();
112+
113+
/// Convert a file:// URI to a local file path.
114+
static std::string uri_to_path(const std::string& uri);
115+
116+
/// Document lifecycle — called from MasterServer handlers.
117+
void open_document(const std::string& uri, std::string text, int version);
118+
void apply_changes(const protocol::DidChangeTextDocumentParams& params);
119+
std::uint32_t close_document(const std::string& uri);
120+
llvm::SmallVector<std::uint32_t> on_save(const std::string& uri);
121+
122+
/// Document accessors.
123+
bool is_file_open(std::uint32_t path_id) const;
124+
const DocumentState* get_document(std::uint32_t path_id) const;
125+
126+
/// Cache persistence.
127+
void load_cache();
128+
void save_cache();
129+
void cleanup_cache(int max_age_days = 7);
130+
131+
/// Build path_to_module reverse mapping from dependency graph.
132+
void build_module_map();
133+
134+
/// Initialize the CompileGraph for C++20 module compilation ordering.
135+
void init_compile_graph();
136+
137+
/// Fill compile arguments for a file (CDB lookup + header context fallback).
138+
bool fill_compile_args(llvm::StringRef path,
139+
std::string& directory,
140+
std::vector<std::string>& arguments);
141+
142+
/// Fill PCM paths for all built modules (for background indexing).
143+
void fill_pcm_deps(std::unordered_map<std::string, std::string>& pcms,
144+
std::uint32_t exclude_path_id = UINT32_MAX) const;
145+
146+
/// Pull-based compilation entry point for user-opened files.
147+
et::task<bool> ensure_compiled(std::uint32_t path_id);
148+
149+
/// Feature request forwarding to workers.
150+
using RawResult = et::task<et::serde::RawValue, et::ipc::Error>;
151+
152+
/// Forward a stateful AST query to the worker owning this file.
153+
RawResult forward_query(worker::QueryKind kind, const std::string& uri);
154+
RawResult forward_query(worker::QueryKind kind,
155+
const std::string& uri,
156+
const protocol::Position& position);
157+
158+
/// Forward a stateless build request (completion/signatureHelp).
159+
RawResult forward_build(worker::BuildKind kind,
160+
const std::string& uri,
161+
const protocol::Position& position);
162+
163+
/// Completion with preamble-aware include/import handling.
164+
RawResult handle_completion(const std::string& uri, const protocol::Position& position);
165+
166+
/// Header context management.
167+
void switch_context(std::uint32_t path_id, std::uint32_t context_path_id);
168+
std::optional<std::uint32_t> get_active_context(std::uint32_t path_id) const;
169+
void invalidate_host_contexts(std::uint32_t host_path_id,
170+
llvm::SmallVectorImpl<std::uint32_t>& stale_headers);
171+
172+
CompileGraph* compile_graph_ptr() {
173+
return compile_graph.get();
174+
}
175+
176+
const llvm::DenseMap<std::uint32_t, std::string>& module_map() const {
177+
return path_to_module;
178+
}
179+
180+
void cancel_all();
181+
182+
/// Callback invoked when indexing should be scheduled (e.g. after compile success).
183+
std::function<void()> on_indexing_needed;
184+
185+
private:
186+
/// Compile module dependencies, build/reuse PCH, and fill PCM paths.
187+
et::task<bool> ensure_deps(std::uint32_t path_id,
188+
llvm::StringRef path,
189+
const std::string& text,
190+
const std::string& directory,
191+
const std::vector<std::string>& arguments,
192+
std::pair<std::string, uint32_t>& pch,
193+
std::unordered_map<std::string, std::string>& pcms);
194+
195+
/// Build or reuse PCH for a source file.
196+
et::task<bool> ensure_pch(std::uint32_t path_id,
197+
llvm::StringRef path,
198+
const std::string& text,
199+
const std::string& directory,
200+
const std::vector<std::string>& arguments);
201+
202+
/// Check if a file's AST or PCH deps have changed since last compile.
203+
bool is_stale(std::uint32_t path_id);
204+
205+
/// Record dependency snapshot after a successful compile.
206+
void record_deps(std::uint32_t path_id, llvm::ArrayRef<std::string> deps);
207+
208+
void publish_diagnostics(const std::string& uri, int version, const et::serde::RawValue& diags);
209+
void clear_diagnostics(const std::string& uri);
210+
211+
/// Clean up compilation state for a closed file.
212+
void on_file_closed(std::uint32_t path_id);
213+
214+
/// Invalidate artifacts after a file save.
215+
/// Returns path_ids of all files dirtied (via compile_graph cascade).
216+
llvm::SmallVector<std::uint32_t> on_file_saved(std::uint32_t path_id);
217+
218+
/// Header context resolution.
219+
std::optional<HeaderFileContext> resolve_header_context(std::uint32_t header_path_id);
220+
bool fill_header_context_args(llvm::StringRef path,
221+
std::uint32_t path_id,
222+
std::string& directory,
223+
std::vector<std::string>& arguments);
224+
225+
/// Include/import completion helpers.
226+
PreambleCompletionContext detect_completion_context(const std::string& text, uint32_t offset);
227+
et::serde::RawValue complete_include(const PreambleCompletionContext& ctx,
228+
llvm::StringRef path);
229+
et::serde::RawValue complete_import(const PreambleCompletionContext& ctx);
230+
231+
private:
232+
et::event_loop& loop;
233+
et::ipc::JsonPeer& peer;
234+
PathPool& path_pool;
235+
WorkerPool& pool;
236+
Indexer& indexer;
237+
const CliceConfig& config;
238+
CompilationDatabase& cdb;
239+
DependencyGraph& dep_graph;
240+
241+
/// Open document state, keyed by server-level path_id.
242+
llvm::DenseMap<std::uint32_t, DocumentState> documents;
243+
244+
/// PCH/PCM cache state.
245+
llvm::DenseMap<std::uint32_t, PCHState> pch_states;
246+
llvm::DenseMap<std::uint32_t, PCMState> pcm_states;
247+
llvm::DenseMap<std::uint32_t, std::string> pcm_paths;
248+
249+
/// Module compilation ordering.
250+
llvm::DenseMap<std::uint32_t, std::string> path_to_module;
251+
std::unique_ptr<CompileGraph> compile_graph;
252+
253+
/// Per-file compilation state.
254+
llvm::DenseMap<std::uint32_t, DepsSnapshot> ast_deps;
255+
llvm::DenseMap<std::uint32_t, HeaderFileContext> header_file_contexts;
256+
llvm::DenseMap<std::uint32_t, std::uint32_t> active_contexts;
257+
};
258+
259+
} // namespace clice

0 commit comments

Comments
 (0)