Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 43 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,63 @@
<!-- Begin section: Overview -->

# clice

![C++ Standard](https://img.shields.io/badge/C++-23-blue.svg)
[![GitHub license](https://img.shields.io/github/license/clice-io/clice)](https://github.com/clice-io/clice/blob/main/LICENSE)
[![Actions status](https://github.com/clice-io/clice/workflows/CI/badge.svg)](https://github.com/clice-io/clice/actions)
[![Actions status](https://github.com/clice-io/clice/workflows/main/badge.svg)](https://github.com/clice-io/clice/actions)
[![Documentation](https://img.shields.io/badge/view-documentation-blue)](https://docs.clice.io/clice/)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/clice-io/clice)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.gg/PA3UxW2VA3)

clice is a next-generation language server designed for modern C++. Through excellent asynchronous task scheduling and intelligent caching, it achieves a lower memory footprint and faster response times.
A C++ language server written from scratch on LLVM/Clang, with selected components ported from [clangd](https://clangd.llvm.org/). clice redesigns key architectural decisions to solve long-standing problems in C++ tooling.

## Why clice?

Some problems in C++ tooling require architectural changes that cannot be easily retrofitted into existing language servers. clice addresses these from the ground up:

**Template intelligence** — Type `vec2[0].` inside a template body and get full completions. clice uses pseudo-instantiation to resolve dependent types through nested typedefs and template specializations without needing concrete type arguments. Types like `std::vector<std::vector<T>>::reference` resolve to `std::vector<T>&`, enabling completion, hover, and go-to-definition inside generic code.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not claim pseudo-instantiation powers editor features

The current editor feature paths do not use TemplateResolver: repo-wide usage only constructs it, while code completion goes through Clang code completion, hover uses Clang's HeuristicResolver, and the stateful go-to-definition fallback is still a TODO. In dependent template bodies such as the example here, users should not be promised pseudo-instantiation-backed completion/hover/navigation until the resolver is actually wired into those LSP paths.

Useful? React with 👍 / 👎.


**Compilation context** — A first-class concept in clice. For source files, switch between different compilation commands (e.g. different build configurations). For header files, switch which source file provides the including context (preprocessor state, preceding declarations). This handles non-self-contained headers and context-dependent macros naturally, with automatic context switching as you navigate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not document source-file context switching as available

For source files with multiple CDB entries, clice/switchContext has no way to select a specific entry: it stores only a path id, fill_compile_args() normally takes cdb.lookup(...).front(), and an active context sends the source file through the header-context path, which requires an include-chain host and fails for ordinary sources. Users with debug/release entries therefore cannot switch build configurations as described here.

Useful? React with 👍 / 👎.


**C++20 named modules** — Parallel module compilation driven by a dependency-aware compile graph with interest-counted cancellation. Pre-compiled module interfaces (BMIs) are content-addressed and cached across editor sessions, so reopening a project skips redundant builds. The LSP layer provides module name completion on `import` statements and module-aware semantic highlighting.
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved

## Getting Started

Beyond performance, clice provides instantiation-aware template processing, supports switching header contexts between different source files (including non-self-contained headers), and offers comprehensive support for C++20 modules, from code completion to go-to-definition. Our goal is to provide C++ developers with a truly fast, precise, and intelligent development companion.
### Install

> [!IMPORTANT]
> Support for header contexts and C++20 modules are core features currently under active development. They will be progressively refined in upcoming releases. Stay tuned!
Download the latest binary from the [releases page](https://github.com/clice-io/clice/releases), or [build from source](https://docs.clice.io/clice/dev/build).

## Getting started
**Platforms:** Linux (x64, ARM64), macOS (x64, ARM64), Windows (x64, ARM64)

Download the latest `clice` binary from the [releases page](https://github.com/clice-io/clice/releases) and install the [vscode extension](https://marketplace.visualstudio.com/items?itemName=ykiko.clice-vscode). Then, add the following configuration to your `.vscode/settings.json` file:
### Editor Setup

```jsonc
{
// Optional: Set this to an empty string to turn off the clangd.
"clangd.path": "",
| Editor | Setup |
| ----------- | -------------------------------------------------------------------------------------------------------------------------- |
| **VS Code** | Install the [clice extension](https://marketplace.visualstudio.com/items?itemName=ykiko.clice-vscode) from the Marketplace |
| **Neovim** | Add `editors/nvim` to your runtime path: `vim.opt.rtp:append("/path/to/clice/editors/nvim")` |
| **Zed** | Load `editors/zed` as a local extension |
| **Other** | Any LSP client works — point it at `clice server` |

// Point this to the clice binary you downloaded.
"clice.executable": "/path/to/your/clice/executable",
}
### Project Setup

clice reads a [compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html) to understand your project. For CMake:

```shell
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
```

By default clice searches the workspace root and its immediate subdirectories (e.g. `build/`) for `compile_commands.json`. See [configuration](https://docs.clice.io/clice/guide/configuration) for custom paths and other build systems.

> [!NOTE]
> As an early version, please do not use it in a production environment. Crashes are expected, and we welcome you to submit issues.
> clice is approaching its first stable release. Most features work well, but some edge cases remain. Bug reports via [issues](https://github.com/clice-io/clice/issues) are welcome.

## Documentation

To learn more about building, installing, and configuring clice, or to dive deep into its features and architecture, please visit our official documentation at [**clice.io**](https://docs.clice.io/clice/).
Full docs at [**docs.clice.io/clice**](https://docs.clice.io/clice/) covering configuration, architecture, and the feature checklist.

## Contributing

See the [contribution guide](https://docs.clice.io/clice/dev/contribution) or join our [Discord](https://discord.gg/PA3UxW2VA3).

```shell
pixi run build # configure + build
pixi run test # unit + integration + smoke tests
```
4 changes: 2 additions & 2 deletions docs/clice.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@

[project]
# Enable experimental clang-tidy diagnostics.
# This feature is tracked in https://github.com/clice-project/clice/issues/90.
# This feature is tracked in https://github.com/clice-io/clice/issues/90.
clang_tidy = false
# Maximum number of active files to keep in memory. If the number of active files
# exceeds this limit, the least recently used files will be removed.
# The default value is 8. Whatever the number you set, the minimum is 1, the maximum is 512.
# The default value is 8.
max_active_file = 8
# Directory for storing PCH and PCM files.
cache_dir = "${workspace}/.clice/cache"
Expand Down
104 changes: 90 additions & 14 deletions docs/en/design/architecture.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,105 @@
# Architecture

## Protocol
clice uses a **multi-process** architecture: a master server coordinates multiple worker processes. The master handles LSP requests, manages state, and dispatches compilation tasks. Workers perform CPU-intensive work (parsing, indexing) in separate processes with memory limits.

Use C++ to describe type definitions in the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/).
The async runtime is provided by [kotatsu](https://github.com/clice-io/kotatsu) (`kota`), which wraps libuv with C++20 coroutines. clice never calls libuv directly.

## AST
## Source Modules

Some convenient wrappers for clang AST interfaces.
### `src/command/`

## Async
CLI parsing, compilation database loading, and toolchain detection. Implements the subcommand-based interface (`clice server`, `clice query`, etc.).

Wrapper for libuv coroutines using C++20 coroutines.
### `src/compile/`

## Compiler
Compilation unit abstraction. Wraps Clang's AST and preprocessor state, providing:

Wrapper for clang compilation interfaces, responsible for actual compilation processes and obtaining various compilation information.
- `CompilationUnitRef` — unified access to source locations, directives, file content
- `DiagnosticID` — diagnostic classification (deprecated, unused, etc.)
- Preprocessor directive tracking (includes, embeds, module declarations)

## Feature
### `src/feature/`

Specific implementations of various LSP features.
LSP feature implementations, each consuming a `CompilationUnitRef`:

## Server
- Code completion, hover, signature help
- Semantic tokens, inlay hints, document symbols
- Document links, folding ranges, formatting, diagnostics

clice is a language server, first and foremost a server. It uses [libuv](https://github.com/libuv/libuv) as the event library, adopting a common event-driven compilation model. The main thread is responsible for handling requests and dispatching tasks, while the thread pool is responsible for executing time-consuming tasks, such as compilation tasks. Related code is located in the `Server` directory.
### `src/index/`

## Support
Symbol indexing for cross-TU queries:

Some other utility libraries.
- `TUIndex` — per-translation-unit symbol data
- `ProjectIndex` — global symbol index with workspace-wide queries
- `MergedIndex` — per-file shards merging header contexts
- Include graph tracking

Serialized with FlatBuffers for efficient disk storage.

### `src/semantic/`

Semantic analysis beyond what raw Clang provides:

- `SemanticVisitor` — AST traversal recording symbol occurrences and relations
- `TemplateResolver` — pseudo-instantiation for dependent name resolution
- `find_target` — resolve nodes to their declaration targets
- Symbol kind/modifier classification

### `src/syntax/`

Lightweight syntax-level processing (no full AST needed):

- `Lexer` (`lexer.h`) and directive scanning (`scan.h`, wraps Clang's `DependencyDirectivesScanner`) — token-level utilities
- `DependencyGraph` — module/include dependency tracking
- Include path completion, module import completion

### `src/server/`

The server core, split into sub-modules:

#### `src/server/service/`

- `MasterServer` — top-level LSP server, routes requests to sessions/workers
- `LSPClient` — LSP protocol handler, capability registration
- `Session` — per-file state (active context, pending requests)

#### `src/server/compiler/`

- `Compiler` — compilation orchestration (PCH, PCM, index builds)
- `CompileGraph` — interest-counted module DAG with cancellation
- `Indexer` — background indexing, query resolution

#### `src/server/worker/`

- `StatefulWorker` — holds ASTs in memory, serves queries (hover, semantic tokens, etc.)
- `StatelessWorker` — ephemeral tasks (PCH/PCM builds, completion, signature help)
- `WorkerPool` — process lifecycle management

#### `src/server/workspace/`

- `Workspace` — project state, config, dependency graph
- `Config` — TOML/JSON config loading and validation

### `src/support/`

Utilities: logging, filesystem helpers, JSON serialization, string operations.

## Process Model

```plaintext
┌─────────────────────────────────────────────────┐
│ Master Process │
│ event loop (kota) + LSP I/O + state management │
└────────────┬──────────────────┬─────────────────┘
│ bincode IPC │ bincode IPC
┌───────▼───────┐ ┌─────▼──────────┐
│ Stateful │ │ Stateless │
│ Workers (×2) │ │ Workers (×N/2) │
│ hold ASTs, │ │ build PCH/PCM, │
│ serve queries │ │ completion, │
└───────────────┘ │ signature help │
└─────────────────┘
```

Workers communicate with the master via stdin/stdout using bincode serialization. Each worker runs in its own process with a configurable memory limit (default 4 GB).
55 changes: 52 additions & 3 deletions docs/en/design/compilation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

## Incremental Parsing

Every time you modify code, clice must re-parse the file. clice uses a mechanism called preamble to implement incremental compilation to speed up re-parsing. Preamble can be considered a special case of [Precompiled Header](https://clang.llvm.org/docs/PCHInternals.html) (embedded in source files). When opening a file, it builds the first few preprocessor directives at the beginning of the file (called preamble) into a PCH cache on disk. Later, when parsing, it can directly load the PCH file, thus skipping the first few preprocessor directives, which can greatly reduce the amount of code that needs to be re-parsed.
Every time you modify code, clice must re-parse the file. clice uses a mechanism called preamble to implement incremental compilation. The preamble is a special case of [Precompiled Header](https://clang.llvm.org/docs/PCHInternals.html) — it builds the initial preprocessor directives (the `#include` block at the top of a file) into a PCH cache on disk. Later, when parsing, it loads the PCH directly, skipping the preamble section entirely.

For example, for the following code:
For example:

```cpp
#include <iostream>
Expand All @@ -14,6 +14,55 @@ int main () {
}
```

The `iostream` header file has about 20,000 lines of code. clice will first build the line `#include <iostream>` into a PCH file, and after completion, use this PCH file to parse the subsequent code. This way, the amount of code that needs to be re-parsed later is reduced to just 5 lines instead of the original 20,000 lines, making it very fast. Unless you modify the code in the preamble section, which requires building a new preamble.
The `iostream` header expands to ~20,000 lines. clice builds `#include <iostream>` into a PCH file, reducing subsequent re-parses from 20,000 lines to just 5 lines. Only modifying the preamble section (adding/removing includes) requires rebuilding the PCH.
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved

### Staleness Detection

PCH freshness is checked via a two-layer scheme:

1. **Mtime check** — fast path: if no dependency has a newer mtime than the cached PCH, it's still valid.
2. **Content hash** — if mtime changed (e.g., a `touch` without real edits), hash the file contents. Only if the hash differs is the PCH rebuilt.

This avoids expensive rebuilds on timestamp-only changes (common with build systems that regenerate files in-place).

## Multi-Process Pipeline

Compilation tasks are distributed across worker processes:

- **Stateless workers** handle PCH builds, PCM builds (C++20 modules), completion, and signature help. These are ephemeral — no state persists between requests.
- **Stateful workers** hold parsed ASTs in memory and serve queries (hover, semantic tokens, inlay hints, etc.). Files are cached via LRU eviction.

The master process never performs compilation directly. It dispatches tasks and collects results via bincode IPC over stdin/stdout.

## C++20 Modules

Module compilation follows a dependency graph (`CompileGraph`). The master resolves module dependencies from the syntax-level `DependencyGraph`, then builds PCM files in topological order via stateless workers.

Each module unit has a `CompileUnit` in the compile graph with:

- A refcount (interest count) tracking how many downstream dependents need it
- A cancellation token that fires when the refcount drops to zero

When a file is modified, dependent module units are automatically recompiled in dependency order.

## Cancel Compilation

clice uses **interest-counted cancellation** to avoid wasting CPU on compilations that are no longer needed.

### The Problem

In a module DAG like `A → B → C`, if the user opens file C, compilation starts for A, then B, then C. But if the user closes C (or switches to another file) while A is still building, continuing to build B and C is pointless.

### The Mechanism

Each `CompileUnit` in the compile graph maintains a reference count:

- When a file needs a module's PCM, it increments (`acquire`) that module's refcount.
- When it no longer needs it (file closed, edit superseded), it decrements (`release`).
- When refcount reaches zero, a one-tick grace period fires. If still zero after the grace period, the compilation's cancellation token is triggered.

The grace period prevents thrashing — rapid open/close sequences don't cause redundant cancel-and-restart cycles.

### Supersede Logic

When a file is edited while its previous compilation is in-flight, the old build is "superseded" — its interest is released and a new build starts. The old worker task observes the cancellation token and aborts early.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not say superseded workers abort early

The cancellation token is applied to the compile-graph coroutine, but the PCM dispatch itself is just pool.send_stateless(bp) with no token or cancellation message to the stateless worker. When a module build is superseded, the master can stop waiting and drop interest, but the worker process continues compiling until that IPC request finishes, so document this as abandoning stale results or wire cancellation through to workers before promising early aborts.

Useful? React with 👍 / 👎.

Loading
Loading