75 Commits

Author SHA1 Message Date
ykiko
8f714c3b4a refactor(document links): use Lexer for unified directive argument scanning
Replace hand-written character scanning with the project's Lexer class
to find filename arguments in preprocessor directives. Extend the Lexer
to activate header_name mode for #embed and expose set_header_name_mode()
for __has_include/__has_embed contexts. Remove unused Include::filename_range
field which had a latent assert crash on macro-expanded includes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 21:02:22 +08:00
ykiko
ccc805d0c3 Merge branch 'main' of https://github.com/clice-io/clice into feat/document-links-pch-embed 2026-04-09 19:44:34 +08:00
ykiko
e554660c06 fix(completion): use class name for constructor/deduction guide labels (#416)
## Summary
Fixes a bug where constructors and deduction guides had labels like
`vector<_Tp, _Alloc>` instead of just `vector`, causing:
1. Label deduplication to fail (class `vector` != constructor
`vector<_Tp, _Alloc>`)
2. Selecting the completion to insert invalid text `vector<_Tp, _Alloc>`

Now uses `getParent()->getName()` for constructors and
`getDeducedTemplate()->getName()` for deduction guides.

## Test plan
- [x] All 494 unit tests pass (existing `DeduplicateByLabel` test covers
this)
- [x] `pixi run format` clean

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Constructor and deduction-guide completions now show the parent type
name without template parameters, improving readability and preventing
duplicate entries.

* **Tests**
* Added a unit test verifying completion items for these entries use the
parent type name (no template-parameterized labels) and insertion text
starts with that name.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 19:15:38 +08:00
ykiko
342d82a7aa chore: add CLAUDE.md, clang-tidy, and agent workflow tooling (#419)
## Summary
- Add `.claude/CLAUDE.md` with project guide, C++ coding style,
correction patterns from past AI interactions, and pre-commit/pre-PR
workflows
- Add `.claude/commands/` with `/build`, `/test`, `/format` slash
commands for Claude Code
- Add `.clang-tidy` configuration
- Add `scripts/run_clang_tidy.py` for parallel clang-tidy with progress
reporting (reads CDB, filters to `src/` and `tests/` only)
- Add pixi tasks: `clang-tidy`, `lint-cpp`, `lint-python`, `lint`
- Add `clang-tools` dependency for clang-tidy
- Update `.gitignore` to track `.claude/CLAUDE.md` and
`.claude/commands/`

## Test plan
- [x] `pixi run format` passes
- [x] `pixi run lint-python` passes (ruff check)
- [x] `scripts/run_clang_tidy.py` correctly filters to project files
only

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

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 19:09:39 +08:00
ykiko
d48236de9c refactor: unify include handling with add_link_by_location
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:12:14 +08:00
ykiko
b691ed1d06 refactor: extract add_link_by_location to deduplicate has_include/embed/has_embed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:08:27 +08:00
ykiko
02e4f74347 style: remove verbose comments from integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:06:13 +08:00
ykiko
8af2704723 refactor: reuse find_filename_range for has_include scanning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 17:01:00 +08:00
ykiko
4d8c335c0d fix: re-lookup session after co_await to avoid invalidated iterator
The sessions DenseMap iterator may be invalidated during co_await
(other coroutines can modify the map). Re-lookup by path_id after
the await completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:56:08 +08:00
ykiko
3dab2ead93 feat(completion): snippet insertion for function/method parameters (#412)
## Summary
- Generate LSP snippet placeholders (`${1:param}`, `${2:param}`) for
function and method completions in non-bundle mode
- Controlled by
`CodeCompletionOptions::enable_function_arguments_snippet` (default off)
- No-arg functions produce plain text insertion (no empty snippet)
- Bundle mode is unaffected — snippets only apply when each overload is
a separate item
- Optional chunks (default arguments) are skipped in snippet generation

## Example
```
// Before: typing "fo" and selecting foooo inserts just "foooo"
// After:  typing "fo" and selecting foooo inserts "foooo(${1:int x}, ${2:float y})"
```

## Test plan
- [x] `SnippetFunctionArgs` — verifies placeholders are generated
- [x] `SnippetNoArgs` — no-arg functions don't produce snippet
- [x] `SnippetDisabled` — respects the option flag
- [x] `SnippetBundleMode` — bundle mode doesn't generate snippets
- [x] `SnippetMethod` — works for member methods too
- [x] All 494 unit tests pass
- [x] `pixi run format` clean

Stacked on #411.

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Code completion now generates function argument snippets with
interactive placeholders, helping users efficiently navigate through
parameters during autocompletion. The feature works with functions and
methods, with configurable options to control behavior for overloaded
scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 16:39:46 +08:00
ykiko
bd238fe59c feat(completion): signature display, underscore filtering, label dedup (#411)
## Summary
- Extract function/method signatures from Clang `CodeCompletionString`
into `labelDetails.detail` (parameter list) and
`labelDetails.description` (return type)
- Filter `_`/`__` prefixed internal symbols (e.g. `_Vector_base`,
`_Alloc`) unless the user explicitly typed `_`
- Fix `completion_kind` isa ordering: `CXXMethodDecl` checked before
`FunctionDecl` so methods get correct Kind
- Bundle mode: extend overload bundling to Method and Constructor (was
Function only)
- Bundle mode: deduplicate by label — when the same name appears as
Class + Constructor + deduction guide, keep only one (priority: Class >
Function > Constructor)
- Bundled overloads show `(…) +N overloads` in `labelDetails.detail`
instead of `detail`

## Test plan
- [x] 12 unit tests covering: signature extraction, return type,
overload bundling, underscore filtering, label deduplication, non-bundle
mode, method signatures
- [x] All 489 unit tests pass
- [x] `pixi run 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

## Release Notes

* **New Features**
* Code completion now displays function signatures and return types in
completion items
  * Overloaded functions are bundled together with a count indicator
* Internal symbols (underscore-prefixed) are filtered from suggestions
unless explicitly typed
* Duplicate completion items are deduplicated while preserving
higher-priority variants

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 16:08:14 +08:00
ykiko
4926b4ac32 test(document links): add __has_embed integration tests
Cover both existing-file (produces link) and missing-file (no link)
cases for __has_embed directives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:05:54 +08:00
ykiko
13527b7084 feat(feature): preserve PCH document links and add #embed/#has_embed support
PCH compilation now serializes document links and stores them in PCHState.
The master server merges PCH links with main-file links on DocumentLink
requests, fixing missing links for includes inside the preamble.

Also adds document link support for #embed and __has_embed directives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 15:56:19 +08:00
ykiko
8b3e3a9595 refactor(tests): reorganize integration tests into domain-based subdirectories (#409)
## Summary
- Extract shared test utilities into `tests/integration/utils/` (client,
workspace, assertions, wait, cache)
- Migrate 12 test files into categorized subdirectories: `lifecycle/`,
`compilation/`, `features/`, `modules/`, `extensions/`, `stress/`
- Merge `test_include_completion.py` + `test_import_completion.py` →
`features/test_completion.py`
- Remove stale directory-tree comments and section divider comments

## Test plan
- [x] `pytest --collect-only` collects all 113 tests
- [x] All test module imports verified
- [x] `pixi run 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

* **Tests**
* Reorganized integration suites: added new feature tests (completion,
server) and removed older duplicated modules.
* Centralized shared test utilities and assertion helpers for
diagnostics, workspace operations, waiting/synchronization, and cache
inspection.
* **Chores / Refactor**
* Standardized test client lifecycle and helper usage across suites for
more reliable test flows.
* Improved server session lifecycle handling for more predictable
document/session resets during tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 02:40:10 +08:00
ykiko
2bbdf6c02b refactor(command): split CompilationContext into ResolvedFlags → CompileCommand → to_argv() (#408)
## Summary

- Replace flat `CompilationContext { directory, arguments }` with a
three-layer abstraction: `ResolvedFlags` (file-independent flags) →
`CompileCommand` (+ source file) → `to_argv()` (full argv on demand)
- `ResolvedFlags.flags` never contains source file path or
`-main-file-name`, making it directly usable as a clean cache key input
(e.g. PCH sharing across files with identical preambles)
- `to_argv()` handles `-main-file-name` insertion for cc1 mode
automatically — consumers no longer need to search/replace in the
argument list
- Eliminates the pollute-then-clean anti-pattern in `lookup()` and the
manual source-file replacement in `fill_header_context_args()`

## Test plan

- [x] `pixi run format` — no changes
- [x] `pixi run unit-test` — 481 passed
- [x] `pixi run integration-test` — 113 passed

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Unified compile-command handling across the server and tools for more
consistent argument and flag behavior (driver vs frontend modes).
* **New Features**
* Added an LRU-backed in-memory cache to improve performance and
eviction control.
* **Chores**
* Added an option to control injection of resource-directory flags
(enabled by default).
* **Tests**
* Updated unit and integration tests to adopt the new command
representation and verify cache behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 22:18:25 +08:00
ykiko
9c9e6b0bcb refactor: introduce Workspace/Session state model and clarify component responsibilities (#406)
## Summary

Introduces a two-layer state model that cleanly separates disk-based
project state from per-open-file editing state, and redistributes
responsibilities across server components so each has a single, clear
role.

## New types

**Workspace** — all persistent, project-wide shared state:
- CompilationDatabase, PathPool, DependencyGraph, CompileGraph
- path_to_module mapping, PCH cache, PCM cache, PCM paths
- ProjectIndex, MergedIndex shards
- CliceConfig
- Methods: on_file_saved(), on_file_closed(), load/save/cleanup_cache(),
build_module_map(), fill_pcm_deps(), cancel_all()

**Session** — volatile per-open-file editing state:
- text, version, generation, ast_dirty
- pch_ref (references Workspace.pch_cache), ast_deps, header_context
- file_index (OpenFileIndex for unsaved buffer)
- path_id member for self-identification

## Component responsibilities after refactor

| Component | Role | Owns state? |
|-----------|------|-------------|
| **Workspace** | Disk truth + shared caches | Yes (all project state) |
| **Session** | One open file editing state | Yes (per-file only) |
| **Compiler** | Compilation pipeline, worker communication | No
(references only) |
| **Indexer** | Index queries + background indexing scheduling |
Scheduling state only |
| **MasterServer** | LSP protocol dispatch + lifecycle coordination |
sessions map |

## What moved where

**Into Workspace** (from Compiler/MasterServer):
- PCH/PCM cache management (load_cache, save_cache, cleanup_cache)
- Module map building (build_module_map, fill_pcm_deps)
- File lifecycle hooks (on_file_saved, on_file_closed)
- cancel_all, OpenFileIndex/MergedIndexShard type definitions

**Into Session** (from Compiler documents map):
- Document text, version, generation, ast_dirty
- PCH reference, dependency snapshot, header context

**Into Indexer** (from MasterServer):
- Background indexing queue, scheduling state, idle timer
- schedule(), enqueue(), run_background_indexing()

**Into syntax/completion.h** (from Compiler):
- detect_completion_context() — pure text parsing
- complete_module_import() — prefix match on module names
- complete_include_path() — directory listing against search paths

**Inlined into MasterServer** (from Compiler):
- didOpen/didChange/didClose/didSave handlers
- switchContext/currentContext
- publish_diagnostics/clear_diagnostics

**Deleted from Compiler** (9 methods):
- open_document, apply_changes, close_document, on_save
- switch_context, get_active_context, invalidate_host_contexts
- on_file_closed, on_file_saved, complete_include, complete_import

## Tests

- 481 tests pass (465 existing + 16 new completion tests)
- New: tests/unit/syntax/completion_tests.cpp

## Diff stats

15 files changed, +1857, -1555

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Enhanced completion support for include paths and module imports with
improved context detection.
* Added background indexing system for automatic project symbol
indexing.

* **Bug Fixes**
* Improved reliability of document change tracking and compilation state
management.
  * Better handling of header file compilation contexts.

* **Tests**
* Added unit tests for completion context detection and module/include
path completion.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 14:03:39 +08:00
ykiko
bb0b160a28 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>
2026-04-07 13:30:12 +08:00
ykiko
ada202e489 feat(index): piggyback indexing on PCH/PCM builds and open-file compiles (#402)
## Summary

Piggyback index construction onto existing compilation steps,
eliminating redundant recompilation in background indexing:

- **`TUIndex::build` gains `interested_only` parameter**: `true`
traverses only the main file's top-level decls; `false` (default)
traverses the full AST
- **PCH build indexes preamble headers**: stateless worker calls
`TUIndex::build(unit)` (full traversal) after successful `BuildPCH`,
clears `main_file_index`, serializes and sends back; master merges into
MergedIndex
- **PCM build indexes module interface**: stateless worker calls
`TUIndex::build(unit, true)` after successful `BuildPCM`; master merges
into MergedIndex
- **Open-file compile indexes main file**: stateful worker calls
`TUIndex::build(unit, true)` after successful `Compile`, serialized in
`CompileResult`
- **New `OpenFileIndex` in-memory structure**: master holds `FileIndex +
SymbolTable + buffer text` per open file — not persisted to disk, not
merged, discarded on close
- **Dual-source query path**: `query_index_relations`,
`lookup_symbol_at_position`, `find_symbol_definition_location`, all
hierarchy handlers, and `workspace/symbol` check `OpenFileIndex` first
(fresher), then fall back to `MergedIndex` (disk-indexed)
- **Background indexing skips open files**: checked via
`documents.count()`; on `didClose` the file is re-queued into
`index_queue`
- **`didSave` re-queues non-open dependents**: dirtied files from
`compile_graph->update()` that are not open get pushed into
`index_queue` for background re-indexing
- **Extract `lookup_occurrence` helper**: binary search + forward scan
picking the innermost (narrowest) match, replacing a broken
`while/break/break` pattern
- **Extract `find_symbol_info` helper**: consolidates 6 duplicated
"search open file indices then ProjectIndex" lookups into one method
- **`resolve_hierarchy_item` checks open file indices**: no longer
limited to ProjectIndex only

## Test plan

- [x] 465 unit tests pass
- [x] 105 integration tests pass (including all `test_index` cases:
GoToDefinition, FindReferences, CallHierarchy, TypeHierarchy,
WorkspaceSymbol)
- [x] Manual: open a file and immediately use GoToDefinition — should
work without waiting for background indexing
- [x] Manual: close a file and verify background indexing picks it up
and produces a MergedIndex shard

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 20:50:38 +08:00
ykiko
836f415e50 feat: header context protocol — queryContext, currentContext, switchContext (#398)
## Summary

Add three LSP protocol extensions that allow users to manage compilation
contexts for header files and source files with multiple CDB entries.

### Protocol extensions (`protocol.h`)

| Command | Purpose |
|---------|---------|
| `clice/queryContext` | List all possible contexts for a file. Headers
→ host source files; sources → CDB entries. Paginated (10 per page,
`offset` param). |
| `clice/currentContext` | Query the active context override for a file
(null if default). |
| `clice/switchContext` | Set the active context, invalidate caches,
trigger recompilation. |

### Header context resolution (`master_server.cpp`,
`dependency_graph.cpp`)

- `find_host_sources()`: BFS the reverse include graph to find source
files that transitively include a header
- `find_include_chain()`: BFS the forward include graph to find the
shortest include chain from host to header
- `resolve_header_context()`: walks the include chain, extracts content
before each `#include` directive, concatenates with `#line` markers into
a preamble file (hash-addressed under `.clice/header_context/`)
- `fill_header_context_args()`: uses the host source's CDB entry,
replaces source path with header path, injects `-include preamble.h`

### Compilation flow

- Default: headers compile as standalone files (no context)
- After `switchContext`: `fill_compile_args` checks `active_contexts`
first → uses host's CDB entry + preamble injection
- Fallback: if no CDB entry and no active context, auto-resolves via
`resolve_header_context`
- `#include` directive matching uses precise filename extraction from
`"..."` / `<...>`, not substring matching

### Source file multiple contexts (`multi_context` workspace)

- `queryContext` on a source file returns all CDB entries with
distinguishing labels (extracted from `-D`, `-O`, `-std=` flags)

### Test data

- `header_context/`: non-self-contained 3-level chain (`main.cpp` →
`utils.h` → `inner.h`), `types.h` provides `Point` struct
- `multi_context/`: single source with two CDB entries (`-DCONFIG_A`,
`-DCONFIG_B`)

### Tests (9 integration tests)

- queryContext returns host sources for headers
- queryContext returns CDB entries for source files
- currentContext defaults to null
- switchContext sets active context, currentContext reflects it
- Full flow: open → query → switch → hover works in non-self-contained
header
- Deep nested: switchContext + hover on `inner.h` (3 levels deep)
- Multiple CDB entries: queryContext returns both CONFIG_A and CONFIG_B

## Test plan
- [x] Unit tests: 465 passed
- [x] Integration tests: 113 passed (9 new header context tests)
- [x] Smoke test: 1/1 passed
- [ ] Manual VSCode testing

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:55:06 +08:00
dragon-archer
1627b96d2b fix: linking required LLVM libs only (#339)
This patch restricts the GLOB expression to only LLVM & Clang libraries,
avoiding linking to a large list of libraries when using system LLVM.

Also, this patch explicitly avoids linking to `LLVM-<ver>` and
`clang-cpp`, as they will cause lots of duplicate symbol errors

Below is a small set of errors generated when linking to `clang-cpp`:
```
ld.lld: error: duplicate symbol: clang::ast_matchers::internal::DynTypedMatcher::constructRestrictedWrapper(clang::ast_matchers::internal::DynTypedMatcher const&, clang::ASTNodeKind)
>>> defined at libclangASTMatchers.a(ASTMatchersInternal.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::Stmt::getEndLoc() const
>>> defined at libclangAST.a(Stmt.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::ast_matchers::internal::createAndVerifyRegex(llvm::StringRef, llvm::Regex::RegexFlags, llvm::StringRef)
>>> defined at libclangASTMatchers.a(ASTMatchersInternal.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::ast_matchers::internal::BoundNodesTreeBuilder::addMatch(clang::ast_matchers::internal::BoundNodesTreeBuilder const&)
>>> defined at libclangASTMatchers.a(ASTMatchersInternal.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::Stmt::getStmtClassName() const
>>> defined at libclangAST.a(Stmt.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::ast_matchers::internal::getExpansionLocOfMacro(llvm::StringRef, clang::SourceLocation, clang::ASTContext const&)
>>> defined at libclangASTMatchers.a(ASTMatchersInternal.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::transformer::remove(std::__1::function<llvm::Expected<clang::CharSourceRange> (clang::ast_matchers::MatchFinder::MatchResult const&)>)
>>> defined at libclangTransformer.a(RewriteRule.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::ast_matchers::internal::hasAnyOverloadedOperatorNameFunc(llvm::ArrayRef<llvm::StringRef const*>)
>>> defined at libclangASTMatchers.a(ASTMatchersInternal.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::transformer::detail::makeEditGenerator(clang::transformer::ASTEdit)
>>> defined at libclangTransformer.a(RewriteRule.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::Stmt::stripLabelLikeStatements() const
>>> defined at libclangAST.a(Stmt.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::Stmt::IgnoreContainers(bool)
>>> defined at libclangAST.a(Stmt.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::WhileStmt::getConditionVariable()
>>> defined at libclangAST.a(Stmt.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::ValueStmt::getExprStmt() const
>>> defined at libclangAST.a(Stmt.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::ast_matchers::internal::matchesAnyBase(clang::CXXRecordDecl const&, clang::ast_matchers::internal::Matcher<clang::CXXBaseSpecifier> const&, clang::ast_matchers::internal::ASTMatchFinder*, clang::ast_matchers::internal::BoundNodesTreeBuilder*)
>>> defined at libclangASTMatchers.a(ASTMatchersInternal.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::IfStmt::getConditionVariable()
>>> defined at libclangAST.a(Stmt.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)

ld.lld: error: duplicate symbol: clang::transformer::detail::buildMatchers(clang::transformer::RewriteRuleBase const&)
>>> defined at libclangTransformer.a(RewriteRule.cpp.obj)
>>> defined at libclang-cpp.dll.a(libclang-cpp.dll)
```

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Chores**
* Improved build configuration to explicitly discover and link LLVM and
Clang libraries plus optional z/zstd compression libraries; updated
linking to include these groups and retained the static Clang build
definition, resulting in more reliable and consistent compilation across
environments.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 15:56:09 +08:00
ykiko
a40c0b3bf8 docs: expand compilation database generation guide (#401)
## Summary
- Fill in Visual Studio, Makefile, Meson sections (previously TODO)
- Expand Xmake section with CLI and VSCode extension workflows
- Simplify Others section to recommend
[catter](https://github.com/clice-io/catter)
- Fix CJK-Latin spacing in Chinese docs
- English and Chinese docs updated in sync

Supersedes #313 by @Stehsaer.

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

---------

Co-authored-by: Stehsaer <Stehsaer@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:51:46 +08:00
Shiyu
d253c1f099 feat: collect #embed and __has_embed directives in PPCallbacks (#309)
## Summary
- Add `Embed` and `HasEmbed` structs to `directive.h` for storing
`#embed` / `__has_embed` directive info
- Implement `EmbedDirective` and `HasEmbed` PPCallbacks in
`DirectiveCollector`, using the current `CompilationUnitRef` API
- Add unit tests for both directives (including non-existent file
handling)

Rebased onto current main, resolving conflicts from the `Compiler/` →
`compile/` restructuring and the `CompilationUnitRef` API migration.

Original PR by @Guo-Shiyu.

## Test plan
- [x] `pixi run unit-test` — 465 tests passed, 0 failures

Co-authored-by: ykiko <ykikoykikoykiko@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:51:26 +08:00
ykiko
0c107fc2c5 chore: disable coderabbit auto summary on PRs (#400)
## Summary
- Disable CodeRabbit's auto-generated PR summary while keeping code
review enabled

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
  * Configuration updates to automated review processes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:38:43 +08:00
ykiko
018bad4ea8 ci: add conventional commit format check (#399)
## Summary
- Add `conventional-commit` CI job that validates PR titles and push
commit messages follow the `type(scope)?: description` format
- Valid types: `feat`, `fix`, `refactor`, `chore`, `build`, `ci`,
`docs`, `test`, `perf`, `style`, `revert`
- Runs on both PR and push events, skips tag pushes
- Zero dependencies, pure bash regex check

## Test plan
- [x] PR title of this PR itself passes the check
- [ ] CI runs and passes

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Enhanced continuous integration pipeline to validate commit message
formatting standards on all pull requests and commits.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:35:30 +08:00
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
Myriad-Dreamin
aae246e465 feat: classify more semantic token modifiers (#395)
## Summary
- add declaration helpers to classify semantic token modifiers such as
readonly, static, abstract, virtual, default library, and
constructor/destructor
- unwrap template declarations before applying attribute-style modifiers
so modifier checks hit the underlying declaration
- keep templated/dependent-name handling and expand emitted semantic
token modifiers in `semantic_tokens.cpp`

## Testing
- Not run (not requested in this turn)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Enhanced semantic token analysis to improve code recognition and more
accurate classification of declarations across C++ and Objective-C
codebases.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 11:12:28 +08:00
Myriad-Dreamin
d04bc6f774 feat: register server capability correctly (#397)
## Summary
- register workspace and text document capabilities through the
structured LSP capability types
- advertise completion, signature help, declaration, definition,
implementation, type definition, and reference support more explicitly
- add placeholder handlers for declaration, type definition, and
implementation requests so the advertised capabilities have matching
routes

## Testing
- Not run (not requested)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added workspace folder support for improved project tracking.
* Registered navigation handlers for type-definition, implementation,
and declaration (currently return a “not supported yet” placeholder).
* **Improvements**
* Enhanced completion and signature help with explicit trigger
characters and clearer capability declarations.
* **Tests**
* Relaxed capability assertions to recognize more nuanced
enabled/disabled states.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 10:23:38 +08:00
Myriad-Dreamin
8d4ad26834 feat: classify dependent-name semantic token modifier (#387)
## Summary
- classify unresolved using declarations with the `dependentName`
semantic token modifier
- include the declaration modifier on definitions to match clangd's
semantic token behavior
- extend the semantic token modifier legend coverage in integration
tests

## Testing
- pixi run python -m pytest -s --log-cli-level=INFO
tests/integration/test_server.py -k 'semantic_token_modifier_legend or
capabilities' --executable=./build/bin/clice

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added many new semantic token modifiers (deprecated, deduced,
readonly, static, abstract, virtual, dependent-name,
constructor/destructor, user-defined, mutable-usage flags) and new scope
markers (function, class, file, global).
* Improved tagging for declarations, definitions and dependent names in
semantic tokens.

* **Tests**
* Added an integration test verifying the semantic token modifier legend
and its ordering.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-04-06 00:38:09 +08:00
ykiko
b6886d222b feat: per-session file-based logging with crash capture (#393)
## Summary

Implement structured file-based logging with per-component separation
and crash stacktrace capture.

### Log output structure
```
.clice/logs/2026-04-05_10-30-00_<pid>/
  master.log
  SF-0.log
  SF-1.log
  SL-0.log
```

### Changes

**Logging infrastructure** (`logging.h`, `logging.cpp`)
- `file_logger()` creates a dual-sink logger (file + stderr), so logs go
to both the file and terminal
- Pre-checks log directory creation and file writability before
constructing spdlog sinks; falls back to existing stderr logger on
failure
- `install_crash_handler()` uses LLVM's `AddSignalHandler` +
`PrintStackTraceOnErrorSignal` to write crash stacktraces into the
component's log file (and also to stderr)
- Fix `LOG_MESSAGE` macro: wrap in `do { } while(0)` to prevent
dangling-else
- Fix typo: `file_loggger` → `file_logger`

**Config** (`config.h`, `config.cpp`)
- Add `logging_dir` field to `CliceConfig`, defaulting to
`<cache_dir>/logs/`
- Apply `${workspace}` variable substitution to `logging_dir`

**Master server** (`master_server.h`, `master_server.cpp`)
- After config loads, create a session directory named
`<timestamp>_<pid>` under `logging_dir` and switch master to file
logging
- Pass session log directory to worker pool

**Worker pool** (`worker_pool.h`, `worker_pool.cpp`)
- Pass `--worker-name` (e.g. `SF-0`, `SL-1`) and `--log-dir` to spawned
worker processes
- Add `log_dir` to `WorkerPoolOptions`

**Workers** (`stateful_worker.h/cpp`, `stateless_worker.h/cpp`)
- Accept `worker_name` and `log_dir` parameters; switch to file logging
when `log_dir` is provided

**CLI cleanup** (`clice.cc`)
- Remove `--stateful-worker-count`, `--stateless-worker-count` from CLI
(config-file only)
- Group internal worker args (`--worker-memory-limit`, `--worker-name`,
`--log-dir`) separately

**Docs** (`docs/clice.toml`)
- Fix `logging_dir` example: `.clice/logging` → `.clice/logs`

## Test plan
- [x] `pixi run cmake-build RelWithDebInfo` compiles successfully
- [ ] Verify log files created under `.clice/logs/<timestamp>_<pid>/`
- [ ] Verify each component writes to its own file
- [ ] Verify crash stacktrace appears in component log file
- [ ] Verify `logging_dir` override in `clice.toml` works
- [ ] Verify graceful fallback when log directory is not writable

🤖 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**
* Session-specific logging directories (timestamped) and per-worker log
files
* CLI options to set worker name and log directory; general log level
control
  * Configurable logging directory with default `<cache_dir>/logs/`

* **Bug Fixes**
* Fixed file-logging name/initialization issues; ensures directory
creation and deterministic filenames
  * Added crash-handler support to append stack traces to logs

* **Documentation**
  * Updated example config to use `${workspace}/.clice/logs`

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:55:22 +08:00
ykiko
c14b8de18f chore: remove xmake, clean up unused files, simplify CLI parsing (#392)
## Summary
- **Remove xmake build system**: delete `xmake.lua`, `test-xmake.yml`
workflow, and all xmake-related pixi tasks
- **Migrate packaging to CMake**: `publish-clice.yml` now uses `cmake
-DCLICE_RELEASE=ON` instead of xmake pack
- **Clean up**: remove unused `tests/uv.lock`, `.xmake/` from
`.gitignore`, xmake references from docs
- **Benchmark CI**: change trigger from `pull_request` to
`workflow_dispatch` (manual only)
- **Simplify CLI parsing**: use `DecoKV(style =
KVStyle::JoinedOrSeparate)` in `clice.cc` and `unit_tests.cc`, replacing
verbose `DecoKVStyled` with manual `static_cast` bitmask; use comma
separators; explicit `names` only for underscore fields

## Test plan
- [x] `pixi run cmake-build RelWithDebInfo` compiles successfully
- [x] Verify `pixi run test` passes
- [x] Verify `pixi run package` produces correct archives via CMake
release build
- [x] Verify benchmark workflow can be triggered manually via `gh
workflow run benchmark`

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Consolidated CI workflows; some automated triggers converted to manual
invocation
* Standardized shell for workflow steps and removed legacy build
workflow
* Switched packaging/build tasks to a CMake/Ninja flow and updated
artifact paths
  * Adjusted ignore rules to include previously-ignored build metadata

* **Documentation**
* Removed XMake-specific build and test instructions; docs now reflect
the CMake-based workflow

* **Style**
* Updated CLI option declaration style (no user-facing flag name
changes)

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:33:20 +08:00
ykiko
3838bedcbf feat: persistent PCH/PCM cache across sessions (#391)
## Summary

PCH and PCM artifacts are now cached to disk at
`.clice/cache/{pch/,pcm/}` with content-addressed filenames, so they
survive server restarts. Dependency metadata is persisted in
`cache.json` (using eventide serde) with a shared path table for
deduplication.

### Key changes

- **protocol.h**: `output_path` field on `BuildPCHParams` /
`BuildPCMParams` so master specifies where workers write
- **stateless_worker.cpp**: Atomic write via `.tmp` + `fs::rename`;
`CompilationUnit` destroyed before rename to flush the file to disk;
fallback to temp file when `output_path` is empty (unit tests)
- **master_server.h**: `PCMState` struct, `pcm_states` map,
`load_cache()` / `save_cache()` / `cleanup_cache()` methods
- **master_server.cpp**: Cache lifecycle — load from `cache.json` on
startup, save after each PCH/PCM build and on exit; deterministic path
computation (`xxh3` preamble hash for PCH, module name + source path
hash for PCM); stale files (>7 days) cleaned on startup; `cache.json`
uses shared path table to avoid redundant storage of header paths across
entries
- **filesystem.h**: `fs::rename()` helper; `ThreadSafeFS` broadened to
match `.pch` extension instead of `preamble-` prefix
- **tests**: 11 new integration tests covering PCH/PCM persistence,
cross-session reuse, staleness detection, shared preamble dedup, and
restart survival; unit tests updated with `output_path`

### Naming scheme

- **PCH**: `.clice/cache/pch/<016x(xxh3(preamble))>.pch`
- **PCM**:
`.clice/cache/pcm/<module_name>-<016x(xxh3(source_path))>.pcm`

## Test plan

- [x] Unit tests — 448 passed
- [x] Integration tests — 92 passed (including 11 new persistent cache
tests)
- [x] Smoke tests — 1 passed

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:29:21 +08:00
ykiko
31d9c609b6 fix: data race in stateful worker between Compile and DocumentUpdate (#389)
## Summary

Fix two data races in the stateful worker that caused spurious
"redefinition" errors during rapid edits, and remove a didChange
workaround that is no longer needed after clice-io/eventide#95.

### stateful_worker.cpp

**Compile handler**: move `params` → `doc` field copy **after**
`strand.lock()`. Previously the copy happened before the lock, so a
concurrent Compile request waiting on the strand could overwrite
`doc.text` while `et::queue` was reading it on the thread pool:

```
T1: Compile A → doc.text = text_A → lock → et::queue reads doc.text
T2: Compile B → doc.text = text_B → waits for strand (overwrites!)
T3: et::queue sees text_B instead of text_A → PCH/text mismatch
```

**DocumentUpdate handler**: only mark `dirty`, stop modifying
`doc.text`/`doc.version`. The event loop notification can fire while
`et::queue` work is running on the thread pool — writing `doc.text` from
one thread while reading it from another is a data race.

### master_server.cpp

Remove the `{0,0}-{0,0}` range workaround for whole-document
`didChange`. eventide's variant deserialization now correctly rejects
`TextDocumentContentChangePartial` when the `range` field is absent
(clice-io/eventide#95), so `TextDocumentContentChangeWholeDocument` is
matched as intended.

### protocol.h

Remove `text` field from `DocumentUpdateParams` — the worker no longer
needs it since DocumentUpdate only sets the dirty flag.

### Integration tests (+312 lines)

Extend test_staleness.py from 5 to 14 tests covering document lifecycle:
- `didChange` body edit → recompilation with updated diagnostics
- `didChange` preamble edit → PCH rebuild + clean recompilation
- `didClose` + reopen → compiles fresh from disk
- `didClose` → hover returns None
- `didSave` header → dependent file recompiles
- `didSave` module → CompileGraph dependents invalidated

## Test plan

- [x] 422 unit tests pass (426 on CI with extra test suites)
- [x] 14 integration tests pass locally
- [x] Depends on clice-io/eventide#95 (merged)

🤖 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**
* Smaller document-update notifications sent to background workers (only
path and version).

* **Bug Fixes**
  * Reduced races and unnecessary work between update and compile flows.
* Prevented notifications from overwriting in-memory document text,
improving state consistency.
* Safer concurrent handling to avoid mid-request eviction of active
documents.

* **Tests**
* Added integration tests for staleness, dependency propagation, and LSP
lifecycle.
  * Updated unit tests to match revised update behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:20:13 +08:00
ykiko
a1b6c0632d refactor(semantic): rewrite template resolver — eliminate SubstType, fix crashes (#388)
## Summary

Rewrites the template resolver to eliminate
`SubstType`/`CodeSynthesisContexts` dependency, fixing widespread
crashes on real-world C++ code.

### What changed

**Architecture**: replaced double-TreeTransform (PseudoInstantiator +
SubstType) with single-layer design:
- **`SubstituteOnly`** — new lightweight TreeTransform for Phase 2
(typedef expansion + parameter substitution). Does NOT override
`TransformDependentNameType`, breaking the typedef ↔ lookup infinite
cycle.
- **`PseudoInstantiator`** — retains heuristic lookup (the unique value
clang doesn't provide), delegates substitution to `SubstituteOnly`.

**Deleted**:
- `DesugarOnly` class
- `instantiate()` method and all `CodeSynthesisContexts` / `SubstType`
usage
- `state()` / `rewind()` stack management
- `std::abort()` on valid NNS kinds
- `#ifndef NDEBUG` debug flag + `std::print` logging

**Added**:
- `SubstituteOnly` class with depth guard
- `InstantiationStack::findArgument()` — depth/index based parameter
lookup
- CTD→TST resolution for `DependentTemplateSpecializationType` (enables
`__alloc_traits::rebind<T>::other` resolution)
- `active_resolutions` (DNT cycle detection) + `active_ctd_lookups` (CTD
cycle detection via RAII guard)
- Stack frame pollution fix: pop lookup frames before further
`TransformType`
- Pack argument support (single-element forwarding)
- Null safety on all Transform return paths
- Structured `LOG_DEBUG` trace logging with indentation
- `--log-level` / `--test-filter` CLI options for unit test runner
- Bounds checks in `hole()`, `ResugarOnly`, `visitTemplateDeclContexts`

**Tests**: 20 → 36 passing test cases (+5 documented TODOs for known
limitations). New coverage: recursive base classes, multiple
inheritance, typedef chains, CRTP, `remove_reference` partial specs,
`std::map`, `std::basic_string`, pack forwarding.

### Stress test result

```
CDB: llvm-project build (4669 C++ files)
Types resolved: 3,690,190
Types unchanged: 75,907,298
Crashes: 0
```

Before this PR, the same test produced ~52% crash rate (413 crashes in
800 files).

### Known limitations (documented as TODOs)

- NTTP partial specialization matching (`enable_if<true, X>`, `A<X, 0>`)
- Template template parameter deduction
- Non-dependent qualifier nested class templates
(`Outer<int>::Inner<X>`)
- Multi-element pack expansion
- `CXXDependentScopeMemberExpr` lookup (unimplemented)
- Operator-name lookup in dependent contexts

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **New Features**
* Enhanced template resolution with improved cycle detection to prevent
infinite loops
  * Better type substitution handling for complex dependent types

* **Bug Fixes**
  * Fixed edge cases in template specialization resolution
  * Improved null-safety in type transformations
  * Enhanced handling of standard library template traits

* **Tests**
  * Expanded test coverage for recursive and complex template patterns
  * Added validation for standard library type resolution

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:18:07 +08:00
ykiko
1dd94e54c0 feat: two-layer staleness tracking with concurrent compilation dedup (#386)
## Summary

Replace the `didSave` sledgehammer (`pch_hashes.clear()` +
mark-all-dirty) with precise per-file dependency tracking that avoids
unnecessary recompilation.

### Two-layer staleness detection

After each successful compilation, a `DepsSnapshot` is captured
(interned path IDs + xxh3 content hashes + timestamp). On the next
feature request, `deps_changed()` checks:

- **Layer 1 (fast):** stat each dep, compare mtime against `build_at`.
If mtime is older → skip. Uses strict `<` so same-second modifications
fall through.
- **Layer 2 (precise):** for files with newer mtime, re-hash content and
compare. Catches touch-without-change (git checkout, backup restore)
without false rebuilds.

Special cases: files unreadable at build time (hash=0) always fall
through to Layer 2; disappeared files are detected via stat failure.

### Consolidated PCH state

Scatter of four maps (`pch_paths`, `pch_bounds`, `pch_hashes`,
`pch_building`) → single `PCHState` struct with `path`, `bound`, `hash`,
`deps`, `building` fields. `DepsSnapshot` and `SymbolInfo` moved out of
`MasterServer` to namespace scope.

### Concurrent compilation dedup

- **`ensure_compiled`:** `DocumentState::compiling` event prevents
duplicate AST compilations. Waiters `co_await` the event and check
`ast_dirty` after waking. When deps change is detected during an
in-flight build, `generation` is bumped so the builder's generation
check prevents it from incorrectly clearing `ast_dirty`.
- **`ensure_pch`:** `PCHState::building` event deduplicates PCH builds.
Waiters re-validate `preamble_hash` after waking to handle edits during
the wait. The `bound==0` path waits for in-flight builds before erasing.
Old PCH is deleted and path cleared before rebuild starts, so waiters
never see a stale path on failure.

### `didSave` changes

Removed the blanket `pch_hashes.clear()` + mark-all-dirty on save.
Staleness is now detected lazily via `deps_changed()` at the next
feature request. `didSave` still invalidates `CompileGraph` dependents
for module deps.

### FIXME noted

Rapid `didChange` edits (especially preamble changes) can cause the
stateful worker to compile with stale/concatenated text. Root cause is
in the worker, not in staleness tracking — noted as FIXME for a
follow-up PR.

## Test plan

13 integration tests covering:
- [x] Header mtime change → AST recompilation
- [x] Preamble header change → PCH rebuild
- [x] No change → fast path (cached AST reused)
- [x] Touch without content change → Layer 2 hash skips recompile
- [x] Header replaced with different content → detected
- [x] Fix error in header → diagnostics clear
- [x] Multiple files sharing header → each detects independently
- [x] Transitive header change → detected through include chain
- [x] didChange body edit → recompilation with updated diagnostics
- [x] didClose + reopen → compiles new disk content
- [x] didClose → hover returns None
- [x] didSave header → dependent file recompiles
- [x] didSave module → CompileGraph dependents invalidated

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:57:53 +08:00
ykiko
e24eff6c16 refactor: pull-based compilation for document lifecycle (#385)
## Summary

Replace the push-based compilation model with a pull-based (lazy) model
where compilation is driven entirely by feature requests.

### Server core (`master_server.cpp/h`)
- **Remove** `schedule_build()`, `run_build_drain()`, debounce timers,
and `DocumentState` flags (`build_running`, `build_requested`,
`drain_scheduled`)
- **Remove** `debounce_ms` config field
- `didOpen`/`didChange` only update `DocumentState` and mark `ast_dirty`
— no compilation triggered
- `didSave` marks dependent docs dirty via `CompileGraph::update()`,
invalidates PCH hashes, marks **all** open documents `ast_dirty` (header
saves), and queues background indexing
- **Implement** `ensure_compiled(path_id)` — the pull-based entry point
called by `forward_stateful()`/`forward_stateless()` before every
feature request:
  1. Fast-path if `!ast_dirty`
  2. Compile C++20 module deps via `compile_graph->compile_deps()`
  3. Build/reuse PCH via `ensure_pch()` (only attach on success)
  4. Send `CompileParams` to stateful worker
  5. Publish diagnostics, clear dirty, schedule indexing
  6. Generation mismatch → return `false`, keep dirty for retry
- `forward_stateless()` now also calls `compile_graph->compile_deps()`
before stateless requests (completion/signatureHelp)
- Move module-implementation-unit implicit dependency handling into
`resolve_fn` (was duplicated in `run_build_drain` and `ensure_compiled`)

### CompileGraph (`compile_graph.cpp/h`)
- **Add** `compile_deps(path_id)` — compiles all transitive module
dependencies but NOT the file itself (used for plain .cpp files that
`import` modules)
- Unify `compile`/`compile_deps` via `compile_impl(path_id, ancestors,
dispatch_self)` parameter
- `compile_deps` compiles dependencies concurrently via `when_all`
- Extract `finish()` lambda to deduplicate `compiling=false;
completion->set()` cleanup across all exit paths
- Use `std::ranges::remove` instead of legacy `std::remove`

### Test infrastructure (`conftest.py`)
- `open_and_wait()` now sends a hover request to trigger
`ensure_compiled()` (pull-based model requires a feature request to
compile)
- Fix URI handling: send percent-encoded URI on the wire, normalize for
internal lookups, store diagnostics under both raw and normalized URI
keys
- Add `_normalize_uri()` helper using `urllib.parse.unquote`

### Integration tests
- Update all tests for pull-based model: no more waiting on `didOpen`
diagnostics
- `_wait_for_index()` sends hover to trigger compilation before polling
`workspace/symbol`
- `test_hover_save_close` simplified — hover directly triggers
compilation
- `test_save_recompile` and `test_pch_*` wait for fresh diagnostics
after hover-triggered recompilation

### Unit tests (`compile_graph_tests.cpp`)
- Extract `compiled`/`graph` as TEST_SUITE members with
`std::optional<CompileGraph>`
- Extract `execute(callback)` helper to deduplicate event_loop
boilerplate
- Add 8 new `compile_deps` tests: no-deps, single dep, chain, diamond,
failure, plain-cpp, concurrent dedup, resolve-once
- Remove redundant `inline` on file-scope helpers

## Test plan
- [x] Unit tests: 426 passed, 5 skipped
- [x] Smoke tests: 1/1 passed
- [x] Integration tests: 69 passed, 0 failed, no hangs

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 02:35:17 +08:00
ykiko
c697ffcf91 chore(vscode): move dev configs to repo root (#384)
## What changed

This PR moves the shared VS Code development workflow to the repository
root.

It adds repo-level `.vscode/launch.json` and `.vscode/tasks.json` so
contributors can debug `clice` and the VS Code extension from the root
workspace with relative paths.

The checked-in tasks are intentionally limited to extension-side `pnpm`
workflows such as installing dependencies and running the extension
watcher. They do not build the `clice` executable or unit tests.

It also updates `.gitignore` to allow only those two shared VS Code
config files to be committed, while continuing to ignore other local
`.vscode` files.

Finally, it removes the duplicate `editors/vscode/.vscode` folder so
there is a single supported VS Code setup in the repo.

## Why

The previous setup split VS Code configs between local-only root files
and tracked configs under `editors/vscode/.vscode`, which made the
development/debug flow less consistent.

Keeping only extension-side tasks avoids baking a specific native build
workflow into committed editor config while still making extension
development usable out of the box.

## Developer impact

Contributors can now open the repository root in VS Code and use the
checked-in launch/tasks configs directly.

The extension debug configs only disable competing C/C++ language
extensions (`clangd` and `cpptools`) instead of disabling every
extension.

All committed paths remain relative to `${workspaceFolder}`.

## Validation

- Ran `python3 -m json.tool .vscode/launch.json`
- Ran `python3 -m json.tool .vscode/tasks.json`
2026-04-03 23:25:52 +08:00
ykiko
94bc872cdb feat: add LSP trace recording and smoke test replay (#383)
## Summary

Add LSP trace recording and replay-based smoke testing infrastructure.

### clice changes (`src/clice.cc`)
- Add `--log-level` CLI option with validation (rejects unknown levels
instead of silently defaulting to off)
- Add `--record <path>` CLI option that wraps the transport with
`RecordingTransport` to capture client→server messages as timestamped
JSONL
- Works in both pipe and socket modes
- Fix exit code: `loop.run()` returns non-zero after `uv_stop()`,
explicitly return 0 after clean shutdown

### Compile logging (`src/compile/compilation.cpp`)
- Print compile commands at debug log level

### Replay script (`tests/replay.py`)
- Timestamp-based pacing: sleeps between messages based on recorded
intervals, faithful to original editor session
- Automatic workspace path rewriting: infers repo root from script
location, rewrites absolute paths in trace so CI replay works without
extra arguments
- Handles server→client requests (workDoneProgress/create,
registerCapability, etc.) with default responses
- Waits for all pending responses before sending shutdown/exit
- Detects server exit mid-replay and fails pending futures immediately
instead of hanging
- Reports PASS/FAIL/SKIP with stderr tail on failure

### CI & config
- Add `smoke-test` pixi task and CI workflow step (runs after
integration tests)
- `.gitattributes`: mark `tests/smoke/*.jsonl` as `linguist-generated
binary` to suppress diffs
- Add sample trace file `tests/smoke/session.jsonl`

### VSCode extension
- Add restart command (`clice.restart`)
- Support `CLICE_MODE` env var to override mode setting (for debug
launch configs)
- Split launch configs into socket/pipe variants with
`--disable-extensions`

## Test plan
- [x] `python tests/replay.py tests/smoke/session.jsonl --clice
./build/RelWithDebInfo/bin/clice` passes locally
- [ ] CI smoke test passes on Linux/macOS/Windows

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

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:21:28 +08:00
ykiko
e43bb14998 feat: implement index system with LSP query handlers (#382)
## Summary

Implement the complete index system for cross-file LSP features. This
adds persistent two-tier indexing (ProjectIndex + per-file MergedIndex
shards), background indexing triggered on idle, and index-based query
handlers for major LSP requests.

### Index Data Layer (`src/index/`)

- **TUIndex**: Add binary serialization/deserialization via FlatBuffers,
enabling IPC between stateless worker and master server
- **ProjectIndex**: Add symbol name/kind storage, `PathPool` path
normalization (backslash -> forward slash), and binary persistence
- **MergedIndex**: Add `content` field to store file content for
reliable offset<->position mapping; add `removed` bitmap for garbage
collection of deleted entries; filter removed IDs in `lookup()` queries
- **schema.fbs**: Add TUIndex tables, `Symbol.name` field,
`MergedIndex.removed` bitmap and `MergedIndex.content` string

### Server (`src/server/`)

- **Background indexing**: Idle-triggered coroutine dequeues files from
CDB, dispatches `IndexParams` to stateless workers, merges returned
`TUIndex` into ProjectIndex/MergedIndex, and persists to `.clice/index/`
- **Index persistence**: `save_index()` / `load_index()` for startup
restoration; only rewrites shards flagged `need_rewrite()`
- **LSP handlers**:
- `textDocument/definition` -- index-first lookup with stateful worker
fallback
  - `textDocument/references` -- cross-file reference query via index
- `callHierarchy/prepare`, `incomingCalls`, `outgoingCalls` --
Caller/Callee relation traversal
- `typeHierarchy/prepare`, `supertypes`, `subtypes` -- Base/Derived
relation traversal
- `workspace/symbol` -- case-insensitive substring search over
ProjectIndex symbols
- **Stateless worker**: Add `Index` request handler that builds
`TUIndex` from compiled AST and returns serialized data
- **Config**: Add `enable_indexing` (default true) and `idle_timeout_ms`
(default 3000ms)

### Fixes and Cross-platform

- **ElaboratedType handling** in `decl_of()` for correct Base/Derived
relation emission
- **Windows path normalization** in `PathPool::intern()` and
`ProjectIndex::from()` (backslash -> forward slash)
- **`.gitattributes`**: Force LF in `tests/data/**` to prevent CRLF
byte-offset mismatches on Windows CI
- **Test fixture**: Clean `.clice/` before each test for hermetic index
state

### Tests

- **370-line** `index_query_tests.cpp`: unit tests for occurrence
lookup, relation queries, content retrieval, removed bitmap filtering
- **282-line** `test_index.py`: E2E integration tests for
GoToDefinition, FindReferences, CallHierarchy
(prepare/incoming/outgoing), TypeHierarchy
(prepare/supertypes/subtypes), WorkspaceSymbol
- Updated existing MergedIndex and ProjectIndex tests for new schema
fields

## Test plan

- [x] 414 C++ unit tests pass (including new IndexQuery, MergedIndex,
ProjectIndex tests)
- [x] 69 Python integration tests pass (including 10 new index feature
tests)
- [x] CI green on Linux, macOS, Windows
- [ ] Manual smoke test with VSCode extension

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-02 00:20:41 +08:00
ykiko
21a969af27 feat: integrate PCH into MasterServer build drain (#381)
## Summary
- Add `ensure_pch()` helper to MasterServer that builds/reuses
precompiled headers via stateless workers, with preamble hash-based
staleness detection (xxh3_64bits)
- Fix `BuildPCHParams` to carry `preamble_bound` so the stateless worker
truncates content at the preamble boundary (fixes redefinition errors
when PCH included full file)
- Wire PCH into both `run_build_drain` (stateful compile path) and
`forward_stateless` (completion/signatureHelp path)
- Add PCH state cleanup on `didClose` and hash invalidation on `didSave`

## Test plan
- [x] 398 unit tests pass (including 6 new PCH tests: PreambleHash x3,
PCHWorker x2, BuildPCHRequest assertion)
- [x] 5 new integration tests pass (`test_pch.py`: diagnostics on open,
body edit recompile, no-include file, hover with PCH, completion with
PCH)
- [x] 21 existing integration tests pass unchanged
- [x] Build succeeds with 0 errors

🤖 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**
* Precompiled header (PCH) caching to speed compilations and reduce edit
latency
* Automatic attachment of cached PCH to compile requests, improving
hover and completion responsiveness
* Module-aware completions expanded to include available module
artifacts from other files

* **Bug Fixes**
* PCH cache cleared on file close; saving now triggers broader PCH
invalidation to prevent stale PCH use

* **Tests**
* Added unit and integration tests exercising PCH build, reuse, and
editor interactions
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 18:33:33 +08:00
ykiko
084f3b2d22 docs(cmake): restore ASan workaround comments (#380)
## Summary
- Restore comment explaining clang-cl manual ASan runtime linking
workaround
- Restore comment explaining OPT:NOICF for ASan ODR false positives on
Windows

These were accidentally removed in #379.

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved internal build configuration for Debug builds on Windows to
enhance development and testing infrastructure with better error
detection and optimization settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 21:18:27 +08:00
ykiko
eb0a6b35ee refactor(cmake): clean up toolchain and CMakeLists separation (#379)
## Summary

Improve CMake build system: cleaner separation, compiler caching, and
release packaging.

### Toolchain & Build
- Clean up `toolchain.cmake` to only contain clang/lld-specific setup
(compiler paths, linker selection, llvm tools), allowing other
toolchains like GCC to work without it
- Add ccache (Linux/macOS) and sccache (Windows) support via toolchain
auto-detection
- Move project-universal flags (`-ffunction-sections`, `--gc-sections`,
`-static-libstdc++`, etc.) to `CMakeLists.txt` so they apply regardless
of toolchain
- Add `-fno-exceptions` to project compile options; fix `/EHs-c-` for
proper MSVC exception disabling
- Use MSVC/clang-cl frontend detection instead of `WIN32` for
MSVC-specific linker flags
- Declare missing options: `CLICE_USE_LIBCXX`, `CLICE_OFFLINE_BUILD`,
`CLICE_ENABLE_BENCHMARK`, `CLICE_RELEASE`

### Release Packaging (`cmake/release.cmake`)
- Strip debug symbols and produce separate symbol archives (`.debug` /
`.dSYM` / `.pdb`)
- Windows: copy PDB via `$<TARGET_PDB_FILE:clice>`; macOS: use
`copy_directory` for dSYM bundle
- Package clice binary + clang resource dir + config into distributable
tarball/zip
- `cmake/archive.cmake` helper for cross-platform archive creation
- Activated via `-DCLICE_RELEASE=ON` (auto-enables LTO)

### Code Cleanup
- Replace manual 40+ line source file list with `GLOB_RECURSE` for
clice-core
- Fix duplicate `include_resolver.cpp` entry
- Use build-time `add_custom_target` for clang resource dir copy
(instead of configure-time `file(COPY)`)
- Gate `scan_benchmark` behind `CLICE_ENABLE_BENCHMARK` option

### CI
- Add compiler cache with env var control (`CCACHE_DIR`/`SCCACHE_DIR`)
and `actions/cache` for persistence
- Proper cache lifecycle: zero-stats before build, show-stats +
stop-server after
- Stop sccache server before pixi cleanup to fix Windows EBUSY error
- Pass `CLICE_ENABLE_BENCHMARK=ON` in benchmark workflow
- Platform-specific ccache/sccache dependencies in pixi.toml

## Test plan
- [x] Local build (RelWithDebInfo) passes
- [x] Local release build (LTO + strip + pack) produces correct archives
- [ ] CI: Linux Debug/RelWithDebInfo
- [ ] CI: macOS Debug/RelWithDebInfo
- [ ] CI: Windows Debug/RelWithDebInfo
- [ ] CI: Benchmark (all platforms)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 20:51:01 +08:00
ykiko
bc04845293 refactor(tests): CMake-based CDB, workspace fixture, test cleanup (#378)
## Summary

- **CMake-based CDB generation for module tests**: Replace hand-written
compile_commands.json with CMakeLists.txt (CMake 3.28 `FILE_SET
CXX_MODULES`) in all 26 `tests/data/modules/*/` directories. CDB is
generated on-the-fly via `cmake -G Ninja` during test setup.
- **`@pytest.mark.workspace()` decorator**: Introduce a marker + fixture
pattern so tests declare their workspace via decorator and receive a
resolved `workspace` path. The fixture auto-generates CDB when a
CMakeLists.txt is present.
- **`CliceClient` helper methods**: Add `initialize()`, `open()`,
`wait_diagnostics()`, and `open_and_wait()` to reduce boilerplate across
all test files.
- **Use `asyncio_mode = "auto"`**: Switch from `@pytest_asyncio.fixture`
+ `@pytest.mark.asyncio` to `@pytest.fixture` + auto mode for proper
Pylance type inference on fixtures.
- **Test cleanup**: Remove redundant section separators and docstrings,
delete `tests/pyproject.toml` (config moved to `pytest.ini`).
- **Format task**: Add `.cppm` to `format-cpp` glob pattern.
- **CI fix**: Disable `CMAKE_CXX_SCAN_FOR_MODULES` and prefer pixi
clang++ to fix macOS CI where CMake rejects module scanning.

## Test plan

- [x] All 26 module test directories have CMakeLists.txt with FILE_SET
CXX_MODULES
- [x] generate_cdb() produces valid compile_commands.json with module
flags
- [x] Integration tests pass locally
- [ ] CI passes on all platforms (Linux, macOS, Windows)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Unified fixtures and client workflow: new init/open/wait helpers,
workspace marker support, bounded diagnostics waiting, CMake-based
compilation-database generation, and directory-backed temp-file
workflows; enabled asyncio test mode.
* **Chores**
* Added many C++20 module test projects and test data; removed prior
test pyproject in favor of pytest config; updated formatter to include
.cppm files.
* **Style**
* Reformatted many module/source implementations to consistent
multi-line function bodies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 16:57:48 +08:00
ykiko
0a891d8b4a refactor(tests): use Tester fixture, normalize helpers, add index tests (#377)
## Summary

- **Use `Tester` as fixture base** for all test suites that need
compilation, replacing `TesterFixture` and removing redundant
`tester.clear()` calls (eventide zest now creates fresh instances per
TEST_CASE)
- **Remove local `Tester` variables** in `compilation_tests`,
`template_resolver_tests`, `selection_tests` — use inherited fixture
members directly
- **Normalize helper naming**: `expect_xxx` → `EXPECT_XXX`,
`go_to_definition` → `GO_TO_DEFINITION` for consistency
- **Extract shared `test/cdb_helper.h`**: deduplicate `CDBEntry`,
`json_escape`, `build_cdb_json` from `dependency_graph_tests` and
`compile_graph_integration_tests`
- **Add new test files/cases**: `project_index_tests.cpp`, expanded
`tu_index_tests`, `merged_index_tests`, `compilation_tests`

## Test plan

- [x] All existing unit tests pass
- [x] New index tests (TUIndex, MergedIndex, ProjectIndex) pass
- [x] Compilation tests (PCH, PCM, stop) pass

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

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Standardized test fixtures and helper naming, moved suites to a shared
fixture, and unified in-memory VFS and compile flows.
* Added broad new coverage: indexing, project indexing, compilation/PCH,
diagnostics, semantic features, and many targeted unit cases.
* Introduced a small compile-database helper and improved driver-style
test compilation paths.

* **Chores**
* Consolidated and reorganized test utilities and tester APIs for easier
maintenance and reuse.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 10:29:49 +08:00
ykiko
6d3b6acc82 feat: initial CompileGraph integration into MasterServer (#376)
## Summary

Initial integration of `CompileGraph` (#375) into `MasterServer`,
enabling basic end-to-end C++20 module support: on-demand PCM building,
dependency-ordered compilation, cascade invalidation on save, and
diagnostic integration.

This is a **first-pass implementation** — the core pipeline works, but
there are known areas for follow-up:

- PCM files go to system temp dir instead of `.clice/cache/`; no disk
cleanup on invalidation
- `run_build_drain` scans imports itself rather than delegating fully to
CompileGraph
- No incremental/partial rebuild (full PCM rebuild on any change)
- Cycle detection is tested at unit level but integration-level coverage
is minimal

## Changes

### Module dependency compilation (`master_server.cpp`)

Before sending a file to the stateful worker, `run_build_drain` now:

1. Scans imports via `scan_precise()` to discover module dependencies
2. Compiles each dep through `compile_graph->compile()`, which
recursively builds transitive PCMs
3. Handles implementation units — `module M;` implicitly needs the
interface PCM
4. Passes all built PCMs to the stateful worker, excluding the file's
own PCM
5. Skips compile on dep failure and resets `build_running` /
`drain_scheduled`
6. Re-lookups iterators after `co_await` to avoid use-after-invalidation

### Cascade invalidation (`didSave` / `didClose`)

- `didSave`: calls `compile_graph->update()` to mark transitive
dependents dirty, removes stale PCM paths, schedules rebuilds for open
dirtied files
- `didClose`: cancels in-flight compilations for the closed file

### Other fixes in this PR

- Debounce timers switched to `shared_ptr` to prevent use-after-free
when `didClose` destroys the timer mid-wait
- `fill_compile_args` returns `bool`; callers handle empty CDB
gracefully
- Adapt all `PositionMapper` call sites to the new `optional` return API
from eventide

## Test plan

- [x] 25 C++ unit tests for CompileGraph (cycles, partial failure,
cancel, update, empty graph)
- [x] 24 C++ integration tests with real clang PCM compilation
- [x] 3 worker-level module tests (BuildPCM, PCM-dependent compile,
multi-module)
- [x] 26 Python LSP integration tests (single module through circular
deps, hover, error diagnostics)
- [x] 371 unit tests + 54 integration tests pass

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 20:05:58 +08:00
ykiko
7ed558c1e7 feat: add CompileGraph for pull-based module dependency compilation (#375)
## Summary

Add `CompileGraph`, a pull-based async scheduler for C++20 module
compilation. When a file is compiled that imports modules, the graph
automatically resolves, builds, and caches PCM dependencies in the
correct order before the main compile proceeds.

## Design

### Data model

Each compilation unit (`CompileUnit`) tracks:
- `dependencies` / `dependents` — forward and reverse dependency edges
- `dirty` / `compiling` — current state flags
- `generation` — monotonic counter incremented by `update()`, used for
ABA-safe stale detection
- `source` + `completion` — cancellation token source and completion
event for cooperative async

### `compile(path_id)` — pull-based compilation

Lazily resolves dependencies (via `resolve_fn`) on first access, then
recursively compiles all transitive deps before dispatching the unit
itself:

- **Concurrent**: sibling deps compiled in parallel via `when_all`
- **Dedup**: diamond dependencies (A->B->D, A->C->D) — the second branch
waits on the first via `completion.wait()` instead of re-compiling
- **Cycle detection**: per-branch `ancestors` set (passed by value)
catches direct cycles; `has_wait_cycle()` BFS catches cross-branch
cycles (e.g. `1->{2,3}, 2->3, 3->2`) that would deadlock at
`completion.wait()`
- **Cancellation**: all `co_await` wrapped with `with_token()`, so
`update()` can cancel in-flight compilations immediately
- **Generation check**: captures generation counter before `co_await`;
if `update()` bumped it during dispatch, the result is discarded (unit
stays dirty)

### `update(path_id)` — cascade invalidation

BFS along `dependents` edges to mark the entire reverse-transitive
closure as dirty. For the source node, clears `resolved` and dependency
edges so they are re-scanned on next compile. Cancels any in-flight
compilations via `source->cancel()`.

## Test plan

22 unit tests covering:
- [x] No deps, single dep, chain, diamond (compile ordering + dedup)
- [x] Update invalidation, cascade through chains and diamonds
- [x] Re-resolution after update (deps can change)
- [x] Stale back-edge cleanup
- [x] Direct cycle detection (A->B->A)
- [x] Cross-branch cycle detection (when_all deadlock case)
- [x] Self-loop
- [x] Dispatch failure propagation
- [x] cancel_all + recompile
- [x] Update during in-flight compile (cancellation + generation check)
- [x] CI green on Linux, macOS, Windows

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 14:38:15 +08:00
ykiko
a536865fca feat: add scan_module_decl() fallback for conditional module declarations (#373)
## Summary

- Add `scan_module_decl()` — a lightweight preprocessor-based fallback
that resolves module declarations inside `#if`/`#ifdef` conditionals.
When `scan()` detects `need_preprocess=true`, this function runs clang's
preprocessor to evaluate conditions and extract the actual module name.
It stops lexing as soon as the module declaration is found, making it
much cheaper than `scan_precise()`.
- Integrate the fallback into `scan_dependency_graph()` for wave 0
source files, so conditional module declarations (e.g. `#ifdef
USE_MODULES / export module M; / #endif`) are correctly registered in
the dependency graph.
- Add comprehensive test cases covering all C++20 module declaration
forms from cppreference, including `scan_module_decl()` tests for
conditional resolution and `scan_precise()` tests for module import
semantics.

## Test plan

- [x] All 310 unit tests pass (0 failures, 9 skipped)
- [x] `scan()` tests cover: primary interface, implementation, dotted
names, partitions, GMF, conditional module declarations, private module
fragment
- [x] `scan_module_decl()` tests cover: basic, conditional with `-D`,
conditional with `#if` expression, GMF with conditional, implementation
unit, dotted name, partition, no-module file
- [x] `scan_precise()` tests cover: named import, multiple imports,
dotted import, partition import, export-import, export-import partition,
implementation import, GMF with import, mixed includes/imports,
no-module file

🤖 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**
* Improved detection of module declarations hidden by conditional
compilation via a lightweight fallback scan. Resolved module vs.
interface classification is cached to avoid repeated work and is used
consistently in dependency mapping.
* Better handling and classification of module imports, partitions, and
global-fragment includes when building module relationships.

* **Tests**
* Added comprehensive unit tests covering module declaration extraction,
fallback resolution under preprocessor guards, imports, partitions,
includes, and macro-driven cases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 22:27:49 +08:00
ykiko
f8a39147a7 feat: add include resolver, dependency graph, BFS scanner (#368)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 17:40:29 +08:00
ykiko
46ba1e4db6 refactor: simplify CompilationDatabase, extract ArgumentParser, remove pimpl (#371)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 13:15:49 +08:00
ykiko
498c975042 feat: add SearchConfig, ToolchainProvider, PathPool and related tests (#370)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 21:27:18 +08:00
ykiko
848065265c refactor: move resource_dir to CompilationDatabase, rename test dirs (#369)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 10:40:20 +08:00
ykiko
f7a8d104ce refactor: move command files to src/command/, remove scan_fuzzy (#366)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 23:54:32 +08:00
ykiko
020c2cb3cc feat: implement multi-process LSP server architecture (#364)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 23:37:08 +08:00
ykiko
73afcfbb58 refactor: introduce syntax/scan module with DependencyDirectivesGetter (#357)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 21:45:31 +08:00
ykiko
ce2f355988 refactor: introduce eventide and rewrite server architecture (#355) 2026-03-01 18:17:02 +08:00
ykiko
5b016b1317 ci: add doc publishment workflow (#353)
Co-authored-by: yihtseu <i@yizhou.ac.cn>
2026-02-01 23:01:06 +08:00
ykiko
c0ffd2369b refactor: unify the CompilationUnitRef usage (#346) 2026-01-12 00:21:35 +08:00
Myriad-Dreamin
d6733dd43d feat: replace compile_commands_dirs with compile_commands_paths (#343) 2026-01-10 23:23:32 +08:00
Myriad-Dreamin
53689f2256 fix: prevent bad argument pop back when querying toolchain (#342) 2026-01-10 22:43:47 +08:00
Myriad-Dreamin
f30f68f573 fix: compiling C source file without -std=c++20 (#334) 2026-01-07 23:13:21 +08:00
ykiko
dd8f0dd90d refactor: diagnostic handling (#337) 2026-01-03 16:23:06 +08:00
ykiko
dee5e136b7 fix: docs spelling and workflow check (#336) 2026-01-01 00:48:14 +08:00
ykiko
4d16cf7b0a docs: update build (#335) 2026-01-01 00:36:07 +08:00
Myriad-Dreamin
c6d87cccf3 fix: stuck caused by Network:on_read (#333) 2025-12-29 22:58:47 +08:00
ykiko
aa3e5111de fix: only publish when a tag is created (#332) 2025-12-29 11:55:18 +08:00
ykiko
7a29560065 build: optimize the workflow (#331) 2025-12-29 09:19:45 +08:00
ykiko
7105e36803 chore: use pixi to manage the format tools and format the world (#330) 2025-12-28 19:36:06 +08:00
star9029
bc523b0681 build: use pixi in package ci (#326) 2025-12-21 16:47:42 +08:00
star9029
b8da7e79db build: use pixi in ci (#325)
Co-authored-by: ykiko <ykikoykikoykiko@gmail.com>
2025-12-17 13:04:02 +08:00
ykiko
1da34574c9 build: use pixi for managing build toolchains (#322) 2025-12-16 01:53:25 +08:00
ykiko
cec13ec29b refactor: tests and format the world (#314) 2025-11-30 15:21:27 +08:00
ykiko
2214d53ea5 chore: simplify config file (#319) 2025-11-28 02:07:06 +08:00
ykiko
8f74adf2b9 extension: migrate zed to editors/ (#318) 2025-11-28 01:46:51 +08:00
ykiko
2c11be9365 extension: migrate nvim to editors/ (#317) 2025-11-28 01:46:37 +08:00
ykiko
caf9a172d6 extension: migrate vscode to editors/ (#316) 2025-11-28 00:37:22 +08:00
ykiko
8aff090a08 refactor: incremental update for compilation database and introduce query toolchain (#311) 2025-11-23 18:43:36 +08:00
579 changed files with 52075 additions and 27921 deletions

View File

@@ -1,5 +1,4 @@
# clang-format configuration
# compatible with clang-format 18
UseTab: Never
ColumnLimit: 100
@@ -73,7 +72,7 @@ SpaceBeforeParensOptions:
AfterFunctionDeclarationName: false
AfterFunctionDefinitionName: false
AfterIfMacros: false
AfterOverloadedOperator: true
AfterOverloadedOperator: false
AfterRequiresInClause: true
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
@@ -93,11 +92,62 @@ SpacesInParensOptions:
SpacesInSquareBrackets: false
WrapNamespaceBodyWithEmptyLines: Always
# Order
QualifierAlignment: Custom
QualifierOrder: ["constexpr", "const", "inline", "static", "type"]
SortIncludes: Never
SortIncludes: true
SortUsingDeclarations: Never
IncludeBlocks: Merge
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^["<](spdlog|toml\+\+|coraing|cpptrace|flatbuffers)/'
Priority: 30
SortPriority: 31
- Regex: '^["<](llvm)/'
Priority: 30
SortPriority: 32
- Regex: '^["<](clang)/'
Priority: 30
SortPriority: 33
- Regex: '^["<](clang-tidy)/'
Priority: 30
SortPriority: 34
- Regex: '^["<](Test)/'
Priority: 20
SortPriority: 22
- Regex: "^<.*"
Priority: 10
SortPriority: 10
- Regex: '^".*/.*"'
Priority: 20
SortPriority: 23
- Regex: ".*"
Priority: 20
SortPriority: 21
ForEachMacros: ["REFLECTABLE_RECORD"]
NamespaceMacros: [TEST_SUITE]
KeepEmptyLines:
AtEndOfFile: false
AtStartOfBlock: false
AtStartOfFile: false
StatementMacros:
- DECO_CFG_START
- DECO_CFG
- DECO_CFG_END
- DecoKV
- DecoFlag
- DecoComma
- DecoInput
- DecoPack
- DecoKVStyled
- DecoMulti

50
.clang-tidy Normal file
View File

@@ -0,0 +1,50 @@
---
Checks: >
-*,
bugprone-*,
modernize-*,
performance-*,
readability-*,
-modernize-use-trailing-return-type,
-readability-magic-numbers,
-readability-else-after-return,
-readability-braces-around-statements,
-readability-avoid-const-params-in-decls,
-readability-named-parameter,
-readability-implicit-bool-conversion,
-readability-use-anyofallof,
-bugprone-easily-swappable-parameters,
-bugprone-exception-escape,
-bugprone-narrowing-conversions,
-modernize-use-nodiscard,
WarningsAsErrors: ""
HeaderFilterRegex: "(src|tests)/.*"
CheckOptions:
# Naming conventions matching project style
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.EnumCase
value: CamelCase
- key: readability-identifier-naming.EnumConstantCase
value: CamelCase
- key: readability-identifier-naming.TemplateParameterCase
value: CamelCase
- key: readability-identifier-naming.TypeAliasCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.MethodCase
value: lower_case
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.ParameterCase
value: lower_case
- key: readability-identifier-naming.MemberCase
value: lower_case
- key: readability-identifier-naming.NamespaceCase
value: lower_case

View File

@@ -1,2 +0,0 @@
Diagnostics:
UnusedIncludes: None

268
.claude/CLAUDE.md Normal file
View File

@@ -0,0 +1,268 @@
# clice — Project Guide
## Project Overview
clice is a next-generation C++ language server (LSP) built on LLVM/Clang, targeting modern C++ (C++20/23). It uses a multi-process architecture with a master server coordinating stateless and stateful workers.
## Core Correction Patterns — Lessons from Past Interactions
The following patterns were extracted from extensive real-world collaboration. These are recurring mistakes that MUST be avoided. Read them carefully — they represent hard-won lessons, not hypothetical concerns.
### Pattern 1: Misjudging Real-World Priorities
AI tends to optimize whatever metric looks most impressive, rather than what actually matters in the user's real scenario.
**Example**: During performance optimization, the AI proudly reported "hot cache is 4-7x faster!" — but the function in question runs at LSP server startup, which is ALWAYS a cold start. Optimizing hot cache was completely meaningless.
**Rule**: Before optimizing or analyzing anything, first understand the REAL usage scenario. Ask yourself: "When does this code actually run? What does the user actually experience?" Do not chase metrics that look good on paper but are irrelevant in practice.
### Pattern 2: Pushing Without Local Verification
The most common and most damaging pattern. AI proposes a fix, pushes it immediately, CI fails, then another fix, push, fail again — wasting CI cycles and the user's time.
**Rule**: NEVER push code that you haven't verified locally. Before every push:
- Build locally with the same configuration CI uses.
- Run the relevant tests locally and confirm they pass.
- If you cannot reproduce the CI environment locally, say so — do not just "try and see."
- "It compiles" is NOT sufficient. Tests must pass.
### Pattern 3: Superficial Refactoring
When asked to refactor, AI tends to do mechanical code movement (copy functions from A to B) without understanding the deeper design intent (ownership, responsibility boundaries, API cleanliness).
**Example**: When splitting `MasterServer` into `Workspace` and `Session`, the AI moved functions but kept ugly APIs like `f(path_id, sessions_map)` instead of the clean `f(Session&)` that the refactoring was meant to achieve.
**Rule**: When refactoring, understand the WHY. Ask: "What design problem is this refactoring solving?" If you're just moving code around without improving the abstractions, you're not refactoring — you're rearranging deck chairs.
### Pattern 4: Fixing Only the Immediate Instance, Not the Pattern
When given a cleanup instruction, AI applies it to the single file or function currently being discussed, ignoring all other occurrences in the project.
**Example**: User says "remove decorative `===` comment separators." AI removes them from `workspace.cpp` only. User has to say: "The other files too!"
**Rule**: When given a cleanup or style instruction, apply it project-wide. Use `Grep` to find ALL occurrences and fix them all in one pass. Think: "Where else does this pattern appear?"
### Pattern 5: Never Skip, Disable, or Work Around Failing Tests
When stuck on a difficult bug (especially flaky CI, race conditions, platform-specific issues), AI may propose marking tests as `continue-on-error`, skipping them, or adding `expected-failure` annotations to make CI green.
**Rule**: This is ABSOLUTELY FORBIDDEN. If a test fails, fix the root cause. There are ZERO exceptions. Skipping a test to make CI green is not "fixing" — it is hiding a bug. If you ever find yourself thinking "maybe we should just skip this test," stop and reconsider your approach entirely.
### Pattern 6: Excessive Confirmation Seeking vs. Premature Execution
AI oscillates between two extremes: asking "should I do X?" for every trivial decision, or silently executing major changes without confirmation.
**Rule**: Calibrate based on reversibility and impact:
- **Small, reversible changes** (formatting, renaming a local variable, adding a test): just do it.
- **Architecture decisions, API changes, large refactors**: propose the plan first, wait for confirmation.
- **Pushing to remote, creating PRs, modifying CI**: always confirm.
- When the user says "go ahead" or "do it," execute fully without asking again mid-way.
## Code Reuse & Understanding Before Implementation
**This is the single most important rule in this project.** Before writing ANY new code, you MUST thoroughly read and understand the existing codebase first. This project has a rich set of utilities, abstractions, and patterns already in place — duplicating them wastes effort and creates maintenance burden.
Concrete requirements:
1. **Read before you write.** Before implementing a feature or fix, explore the relevant modules in `src/`. Search for existing helpers, utilities, and patterns that solve the same or similar problems. Use `Grep`, `Glob`, and `Agent` tools to investigate thoroughly — do not assume something doesn't exist just because you haven't seen it yet.
2. **Reuse existing infrastructure.** This project already has:
- A `Lexer` class (`src/syntax/lexer.h`) — do not hand-write token scanning logic.
- A `PositionMapper` for source location conversion — do not reimplement offset-to-line/column math.
- `CompilationUnitRef` methods (`decompose_location`, `decompose_range`, `file_path`, `directives`, etc.) — use them instead of raw Clang APIs.
- `SemanticVisitor` for AST traversal — extend it, do not write custom recursive AST walkers.
- `Tester` framework for unit tests with VFS, annotation support, and multi-phase compilation — use it, do not create ad-hoc test setups.
- Utility functions in `src/support/` — check there before writing new helpers.
3. **Follow established patterns.** When adding a new feature (e.g., a new LSP request handler), look at how 2-3 existing features of the same kind are implemented. Match their structure: same file organization, same function signatures, same error handling patterns. If every other feature in `src/feature/` follows a certain pattern, yours should too.
4. **Do not reinvent what the project already has.** If you find yourself writing a helper function that feels generic (string manipulation, path handling, JSON serialization, source range conversion), STOP and search the codebase first. There is a high probability it already exists. Creating duplicates leads to inconsistencies and bugs when one copy gets updated but the other doesn't.
5. **When in doubt, ask.** If you're unsure whether an existing utility covers your use case or whether to extend an existing abstraction vs. create a new one, ask the user rather than guessing.
## Source Layout
- `src/server/` — LSP server core: master server, compiler, indexer, stateful/stateless workers
- `src/feature/` — LSP feature implementations: hover, completion, document links, semantic tokens, etc.
- `src/compile/` — Compilation orchestration: compilation unit, directives, diagnostics
- `src/index/` — Symbol indexing: TUIndex, ProjectIndex, MergedIndex, include graph
- `src/semantic/` — Semantic analysis: symbol kinds, relations, AST visitor, template resolver
- `src/syntax/` — Lexer, scanner, token types, dependency graph
- `src/command/` — CLI parsing, compilation database, toolchain detection
- `src/support/` — Utilities: logging, filesystem, JSON, string helpers
## Build System
- Uses **pixi** for environment management and **CMake + Ninja** for building.
- Two build types: `Debug` and `RelWithDebInfo` (default).
- Build output goes to `build/[type]/`.
- See `/build`, `/test`, `/format` commands for common operations.
## Commit Message Format
Use **conventional commits** — enforced by CI:
```
<type>(<scope>): <short description>
```
- **Types**: `feat`, `fix`, `refactor`, `chore`, `docs`, `ci`, `test`
- **Scopes**: match `src/` subdirectories or feature names, e.g. `completion`, `server`, `index`, `tests`, `document links`
- Keep the subject line under 70 characters.
## Tests
Three types of tests, all must pass before committing:
- **Unit tests** (`tests/unit/`): C++ tests using the project's own test framework. Test names should be at most 4 words.
- **Integration tests** (`tests/integration/`): Python pytest tests that start a real clice server and communicate via LSP.
- **Smoke tests** (`tests/smoke/`): Replay recorded LSP sessions via `tests/replay.py`.
### Integration Test Style
- Keep tests concise. Do NOT write large comment blocks explaining the test layout or expected behavior.
- Use descriptive test function names and short inline comments only where logic is non-obvious.
## Pre-PR Review
Before opening a PR, launch **3 parallel subagents** to review the diff independently:
1. **Correctness reviewer**: Check for logic errors, edge cases, undefined behavior, and off-by-one mistakes.
2. **Style reviewer**: Verify the code follows this project's naming conventions, coding style, and CLAUDE.md rules.
3. **Test reviewer**: Confirm test coverage is adequate — new functionality has tests, edge cases are covered, and no existing tests were broken or weakened.
Each agent should read the full diff (`git diff main...HEAD`) and report issues. Fix all reported issues before opening the PR.
## Pre-commit Checklist
Before committing code, you MUST:
1. **Run `pixi run format`** to format all source files.
2. **Pass all three types of tests:**
- Unit tests: `pixi run unit-test [type]`
- Integration tests: `pixi run integration-test [type]`
- Smoke tests: `pixi run smoke-test [type]`
3. **All test failures must be fixed before committing.** This is a HARD REQUIREMENT with NO exceptions:
- If a test fails, it MUST be fixed before you commit. Do NOT commit with known failures.
- Do NOT skip, disable, or mark tests as expected-failure to work around breakage.
- Do NOT argue "this test was already broken before my changes" — if it fails on your branch, it is YOUR responsibility to fix it before committing. The main branch CI is green; any failure on your branch is caused by your changes, period.
- Do NOT defer fixing to a follow-up PR. Fix it NOW, in this branch, before committing.
---
## C++ Coding Style
### Template & Type Traits
- Do NOT blindly add `std::remove_cvref_t` on every template parameter. Understand C++ template argument deduction rules:
- `template<typename T> void f(T x)``T` is always deduced as a non-reference, non-cv-qualified type. No need for `remove_cvref_t`.
- `template<typename T> void f(T& x)``T` is deduced as the referred-to type (possibly cv-qualified, but never a reference). No need for `remove_cvref_t` to strip references.
- `template<typename T> void f(const T& x)``T` is deduced as a non-const, non-reference type. No need for `remove_cvref_t`.
- `template<typename T> void f(T&& x)`**forwarding reference**: `T` CAN be deduced as an lvalue reference (e.g., `int&`). This is the ONLY case where `std::remove_cvref_t<T>` is needed to get the bare type.
- Class template parameters and return types are also never deduced as references; don't add `remove_cvref_t` on them either.
### Type Traits & Concepts (C++20/23)
- This project targets C++20/23. Use variable templates directly for type traits — do NOT use the old pattern of wrapping a class template static member in a variable template. Prefer:
```cpp
// Good: directly specialize a variable template
template<typename T>
inline constexpr bool is_my_type_v = false;
template<>
inline constexpr bool is_my_type_v<MyType> = true;
```
```cpp
// Bad: unnecessary class template wrapper
template<typename T>
struct is_my_type : std::false_type {};
template<>
struct is_my_type<MyType> : std::true_type {};
template<typename T>
inline constexpr bool is_my_type_v = is_my_type<T>::value;
```
- When defining a concept that checks a type trait, do NOT add `std::remove_cvref_t` unless you specifically intend the concept to see through references/cv-qualifiers. If the concept is meant for a bare type, just use `T` directly — the caller is responsible for passing the right type.
```cpp
// Good
template<typename T>
concept MyTrait = is_my_type_v<T>;
// Bad: unnecessary remove_cvref_t
template<typename T>
concept MyTrait = is_my_type_v<std::remove_cvref_t<T>>;
```
### Naming Conventions
- **Variables, member fields, function names**: `snake_case`. Class member fields do NOT use any special suffix/prefix (no trailing `_`, no `m_` prefix).
- **Class names, template parameter names, enum names**: `PascalCase`. Exception: some class names also use `snake_case` — follow the existing style in the project.
- **Enum values**: `PascalCase`.
### String Literals
- Prefer C++11 raw string literals `R"(...)"` over escaped strings. Avoid `\"`, `\\`, `\n` in string literals when a raw literal is cleaner.
### Error Handling
- **Prefer `if` with init-statements to tightly scope error variables**, but avoid them when they compromise code readability or flatten control flow.
- **Omit redundant conditions:** If the error type provides an `operator bool` or evaluates implicitly (e.g., standard error codes, custom error wrappers), omit the redundant condition check.
- **Avoid forced `else` branches:** If scoping the variable inside the `if` requires you to introduce an `else` block for the success path (especially when returning early on error), declare the variable in the local scope instead to keep the control flow flat.
```cpp
// Good: Omit redundant condition when the type has operator bool
if (auto err = foo()) {
/* handle error */
}
// Bad: Redundant condition check
if (auto err = foo(); err) {
/* handle error */
}
// Good: Use init-statement when a custom condition is required,
// AND the variable isn't needed outside the if-statement
if (auto result = foo(); !result.has_value()) {
/* handle error */
}
// --- Scope and Control Flow Considerations ---
// Bad: Using init-statement forces an 'else' block because 'result'
// goes out of scope, leading to nested/redundant code.
if (auto result = get_data(); !result.has_value()) {
return result.error();
} else {
process(result.value()); // Success path is forced into a nested block
}
// Good: Declare as a regular local variable to allow early exit
// and keep the success path un-nested (flat control flow).
auto result = get_data();
if (!result.has_value()) {
return result.error();
}
process(result.value());
```
### Style
- Prefer `[[maybe_unused]]` over `(void)` for intentionally unused variables or parameters.
### Modern C++ Usage
- Use C++20/23 APIs whenever possible. Do NOT use `<iostream>` facilities (`std::cout`, `std::cin`, `std::cerr`, etc.). Also do NOT use C-style I/O (`printf`, `fprintf`, etc.).
- Prefer `std::ranges` / `std::views` APIs over raw loops and traditional `<algorithm>` calls.
- If the project depends on LLVM, prefer LLVM's efficient data structures (e.g., `llvm::SmallVector`, `llvm::DenseMap`, `llvm::StringMap`, `llvm::StringRef`) over their `std` counterparts when appropriate.
### Parameter Passing Preferences
- For string parameters, prefer `llvm::StringRef` > `std::string_view` > `const std::string&`.
- For array/span parameters, prefer `llvm::ArrayRef` > `std::span` > `const std::vector&`.

15
.claude/commands/build.md Normal file
View File

@@ -0,0 +1,15 @@
Build the project. Accepts an optional argument for build type: `Debug` or `RelWithDebInfo` (default).
Available build commands:
- CMake configure only: `pixi run cmake-config [type]`
- CMake build only (skip configure): `pixi run cmake-build [type]`
- Full build (configure + build): `pixi run build [type]`
- Build a specific target: `pixi run cmake-build [type]` then `cmake --build build/[type] --target [target]`
Common targets: `clice`, `unit_tests`
Example usage:
- `/build` — full build RelWithDebInfo
- `/build Debug` — full build Debug

View File

@@ -0,0 +1,5 @@
Format all project source files.
Run: `pixi run format`
Formats C++, Python, Lua, JS/TS, Markdown, JSON, TOML, and YAML files.

19
.claude/commands/test.md Normal file
View File

@@ -0,0 +1,19 @@
Run tests. Accepts an optional argument for build type: `Debug` or `RelWithDebInfo` (default).
Available test commands:
- Unit tests: `pixi run unit-test [type]`
- Integration tests: `pixi run integration-test [type]`
- Smoke tests: `pixi run smoke-test [type]`
- All tests (unit + integration): `pixi run test [type]`
Filtering specific tests:
- Unit tests: `pixi run unit-test [type] --test-filter=SuiteName.CaseName`
- Integration tests: `pixi run pytest tests/integration -k "test_name" --executable=./build/[type]/bin/clice`
- Smoke tests: `pixi run python tests/replay.py tests/smoke/specific.jsonl --clice=./build/[type]/bin/clice`
Example usage:
- `/test` — run all tests (RelWithDebInfo)
- `/test Debug` — run all tests (Debug)

7
.coderabbit.yaml Normal file
View File

@@ -0,0 +1,7 @@
chat:
auto_reply: false
reviews:
auto_review:
enabled: true
summary:
enabled: false

View File

@@ -1,9 +0,0 @@
build/
out/
.cache
.clice/
.llvm*/
.xmake/
.vscode/
.vs/

9
.gitattributes vendored Normal file
View File

@@ -0,0 +1,9 @@
# SCM syntax highlighting & preventing 3-way merges
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
# Force LF line endings for test data so that byte offsets from clang
# (which reads from disk) match the content sent by didOpen in tests.
tests/data/** text eol=lf
# Treat trace files as binary to suppress text diffs
tests/smoke/*.jsonl linguist-generated=true binary

View File

@@ -1,10 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
title: ""
labels: ""
assignees: ""
---
**Describe the bug**
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
@@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,10 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
title: ""
labels: ""
assignees: ""
---
**Is your feature request related to a problem? Please describe.**

20
.github/actions/setup-pixi/action.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: "Setup Pixi"
description: "setup pixi"
inputs:
environments:
description: "The pixi environments to install (e.g. default, docs, test)"
required: false
default: "default"
runs:
using: "composite"
steps:
- name: Setup Pixi
uses: prefix-dev/setup-pixi@v0.9.3
with:
pixi-version: v0.62.0
environments: ${{ inputs.environments }}
activate-environment: true
cache: true
locked: true

44
.github/workflows/benchmark.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: benchmark
on:
workflow_dispatch:
jobs:
benchmark:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15, windows-2025]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: ./.github/actions/setup-pixi
- name: Build scan_benchmark
run: |
pixi run cmake-config RelWithDebInfo ON "-DCLICE_ENABLE_BENCHMARK=ON"
cmake --build build/RelWithDebInfo --target scan_benchmark
- name: Clone LLVM
run: git clone --depth 1 https://github.com/llvm/llvm-project.git
- name: Generate CDB
run: |
cmake -B llvm-build -G Ninja \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/cmake/toolchain.cmake" \
-DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld;lldb;mlir;polly;flang;bolt" \
-DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \
llvm-project/llvm
- name: Run benchmark
run: ./build/RelWithDebInfo/bin/scan_benchmark --runs 20 llvm-build/compile_commands.json
- name: Stop sccache server
if: runner.os == 'Windows'
run: pixi run -- sccache --stop-server || true

191
.github/workflows/build-llvm.yml vendored Normal file
View File

@@ -0,0 +1,191 @@
name: build llvm
on:
pull_request:
# if you want to run this workflow, change the branch name to main,
# if you want to turn off it, change it to non existent branch.
branches: [main-turn-off]
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: windows-2025
llvm_mode: Debug
lto: OFF
- os: windows-2025
llvm_mode: RelWithDebInfo
lto: OFF
- os: windows-2025
llvm_mode: RelWithDebInfo
lto: ON
- os: ubuntu-24.04
llvm_mode: Debug
lto: OFF
- os: ubuntu-24.04
llvm_mode: RelWithDebInfo
lto: OFF
- os: ubuntu-24.04
llvm_mode: RelWithDebInfo
lto: ON
- os: macos-15
llvm_mode: Debug
lto: OFF
- os: macos-15
llvm_mode: RelWithDebInfo
lto: OFF
- os: macos-15
llvm_mode: RelWithDebInfo
lto: ON
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Free Disk Space
if: runner.os == 'Linux'
uses: jlumbroso/free-disk-space@main
- name: Increase Swap Space
if: runner.os == 'Linux'
run: |
echo "===== Initial Status ====="
sudo swapon --show
free -h
echo "===== Creating Swap File ====="
sudo swapoff -a
sudo fallocate -l 16G /mnt/swapfile
sudo chmod 600 /mnt/swapfile
sudo mkswap /mnt/swapfile
sudo swapon /mnt/swapfile
echo "===== Final Status ====="
sudo swapon --show
free -h
df -h
- name: Setup Pixi
uses: prefix-dev/setup-pixi@v0.9.3
with:
pixi-version: v0.59.0
environments: package
activate-environment: true
cache: true
locked: true
- name: Clone llvm-project (21.1.4)
shell: bash
run: |
git clone --branch llvmorg-21.1.4 --depth 1 https://github.com/llvm/llvm-project.git .llvm
- name: Build LLVM (install-distribution)
shell: bash
run: |
pixi run build-llvm --llvm-src=.llvm --mode="${{ matrix.llvm_mode }}" --lto="${{ matrix.lto }}" --build-dir=build
- name: Build clice using installed LLVM
shell: bash
run: |
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=${{ matrix.llvm_mode }} \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \
-DCLICE_ENABLE_TEST=ON \
-DCLICE_CI_ENVIRONMENT=ON \
-DCLICE_ENABLE_LTO=${{ matrix.lto }} \
-DLLVM_INSTALL_PATH=".llvm/build-install"
cmake --build build
- name: Run tests
shell: bash
run: |
EXE_EXT=""
if [[ "${{ runner.os }}" == "Windows" ]]; then
EXE_EXT=".exe"
fi
./build/bin/unit_tests${EXE_EXT} --test-dir="./tests/data"
uv run --project tests pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice${EXE_EXT}
- name: Prune LLVM static libraries (Debug/RelWithDebInfo no LTO)
if: matrix.llvm_mode == 'Debug' || (matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF')
shell: bash
run: |
MANIFEST="pruned-libs-${{ matrix.os }}.json"
echo "LLVM_PRUNED_MANIFEST=${MANIFEST}" >> "${GITHUB_ENV}"
python3 scripts/prune-llvm-bin.py \
--action discover \
--install-dir ".llvm/build-install/lib" \
--build-dir "build" \
--max-attempts 60 \
--sleep-seconds 60 \
--manifest "${MANIFEST}"
- name: Upload pruned-libs manifest
if: matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'
uses: actions/upload-artifact@v4
with:
name: llvm-pruned-libs-${{ matrix.os }}
path: ${{ env.LLVM_PRUNED_MANIFEST }}
if-no-files-found: error
compression-level: 0
- name: Apply pruned-libs manifest (RelWithDebInfo + LTO)
if: matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'ON'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
MANIFEST="pruned-libs-${{ matrix.os }}.json"
python3 scripts/prune-llvm-bin.py \
--action apply \
--manifest "${MANIFEST}" \
--install-dir ".llvm/build-install/lib" \
--build-dir "build" \
--gh-run-id "${{ github.run_id }}" \
--gh-artifact "llvm-pruned-libs-${{ matrix.os }}" \
--gh-download-dir "artifacts" \
--max-attempts 60 \
--sleep-seconds 60
- name: Package LLVM install directory
shell: bash
run: |
MODE_TAG="releasedbg"
if [[ "${{ matrix.llvm_mode }}" == "Debug" ]]; then
MODE_TAG="debug"
fi
ARCH="x64"
PLATFORM="linux"
TOOLCHAIN="gnu"
if [[ "${{ matrix.os }}" == windows-* ]]; then
PLATFORM="windows"
TOOLCHAIN="msvc"
elif [[ "${{ matrix.os }}" == macos-* ]]; then
ARCH="arm64"
PLATFORM="macos"
TOOLCHAIN="clang"
fi
SUFFIX=""
if [[ "${{ matrix.lto }}" == "ON" ]]; then
SUFFIX="-lto"
fi
if [[ "${{ matrix.llvm_mode }}" == "Debug" ]]; then
SUFFIX="${SUFFIX}-asan"
fi
ARCHIVE="${ARCH}-${PLATFORM}-${TOOLCHAIN}-${MODE_TAG}${SUFFIX}.tar.xz"
set -eo pipefail
tar -C .llvm -cf - build-install | xz -T0 -9 -c > "${ARCHIVE}"
echo "LLVM_INSTALL_ARCHIVE=${ARCHIVE}" >> "${GITHUB_ENV}"
- name: Upload LLVM install artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.LLVM_INSTALL_ARCHIVE }}
path: ${{ env.LLVM_INSTALL_ARCHIVE }}
if-no-files-found: error

View File

@@ -1,48 +1,34 @@
name: format
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_call:
jobs:
check:
check-format:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v6
- uses: ./.github/actions/setup-pixi
with:
environments: format
- name: Run check
id: precommit
run: |
uv venv
uv pip install pre-commit
uv run pre-commit run --all-files
- name: Run formatter
run: pixi run format
continue-on-error: true
- name: Show diff on failure
if: steps.precommit.outcome == 'failure'
run: |
echo "❌ pre-commit found issues that it auto-fixed."
echo " Please run 'pre-commit run --all-files' locally,"
echo " commit the changes, and push again."
echo ""
echo "👇 The required changes are shown below:"
git diff
- name: Fail the job if pre-commit failed
if: steps.precommit.outcome == 'failure'
run: |
echo "pre-commit checks failed."
exit 1
- name: Auto correct
uses: huacnlee/autocorrect-action@main
uses: huacnlee/autocorrect-action@v2
with:
args: --lint ./docs
continue-on-error: true
- name: Check diff
run: |
if ! git diff --quiet; then
echo "::error::Formatting changes detected. Please run 'pixi run format' and commit the result."
git --no-pager diff --stat
git --no-pager diff
exit 1
fi

View File

@@ -1,107 +0,0 @@
name: cmake
on:
push:
branches: [main]
paths:
- ".github/workflows/cmake.yml"
- "include/**"
- "src/**"
- "tests/**"
- "CMakeLists.txt"
pull_request:
branches: [main]
paths:
- ".github/workflows/cmake.yml"
- "include/**"
- "src/**"
- "tests/**"
- "config/**"
- "CMakeLists.txt"
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: windows-2025
build_type: RelWithDebInfo
cc: clang
cxx: clang++
- os: ubuntu-24.04
build_type: Debug
cc: clang-20
cxx: clang++-20
- os: macos-15
build_type: Debug
cc: clang
cxx: clang++
runs-on: ${{ matrix.os }}
steps:
- name: Setup dependencies (Windows)
if: runner.os == 'Windows'
uses: MinoruSekine/setup-scoop@v4.0.1
with:
buckets: main
apps: ninja
- name: Setup dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y gcc-14 g++-14 libstdc++-14-dev
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
sudo update-alternatives --set gcc /usr/bin/gcc-14
sudo update-alternatives --set g++ /usr/bin/g++-14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
sudo apt install -y cmake ninja-build
- name: Setup dependencies (MacOS)
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
run: |
brew install llvm@20 lld@20
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup msvc sysroot for cmake
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Build clice
shell: bash
run: |
if [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
fi
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_C_COMPILER=${{ matrix.cc }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
-DCLICE_ENABLE_TEST=ON \
-DCLICE_CI_ENVIRONMENT=ON
cmake --build build
- name: Install uv for integration tests
uses: astral-sh/setup-uv@v6
- name: Run tests
shell: bash
run: |
EXE_EXT=""
if [[ "${{ runner.os }}" == "Windows" ]]; then
EXE_EXT=".exe"
fi
./build/bin/unit_tests${EXE_EXT} --test-dir="./tests/data"
uv run pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice${EXE_EXT}

30
.github/workflows/deploy-docs.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: deploy
on:
workflow_call:
jobs:
deploy-docs:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: ./.github/actions/setup-pixi
with:
environments: node
- name: Build docs
run: pixi run build-docs
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
if: github.ref == 'refs/heads/main'
with:
personal_token: ${{ secrets.PUBLISH_DOCS }}
external_repository: clice-io/docs
publish_dir: ./docs/.vitepress/dist
destination_dir: clice
keep_files: true

View File

@@ -1,43 +0,0 @@
name: deploy
on:
push:
branches: [main]
paths:
- "docs/**"
- ".github/workflows/deploy.yml"
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
cache-dependency-path: docs/package-lock.json
- name: Install dependencies in docs
run: |
cd docs
npm install
- name: Build docs
run: |
cd docs
npm run docs:build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/.vitepress/dist
cname: clice.io

View File

@@ -1,54 +0,0 @@
name: docker
on:
release:
types: [created]
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
PLATFORMS: linux/amd64,darwin/arm64
jobs:
build-and-publish:
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
submodules: recursive
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2.5.0
with:
platforms: ${{ env.PLATFORMS }}
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v2.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4.3.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# https://stackoverflow.com/questions/71157844/how-can-i-copy-git-directory-to-the-container-with-github-actions
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v4.0.0
with:
push: true
context: .
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ env.PLATFORMS }}
cache-from: type=gha
cache-to: type=gha,mode=max

133
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,133 @@
name: main
on:
push:
branches: [main]
tags: ["v*"]
pull_request:
branches: [main]
jobs:
changes:
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
format: ${{ steps.filter.outputs.format }}
docs: ${{ steps.filter.outputs.docs }}
clice: ${{ steps.filter.outputs.clice }}
vscode: ${{ steps.filter.outputs.vscode }}
cmake: ${{ steps.filter.outputs.cmake }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
format:
- '**/*.{h,c,cpp,hpp,ts,js,lua,md,yml,yaml}'
docs:
- 'docs/**'
- '.github/workflows/deploy-docs.yml'
clice:
- 'src/**'
- 'include/**'
- 'CMakeLists.txt'
- '.github/workflows/publish-clice.yml'
vscode:
- 'editors/vscode/**'
- '.github/workflows/publish-vscode.yml'
cmake:
- 'CMakeLists.txt'
- 'src/**'
- 'include/**'
- 'tests/**'
- 'config/**'
- '.github/workflows/test-cmake.yml'
conventional-commit:
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: ubuntu-latest
steps:
- name: Check conventional commit format
env:
IS_PR: ${{ github.event_name == 'pull_request' }}
PR_TITLE: ${{ github.event.pull_request.title }}
COMMIT_MSG: ${{ github.event.head_commit.message }}
run: |
pattern='^(feat|fix|refactor|chore|build|ci|docs|test|perf|style|revert)(\(.+\))?: .+'
if [[ "$IS_PR" == "true" ]]; then
subject="$PR_TITLE"
label="PR title"
else
subject=$(echo "$COMMIT_MSG" | head -n1)
label="Commit message"
fi
if [[ ! "$subject" =~ $pattern ]]; then
echo "::error::$label must follow conventional commit format: type(scope)?: description"
echo " Valid types: feat, fix, refactor, chore, build, ci, docs, test, perf, style, revert"
echo " Got: '$subject'"
exit 1
fi
format:
needs: changes
if: ${{ needs.changes.outputs.format == 'true' }}
uses: ./.github/workflows/check-format.yml
deploy:
needs: changes
if: ${{ needs.changes.outputs.docs == 'true' }}
permissions:
contents: write
uses: ./.github/workflows/deploy-docs.yml
secrets: inherit
# clice:
# needs: changes
# if: ${{ needs.changes.outputs.clice == 'true' }}
# uses: ./.github/workflows/publish-clice.yml
vscode:
needs: changes
if: ${{ needs.changes.outputs.vscode == 'true' }}
uses: ./.github/workflows/publish-vscode.yml
cmake:
needs: changes
if: ${{ needs.changes.outputs.cmake == 'true' }}
uses: ./.github/workflows/test-cmake.yml
release-clice:
permissions:
contents: write
if: startsWith(github.ref, 'refs/tags/v')
uses: ./.github/workflows/publish-clice.yml
secrets: inherit
release-vscode:
permissions:
contents: write
if: startsWith(github.ref, 'refs/tags/v')
uses: ./.github/workflows/publish-vscode.yml
secrets: inherit
checks-passed:
if: ${{ always() && !startsWith(github.ref, 'refs/tags/') }}
needs:
- conventional-commit
- format
- deploy
# - clice
- vscode
- cmake
runs-on: ubuntu-latest
steps:
- name: Check results
uses: re-actors/alls-green@release/v1
with:
allowed-skips: conventional-commit,format,deploy,clice,vscode,cmake
jobs: ${{ toJSON(needs) }}

View File

@@ -1,137 +0,0 @@
name: package
permissions:
contents: write
on:
push:
tags:
- "v*"
pull_request:
branches: [main]
paths:
- ".github/workflows/package.yml"
jobs:
package:
strategy:
fail-fast: false
matrix:
include:
- os: windows-2025
artifact_name: clice.zip
asset_name: clice-x64-windows-msvc.zip
symbol_artifact_name: clice-symbol.zip
symbol_asset_name: clice-x64-windows-msvc-symbol.zip
toolchain: clang-cl
- os: ubuntu-24.04
artifact_name: clice.tar.gz
asset_name: clice-x86_64-linux-gnu.tar.gz
symbol_artifact_name: clice-symbol.tar.gz
symbol_asset_name: clice-x86_64-linux-gnu-symbol.tar.gz
toolchain: clang-20
- os: macos-15
artifact_name: clice.tar.gz
asset_name: clice-arm64-macos-darwin.tar.gz
symbol_artifact_name: clice-symbol.tar.gz
symbol_asset_name: clice-arm64-macos-darwin-symbol.tar.gz
toolchain: clang
runs-on: ${{ matrix.os }}
steps:
- name: Free disk space (Linux)
if: runner.os == 'Linux'
uses: jlumbroso/free-disk-space@main
- name: Increase swap file size (Linux)
if: runner.os == 'Linux'
run: |
echo "===== Initial Status ====="
sudo swapon --show
free -h
echo "===== Creating Swap File ====="
sudo swapoff -a
sudo fallocate -l 16G /mnt/swapfile
sudo chmod 600 /mnt/swapfile
sudo mkswap /mnt/swapfile
sudo swapon /mnt/swapfile
echo "===== Final Status ====="
sudo swapon --show
free -h
df -h
- name: Setup dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y gcc-14 g++-14 libstdc++-14-dev cmake ninja-build
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
sudo update-alternatives --set gcc /usr/bin/gcc-14
sudo update-alternatives --set g++ /usr/bin/g++-14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
- name: Setup dependencies (MacOS)
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
run: |
brew install llvm@20 lld@20
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: 3.0.4
actions-cache-folder: ".xmake-cache"
actions-cache-key: ${{ matrix.os }}
package-cache: true
package-cache-key: ${{ matrix.os }}-pkg-release-v1
build-cache: true
build-cache-key: ${{ matrix.os }}-build-release-v1
- name: Configure and Package
shell: bash
run: |
if [[ "${{ runner.os }}" == "Windows" ]]; then
xmake config --yes --enable_test=n --dev=n --release=y --mode=releasedbg --toolchain=${{ matrix.toolchain }} -p windows
elif [[ "${{ runner.os }}" == "Linux" ]]; then
xmake config --yes --enable_test=n --dev=n --release=y --mode=releasedbg --toolchain=${{ matrix.toolchain }}
elif [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
xmake config --yes --enable_test=n --dev=n --release=y --mode=releasedbg --toolchain=${{ matrix.toolchain }} --sdk=/opt/homebrew/opt/llvm@20
fi
xmake pack -v
- name: Upload Main Package to Release
if: github.event_name == 'push'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/xpack/clice/${{ matrix.artifact_name }}
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
overwrite: true
- name: Upload Symbol Package to Release
if: github.event_name == 'push'
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/xpack/clice/${{ matrix.symbol_artifact_name }}
asset_name: ${{ matrix.symbol_asset_name }}
tag: ${{ github.ref }}
overwrite: true

65
.github/workflows/publish-clice.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: clice
on:
workflow_call:
jobs:
publish-clice:
strategy:
fail-fast: false
matrix:
include:
- os: windows-2025
artifact_name: clice.zip
asset_name: clice-x64-windows-msvc.zip
symbol_artifact_name: clice-symbol.zip
symbol_asset_name: clice-x64-windows-msvc-symbol.zip
- os: ubuntu-24.04
artifact_name: clice.tar.gz
asset_name: clice-x86_64-linux-gnu.tar.gz
symbol_artifact_name: clice-symbol.tar.gz
symbol_asset_name: clice-x86_64-linux-gnu-symbol.tar.gz
- os: macos-15
artifact_name: clice.tar.gz
asset_name: clice-arm64-macos-darwin.tar.gz
symbol_artifact_name: clice-symbol.tar.gz
symbol_asset_name: clice-arm64-macos-darwin-symbol.tar.gz
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: ./.github/actions/setup-pixi
with:
environments: package
- name: Package
run: pixi run package
- name: Upload Main Package to Release
if: startsWith(github.ref, 'refs/tags/v')
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/RelWithDebInfo/${{ matrix.artifact_name }}
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
overwrite: true
- name: Upload Symbol Package to Release
if: startsWith(github.ref, 'refs/tags/v')
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/RelWithDebInfo/${{ matrix.symbol_artifact_name }}
asset_name: ${{ matrix.symbol_asset_name }}
tag: ${{ github.ref }}
overwrite: true

38
.github/workflows/publish-vscode.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: vscode
on:
workflow_call:
jobs:
publish-vscode:
runs-on: ubuntu-latest
defaults:
run:
working-directory: editors/vscode
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: ./.github/actions/setup-pixi
with:
environments: node
- name: Publish and Package to Marketplace
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
run: |
FLAG="${{ contains(github.ref_name, '-') && '--pre-release' || '' }}"
pixi run build-vscode $FLAG
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
pixi run publish-vscode -p "$VSCE_PAT" $FLAG
fi
- name: Upload .vsix to Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/v')
with:
files: "editors/vscode/*.vsix"
tag_name: ${{ github.ref }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

69
.github/workflows/test-cmake.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: cmake
on:
workflow_call:
env:
CCACHE_DIR: ${{ github.workspace }}/.cache/ccache
SCCACHE_DIR: ${{ github.workspace }}/.cache/sccache
CCACHE_BASEDIR: ${{ github.workspace }}
SCCACHE_BASEDIRS: ${{ github.workspace }}
CCACHE_COMPILERCHECK: content
CCACHE_MAXSIZE: 2G
SCCACHE_CACHE_SIZE: 2G
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [windows-2025, ubuntu-24.04, macos-15]
build_type: [Debug, RelWithDebInfo]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: ./.github/actions/setup-pixi
- name: Restore compiler cache
uses: actions/cache@v4
with:
path: ${{ runner.os == 'Windows' && '.cache/sccache' || '.cache/ccache' }}
key: ${{ runner.os }}-${{ matrix.build_type }}-ccache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ matrix.build_type }}-ccache-
- name: Zero cache stats
run: |
if [ "$RUNNER_OS" = "Windows" ]; then
pixi run -- sccache --stop-server || true
pixi run -- sccache --zero-stats || true
else
pixi run -- ccache --zero-stats || true
fi
shell: bash
- name: Build
run: pixi run build ${{ matrix.build_type }} ON
- name: Unit Test
run: pixi run unit-test ${{ matrix.build_type }}
- name: Integration Test
run: pixi run integration-test ${{ matrix.build_type }}
- name: Smoke Test
if: success() || failure()
run: pixi run smoke-test ${{ matrix.build_type }}
- name: Print cache stats and stop server
if: always()
run: |
if [ "$RUNNER_OS" = "Windows" ]; then
pixi run -- sccache --show-stats
pixi run -- sccache --stop-server || true
else
pixi run -- ccache --show-stats
fi
shell: bash

38
.github/workflows/upload-llvm.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: upload-llvm
permissions:
contents: write
on:
pull_request:
# if you want to run this workflow, change the branch name to main,
# if you want to turn off it, change it to non existent branch.
branches: [main-turn-off]
workflow_dispatch:
inputs:
workflow_id:
description: "Workflow run ID to pull artifacts from"
required: true
type: string
version:
description: "Release version/tag to publish (e.g., v1.2.3)"
required: true
type: string
jobs:
upload:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Download artifacts from workflow
env:
GH_TOKEN: ${{ github.token }}
run: scripts/download-llvm.sh "${{ inputs.workflow_id }}"
- name: Recreate release with artifacts
env:
GH_TOKEN: ${{ secrets.UPLOAD_LLVM }}
TARGET_REPO: clice-io/clice-llvm
run: python3 scripts/upload-llvm.py "${{ inputs.version }}" "${TARGET_REPO}" "${{ inputs.workflow_id }}"

View File

@@ -1,100 +0,0 @@
name: xmake
on:
push:
branches: [main]
paths:
- ".github/workflows/xmake.yml"
- "include/**"
- "src/**"
- "tests/**"
- "xmake.lua"
pull_request:
branches: [main]
paths:
- ".github/workflows/xmake.yml"
- "include/**"
- "src/**"
- "tests/**"
- "config/**"
- "xmake.lua"
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [windows-2025, ubuntu-24.04, macos-15]
build_type: [debug, releasedbg]
exclude:
- os: windows-2025
build_type: debug
runs-on: ${{ matrix.os }}
steps:
- name: Setup dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y gcc-14 g++-14 libstdc++-14-dev
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
sudo update-alternatives --set gcc /usr/bin/gcc-14
sudo update-alternatives --set g++ /usr/bin/g++-14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
sudo apt install -y cmake ninja-build
- name: Setup dependencies (MacOS)
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
run: |
brew install llvm@20 lld@20
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: 3.0.4
actions-cache-folder: ".xmake-cache"
actions-cache-key: ${{ matrix.os }}
package-cache: true
package-cache-key: ${{ matrix.os }}
build-cache: true
build-cache-key: ${{ matrix.os }}-${{ matrix.build_type }}
- name: Build clice
shell: bash
run: |
if [[ "${{ runner.os }}" == "Windows" ]]; then
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang -p windows
elif [[ "${{ runner.os }}" == "Linux" ]]; then
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang-20
elif [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang --sdk=/opt/homebrew/opt/llvm@20
fi
xmake build --verbose --diagnosis --all
- name: Install uv for integration tests
uses: astral-sh/setup-uv@v6
- name: Run tests
shell: bash
run: |
# Workaround for macOS
if [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
fi
xmake test --verbose
- name: Remove llvm package (Linux)
if: runner.os == 'Linux'
run: rm -rf /home/runner/.xmake/packages/c/clice-llvm

59
.gitignore vendored
View File

@@ -16,10 +16,6 @@
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
@@ -31,28 +27,49 @@
*.out
*.app
# Evil things
.claude
# Fortran module files
*.mod
*.smod
# LSP & IDE
compile_commands.json
.vscode/
.vs/
.idea/
temp/
# Build & Toolchain
*build*/
temp/
.cache/
.clice/
.llvm*/
.xmake/
__pycache__/
.pytest_cache/
tests/unit/Local/
.clice/
compile_commands.json
perf.data
flamegraph.svg
# Nodejs & Web
*.vsix
dist/
out/
cache/
node_modules/
.vscode-test/
**/node_modules
docs/.vitepress/dist
docs/.vitepress/cache
# Python & Testing
__pycache__/
.pytest_cache/
tests/unit/Local/
# IDEs & Editors
/.vscode/*
!/.vscode/launch.json
!/.vscode/tasks.json
.vs/
.idea/
.clangd
# pixi environments
.env
.pixi/*
!.pixi/config.toml
.codex/
.claude/*
!.claude/CLAUDE.md
!.claude/commands/
openspec/

View File

@@ -1,25 +0,0 @@
# .pre-commit-config.yaml
exclude: "\\.patch$"
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.12
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v21.1.0
hooks:
- id: clang-format
types_or: [c++, c]

23
.prettierrc.yaml Normal file
View File

@@ -0,0 +1,23 @@
# .prettierrc.yaml
printWidth: 100
tabWidth: 4
useTabs: false
semi: true
singleQuote: false
jsxSingleQuote: false
quoteProps: "as-needed"
trailingComma: "all"
bracketSpacing: true
arrowParens: "always"
endOfLine: "lf"
overrides:
- files: "*.md"
options:
proseWrap: "preserve"
tabWidth: 2
- files: ["*.json", "*.yaml", "*.yml", ".clang-format", ".clang-tidy"]
options:
tabWidth: 2

View File

@@ -1 +0,0 @@
3.13

83
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,83 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug clice",
"program": "${workspaceFolder}/build/Debug/bin/clice",
"args": ["--mode=socket", "--port=50051"],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug clice (socket, RelWithDebInfo)",
"program": "${workspaceFolder}/build/RelWithDebInfo/bin/clice",
"args": ["--mode=socket", "--port=50051"],
"cwd": "${workspaceFolder}"
},
{
"name": "VSCode Extension (pipe)",
"type": "extensionHost",
"request": "launch",
"args": [
"--disable-extension=llvm-vs-code-extensions.vscode-clangd",
"--disable-extension=ms-vscode.cpptools",
"--disable-extension=ms-vscode.cpptools-extension-pack",
"--extensionDevelopmentPath=${workspaceFolder}/editors/vscode"
],
"env": {
"CLICE_MODE": "pipe"
},
"outFiles": ["${workspaceFolder}/editors/vscode/dist/**/*.js"],
"preLaunchTask": "npm: watch vscode ext"
},
{
"name": "VSCode Extension (socket)",
"type": "extensionHost",
"request": "launch",
"args": [
"--disable-extension=llvm-vs-code-extensions.vscode-clangd",
"--disable-extension=ms-vscode.cpptools",
"--disable-extension=ms-vscode.cpptools-extension-pack",
"--extensionDevelopmentPath=${workspaceFolder}/editors/vscode"
],
"env": {
"CLICE_MODE": "socket"
},
"outFiles": ["${workspaceFolder}/editors/vscode/dist/**/*.js"],
"preLaunchTask": "npm: watch vscode ext"
},
{
"type": "lldb",
"request": "launch",
"name": "Unit Test",
"program": "${workspaceFolder}/build/Debug/bin/unit_tests",
"args": ["--test-dir=./tests/data", "--test-filter=${input:filter}"],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Release Unit Test",
"program": "${workspaceFolder}/build/RelWithDebInfo/bin/unit_tests",
"args": ["--test-dir=./tests/data", "--test-filter=${input:filter}"],
"cwd": "${workspaceFolder}"
}
],
"compounds": [
{
"name": "clice + VSCode Extension (socket)",
"configurations": ["Debug clice", "VSCode Extension (socket)"],
"stopAll": true
}
],
"inputs": [
{
"id": "filter",
"type": "promptString",
"description": "Unit Test Filter"
}
]
}

42
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,42 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "npm: install vscode deps",
"type": "shell",
"command": "pnpm",
"args": ["install"],
"options": {
"cwd": "${workspaceFolder}/editors/vscode"
},
"problemMatcher": []
},
{
"label": "npm: watch vscode ext",
"type": "shell",
"command": "pnpm",
"args": ["run", "watch"],
"options": {
"cwd": "${workspaceFolder}/editors/vscode"
},
"dependsOn": "npm: install vscode deps",
"problemMatcher": "$ts-webpack-watch",
"isBackground": true,
"presentation": {
"reveal": "never",
"group": "watchers"
}
},
{
"label": "npm: package vscode ext",
"type": "shell",
"command": "pnpm",
"args": ["run", "package"],
"options": {
"cwd": "${workspaceFolder}/editors/vscode"
},
"dependsOn": "npm: install vscode deps",
"problemMatcher": []
}
]
}

View File

@@ -1,8 +1,10 @@
cmake_minimum_required(VERSION 3.20)
cmake_minimum_required(VERSION 3.30)
project(CLICE_PROJECT LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include(GNUInstallDirs)
@@ -10,127 +12,159 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
# Make sure all third libraries are affected by ABI related options
option(CLICE_ENABLE_LTO "Enable ThinLTO for all targets" OFF)
option(CLICE_USE_LIBCXX "Use libc++ instead of libstdc++" OFF)
option(CLICE_OFFLINE_BUILD "Disable network downloads during configuration" OFF)
option(CLICE_ENABLE_TEST "Build unit tests" OFF)
option(CLICE_CI_ENVIRONMENT "Enable CI-specific configuration" OFF)
option(CLICE_ENABLE_BENCHMARK "Build benchmarks" OFF)
option(CLICE_RELEASE "Enable release packaging (LTO + strip + pack)" OFF)
# Global flags that apply to all targets (including FetchContent dependencies).
if(NOT MSVC)
add_compile_options(-ffunction-sections -fdata-sections)
endif()
if(APPLE)
# https://conda-forge.org/docs/maintainer/knowledge_base/#newer-c-features-with-old-sdk
add_compile_definitions(_LIBCPP_DISABLE_AVAILABILITY=1)
endif()
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,/OPT:REF")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,/OPT:REF")
elseif(APPLE)
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,-dead_strip")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,-dead_strip")
else()
string(APPEND CMAKE_EXE_LINKER_FLAGS " -static-libstdc++ -static-libgcc -Wl,--gc-sections")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,--gc-sections")
endif()
if(CLICE_USE_LIBCXX)
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -stdlib=libc++")
endif()
if(NOT WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
string(APPEND CMAKE_CXX_FLAGS " -fsanitize=address")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
if(CLICE_RELEASE)
set(CLICE_ENABLE_LTO ON)
endif()
if(CLICE_ENABLE_LTO)
string(APPEND CMAKE_C_FLAGS " -flto=thin")
string(APPEND CMAKE_CXX_FLAGS " -flto=thin")
string(APPEND CMAKE_EXE_LINKER_FLAGS " -flto=thin")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -flto=thin")
string(APPEND CMAKE_MODULE_LINKER_FLAGS " -flto=thin")
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_compile_options(-fsanitize=address)
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
# clang-cl (MSVC frontend): manually link ASan runtime since clang-cl
# doesn't handle -fsanitize=address linking automatically.
execute_process(
COMMAND ${CMAKE_CXX_COMPILER} --print-resource-dir
OUTPUT_VARIABLE CLANG_RESOURCE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(ASAN_LIB_PATH "${CLANG_RESOURCE_DIR}/lib/windows")
link_directories(${ASAN_LIB_PATH})
set(ASAN_LINK_FLAGS "")
list(APPEND ASAN_LINK_FLAGS "clang_rt.asan_dynamic-x86_64.lib")
list(APPEND ASAN_LINK_FLAGS "/wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib")
foreach(flag ${ASAN_LINK_FLAGS})
string(APPEND CMAKE_EXE_LINKER_FLAGS " ${flag}")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " ${flag}")
string(APPEND CMAKE_MODULE_LINKER_FLAGS " ${flag}")
endforeach()
else()
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
endif()
if(WIN32)
# Disable Identical COMDAT Folding in Debug to avoid ASan ODR false positives.
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,/OPT:NOICF")
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,/OPT:NOICF")
endif()
endif()
include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
# Project-specific options (not applied to third-party deps).
add_library(clice_options INTERFACE)
if(CLICE_CI_ENVIRONMENT)
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT)
endif()
if(WIN32)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
target_link_options(clice_options INTERFACE
-fuse-ld=lld-link
-Wl,/OPT:REF
)
elseif(APPLE)
target_link_options(clice_options INTERFACE
-fuse-ld=lld
-Wl,-dead_strip
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_options(clice_options INTERFACE
-fuse-ld=lld
-Wl,--gc-sections
)
endif()
if (MSVC)
target_compile_options(clice_options INTERFACE
/GR-
/EHsc-
/Zc:preprocessor
)
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
target_compile_options(clice_options INTERFACE /GR- /EHs-c- /Zc:preprocessor)
else()
target_compile_options(clice_options INTERFACE
-fno-rtti
-fno-exceptions
-Wno-deprecated-declarations
-Wno-undefined-inline
-ffunction-sections
-fdata-sections
$<$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>:-Wno-undefined-inline>
)
endif()
set(FBS_SCHEMA_FILE "${CMAKE_CURRENT_SOURCE_DIR}/include/Index/schema.fbs")
set(GENERATED_HEADER "${CMAKE_CURRENT_BINARY_DIR}/generated/schema_generated.h")
if(WIN32)
target_link_libraries(clice_options INTERFACE version ntdll)
endif()
if(CLICE_ENABLE_TEST)
target_compile_definitions(clice_options INTERFACE CLICE_ENABLE_TEST=1)
endif()
if(CLICE_CI_ENVIRONMENT)
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
endif()
set(FBS_SCHEMA_FILE "${PROJECT_SOURCE_DIR}/src/index/schema.fbs")
set(GENERATED_HEADER "${PROJECT_BINARY_DIR}/generated/schema_generated.h")
add_custom_command(
OUTPUT ${GENERATED_HEADER}
COMMAND $<TARGET_FILE:flatc> --cpp -o ${CMAKE_CURRENT_BINARY_DIR}/generated ${FBS_SCHEMA_FILE}
DEPENDS ${FBS_SCHEMA_FILE}
OUTPUT "${GENERATED_HEADER}"
COMMAND $<TARGET_FILE:flatc> --cpp -o "${PROJECT_BINARY_DIR}/generated" "${FBS_SCHEMA_FILE}"
DEPENDS "${FBS_SCHEMA_FILE}"
COMMENT "Generating C++ header from ${FBS_SCHEMA_FILE}"
)
add_custom_target(
generate_flatbuffers_schema
DEPENDS ${GENERATED_HEADER}
)
add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}")
set(CONFIG_SOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/config/clang-tidy-config.h")
set(CONFIG_GENERATED_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated/clang-tidy-config.h")
add_custom_command(
OUTPUT ${CONFIG_GENERATED_FILE}
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CONFIG_SOURCE_FILE} ${CONFIG_GENERATED_FILE}
DEPENDS ${CONFIG_SOURCE_FILE}
COMMENT "Generating C++ header from ${CONFIG_SOURCE_FILE}"
)
add_custom_target(
generate_config
DEPENDS ${CONFIG_GENERATED_FILE}
)
file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
"${PROJECT_SOURCE_DIR}/src/AST/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Async/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Basic/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Compiler/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Index/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Feature/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Server/*.cpp"
"${PROJECT_SOURCE_DIR}/src/Support/*.cpp"
)
add_library(clice-core STATIC "${CLICE_SOURCES}")
add_dependencies(clice-core generate_flatbuffers_schema generate_config)
file(GLOB_RECURSE CLICE_CORE_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/*.cpp")
add_library(clice-core STATIC ${CLICE_CORE_SOURCES})
add_library(clice::core ALIAS clice-core)
add_dependencies(clice-core generate_flatbuffers_schema)
target_include_directories(clice-core PUBLIC
"${PROJECT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_BINARY_DIR}/generated"
"${PROJECT_SOURCE_DIR}/src"
"${PROJECT_BINARY_DIR}/generated"
)
target_link_libraries(clice-core PUBLIC
clice_options
libuv::libuv
llvm-libs
spdlog::spdlog
tomlplusplus::tomlplusplus
roaring::roaring
flatbuffers
llvm-libs
eventide::ipc::lsp
eventide::serde::toml
simdjson::simdjson
)
add_executable(clice "${PROJECT_SOURCE_DIR}/bin/clice.cc")
target_link_libraries(clice PRIVATE clice-core)
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
target_link_libraries(clice PRIVATE clice::core eventide::deco)
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
message(STATUS "Copying resource directory for development build")
file(
COPY "${LLVM_INSTALL_PATH}/lib/clang"
DESTINATION "${PROJECT_BINARY_DIR}/lib"
add_custom_target(copy_clang_resource ALL
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${LLVM_INSTALL_PATH}/lib/clang"
"${PROJECT_BINARY_DIR}/lib/clang"
COMMENT "Copying clang resource directory"
)
install(
DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang"
@@ -139,11 +173,35 @@ install(
if(CLICE_ENABLE_TEST)
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS
"${PROJECT_SOURCE_DIR}/tests/unit/*/*.cpp")
add_executable(unit_tests
"${CLICE_TEST_SOURCES}"
"${PROJECT_SOURCE_DIR}/bin/unit_tests.cc"
"${PROJECT_SOURCE_DIR}/tests/unit/*/*_tests.cpp"
)
target_include_directories(unit_tests PUBLIC "${PROJECT_SOURCE_DIR}")
target_link_libraries(unit_tests PRIVATE clice-core)
set(CLICE_TEST_SUPPORT_SOURCES
"${PROJECT_SOURCE_DIR}/tests/unit/test/annotation.cpp"
"${PROJECT_SOURCE_DIR}/tests/unit/test/tester.cpp"
)
add_executable(unit_tests
"${PROJECT_SOURCE_DIR}/tests/unit/unit_tests.cc"
${CLICE_TEST_SOURCES}
${CLICE_TEST_SUPPORT_SOURCES}
)
target_include_directories(unit_tests PRIVATE
"${PROJECT_SOURCE_DIR}/src"
"${PROJECT_SOURCE_DIR}/tests/unit"
)
target_link_libraries(unit_tests PRIVATE clice::core eventide::zest eventide::deco)
endif()
if(CLICE_ENABLE_BENCHMARK)
add_executable(scan_benchmark
"${PROJECT_SOURCE_DIR}/benchmarks/scan_benchmark.cpp"
)
target_include_directories(scan_benchmark PRIVATE
"${PROJECT_SOURCE_DIR}/src"
)
target_link_libraries(scan_benchmark PRIVATE clice::core eventide::deco)
endif()
if(CLICE_RELEASE)
include("${PROJECT_SOURCE_DIR}/cmake/release.cmake")
endif()

View File

@@ -1,39 +0,0 @@
FROM debian:13 AS builder
# Installs System Dependencies
RUN apt-get update && apt-get install -y \
ninja-build cmake build-essential curl gcc-14 g++-14 git
# Installs LLVM 20
RUN curl https://apt.llvm.org/llvm-snapshot.gpg.key | tee /usr/share/keyrings/llvm-snapshot.gpg.key && \
echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg.key] http://apt.llvm.org/trixie/ llvm-toolchain-trixie main" >> /etc/apt/sources.list && \
echo "deb [signed-by=/usr/share/keyrings/llvm-snapshot.gpg.key] http://apt.llvm.org/trixie/ llvm-toolchain-trixie-20 main" >> /etc/apt/sources.list && \
apt-get update && apt-get install -y \
clang-20 lld-20
RUN ln -s /usr/bin/lld-20 /usr/bin/lld
# Adds source code
COPY include /app/include
COPY cmake /app/cmake
COPY src /app/src
COPY tests /app/tests
COPY CMakeLists.txt /app/CMakeLists.txt
COPY scripts /app/scripts
WORKDIR /app
# Initializes and installs dependencies
RUN cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCMAKE_BUILD_TYPE=Release -DCLICE_ENABLE_TEST=ON
# Builds clice
RUN cmake --build build -j && \
cmake --install build --prefix=/opt/clice
FROM debian:13
COPY --from=builder /opt/clice /opt/clice/
COPY LICENSE /opt/clice/LICENSE
COPY README.md /opt/clice/README.md
RUN ln -s /opt/clice/bin/clice /usr/local/bin/clice
ENTRYPOINT ["clice"]

View File

@@ -5,7 +5,7 @@
![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)
[![Documentation](https://img.shields.io/badge/view-documentation-blue)](https://clice.io)
[![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)
@@ -22,11 +22,11 @@ Download the latest `clice` binary from the [releases page](https://github.com/c
```jsonc
{
// Optional: Set this to an empty string to turn off the clangd.
"clangd.path": "",
// Optional: Set this to an empty string to turn off the clangd.
"clangd.path": "",
// Point this to the clice binary you downloaded.
"clice.executable": "/path/to/your/clice/executable",
// Point this to the clice binary you downloaded.
"clice.executable": "/path/to/your/clice/executable",
}
```
@@ -35,4 +35,4 @@ Download the latest `clice` binary from the [releases page](https://github.com/c
## 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://clice.io/).
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/).

View File

@@ -0,0 +1,413 @@
/// Benchmark for scan_dependency_graph on a real compilation database.
///
/// Usage:
/// scan_benchmark [OPTIONS] <compile_commands.json>
///
/// Example:
/// ./build/RelWithDebInfo/bin/scan_benchmark \
/// /home/ykiko/C++/clice/.llvm/build-debug/compile_commands.json
///
/// ./build/RelWithDebInfo/bin/scan_benchmark --log-level info --export graph.json \
/// /home/ykiko/C++/clice/.llvm/build-debug/compile_commands.json
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <fstream>
#include <map>
#include <numeric>
#include <print>
#include <set>
#include <thread>
#include "command/command.h"
#include "eventide/deco/deco.h"
#include "eventide/serde/json/serializer.h"
#include "support/filesystem.h"
#include "support/logging.h"
#include "support/path_pool.h"
#include "syntax/dependency_graph.h"
#include "llvm/Support/FileSystem.h"
namespace et = eventide;
using namespace clice;
struct BenchmarkOptions {
DecoKV(names = {"--log-level"}; help = "Log level: trace, debug, info, warn, error, off";
required = false;)
<std::string> log_level = "off";
DecoKV(names = {"--export"}; help = "Export dependency graph as JSON to this path";
required = false;)
<std::string> export_path;
DecoKV(names = {"--runs"}; help = "Number of cold start iterations"; required = false;)
<int> runs = 20;
DecoFlag(names = {"-h", "--help"}; help = "Show help message"; required = false;)
help;
DecoInput(meta_var = "CDB"; help = "Path to compile_commands.json"; required = false;)
<std::string> cdb_path;
};
struct FileNode {
std::string path;
std::string module_name;
std::vector<std::string> includes;
};
struct GraphExport {
std::vector<FileNode> files;
};
void export_graph_json(const PathPool& path_pool,
const DependencyGraph& graph,
llvm::StringRef output_path) {
// Build reverse module map: path_id -> module_name.
llvm::DenseMap<std::uint32_t, llvm::StringRef> path_to_module;
for(auto& [name, path_ids]: graph.modules()) {
for(auto path_id: path_ids) {
path_to_module[path_id] = name;
}
}
GraphExport export_data;
for(std::uint32_t id = 0; id < path_pool.paths.size(); id++) {
auto inc_ids = graph.get_all_includes(id);
if(inc_ids.empty()) {
continue;
}
FileNode node;
node.path = path_pool.paths[id].str();
auto mod_it = path_to_module.find(id);
if(mod_it != path_to_module.end()) {
node.module_name = mod_it->second.str();
}
for(auto flagged_id: inc_ids) {
auto raw_id = flagged_id & DependencyGraph::PATH_ID_MASK;
node.includes.push_back(path_pool.paths[raw_id].str());
}
export_data.files.push_back(std::move(node));
}
auto json = et::serde::json::to_json(export_data);
if(!json) {
std::println(stderr, "Failed to serialize dependency graph");
return;
}
std::ofstream out(output_path.str());
if(!out) {
std::println(stderr, "Failed to open output file: {}", output_path);
return;
}
out << *json;
std::println("Graph exported to {} ({} files)", output_path, export_data.files.size());
}
void print_report(const ScanReport& report) {
std::println("===============================================================");
std::println(" Dependency Scan Report");
std::println("===============================================================");
// Timing.
std::println("");
std::println(" Time: {}ms", report.elapsed_ms);
std::println(" Waves: {}", report.waves);
// File counts.
std::println("");
std::println(" Files");
std::println(" Source files (from CDB): {}", report.source_files);
std::println(" Header files (discovered): {}", report.header_files);
std::println(" Total: {}", report.total_files);
std::println(" Modules: {}", report.modules);
// Include edges.
std::println("");
std::println(" Include Edges");
std::println(" Total: {}", report.total_edges);
std::println(" Unconditional: {}", report.unconditional_edges);
std::println(" Conditional: {} (inside #if/#ifdef)", report.conditional_edges);
// Resolution accuracy.
std::println("");
std::println(" Resolution");
std::println(" #include directives: {}", report.includes_found);
std::println(" Resolved: {}", report.includes_resolved);
auto unresolved_count = report.includes_found - report.includes_resolved;
std::println(" Unresolved: {}", unresolved_count);
if(report.includes_found > 0) {
double rate = 100.0 * static_cast<double>(report.includes_resolved) /
static_cast<double>(report.includes_found);
std::println(" Accuracy: {:.1f}%", rate);
}
// Wall-clock phase breakdown.
std::println("");
std::println(" Phase Breakdown (wall-clock)");
std::println(" Config extraction: {}ms (prewarm={}ms, loop={}ms)",
report.config_ms,
report.prewarm_ms,
report.config_loop_ms);
std::println(" Dir cache pre-pop: {}ms (overlapped with Phase 1)", report.dir_cache_ms);
std::println(" Phase 1 (read+scan, parallel): {}ms", report.phase1_ms);
std::println(" Phase 2 (include resolve): {}ms", report.phase2_ms);
std::println(" Phase 3 (graph build): {}ms", report.phase3_ms);
// Per-wave breakdown.
if(!report.wave_stats.empty()) {
std::println("");
std::println(" Per-Wave Breakdown");
std::println(" {:>5s} {:>8s} {:>8s} {:>8s} {:>8s} {:>8s} {:>10s} {:>10s}",
"Wave",
"Files",
"P1(ms)",
"P2(ms)",
"Next",
"Prefetch",
"DirList",
"DirHits");
for(std::size_t i = 0; i < report.wave_stats.size(); i++) {
auto& ws = report.wave_stats[i];
std::println(" {:>5} {:>8} {:>8} {:>8} {:>8} {:>8} {:>10} {:>10}",
i,
ws.files,
ws.phase1_ms,
ws.phase2_ms,
ws.next_files,
ws.prefetch_count,
ws.dir_listings,
ws.dir_hits);
}
}
// Phase 2 breakdown.
if(report.p2_resolve_us > 0) {
auto other_us = report.phase2_ms * 1000 - report.p2_resolve_us;
std::println("");
std::println(" Phase 2 Breakdown (single-threaded)");
std::println(" resolve_include: {:.1f}ms", report.p2_resolve_us / 1000.0);
std::println(" Other (cache lookup, intern, graph): {:.1f}ms", other_us / 1000.0);
}
// Cumulative I/O statistics.
std::println("");
std::println(" I/O Statistics (cumulative across threads)");
std::println(" File read: {:.1f}ms (sum of all threads)", report.read_us / 1000.0);
std::println(" Lexer scan: {:.1f}ms (sum of all threads)", report.scan_us / 1000.0);
std::println(" Filesystem: {:.1f}ms ({} readdir calls, {} dir cache hits)",
report.fs_us / 1000.0,
report.dir_listings,
report.dir_hits);
std::println(" File lookups: {}", report.fs_lookups);
std::println(" Include cache hits: {}", report.include_cache_hits);
std::println(" Scan result cache hits: {}", report.scan_cache_hits);
if(report.dir_listings + report.dir_hits > 0) {
double hit_rate = 100.0 * static_cast<double>(report.dir_hits) /
static_cast<double>(report.dir_listings + report.dir_hits);
std::println(" Dir cache hit rate: {:.1f}%", hit_rate);
}
std::println("");
std::println("===============================================================");
}
int main(int argc, const char** argv) {
auto args = deco::util::argvify(argc, argv);
auto result = deco::cli::parse<BenchmarkOptions>(args);
if(!result.has_value()) {
std::println(stderr, "Error: {}", result.error().message);
return 1;
}
auto& opts = result->options;
if(opts.help.value_or(false) || !opts.cdb_path.has_value()) {
std::ostringstream oss;
deco::cli::write_usage_for<BenchmarkOptions>(oss, "scan_benchmark [OPTIONS] <cdb>");
std::print("{}", oss.str());
return opts.help.value_or(false) ? 0 : 1;
}
// Configure logging.
auto level = spdlog::level::from_str(*opts.log_level);
clice::logging::options.level = level;
clice::logging::stderr_logger("scan_benchmark", clice::logging::options);
// resource_dir() is self-initializing (lazy static) — no setup needed.
auto& cdb_path = *opts.cdb_path;
auto hw_threads = std::thread::hardware_concurrency();
auto runs = *opts.runs;
if(runs <= 0) {
std::println(stderr, "Error: --runs must be positive (got {})", runs);
return 1;
}
// Set UV_THREADPOOL_SIZE if not already set.
// Use at least libuv's default (4) so low-core CI runners don't regress.
if(!std::getenv("UV_THREADPOOL_SIZE")) {
auto pool_size = std::max(hw_threads, 4u);
static std::string env = "UV_THREADPOOL_SIZE=" + std::to_string(pool_size);
putenv(env.data());
}
std::println("Hardware threads: {}", hw_threads);
std::println("UV_THREADPOOL_SIZE: {}", std::getenv("UV_THREADPOOL_SIZE"));
std::println("Log level: {}", *opts.log_level);
std::println("CDB: {}", cdb_path);
std::println("");
// Load compilation database.
auto t0 = std::chrono::steady_clock::now();
CompilationDatabase cdb;
auto count = cdb.load(cdb_path);
auto t1 = std::chrono::steady_clock::now();
auto load_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();
std::println("CDB loaded: {} entries in {}ms", count, load_ms);
{
std::set<const CompilationInfo*> unique_contexts;
std::set<const CanonicalCommand*> unique_canonicals;
std::map<const CanonicalCommand*, int> canonical_hist;
for(auto& entry: cdb.get_entries()) {
unique_contexts.insert(entry.info.ptr);
unique_canonicals.insert(entry.info->canonical.ptr);
canonical_hist[entry.info->canonical.ptr]++;
}
double dedup_ratio =
unique_contexts.empty() ? 0.0 : static_cast<double>(count) / unique_contexts.size();
std::println(
"Context dedup: {} files -> {} unique contexts ({:.1f}x), {} unique canonicals",
count,
unique_contexts.size(),
dedup_ratio,
unique_canonicals.size());
// If canonical dedup is poor, dump diagnostics.
if(unique_canonicals.size() > 200) {
// Sort canonicals by frequency (descending).
std::vector<std::pair<int, const CanonicalCommand*>> sorted;
for(auto& [ptr, cnt]: canonical_hist)
sorted.push_back({cnt, ptr});
std::ranges::sort(sorted,
std::greater{},
&std::pair<int, const CanonicalCommand*>::first);
// Show top-5 canonical commands.
for(int i = 0; i < std::min(5, (int)sorted.size()); i++) {
auto [cnt, cmd] = sorted[i];
std::println(" canonical[{}] ({} files, {} args):", i, cnt, cmd->arguments.size());
for(auto arg: cmd->arguments)
std::println(" {}", arg);
}
// Show a singleton canonical (count==1) to see what per-file arg leaks in.
for(auto& [cnt, cmd]: sorted) {
if(cnt == 1) {
std::println(" singleton canonical ({} args):", cmd->arguments.size());
for(auto arg: cmd->arguments)
std::println(" {}", arg);
break;
}
}
// Find two canonicals that differ by only a few args.
if(sorted.size() >= 2) {
auto* a = sorted[0].second;
auto* b = sorted[1].second;
std::println(" --- Canonical diff (top-1 vs top-2) ---");
auto max_len = std::max(a->arguments.size(), b->arguments.size());
for(std::size_t i = 0; i < max_len; i++) {
llvm::StringRef av = i < a->arguments.size() ? a->arguments[i] : "<missing>";
llvm::StringRef bv = i < b->arguments.size() ? b->arguments[i] : "<missing>";
if(av != bv)
std::println(" DIFF[{}]: '{}' vs '{}'", i, av, bv);
else
std::println(" SAME[{}]: '{}'", i, av);
}
}
}
}
std::println("\nRunning {} cold start scan(s)...\n", runs);
PathPool path_pool;
DependencyGraph graph;
std::vector<std::int64_t> elapsed_times;
std::vector<std::int64_t> config_times;
std::vector<std::int64_t> phase1_times;
std::vector<std::int64_t> phase2_times;
elapsed_times.reserve(runs);
config_times.reserve(runs);
phase1_times.reserve(runs);
phase2_times.reserve(runs);
for(int i = 0; i < runs; i++) {
// True cold start: rebuild CDB (clears toolchain & config caches),
// reset PathPool and DependencyGraph.
cdb = CompilationDatabase{};
cdb.load(cdb_path);
path_pool = PathPool{};
graph = DependencyGraph{};
auto report = scan_dependency_graph(cdb, path_pool, graph);
elapsed_times.push_back(report.elapsed_ms);
config_times.push_back(report.config_ms);
phase1_times.push_back(report.phase1_ms);
phase2_times.push_back(report.phase2_ms);
std::println("[run {:2}] {}ms | config={}ms phase1={}ms phase2={}ms | files={}",
i + 1,
report.elapsed_ms,
report.config_ms,
report.phase1_ms,
report.phase2_ms,
report.total_files);
// Print detailed report for the first run only.
if(i == 0) {
std::println("");
print_report(report);
}
}
// Summary statistics.
if(runs > 1) {
auto stats = [](std::vector<std::int64_t>& v) {
std::ranges::sort(v);
auto sum = std::accumulate(v.begin(), v.end(), std::int64_t{0});
return std::tuple{v.front(), sum / static_cast<std::int64_t>(v.size()), v.back()};
};
auto [e_min, e_avg, e_max] = stats(elapsed_times);
auto [c_min, c_avg, c_max] = stats(config_times);
auto [p1_min, p1_avg, p1_max] = stats(phase1_times);
auto [p2_min, p2_avg, p2_max] = stats(phase2_times);
std::println("\n Summary ({} runs) min avg max", runs);
std::println(" Total: {:>7} {:>6} {:>6}", e_min, e_avg, e_max);
std::println(" Config extraction: {:>7} {:>6} {:>6}", c_min, c_avg, c_max);
std::println(" Phase 1 (read+scan):{:>7} {:>6} {:>6}", p1_min, p1_avg, p1_max);
std::println(" Phase 2 (resolve): {:>7} {:>6} {:>6}", p2_min, p2_avg, p2_max);
}
// Export dependency graph as JSON if requested.
if(opts.export_path.has_value()) {
export_graph_json(path_pool, graph, *opts.export_path);
}
return 0;
}

View File

@@ -1,136 +0,0 @@
#include "Server/Version.h"
#include "Server/Server.h"
#include "Support/Logging.h"
#include "Support/Format.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Process.h"
#include "llvm/ADT/StringSwitch.h"
namespace cl = llvm::cl;
using namespace clice;
namespace {
static cl::OptionCategory category("clice options");
enum class Mode {
Pipe,
Socket,
Indexer,
};
cl::opt<Mode> mode{
"mode",
cl::cat(category),
cl::value_desc("string"),
cl::init(Mode::Pipe),
cl::values(clEnumValN(Mode::Pipe, "pipe", "pipe mode, clice will listen on stdio"),
clEnumValN(Mode::Socket, "socket", "socket mode, clice will listen on host:port")),
/// clEnumValN(Mode::Indexer, "indexer", "indexer mode, to implement")
cl::desc("The mode of clice, default is pipe, socket is usually used for debugging"),
};
cl::opt<std::string> host{
"host",
cl::cat(category),
cl::value_desc("string"),
cl::init("127.0.0.1"),
cl::desc("The host to connect to (default: 127.0.0.1)"),
};
cl::opt<unsigned int> port{
"port",
cl::cat(category),
cl::value_desc("unsigned int"),
cl::init(50051),
cl::desc("The port to connect to"),
};
cl::opt<logging::ColorMode> log_color{
"log-color",
cl::cat(category),
cl::value_desc("always|auto|never"),
cl::init(logging::ColorMode::automatic),
cl::values(clEnumValN(logging::ColorMode::automatic, "auto", ""),
clEnumValN(logging::ColorMode::always, "always", ""),
clEnumValN(logging::ColorMode::never, "never", "")),
cl::desc("When to use terminal colors, default is auto"),
};
cl::opt<logging::Level> log_level{
"log-level",
cl::cat(category),
cl::value_desc("trace|debug|info|warn|error"),
cl::init(logging::Level::info),
cl::values(clEnumValN(logging::Level::trace, "trace", ""),
clEnumValN(logging::Level::debug, "debug", ""),
clEnumValN(logging::Level::info, "info", ""),
clEnumValN(logging::Level::warn, "warn", ""),
clEnumValN(logging::Level::err, "error", ""),
clEnumValN(logging::Level::off, "off", "")),
cl::desc("The log level, default is info"),
};
} // namespace
int main(int argc, const char** argv) {
llvm::InitLLVM guard(argc, argv);
llvm::setBugReportMsg(
"Please report bugs to https://github.com/clice-io/clice/issues and include the crash backtrace");
cl::SetVersionPrinter([](llvm::raw_ostream& os) {
os << std::format("clice version: {}\nllvm version: {}\n",
clice::config::version,
clice::config::llvm_version);
});
cl::HideUnrelatedOptions(category);
cl::ParseCommandLineOptions(argc,
argv,
"clice is a new generation of language server for C/C++");
logging::options.color = log_color;
logging::options.level = log_level;
logging::stderr_logger("clice", logging::options);
if(auto result = fs::init_resource_dir(argv[0]); !result) {
LOGGING_FATAL("Cannot find default resource directory, because {}", result.error());
}
for(int i = 0; i < argc; ++i) {
LOGGING_INFO("argv[{}] = {}", i, argv[i]);
}
async::init();
/// The global server instance.
static Server instance;
auto loop = [&](json::Value value) -> async::Task<> {
co_await instance.on_receive(value);
};
switch(mode) {
case Mode::Pipe: {
async::net::listen(loop);
LOGGING_INFO("Server starts listening on stdin/stdout");
break;
}
case Mode::Socket: {
async::net::listen(host.c_str(), port, loop);
LOGGING_INFO("Server starts listening on {}:{}", host.getValue(), port.getValue());
break;
}
case Mode::Indexer: {
/// TODO:
break;
}
}
async::run();
LOGGING_INFO("clice exit normally!");
return 0;
}

View File

@@ -1,202 +0,0 @@
#include "Test/Test.h"
#include "Support/Logging.h"
#include "Support/GlobPattern.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Signals.h"
using namespace clice;
using namespace clice::testing;
constexpr static std::string_view GREEN = "\033[32m";
constexpr static std::string_view YELLOW = "\033[33m";
constexpr static std::string_view RED = "\033[31m";
constexpr static std::string_view CLEAR = "\033[0m";
namespace {
namespace cl = llvm::cl;
cl::OptionCategory unittest_category("clice Unittest Options");
cl::opt<std::string> test_dir{
"test-dir",
cl::desc("Specify the test source directory path"),
cl::value_desc("path"),
cl::Required,
cl::cat(unittest_category),
};
cl::opt<std::string> test_filter{
"test-filter",
cl::desc("A glob pattern to run subset of tests"),
cl::cat(unittest_category),
};
cl::opt<bool> enable_example{
"enable-example",
cl::init(false),
cl::cat(unittest_category),
};
std::optional<GlobPattern> pattern;
} // namespace
namespace clice::testing {
Runner& Runner::instance() {
static Runner runner;
return runner;
}
void Runner::add_suite(std::string_view name, Suite suite) {
suites[name].emplace_back(suite);
}
void Runner::on_test(std::string_view name, Test test, bool skipped) {
std::string full_name = std::format("{}.{}", curr_suite_name, name);
/// If this test if filter, directly return.
if(pattern && !pattern->match(full_name)) {
return;
}
/// If there is any test in the suite, we print the suite start info.
if(all_skipped) {
std::println("{}[----------] tests from {}{}", GREEN, curr_suite_name, CLEAR);
all_skipped = false;
}
if(skipped) {
/// If this test is marked as skipped, only print skip information.
std::println("{}[ SKIPPED ] {}{}", YELLOW, full_name, CLEAR);
return;
}
/// Reset whether this test is failed or fatal.
curr_failed = false;
curr_fatal = false;
using namespace std::chrono;
std::println("{}[ RUN ] {}.{}{}", GREEN, curr_suite_name, name, CLEAR);
auto begin = system_clock::now();
test();
auto duration = duration_cast<milliseconds>(system_clock::now() - begin);
std::println("{}[ {} ] {} ({} ms){}",
curr_failed ? RED : GREEN,
curr_failed ? "FAILED" : " OK",
full_name,
duration.count(),
CLEAR);
/// Update test information.
curr_tests_count += 1;
total_tests_count += 1;
curr_test_duration += duration;
total_test_duration += duration;
if(curr_failed) {
curr_failed_tests_count += 1;
total_failed_tests_count += 1;
}
}
void Runner::fail(const may_failure& failure) {
if(failure.failed) {
curr_failed = true;
std::println("{}Failure at {}:{}:{}! [{}]{}",
RED,
failure.location.file_name(),
failure.location.line(),
failure.location.column(),
failure.expression,
CLEAR);
std::println("{}", failure.message);
}
if(failure.fatal) {
curr_fatal = true;
std::println("{}--> Test stopped due to fatal error.{}", RED, CLEAR);
std::exit(1);
}
}
int Runner::run_tests() {
/// Register all tests.
std::println("{}[----------] Global test environment set-up.{}", GREEN, CLEAR);
for(auto& [suite_name, suite]: suites) {
if(!enable_example && suite_name == "TEST.Example") {
continue;
}
if(!test_filter.empty()) {
auto pos = test_filter.find_first_of('.');
if(pos != std::string::npos && test_filter.substr(0, pos) != suite_name) {
continue;
}
}
curr_fatal = false;
all_skipped = true;
curr_suite_name = suite_name;
curr_tests_count = 0;
curr_failed_tests_count = 0;
curr_test_duration = std::chrono::milliseconds();
for(auto& callback: suite) {
callback();
}
/// If there is any test in the suite, we print the suite info.
if(!all_skipped) {
total_suites_count += 1;
std::println("{}[----------] {} tests from {} ({} ms total)\n{}",
GREEN,
curr_tests_count,
suite_name,
total_test_duration.count(),
CLEAR);
}
}
std::println("{}[----------] Global test environment tear-down{}", GREEN, CLEAR);
std::println("{}[==========] {} tests from {} test suites ran. ({} ms total){}",
GREEN,
total_tests_count,
total_suites_count,
total_test_duration.count(),
CLEAR);
return total_failed_tests_count != 0;
}
} // namespace clice::testing
int main(int argc, const char* argv[]) {
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
llvm::cl::HideUnrelatedOptions(unittest_category);
llvm::cl::ParseCommandLineOptions(argc, argv, "clice test\n");
logging::stderr_logger("clice", logging::options);
if(!test_filter.empty()) {
if(auto result = GlobPattern::create(test_filter)) {
pattern.emplace(std::move(*result));
}
}
if(auto result = fs::init_resource_dir(argv[0]); !result) {
std::println("Failed to get resource directory, because {}", result.error());
return 1;
}
using namespace clice::testing;
return Runner::instance().run_tests();
}

17
cmake/archive.cmake Normal file
View File

@@ -0,0 +1,17 @@
if(OUTPUT MATCHES "\\.tar\\.gz$")
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar czf "${OUTPUT}" .
WORKING_DIRECTORY "${WORK_DIR}"
COMMAND_ERROR_IS_FATAL ANY
)
elseif(OUTPUT MATCHES "\\.zip$")
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar cf "${OUTPUT}" --format=zip .
WORKING_DIRECTORY "${WORK_DIR}"
COMMAND_ERROR_IS_FATAL ANY
)
else()
message(FATAL_ERROR "Unsupported archive format: ${OUTPUT}")
endif()
message(STATUS "Created: ${OUTPUT}")

View File

@@ -1,57 +0,0 @@
include(${CMAKE_CURRENT_LIST_DIR}/utils.cmake)
# Look up a Git tag's corresponding commit SHA from a GitHub repository
function(github_lookup_tag_commit REPO_OWNER REPO_NAME TAG_NAME OUTPUT_VAR)
set(GITHUB_API_URL "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/git/ref/tags/${TAG_NAME}")
message(STATUS "Fetching tag info from: ${GITHUB_API_URL}")
# Download tag info directly into a string
download_to_string(${GITHUB_API_URL} TAG_JSON DOWNLOAD_RESULT)
if(NOT DOWNLOAD_RESULT EQUAL "0")
message(WARNING "Failed to fetch tag info. Result: ${DOWNLOAD_RESULT}")
set(${OUTPUT_VAR} "NOTFOUND" PARENT_SCOPE)
return()
endif()
# Parse the JSON to get object type and SHA
parse_json_field_from_string("${TAG_JSON}" object "type" OBJECT_TYPE)
parse_json_field_from_string("${TAG_JSON}" object "sha" OBJECT_SHA)
if(OBJECT_TYPE STREQUAL "NOTFOUND" OR OBJECT_SHA STREQUAL "NOTFOUND")
message(WARNING "Could not find object type or SHA in the JSON response.")
set(${OUTPUT_VAR} "NOTFOUND" PARENT_SCOPE)
return()
endif()
set(COMMIT_SHA "NOTFOUND")
if(OBJECT_TYPE STREQUAL "commit")
# Direct commit reference
set(COMMIT_SHA ${OBJECT_SHA})
elseif(OBJECT_TYPE STREQUAL "tag")
# Annotated tag - need to fetch the actual commit
set(TAG_API_URL "https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/git/tags/${OBJECT_SHA}")
message(STATUS "Fetching annotated tag info from: ${TAG_API_URL}")
# Download annotated tag info directly into a string
download_to_string(${TAG_API_URL} ANNOTATED_TAG_JSON TAG_DOWNLOAD_RESULT)
if(NOT TAG_DOWNLOAD_RESULT EQUAL "0")
message(WARNING "Failed to fetch annotated tag info. Result: ${TAG_DOWNLOAD_RESULT}")
set(${OUTPUT_VAR} "NOTFOUND" PARENT_SCOPE)
return()
endif()
# Parse the annotated tag JSON to get the commit SHA
parse_json_field_from_string("${ANNOTATED_TAG_JSON}" object "sha" COMMIT_SHA_FROM_TAG)
if(NOT COMMIT_SHA_FROM_TAG STREQUAL "NOTFOUND")
set(COMMIT_SHA ${COMMIT_SHA_FROM_TAG})
endif()
else()
message(WARNING "Unknown object type: ${OBJECT_TYPE}")
endif()
set(${OUTPUT_VAR} ${COMMIT_SHA} PARENT_SCOPE)
endfunction()

115
cmake/llvm.cmake Normal file
View File

@@ -0,0 +1,115 @@
include_guard()
function(setup_llvm LLVM_VERSION)
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(LLVM_SETUP_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/.llvm/setup-llvm.json")
set(LLVM_SETUP_SCRIPT "${PROJECT_SOURCE_DIR}/scripts/setup-llvm.py")
set(LLVM_SETUP_ARGS
"--version" "${LLVM_VERSION}"
"--build-type" "${CMAKE_BUILD_TYPE}"
"--binary-dir" "${CMAKE_CURRENT_BINARY_DIR}"
"--manifest" "${PROJECT_SOURCE_DIR}/config/llvm-manifest.json"
"--output" "${LLVM_SETUP_OUTPUT}"
)
if(CLICE_ENABLE_LTO)
list(APPEND LLVM_SETUP_ARGS "--enable-lto")
endif()
if(DEFINED LLVM_INSTALL_PATH AND NOT LLVM_INSTALL_PATH STREQUAL "")
list(APPEND LLVM_SETUP_ARGS "--install-path" "${LLVM_INSTALL_PATH}")
endif()
if(DEFINED CLICE_OFFLINE_BUILD AND CLICE_OFFLINE_BUILD)
list(APPEND LLVM_SETUP_ARGS "--offline")
endif()
execute_process(
COMMAND "${Python3_EXECUTABLE}" "${LLVM_SETUP_SCRIPT}" ${LLVM_SETUP_ARGS}
RESULT_VARIABLE LLVM_SETUP_RESULT
OUTPUT_VARIABLE LLVM_SETUP_STDOUT
ERROR_VARIABLE LLVM_SETUP_STDERR
ECHO_OUTPUT_VARIABLE
ECHO_ERROR_VARIABLE
COMMAND_ERROR_IS_FATAL ANY
)
file(READ "${LLVM_SETUP_OUTPUT}" LLVM_SETUP_JSON)
string(JSON LLVM_INSTALL_PATH GET "${LLVM_SETUP_JSON}" install_path)
string(JSON LLVM_CMAKE_DIR GET "${LLVM_SETUP_JSON}" cmake_dir)
set(LLVM_INSTALL_PATH "${LLVM_INSTALL_PATH}" CACHE PATH "Path to LLVM installation" FORCE)
set(LLVM_CMAKE_DIR "${LLVM_CMAKE_DIR}" CACHE PATH "Path to LLVM CMake files" FORCE)
get_filename_component(LLVM_INSTALL_PATH "${LLVM_INSTALL_PATH}" ABSOLUTE)
if(NOT EXISTS "${LLVM_INSTALL_PATH}")
message(FATAL_ERROR "Error: The specified LLVM_INSTALL_PATH does not exist: ${LLVM_INSTALL_PATH}")
endif()
# set llvm include and lib path
add_library(llvm-libs INTERFACE IMPORTED)
# add to include directories
target_include_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/include")
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT WIN32)
target_link_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/lib")
target_link_libraries(llvm-libs INTERFACE
LLVMSupport
LLVMFrontendOpenMP
LLVMOption
LLVMTargetParser
clangAST
clangASTMatchers
clangBasic
clangDriver
clangFormat
clangFrontend
clangLex
clangSema
clangSerialization
clangTidy
clangTidyUtils
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
clangTooling
clangToolingCore
clangToolingInclusions
clangToolingInclusionsStdlib
clangToolingSyntax
)
else()
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}LLVM[a-zA-Z]*${CMAKE_STATIC_LIBRARY_SUFFIX}")
file(GLOB CLANG_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}clang[a-zA-Z]*${CMAKE_STATIC_LIBRARY_SUFFIX}")
# TODO: find a better way to find out whether zlib and zstd are needed
# Currently link if present in the LLVM lib directory
file(GLOB OTHER_REQUIRED_LIBS CONFIGURE_DEPENDS
"${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}z${CMAKE_STATIC_LIBRARY_SUFFIX}"
"${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}zstd${CMAKE_STATIC_LIBRARY_SUFFIX}"
)
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES} ${CLANG_LIBRARIES} ${OTHER_REQUIRED_LIBS})
target_compile_definitions(llvm-libs INTERFACE CLANG_BUILD_STATIC=1)
endif()
endfunction()

View File

@@ -1,217 +0,0 @@
include_guard()
include(${CMAKE_CURRENT_LIST_DIR}/github.cmake)
# Check if LLVM version is supported
function(check_llvm_version LOCAL_LLVM_VERSION LLVM_VERSION OUTPUT_VAR)
if((NOT DEFINED LOCAL_LLVM_VERSION) OR (LOCAL_LLVM_VERSION STREQUAL ""))
message(WARNING "LLVM version is not set.")
set(${OUTPUT_VAR} FALSE PARENT_SCOPE)
elseif(NOT (LOCAL_LLVM_VERSION VERSION_GREATER_EQUAL LLVM_VERSION))
message(WARNING "Unsupported LLVM version: ${LOCAL_LLVM_VERSION}. Only LLVM ${LLVM_VERSION} is supported.")
set(${OUTPUT_VAR} FALSE PARENT_SCOPE)
else()
set(${OUTPUT_VAR} TRUE PARENT_SCOPE)
endif()
endfunction()
# Look up LLVM version's corresponding commit SHA
function(lookup_commit LLVM_VERSION OUTPUT_VAR)
set(LLVM_TAG "llvmorg-${LLVM_VERSION}")
github_lookup_tag_commit("llvm" "llvm-project" ${LLVM_TAG} COMMIT_SHA)
set(${OUTPUT_VAR} ${COMMIT_SHA} PARENT_SCOPE)
endfunction()
# Fetch private Clang header files from LLVM source
function(fetch_private_clang_files LLVM_VERSION)
set(PRIVATE_CLANG_FILE_LIST
"Sema/CoroutineStmtBuilder.h"
"Sema/TypeLocBuilder.h"
"Sema/TreeTransform.h"
)
# Check if all files already exist
set(PRIVATE_FILE_EXISTS TRUE)
foreach(FILE ${PRIVATE_CLANG_FILE_LIST})
if(EXISTS "${LLVM_INSTALL_PATH}/include/clang/${FILE}")
message(STATUS "Private clang file found in LLVM installation: ${FILE}")
elseif(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/include/clang/${FILE}")
message(STATUS "Private clang file already exists: ${FILE}")
else()
set(PRIVATE_FILE_EXISTS FALSE)
break()
endif()
endforeach()
if(PRIVATE_FILE_EXISTS)
message(STATUS "All required private clang files already exist.")
return()
endif()
# Skip download in offline build mode
if(CLICE_OFFLINE_BUILD)
message(WARNING "CLICE_OFFLINE_BUILD is enabled, skipping private clang files download")
message(WARNING "Build may fail if required private headers are missing")
return()
endif()
message(WARNING "Required private clang files incomplete, fetching from llvm-project source...")
# Get the commit SHA for this LLVM version
lookup_commit(${LLVM_VERSION} LLVM_COMMIT)
if(LLVM_COMMIT STREQUAL "NOTFOUND")
message(WARNING "Failed to lookup commit for LLVM ${LLVM_VERSION}, skipping private clang files download")
return()
endif()
message(STATUS "LLVM ${LLVM_VERSION} corresponds to commit ${LLVM_COMMIT}")
# Download missing files
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/clang")
foreach(FILE ${PRIVATE_CLANG_FILE_LIST})
set(FILE_PATH "${CMAKE_CURRENT_BINARY_DIR}/include/clang/${FILE}")
if(NOT EXISTS "${FILE_PATH}")
message(STATUS "Downloading ${FILE}...")
file(DOWNLOAD "https://raw.githubusercontent.com/llvm/llvm-project/${LLVM_COMMIT}/clang/lib/${FILE}"
"${FILE_PATH}"
STATUS DOWNLOAD_STATUS
TLS_VERIFY ON)
list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
if(NOT STATUS_CODE EQUAL 0)
message(FATAL_ERROR "Failed to download private clang file: ${FILE}")
endif()
endif()
endforeach()
endfunction()
# Detect system-installed LLVM using llvm-config
function(detect_llvm OUTPUT_VAR)
find_program(LLVM_CONFIG_EXEC llvm-config)
if(NOT LLVM_CONFIG_EXEC)
set(${OUTPUT_VAR} "" PARENT_SCOPE)
return()
endif()
# Get LLVM version and paths
execute_process(
COMMAND "${LLVM_CONFIG_EXEC}" --version
OUTPUT_VARIABLE LLVM_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND "${LLVM_CONFIG_EXEC}" --prefix
OUTPUT_VARIABLE LLVM_INSTALL_PATH_DETECTED
OUTPUT_STRIP_TRAILING_WHITESPACE
)
execute_process(
COMMAND "${LLVM_CONFIG_EXEC}" --cmakedir
OUTPUT_VARIABLE LLVM_CMAKE_DIR_DETECTED
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Set cache variables
set(LLVM_INSTALL_PATH "${LLVM_INSTALL_PATH_DETECTED}" CACHE PATH "Path to LLVM installation" FORCE)
set(LLVM_CMAKE_DIR "${LLVM_CMAKE_DIR_DETECTED}" CACHE PATH "Path to LLVM CMake files" FORCE)
set(${OUTPUT_VAR} ${LLVM_VERSION} PARENT_SCOPE)
endfunction()
# Download and install prebuilt LLVM binaries with error checking
function(install_prebuilt_llvm LLVM_VERSION)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.llvm")
# Determine platform-specific package name
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(LLVM_BUILD_TYPE "debug")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(LLVM_BUILD_TYPE "releasedbg")
else()
set(LLVM_BUILD_TYPE "release-lto")
endif()
if(WIN32)
set(LLVM_PACKAGE "x64-windows-msvc-${LLVM_BUILD_TYPE}.7z")
elseif(APPLE)
set(LLVM_PACKAGE "arm64-macosx-apple-${LLVM_BUILD_TYPE}.tar.xz")
elseif(UNIX)
set(LLVM_PACKAGE "x86_64-linux-gnu-${LLVM_BUILD_TYPE}.tar.xz")
endif()
if(NOT LLVM_PACKAGE)
message(FATAL_ERROR "Unsupported platform or build type for prebuilt LLVM.")
endif()
set(DOWNLOAD_PATH "${CMAKE_CURRENT_BINARY_DIR}/${LLVM_PACKAGE}")
# Download if file does not exist
if(NOT EXISTS "${DOWNLOAD_PATH}")
message(STATUS "Downloading prebuilt LLVM package: ${LLVM_PACKAGE}")
set(DOWNLOAD_URL "https://github.com/clice-io/clice-llvm/releases/download/${LLVM_VERSION}/${LLVM_PACKAGE}")
file(DOWNLOAD "${DOWNLOAD_URL}"
"${DOWNLOAD_PATH}"
STATUS DOWNLOAD_STATUS
SHOW_PROGRESS
TLS_VERIFY ON)
list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE)
if(NOT STATUS_CODE EQUAL 0)
# Download failed, remove the incomplete file to force a fresh download next time
if(EXISTS "${DOWNLOAD_PATH}")
file(REMOVE "${DOWNLOAD_PATH}")
message(STATUS "Removed incomplete file: ${DOWNLOAD_PATH}")
endif()
message(FATAL_ERROR "Failed to download prebuilt LLVM package from ${DOWNLOAD_URL}.\nError: ${ERROR_MESSAGE}")
endif()
else()
message(STATUS "Prebuilt LLVM package already exists, skipping download.")
endif()
message(STATUS "Extracting LLVM package: ${LLVM_PACKAGE}")
execute_process(
COMMAND "${CMAKE_COMMAND}" -E tar xvf "${DOWNLOAD_PATH}"
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.llvm"
RESULT_VARIABLE TAR_RESULT
OUTPUT_QUIET
ERROR_QUIET
)
if(NOT TAR_RESULT EQUAL "0")
message(FATAL_ERROR "Failed to extract archive. The file may be corrupted or the tool is missing.")
endif()
# Set installation paths
set(LLVM_INSTALL_PATH "${CMAKE_CURRENT_BINARY_DIR}/.llvm" CACHE PATH "Path to LLVM installation" FORCE)
set(LLVM_CMAKE_DIR "${LLVM_INSTALL_PATH}/lib/cmake/llvm" CACHE PATH "Path to LLVM CMake files" FORCE)
message(STATUS "LLVM installation path set to: ${LLVM_INSTALL_PATH}")
endfunction()
# Main function to set up LLVM for the project
function(setup_llvm LLVM_VERSION)
if(NOT DEFINED LLVM_VERSION OR LLVM_VERSION STREQUAL "")
message(FATAL_ERROR "setup_llvm() requires a LLVM_VERSION argument (e.g., '21.1.4').")
endif()
# Use existing LLVM installation if path is already set
if(DEFINED LLVM_INSTALL_PATH AND NOT LLVM_INSTALL_PATH STREQUAL "")
message(STATUS "LLVM_INSTALL_PATH is set to ${LLVM_INSTALL_PATH}, using it directly.")
return()
endif()
set(LLVM_VERSION_OK false)
if (CMAKE_BUILD_TYPE STREQUAL "Release")
# Try to detect system LLVM
detect_llvm(LOCAL_LLVM_VERSION)
check_llvm_version("${LOCAL_LLVM_VERSION}" "${LLVM_VERSION}" LLVM_VERSION_OK)
endif()
# Download prebuilt LLVM if system version is not suitable
if(NOT LLVM_VERSION_OK)
message(WARNING "System LLVM not found, version mismatch or incompatible build type.")
message(WARNING "Downloading prebuilt LLVM ${LLVM_VERSION} ...")
install_prebuilt_llvm("${LLVM_VERSION}")
endif()
# Fetch required private Clang headers
fetch_private_clang_files("${LLVM_VERSION}")
endfunction()

View File

@@ -1,150 +1,56 @@
include_guard()
include(${CMAKE_CURRENT_LIST_DIR}/llvm_setup.cmake)
setup_llvm("21.1.4")
get_filename_component(LLVM_INSTALL_PATH "${LLVM_INSTALL_PATH}" ABSOLUTE)
if(NOT EXISTS "${LLVM_INSTALL_PATH}")
message(FATAL_ERROR "Error: The specified LLVM_INSTALL_PATH does not exist: ${LLVM_INSTALL_PATH}")
endif()
# set llvm include and lib path
add_library(llvm-libs INTERFACE IMPORTED)
# add to include directories
target_include_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/include")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_link_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/lib")
target_link_libraries(llvm-libs INTERFACE
LLVMSupport
LLVMFrontendOpenMP
LLVMOption
LLVMTargetParser
clangAST
clangASTMatchers
clangBasic
clangDriver
clangFormat
clangFrontend
clangLex
clangSema
clangSerialization
clangTidy
clangTidyUtils
# ALL_CLANG_TIDY_CHECKS
clangTidyAndroidModule
clangTidyAbseilModule
clangTidyAlteraModule
clangTidyBoostModule
clangTidyBugproneModule
clangTidyCERTModule
clangTidyConcurrencyModule
clangTidyCppCoreGuidelinesModule
clangTidyDarwinModule
clangTidyFuchsiaModule
clangTidyGoogleModule
clangTidyHICPPModule
clangTidyLinuxKernelModule
clangTidyLLVMModule
clangTidyLLVMLibcModule
clangTidyMiscModule
clangTidyModernizeModule
clangTidyObjCModule
clangTidyOpenMPModule
clangTidyPerformanceModule
clangTidyPortabilityModule
clangTidyReadabilityModule
clangTidyZirconModule
clangTooling
clangToolingCore
clangToolingInclusions
clangToolingInclusionsStdlib
clangToolingSyntax
)
else()
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/*${CMAKE_STATIC_LIBRARY_SUFFIX}")
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES})
target_compile_definitions(llvm-libs INTERFACE CLANG_BUILD_STATIC=1)
endif()
if(WIN32)
target_link_libraries(llvm-libs INTERFACE version ntdll)
endif()
include(${CMAKE_CURRENT_LIST_DIR}/llvm.cmake)
setup_llvm("21.1.4+r1")
# install dependencies
include(FetchContent)
if(WIN32)
set(NULL_DEVICE NUL)
else()
set(NULL_DEVICE /dev/null)
endif()
# libuv
FetchContent_Declare(
libuv
GIT_REPOSITORY https://github.com/libuv/libuv.git
GIT_TAG v1.x
)
if(NOT WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set(ASAN ON CACHE BOOL "Enable AddressSanitizer for libuv" FORCE)
endif()
set(LIBUV_BUILD_SHARED OFF CACHE BOOL "" FORCE)
set(LIBUV_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
# spdlog
FetchContent_Declare(
spdlog
GIT_REPOSITORY https://github.com/gabime/spdlog.git
GIT_TAG v1.15.3
GIT_SHALLOW TRUE
)
# tomlplusplus
FetchContent_Declare(
tomlplusplus
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
)
set(SPDLOG_USE_STD_FORMAT ON CACHE BOOL "" FORCE)
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
# croaring
FetchContent_Declare(
croaring
GIT_REPOSITORY https://github.com/RoaringBitmap/CRoaring.git
GIT_TAG v4.4.2
GIT_TAG v4.4.2
GIT_SHALLOW TRUE
)
set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "" FORCE)
set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
# flatbuffers
FetchContent_Declare(
flatbuffers
GIT_REPOSITORY https://github.com/google/flatbuffers.git
GIT_TAG v25.9.23
GIT_TAG v25.9.23
GIT_SHALLOW TRUE
)
set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers)
if(WIN32)
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()
if(NOT MSVC AND TARGET uv_a)
target_compile_options(uv_a PRIVATE
"-Wno-unused-function"
"-Wno-unused-variable"
"-Wno-unused-but-set-variable"
"-Wno-deprecated-declarations"
"-Wno-missing-braces"
)
endif()
target_compile_definitions(spdlog PUBLIC
SPDLOG_USE_STD_FORMAT=1
SPDLOG_NO_EXCEPTIONS=1
FetchContent_Declare(
eventide
GIT_REPOSITORY https://github.com/clice-io/eventide
GIT_TAG main
GIT_SHALLOW TRUE
)
set(ETD_ENABLE_ZEST ON)
set(ETD_ENABLE_TEST OFF)
set(ETD_SERDE_ENABLE_SIMDJSON ON)
set(ETD_SERDE_ENABLE_YYJSON ON)
set(ETD_SERDE_ENABLE_TOML ON)
set(ETD_ENABLE_EXCEPTIONS OFF)
set(ETD_ENABLE_RTTI OFF)
FetchContent_MakeAvailable(eventide spdlog croaring flatbuffers)

78
cmake/release.cmake Normal file
View File

@@ -0,0 +1,78 @@
include_guard()
set(CLICE_PACK_DIR "${PROJECT_BINARY_DIR}/pack")
set(CLICE_SYMBOL_DIR "${PROJECT_BINARY_DIR}/pack-symbol")
if(WIN32)
set(CLICE_ARCHIVE_EXT ".zip")
set(CLICE_SYMBOL_NAME "clice.pdb")
else()
set(CLICE_ARCHIVE_EXT ".tar.gz")
if(APPLE)
set(CLICE_SYMBOL_NAME "clice.dSYM")
else()
set(CLICE_SYMBOL_NAME "clice.debug")
endif()
endif()
if(WIN32)
add_custom_target(clice-strip ALL
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"$<TARGET_PDB_FILE:clice>"
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
DEPENDS clice
COMMENT "Collecting PDB for clice"
)
elseif(APPLE)
add_custom_target(clice-strip ALL
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
COMMAND dsymutil "$<TARGET_FILE:clice>" -o "${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
COMMAND strip -x "$<TARGET_FILE:clice>"
DEPENDS clice
COMMENT "Extracting dSYM and stripping clice"
)
else()
add_custom_target(clice-strip ALL
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
COMMAND ${CMAKE_OBJCOPY} --only-keep-debug "$<TARGET_FILE:clice>" "${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded "$<TARGET_FILE:clice>"
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink="${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "$<TARGET_FILE:clice>"
DEPENDS clice
COMMENT "Extracting debug symbols and stripping clice"
)
endif()
add_custom_target(clice-pack ALL
DEPENDS clice-strip copy_clang_resource
COMMAND ${CMAKE_COMMAND} -E rm -rf "${CLICE_PACK_DIR}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_PACK_DIR}/clice/bin"
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:clice>" "${CLICE_PACK_DIR}/clice/bin/"
COMMAND ${CMAKE_COMMAND} -E copy_directory "${LLVM_INSTALL_PATH}/lib/clang" "${CLICE_PACK_DIR}/clice/lib/clang"
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/docs/clice.toml" "${CLICE_PACK_DIR}/clice/"
COMMAND ${CMAKE_COMMAND}
-DOUTPUT="${PROJECT_BINARY_DIR}/clice${CLICE_ARCHIVE_EXT}"
-DWORK_DIR="${CLICE_PACK_DIR}"
-P "${PROJECT_SOURCE_DIR}/cmake/archive.cmake"
COMMENT "Packaging clice distribution"
)
if(APPLE)
set(CLICE_COPY_SYMBOL_CMD ${CMAKE_COMMAND} -E copy_directory
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "${CLICE_SYMBOL_DIR}/pack/${CLICE_SYMBOL_NAME}")
else()
set(CLICE_COPY_SYMBOL_CMD ${CMAKE_COMMAND} -E copy
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "${CLICE_SYMBOL_DIR}/pack/")
endif()
add_custom_target(clice-pack-symbol ALL
DEPENDS clice-strip
COMMAND ${CMAKE_COMMAND} -E rm -rf "${CLICE_SYMBOL_DIR}/pack"
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}/pack"
COMMAND ${CLICE_COPY_SYMBOL_CMD}
COMMAND ${CMAKE_COMMAND}
-DOUTPUT="${PROJECT_BINARY_DIR}/clice-symbol${CLICE_ARCHIVE_EXT}"
-DWORK_DIR="${CLICE_SYMBOL_DIR}/pack"
-P "${PROJECT_SOURCE_DIR}/cmake/archive.cmake"
COMMENT "Packaging clice debug symbols"
)

49
cmake/toolchain.cmake Normal file
View File

@@ -0,0 +1,49 @@
cmake_minimum_required(VERSION 3.30)
set(CMAKE_C_COMPILER clang CACHE STRING "")
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "")
find_program(LLVM_AR_PATH "llvm-ar")
if(LLVM_AR_PATH)
set(CMAKE_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
set(CMAKE_C_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
set(CMAKE_CXX_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
endif()
find_program(LLVM_RANLIB_PATH "llvm-ranlib")
if(LLVM_RANLIB_PATH)
set(CMAKE_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
set(CMAKE_C_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
set(CMAKE_CXX_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
endif()
find_program(LLVM_NM_PATH "llvm-nm")
if(LLVM_NM_PATH)
set(CMAKE_NM "${LLVM_NM_PATH}" CACHE FILEPATH "")
endif()
find_program(LLVM_RC_PATH "llvm-rc")
if(LLVM_RC_PATH)
set(CMAKE_RC_COMPILER "${LLVM_RC_PATH}" CACHE FILEPATH "")
endif()
if(WIN32)
find_program(SCCACHE_PATH "sccache")
if(SCCACHE_PATH)
set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "")
set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "")
endif()
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
else()
find_program(CCACHE_PATH "ccache")
if(CCACHE_PATH)
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PATH}" CACHE FILEPATH "")
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}" CACHE FILEPATH "")
endif()
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
endif()

View File

@@ -1,55 +0,0 @@
include_guard()
# Download content from URL directly into a string.
# Usage: download_to_string(URL OUTPUT_VAR RESULT_VAR)
# - URL: The URL to download from.
# - OUTPUT_VAR: The variable to store the downloaded content.
# - RESULT_VAR: The variable to store the status code (0 on success).
function(download_to_string URL OUTPUT_VAR RESULT_VAR)
file(DOWNLOAD
${URL}
${CMAKE_CURRENT_BINARY_DIR}/temp_download_file
STATUS DOWNLOAD_STATUS
TLS_VERIFY ON
TIMEOUT 10 # Set a reasonable timeout
)
list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
if(NOT STATUS_CODE EQUAL 0)
list(GET DOWNLOAD_STATUS 1 ERROR_MSG)
message(WARNING "Failed to download from ${URL}: ${ERROR_MSG}")
set(${RESULT_VAR} ${STATUS_CODE} PARENT_SCOPE)
return()
endif()
file(READ ${CMAKE_CURRENT_BINARY_DIR}/temp_download_file DOWNLOADED_CONTENT)
file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/temp_download_file)
set(${OUTPUT_VAR} ${DOWNLOADED_CONTENT} PARENT_SCOPE)
set(${RESULT_VAR} 0 PARENT_SCOPE)
endfunction()
# Parse a nested JSON field from a string.
# Usage: parse_json_field_from_string(JSON_STRING PARENT_KEY FIELD_NAME OUTPUT_VAR)
# - JSON_STRING: A variable containing the JSON content as a string.
# - PARENT_KEY: A key string to get from the JSON content.
# - FIELD_NAME: The field name to extract.
# - OUTPUT_VAR: The variable to store the extracted value.
function(parse_json_field_from_string JSON_CONTENT)
# Get the last argument as the output variable name
set(ARGS "${ARGN}")
list(GET ARGS -1 OUTPUT_VAR)
list(REMOVE_AT ARGS -1)
string(JSON FIELD_VALUE GET "${JSON_CONTENT}" ${ARGS})
if(NOT FIELD_VALUE)
string(JOIN " " FIELD_PATH_STR ${ARGS})
message(WARNING "Could not parse field '${FIELD_PATH_STR}' from JSON.")
set(${OUTPUT_VAR} "NOTFOUND" PARENT_SCOPE)
return()
endif()
set(${OUTPUT_VAR} ${FIELD_VALUE} PARENT_SCOPE)
endfunction()

View File

@@ -1,6 +0,0 @@
xmake,3.0.2
cmake,3.31.8
python,3.13
gcc,14
clang,20
msvc

83
config/llvm-manifest.json Normal file
View File

@@ -0,0 +1,83 @@
[
{
"version": "21.1.4+r1",
"filename": "arm64-macos-clang-debug-asan.tar.xz",
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
"lto": false,
"asan": true,
"platform": "macosx",
"build_type": "Debug"
},
{
"version": "21.1.4+r1",
"filename": "arm64-macos-clang-releasedbg-lto.tar.xz",
"sha256": "300455b169448f9f01ae95e3bc269f489558a4ca3955e3032171cc75feca0e30",
"lto": true,
"asan": false,
"platform": "macosx",
"build_type": "RelWithDebInfo"
},
{
"version": "21.1.4+r1",
"filename": "arm64-macos-clang-releasedbg.tar.xz",
"sha256": "9abfc6cd65b957d734ffb97610a634fb4a66d3fbe0fcfb5a1c9124ef693c1495",
"lto": false,
"asan": false,
"platform": "macosx",
"build_type": "RelWithDebInfo"
},
{
"version": "21.1.4+r1",
"filename": "x64-linux-gnu-debug-asan.tar.xz",
"sha256": "c1ad3ec476911596a842ac67dd9c9c9475ce9f0a77b81101d3c801840292e7bc",
"lto": false,
"asan": true,
"platform": "linux",
"build_type": "Debug"
},
{
"version": "21.1.4+r1",
"filename": "x64-linux-gnu-releasedbg-lto.tar.xz",
"sha256": "8a869c2184d139dbba704e2d712e7a68336458ad2d70622b3eb906c3e3511e54",
"lto": true,
"asan": false,
"platform": "linux",
"build_type": "RelWithDebInfo"
},
{
"version": "21.1.4+r1",
"filename": "x64-linux-gnu-releasedbg.tar.xz",
"sha256": "552bab86f715d4f2c027f07eaaf5b3d6b8e430af0b74b470142f3f00da4feec6",
"lto": false,
"asan": false,
"platform": "linux",
"build_type": "RelWithDebInfo"
},
{
"version": "21.1.4+r1",
"filename": "x64-windows-msvc-debug-asan.tar.xz",
"sha256": "093667a493d336c22ff3c604c5f1fea2a7d2c927c1179cec44e9a03726906ac1",
"lto": false,
"asan": true,
"platform": "windows",
"build_type": "Debug"
},
{
"version": "21.1.4+r1",
"filename": "x64-windows-msvc-releasedbg-lto.tar.xz",
"sha256": "010539e85621dc3c6ecf359d899feb4075aeca5d0bba6625cdbec0e570e79129",
"lto": true,
"asan": false,
"platform": "windows",
"build_type": "RelWithDebInfo"
},
{
"version": "21.1.4+r1",
"filename": "x64-windows-msvc-releasedbg.tar.xz",
"sha256": "f473c09fbea10053fac00be409d75dc228d4a38bcbc5e4aeb58b56a4b0dde78e",
"lto": false,
"asan": false,
"platform": "windows",
"build_type": "RelWithDebInfo"
}
]

View File

@@ -1,66 +0,0 @@
[
{
"platform": "Windows",
"build_type": "Release",
"is_lto": false,
"filename": "x64-windows-msvc-releasedbg.7z",
"version": "21.1.4",
"sha256": "02634ff12194994d93e9c76902866c03516c836ab7b55952933fd9ebcf039664"
},
{
"platform": "Windows",
"build_type": "Release",
"is_lto": true,
"filename": "x64-windows-msvc-releasedbg-lto.7z",
"version": "21.1.4",
"sha256": "7792cfd1e2d9240b49e3db81a6a04f33cbc44afa91e9a637c0490c28d4eee95c"
},
{
"platform": "Linux",
"build_type": "Debug",
"is_lto": false,
"filename": "x86_64-linux-gnu-debug.tar.xz",
"version": "21.1.4",
"sha256": "9c7b98e198ce1c5611e153c3602fb1dc03912f230bded99baaaa56ac1ea21cb4"
},
{
"platform": "Linux",
"build_type": "Release",
"is_lto": false,
"filename": "x86_64-linux-gnu-releasedbg.tar.xz",
"version": "21.1.4",
"sha256": "99269acd2d9c5debf30062bf57d7bfd41154f2d0baee94abdb56b421c8e5b92c"
},
{
"platform": "Linux",
"build_type": "Release",
"is_lto": true,
"filename": "x86_64-linux-gnu-releasedbg-lto.tar.xz",
"version": "21.1.4",
"sha256": "e5a6c567e30cbe51e4b98151f52071d0013839e7b5eabe7a2a2767c8234d06b2"
},
{
"platform": "macosx",
"build_type": "Debug",
"is_lto": false,
"filename": "arm64-macosx-apple-debug.tar.xz",
"version": "21.1.4",
"sha256": "ae5509d8d1cfc7441c34f6ee4fcac6eb31aae30d8b54a8f9ba3e9948a22fbc8e"
},
{
"platform": "macosx",
"build_type": "Release",
"is_lto": false,
"filename": "arm64-macosx-apple-releasedbg.tar.xz",
"version": "21.1.4",
"sha256": "dc308b0057f472e82c2eb14b300db6ee58fb4160f3428948b5a9dcae931a3378"
},
{
"platform": "macosx",
"build_type": "Release",
"is_lto": true,
"filename": "arm64-macosx-apple-releasedbg-lto.tar.xz",
"version": "21.1.4",
"sha256": "c92d0323ff83e678fec7496c8b024a64b1731c3840752be9a4ade3b99dfd52ff"
}
]

View File

@@ -1,265 +0,0 @@
# build with multi-stage for cache efficiency
FROM ubuntu:24.04 AS basic-tools
# allow build script to bind-mount project source into build container (host path)
ARG BUILD_SRC
# set non-interactive frontend to avoid prompts
ENV DEBIAN_FRONTEND=noninteractive
# ensure user-local bin is on PATH for non-apt installs (xmake, uv, python)
ENV PATH="/root/.local/bin:${PATH}"
# install basic tools
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
# TODO: support more cache for python, xmake installation
# TODO: check why cache doesn't work after add-apt-repository, may we change it to cache?
bash -eux - <<'BASH'
set -e
apt update
# first install minimal apt prerequisites
# software-properties-common for add-apt-repository
# gnupg for gpg to verify cmake installer
# curl, git for downloading sources
# xz-utils, unzip for extracting archives
# make for xmake installation
apt install -y --no-install-recommends \
software-properties-common \
curl \
gnupg \
git \
xz-utils \
unzip \
make
# gcc, llvm PPA
add-apt-repository -y ppa:ubuntu-toolchain-r/ppa
apt update
BASH
# Compiler stage
FROM basic-tools AS compiler-stage
# passed from build arg
ARG COMPILER
# copy instead of bind-mount, to avoid docker build cache invalidation
COPY config/default-toolchain-version /clice/config/default-toolchain-version
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
bash -eux - <<'BASH'
set -e
# Always install libstdc++ development files, required for both gcc and clang to link against libstdc++
GCC_VERSION=$(grep -E '^gcc,' /clice/config/default-toolchain-version | cut -d',' -f2)
apt install -y --no-install-recommends "libstdc++-${GCC_VERSION}-dev"
if [ "$COMPILER" = "gcc" ]; then
apt install -y --no-install-recommends "gcc-${GCC_VERSION}" "g++-${GCC_VERSION}"
update-alternatives --install /usr/bin/cc cc "/usr/bin/gcc-${GCC_VERSION}" 100
update-alternatives --install /usr/bin/gcc gcc "/usr/bin/gcc-${GCC_VERSION}" 100
update-alternatives --install /usr/bin/c++ c++ "/usr/bin/g++-${GCC_VERSION}" 100
update-alternatives --install /usr/bin/g++ g++ "/usr/bin/g++-${GCC_VERSION}" 100
elif [ "$COMPILER" = "clang" ]; then
CLANG_VERSION=$(grep -E '^clang,' /clice/config/default-toolchain-version | cut -d',' -f2)
# install clang toolchain, libstdc++ is already installed
apt install -y --no-install-recommends "clang-${CLANG_VERSION}" "clang-tools-${CLANG_VERSION}" "lld-${CLANG_VERSION}" "libclang-rt-${CLANG_VERSION}-dev"
update-alternatives --install /usr/bin/clang clang "/usr/bin/clang-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/clang++ clang++ "/usr/bin/clang++-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/c++ c++ "/usr/bin/clang++-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/cc cc "/usr/bin/clang-${CLANG_VERSION}" 100
update-alternatives --install /usr/bin/ld ld "/usr/bin/lld-${CLANG_VERSION}" 100
else
echo "Error: Unsupported compiler '$COMPILER'. Use 'gcc' or 'clang'." >&2; exit 1
fi
BASH
FROM compiler-stage AS build-tool-stage
ARG XMAKE_CACHE_DIR="/docker-build-cache/xmake"
ARG CMAKE_CACHE_DIR="/docker-build-cache/cmake"
ARG UV_CACHE_DIR="/var/cache/uv"
ENV XMAKE_CACHE_DIR=${XMAKE_CACHE_DIR}
ENV CMAKE_CACHE_DIR=${CMAKE_CACHE_DIR}
ENV UV_CACHE_DIR=${UV_CACHE_DIR}
COPY ./pyproject.toml /clice/pyproject.toml
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=${XMAKE_CACHE_DIR},sharing=locked \
--mount=type=cache,target=${CMAKE_CACHE_DIR},sharing=locked \
--mount=type=cache,target=${UV_CACHE_DIR},sharing=locked \
bash -eux - <<'BASH'
install_xmake() {
set -e
XMAKE_VERSION=$(grep -E '^xmake,' /clice/config/default-toolchain-version | cut -d',' -f2)
XMAKE_BASE_URL="https://github.com/xmake-io/xmake/releases/download/v${XMAKE_VERSION}"
XMAKE_FILENAME="xmake-bundle-v${XMAKE_VERSION}.linux.x86_64"
XMAKE_CACHED_FILE="${XMAKE_CACHE_DIR}/${XMAKE_FILENAME}"
if [ ! -f "${XMAKE_CACHED_FILE}" ] ; then
rm -f "${XMAKE_CACHE_DIR}/*"
curl -fsSL --retry 3 -o "${XMAKE_CACHED_FILE}" "${XMAKE_BASE_URL}/${XMAKE_FILENAME}"
fi
XMAKE_INSTALL_DIR="/usr/bin"
XMAKE_INSTALLED_FILE="${XMAKE_INSTALL_DIR}/${XMAKE_FILENAME}"
cp "${XMAKE_CACHED_FILE}" "${XMAKE_INSTALLED_FILE}"
chmod +x "${XMAKE_INSTALLED_FILE}"
update-alternatives --install /usr/bin/xmake xmake "${XMAKE_INSTALLED_FILE}" 100
echo "export XMAKE_ROOT=y" >> ~/.bashrc
}
# Attention: DO NOT install cmake via PPA with apt, which would have to install required build-essential compiler tool chain
# We SHOULD NOT install another compiler toolchain, which could cause a lot trouble
# And we should not install compiler toolchain away from compiler stage
# So we install cmake from official installer script, and cache the downloaded files
install_cmake() {
set -e
# cached downloads live under /docker-build-cache/cmake (BuildKit cache mount)
CMAKE_VERSION=$(grep -E '^cmake,' /clice/config/default-toolchain-version | cut -d',' -f2)
ARCH="x86_64"
BASE_URL="https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}"
INSTALLER_FILENAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh"
SHA_FILENAME="cmake-${CMAKE_VERSION}-SHA-256.txt"
ASC_FILENAME="${SHA_FILENAME}.asc"
INSTALLER_PATH="${CMAKE_CACHE_DIR}/${INSTALLER_FILENAME}"
SHA_PATH="${CMAKE_CACHE_DIR}/${SHA_FILENAME}"
ASC_PATH="${CMAKE_CACHE_DIR}/${ASC_FILENAME}"
verify_cmake_installer() {
if ! gpg --verify "${ASC_PATH}" "${SHA_PATH}"; then
echo "Signature verification failed for ${SHA_FILENAME}." >&2
return 1
fi
local expected_hash
expected_hash=$(grep "${INSTALLER_FILENAME}" "${SHA_PATH}" | awk '{print $1}')
local actual_hash
actual_hash=$(sha256sum "${INSTALLER_PATH}" | awk '{print $1}')
if [ "${expected_hash}" != "${actual_hash}" ]; then
echo "Checksum mismatch for ${INSTALLER_FILENAME}." >&2
return 1
fi
echo "Checksum for ${INSTALLER_FILENAME} is valid."
return 0
}
gpg --keyserver keys.openpgp.org --recv-keys C6C265324BBEBDC350B513D02D2CEF1034921684
if [ ! -f "${INSTALLER_PATH}" ] || ! verify_cmake_installer; then
rm -f "${CMAKE_CACHE_DIR}/*"
curl -fsSL --retry 3 -o "${INSTALLER_PATH}" "${BASE_URL}/${INSTALLER_FILENAME}"
curl -fsSL --retry 3 -o "${SHA_PATH}" "${BASE_URL}/${SHA_FILENAME}"
curl -fsSL --retry 3 -o "${ASC_PATH}" "${BASE_URL}/${ASC_FILENAME}"
if ! verify_cmake_installer; then
echo "Verification of the downloaded installer failed. Cleaning cache." >&2
rm -f "${CMAKE_CACHE_DIR}/*"
exit 1
fi
fi
sh "${INSTALLER_PATH}" --skip-license --prefix=/usr/local
}
install_python() {
set -e
PYTHON_VERSION=$(grep -E '^python,' /clice/config/default-toolchain-version | cut -d',' -f2)
curl -LsSf https://astral.sh/uv/install.sh | sh
uv python install "${PYTHON_VERSION}"
uv sync
}
do_install() {
set -e
cd /clice
export PATH="/root/.local/bin:${PATH}"
echo "export XMAKE_ROOT=y" >> ~/.bashrc
install_cmake &
install_xmake &
install_python &
for job in $(jobs -p); do
wait $job || exit 1
done
}
do_install
BASH
# download compile dependencies
FROM build-tool-stage AS dependency-cache-stage
# passed from build arg
# "lto" or "non_lto"
ARG BUILD_SRC
# ARG LTO_TYPE=""
RUN --mount=type=bind,src=./,target=/clice,rw \
bash -eux - <<'BASH'
# cache_xmake_packages() {
# set -e
# export PATH="/root/.local/bin:${PATH}"
# export XMAKE_ROOT=y
# LTO_FLAG=""
# if [ "$LTO_TYPE" = "lto" ]; then
# LTO_FLAG="--release"
# fi
# xmake f -y -v --mode=release ${LTO_FLAG}
# xmake f -y -v --mode=debug ${LTO_FLAG}
# }
do_prepare_dependency() {
set -e
cd /clice
# cache_xmake_packages &
for job in $(jobs -p); do
wait $job || exit 1
done
}
do_prepare_dependency
BASH
FROM dependency-cache-stage AS final
RUN bash -eux - <<'BASH'
set -e
# clice is mounted here, so remove everything to reduce image size
rm -rf /clice
# disable git exception in cmake build when Fetch-Content
git config --global --add safe.directory '*'
BASH
WORKDIR /clice
CMD ["/bin/bash"]

View File

@@ -1,54 +0,0 @@
#!/bin/bash
set -e
# Save original working directory and switch to project root
ORIG_PWD="$(pwd)"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "${SCRIPT_DIR}"
cd "${SCRIPT_DIR}/../.."
PROJECT_ROOT="$(pwd)"
trap 'cd "${ORIG_PWD}"' EXIT
# default configurations
COMPILER="clang"
DOCKERFILE_PATH="docker/linux/Dockerfile"
usage() {
cat <<EOF
Usage: $0 [--compiler <gcc|clang>]
Defaults:
--compiler ${COMPILER}
EOF
}
# parse command line arguments
while [ "$#" -gt 0 ]; do
case "$1" in
--compiler)
COMPILER="$2"; shift 2;;
-h|--help)
usage; exit 0;;
*)
echo "Unknown parameter: $1" >&2; usage; exit 1;;
esac
done
IMAGE_TAG="linux-${COMPILER}"
IMAGE_NAME="clice-io/clice-dev:${IMAGE_TAG}"
echo "==========================================="
echo "Building image: ${IMAGE_NAME}"
echo "Compiler: ${COMPILER}"
echo "Dockerfile: ${DOCKERFILE_PATH}"
echo "==========================================="
# build the docker image with specified arguments
# must run in clice root dir, so that we can mount the project in docker file to acquire essential files
docker buildx build --progress=plain -t "${IMAGE_NAME}" \
--build-arg COMPILER="${COMPILER}" \
--build-arg BUILD_SRC="${PROJECT_ROOT}" \
-f "${DOCKERFILE_PATH}" .
echo "Build complete. Image:${IMAGE_NAME}"

View File

@@ -1,87 +0,0 @@
#!/bin/bash
set -e
# Save original working directory and switch to project root
ORIG_PWD="$(pwd)"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "${SCRIPT_DIR}"
cd "${SCRIPT_DIR}/../.."
PROJECT_ROOT="$(pwd)"
trap 'cd "${ORIG_PWD}"' EXIT
# default configurations
COMPILER="clang"
RESET="false"
usage() {
cat <<EOF
Usage: $0 [--compiler <gcc|clang>] [--reset]
Defaults:
--compiler ${COMPILER}
--reset (re-create the container)
EOF
}
# parse command line arguments
while [ "$#" -gt 0 ]; do
case "$1" in
--compiler)
COMPILER="$2"; shift 2;;
--reset)
RESET="true"; shift 1;;
-h|--help)
usage; exit 0;;
*) echo "Unknown parameter: $1"; usage; exit 1;;
esac
done
IMAGE_TAG="linux-${COMPILER}"
IMAGE_NAME="clice-io/clice-dev:${IMAGE_TAG}"
CONTAINER_NAME="clice-dev-linux-${COMPILER}"
# If the image doesn't exist, build it automatically by invoking build.sh
if ! docker image inspect "${IMAGE_NAME}" >/dev/null 2>&1; then
echo "Image ${IMAGE_NAME} not found, invoking build.sh to create it..."
./docker/linux/build.sh --compiler "${COMPILER}"
fi
# Handle --reset: remove the existing container if it exists
if [ "${RESET}" = "true" ]; then
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Resetting container: stopping and removing existing container ${CONTAINER_NAME}..."
docker stop "${CONTAINER_NAME}" >/dev/null 2>&1 || true
docker rm "${CONTAINER_NAME}" >/dev/null 2>&1
echo "Container ${CONTAINER_NAME} has been removed."
else
echo "Container ${CONTAINER_NAME} does not exist, no need to reset."
fi
exit 0
fi
CONTAINER_WORKDIR="/clice"
# Check if the container exists
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "==========================================="
echo "Attaching to existing container: ${CONTAINER_NAME}"
echo "From image: ${IMAGE_NAME}"
echo "Project mount: ${PROJECT_ROOT} -> ${CONTAINER_WORKDIR}"
echo "==========================================="
docker start "${CONTAINER_NAME}" >/dev/null
docker exec -it -w "${CONTAINER_WORKDIR}" "${CONTAINER_NAME}" /bin/bash
exit 0
fi
DOCKER_RUN_ARGS=(-it -w "${CONTAINER_WORKDIR}")
DOCKER_RUN_ARGS+=(--name "${CONTAINER_NAME}")
DOCKER_RUN_ARGS+=(--mount "type=bind,src=${PROJECT_ROOT},target=${CONTAINER_WORKDIR}")
echo "==========================================="
echo "Creating and running new container: ${CONTAINER_NAME}"
echo "From image: ${IMAGE_NAME}"
echo "Project mount: ${PROJECT_ROOT} -> ${CONTAINER_WORKDIR}"
echo "==========================================="
docker run "${DOCKER_RUN_ARGS[@]}" "${IMAGE_NAME}"

View File

@@ -1,40 +1,38 @@
import { defineConfig } from 'vitepress'
import { genSidebar } from './theme/sidebar'
import { defineConfig } from "vitepress";
import { genSidebar } from "./theme/sidebar";
// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "clice",
description: "a powerful and modern C++ language server",
cleanUrls: true,
base: '/',
rewrites: {
'en/:rest*': ':rest*',
},
locales: {
root: { label: 'English' },
zh: { label: '简体中文' },
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: 'Home', link: '/' },
],
sidebar: {
"/zh/": [
genSidebar('zh', 'design', { title: 'Design' }),
genSidebar('zh', 'dev', { title: 'Development' }),
genSidebar('zh', 'guide', { title: 'Guide' }),
],
"/": [
genSidebar('en', 'design', { title: 'Design' }),
genSidebar('en', 'dev', { title: 'Development' }),
genSidebar('en', 'guide', { title: 'Guide' }),
],
title: "clice",
description: "a powerful and modern C++ language server",
cleanUrls: true,
base: "/clice/",
rewrites: {
"en/:rest*": ":rest*",
},
socialLinks: [
{ icon: 'discord', link: 'https://discord.gg/PA3UxW2VA3' },
{ icon: 'github', link: 'https://github.com/clice-io/clice' },
],
outline: 'deep',
},
})
locales: {
root: { label: "English" },
zh: { label: "简体中文" },
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [{ text: "Home", link: "/" }],
sidebar: {
"/zh/": [
genSidebar("zh", "design", { title: "Design" }),
genSidebar("zh", "dev", { title: "Development" }),
genSidebar("zh", "guide", { title: "Guide" }),
],
"/": [
genSidebar("en", "design", { title: "Design" }),
genSidebar("en", "dev", { title: "Development" }),
genSidebar("en", "guide", { title: "Guide" }),
],
},
socialLinks: [
{ icon: "discord", link: "https://discord.gg/PA3UxW2VA3" },
{ icon: "github", link: "https://github.com/clice-io/clice" },
],
outline: "deep",
},
});

View File

@@ -1,17 +1,17 @@
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import './style.css'
import { h } from "vue";
import type { Theme } from "vitepress";
import DefaultTheme from "vitepress/theme";
import "./style.css";
export default {
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
})
},
enhanceApp({ app, router, siteData }) {
// ...
}
} satisfies Theme
extends: DefaultTheme,
Layout: () => {
return h(DefaultTheme.Layout, null, {
// https://vitepress.dev/guide/extending-default-theme#layout-slots
});
},
enhanceApp({ app, router, siteData }) {
// ...
},
} satisfies Theme;

View File

@@ -1,45 +1,45 @@
import fs from 'fs'
import path from 'path'
import { DefaultTheme } from 'vitepress'
import fs from "fs";
import path from "path";
import { DefaultTheme } from "vitepress";
export const genSidebar = (
lang: string,
dirPath: string,
options: {
title: string
collapsible?: boolean
ignore?: string[]
}
title: string;
collapsible?: boolean;
ignore?: string[];
},
): DefaultTheme.SidebarItem => {
const sidebarPath = path.resolve(process.cwd(), lang, dirPath)
const ignore = options.ignore || ['index.md']
const sidebarPath = path.resolve(process.cwd(), lang, dirPath);
const ignore = options.ignore || ["index.md"];
const files = fs
.readdirSync(sidebarPath)
.filter((file) => file.endsWith('.md') && !ignore.includes(file))
.filter((file) => file.endsWith(".md") && !ignore.includes(file));
const items = files.map((file) => {
const content = fs.readFileSync(path.resolve(sidebarPath, file), 'utf-8')
const match = content.match(/^#\s+(.*)/)
const title = match ? match[1] : file.replace('.md', '')
const content = fs.readFileSync(path.resolve(sidebarPath, file), "utf-8");
const match = content.match(/^#\s+(.*)/);
const title = match ? match[1] : file.replace(".md", "");
let prefix = '/';
if (lang != 'en') {
let prefix = "/";
if (lang != "en") {
prefix += lang;
prefix += '/';
prefix += "/";
}
return {
text: title,
/// Make sure link for en is actually root, beacuse Github Pages
/// doesn't support redirect url.
link: `${prefix}${dirPath}/${file.replace('.md', '')}`
}
})
link: `${prefix}${dirPath}/${file.replace(".md", "")}`,
};
});
return {
text: options.title,
collapsed: options.collapsible || false,
items
}
}
items,
};
};

View File

@@ -44,30 +44,30 @@
* -------------------------------------------------------------------------- */
:root {
--vp-c-default-1: var(--vp-c-gray-1);
--vp-c-default-2: var(--vp-c-gray-2);
--vp-c-default-3: var(--vp-c-gray-3);
--vp-c-default-soft: var(--vp-c-gray-soft);
--vp-c-default-1: var(--vp-c-gray-1);
--vp-c-default-2: var(--vp-c-gray-2);
--vp-c-default-3: var(--vp-c-gray-3);
--vp-c-default-soft: var(--vp-c-gray-soft);
--vp-c-brand-1: var(--vp-c-indigo-1);
--vp-c-brand-2: var(--vp-c-indigo-2);
--vp-c-brand-3: var(--vp-c-indigo-3);
--vp-c-brand-soft: var(--vp-c-indigo-soft);
--vp-c-brand-1: var(--vp-c-indigo-1);
--vp-c-brand-2: var(--vp-c-indigo-2);
--vp-c-brand-3: var(--vp-c-indigo-3);
--vp-c-brand-soft: var(--vp-c-indigo-soft);
--vp-c-tip-1: var(--vp-c-brand-1);
--vp-c-tip-2: var(--vp-c-brand-2);
--vp-c-tip-3: var(--vp-c-brand-3);
--vp-c-tip-soft: var(--vp-c-brand-soft);
--vp-c-tip-1: var(--vp-c-brand-1);
--vp-c-tip-2: var(--vp-c-brand-2);
--vp-c-tip-3: var(--vp-c-brand-3);
--vp-c-tip-soft: var(--vp-c-brand-soft);
--vp-c-warning-1: var(--vp-c-yellow-1);
--vp-c-warning-2: var(--vp-c-yellow-2);
--vp-c-warning-3: var(--vp-c-yellow-3);
--vp-c-warning-soft: var(--vp-c-yellow-soft);
--vp-c-warning-1: var(--vp-c-yellow-1);
--vp-c-warning-2: var(--vp-c-yellow-2);
--vp-c-warning-3: var(--vp-c-yellow-3);
--vp-c-warning-soft: var(--vp-c-yellow-soft);
--vp-c-danger-1: var(--vp-c-red-1);
--vp-c-danger-2: var(--vp-c-red-2);
--vp-c-danger-3: var(--vp-c-red-3);
--vp-c-danger-soft: var(--vp-c-red-soft);
--vp-c-danger-1: var(--vp-c-red-1);
--vp-c-danger-2: var(--vp-c-red-2);
--vp-c-danger-3: var(--vp-c-red-3);
--vp-c-danger-soft: var(--vp-c-red-soft);
}
/**
@@ -75,15 +75,15 @@
* -------------------------------------------------------------------------- */
:root {
--vp-button-brand-border: transparent;
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-brand-3);
--vp-button-brand-hover-border: transparent;
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
--vp-button-brand-active-border: transparent;
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-c-brand-1);
--vp-button-brand-border: transparent;
--vp-button-brand-text: var(--vp-c-white);
--vp-button-brand-bg: var(--vp-c-brand-3);
--vp-button-brand-hover-border: transparent;
--vp-button-brand-hover-text: var(--vp-c-white);
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
--vp-button-brand-active-border: transparent;
--vp-button-brand-active-text: var(--vp-c-white);
--vp-button-brand-active-bg: var(--vp-c-brand-1);
}
/**
@@ -91,31 +91,23 @@
* -------------------------------------------------------------------------- */
:root {
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(
120deg,
#bd34fe 30%,
#41d1ff
);
--vp-home-hero-name-color: transparent;
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe 30%, #41d1ff);
--vp-home-hero-image-background-image: linear-gradient(
-45deg,
#bd34fe 50%,
#47caff 50%
);
--vp-home-hero-image-filter: blur(44px);
--vp-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%);
--vp-home-hero-image-filter: blur(44px);
}
@media (min-width: 640px) {
:root {
--vp-home-hero-image-filter: blur(56px);
}
:root {
--vp-home-hero-image-filter: blur(56px);
}
}
@media (min-width: 960px) {
:root {
--vp-home-hero-image-filter: blur(68px);
}
:root {
--vp-home-hero-image-filter: blur(68px);
}
}
/**
@@ -123,10 +115,10 @@
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-tip-border: transparent;
--vp-custom-block-tip-text: var(--vp-c-text-1);
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-border: transparent;
--vp-custom-block-tip-text: var(--vp-c-text-1);
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
}
/**
@@ -134,5 +126,5 @@
* -------------------------------------------------------------------------- */
.DocSearch {
--docsearch-primary-color: var(--vp-c-brand-1) !important;
--docsearch-primary-color: var(--vp-c-brand-1) !important;
}

View File

@@ -1,4 +1,4 @@
### clice configuration
## # clice configuration
# This section outlines the supported built-in variables for clice.
# These variables can be referenced in strings using the syntax `${var}`.
@@ -9,47 +9,39 @@
# - `${workspace}`: The workspace directory provided by the client.
[project]
# Enable experimental clang-tidy diagnostics.
# This feature is tracked in https://github.com/clice-project/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.
max_active_file = 8
# Directory for storing PCH and PCM files.
cache_dir = "${workspace}/.clice/cache"
# Directory for storing index files.
index_dir = "${workspace}/.clice/index"
logging_dir = "${workspace}/.clice/logging"
# Compile commands directories to search for compile_commands.json files.
compile_commands_dirs = ["${workspace}/build"]
# Enable experimental clang-tidy diagnostics.
# This feature is tracked in https://github.com/clice-project/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.
max_active_file = 8
# Directory for storing PCH and PCM files.
cache_dir = "${workspace}/.clice/cache"
# Directory for storing index files.
index_dir = "${workspace}/.clice/index"
logging_dir = "${workspace}/.clice/logs"
# Compile commands files or directories to search for compile_commands.json files.
compile_commands_paths = ["${workspace}/build"]
# Control the behavior for specific files. Note that Clice matches rules
# in order. If you want to add your own rules, either delete this rule
# or insert your rule before it.
[[rules]]
# Files matching the specified pattern will have this rule applied.
#
# Patterns can use the following syntax:
# - `*`: Matches one or more characters in a path segment.
# - `?`: Matches a single character in a path segment.
# - `**`: Matches any number of path segments, including none.
# - `{}`: Groups conditions (e.g., `**/*.{ts,js}` matches all TypeScript
# and JavaScript files).
# - `[]`: Declares a range of characters to match in a path segment
# (e.g., `example.[0-9]` matches `example.0`, `example.1`, etc.).
# - `[!...]`: Negates a range of characters to match in a path segment
# (e.g., `example.[!0-9]` matches `example.a`, `example.b`, but not `example.0`).
patterns = ["**/*"]
# Commands to append to the original command list (e.g., ["-std=c++17"]).
append = []
# Commands to remove from the original command list.
remove = []
# Files matching the specified pattern will have this rule applied.
#
# Patterns can use the following syntax:
# - `*`: Matches one or more characters in a path segment.
# - `?`: Matches a single character in a path segment.
# - `**`: Matches any number of path segments, including none.
# - `{}`: Groups conditions (e.g., `**/*.{ts,js}` matches all TypeScript
# and JavaScript files).
# - `[]`: Declares a range of characters to match in a path segment
# (e.g., `example.[0-9]` matches `example.0`, `example.1`, etc.).
# - `[!...]`: Negates a range of characters to match in a path segment
# (e.g., `example.[!0-9]` matches `example.a`, `example.b`, but not `example.0`).
patterns = ["**/*"]
# Commands to append to the original command list (e.g., ["-std=c++17"]).
append = []
# Commands to remove from the original command list.
remove = []

183
docs/en/architecture.md Normal file
View File

@@ -0,0 +1,183 @@
# Server Architecture
clice uses a **multi-process architecture** where a single **Master Server** coordinates multiple **Worker** processes. This design isolates Clang AST operations (which are memory-heavy and may crash) from the main LSP event loop.
## Overview
```
┌──────────────┐ JSON/LSP ┌────────────────┐ Bincode/IPC ┌──────────────────┐
│ LSP Client │ ◄──────────► │ Master Server │ ◄─────────────► │ Stateful Workers │
│ (Editor) │ (stdio) │ │ (stdio) │ (AST cache) │
└──────────────┘ │ - Lifecycle │ └──────────────────┘
│ - Documents │
│ - CDB │ Bincode/IPC ┌──────────────────┐
│ - Build drain │ ◄─────────────► │ Stateless Workers│
│ - Indexing │ (stdio) │ (one-shot tasks)│
└────────────────┘ └──────────────────┘
```
## Master Server
The master server (`src/server/master_server.cpp`) is the central coordinator. It runs a single-threaded async event loop and never touches Clang directly. Its responsibilities:
### LSP Lifecycle
The server progresses through these states:
1. **Uninitialized** — waiting for `initialize` request
2. **Initialized** — capabilities exchanged, waiting for `initialized` notification
3. **Ready** — workers spawned, workspace loaded, accepting requests
4. **ShuttingDown**`shutdown` received, draining work
5. **Exited**`exit` received, stopping the event loop
On `initialized`, the master:
- Loads configuration from `clice.toml` (or uses defaults)
- Starts the worker pool (spawns stateful + stateless processes)
- Loads `compile_commands.json` and builds an include graph
- Starts the background indexer coroutine (if enabled)
### Document Management
Each open document is tracked in a `DocumentState` with:
- Current `version` and `text` (kept in sync via `didOpen`/`didChange`)
- A `generation` counter to detect stale compile results
- Build state flags (`build_running`, `build_requested`, `drain_scheduled`)
When a document is opened or changed:
1. The include graph is re-scanned (via dependency directives)
2. The compile unit is registered/updated in the `CompileGraph`
3. A debounced build is scheduled
### Build Drain
The `run_build_drain` coroutine implements debounced compilation:
1. Wait for the debounce timer (default 200ms) to expire
2. Ensure PCH/PCM dependencies are ready via `CompileGraph`
3. Send a `compile` request to the assigned stateful worker
4. Publish diagnostics from the result (or clear them on failure)
5. If more edits arrived during compilation (`build_requested`), loop back to step 2
This ensures rapid typing doesn't trigger a compile per keystroke.
### Request Routing
Feature requests are split between two worker types:
**Stateful workers** (affinity-routed by file path):
- `textDocument/hover`
- `textDocument/semanticTokens/full`
- `textDocument/inlayHint`
- `textDocument/foldingRange`
- `textDocument/documentSymbol`
- `textDocument/documentLink`
- `textDocument/codeAction`
- `textDocument/definition`
**Stateless workers** (round-robin):
- `textDocument/completion`
- `textDocument/signatureHelp`
All feature responses use `RawValue` passthrough — the worker serializes the LSP result to JSON, and the master forwards the raw JSON bytes to the client without deserializing. This avoids bincode↔JSON conversion overhead and serde annotation conflicts.
## Worker Pool
The worker pool (`src/server/worker_pool.cpp`) manages spawning and communicating with worker processes. Each worker is a child process of the same `clice` binary, launched with `--mode stateful-worker` or `--mode stateless-worker`.
### Communication
Workers communicate with the master via **stdio pipes** using a **bincode** serialization format (via `eventide::ipc::BincodePeer`). This is more compact and faster than JSON for internal IPC, while the master handles JSON for the external LSP protocol.
### Stateful Worker Routing
Stateful workers use **affinity routing**: each file is consistently assigned to the same worker so that the worker retains the cached AST. Assignment uses a **least-loaded** strategy for new files, with **LRU tracking** to manage ownership.
When a worker exceeds its document capacity (currently hardcoded at 16 documents), it evicts the least-recently-used document and notifies the master via an `evicted` notification.
### Stateless Worker Routing
Stateless workers use simple **round-robin** dispatch. Each request includes the full source text and compilation arguments, so any worker can handle it independently.
## Stateful Worker
The stateful worker (`src/server/stateful_worker.cpp`) caches compiled ASTs in memory. Key behavior:
- **Compile**: Parses source code into a `CompilationUnit`, caches the AST, and returns diagnostics as a `RawValue` (JSON bytes)
- **Feature queries**: Look up the cached AST and invoke the corresponding `feature::*` function (hover, semantic tokens, etc.), serializing the result to JSON
- **Document updates**: Received as notifications — the worker updates the stored text and marks the document as `dirty`, causing feature queries to return `null` until recompilation
- **Eviction**: LRU-based; evicts the oldest document when capacity is exceeded, notifying the master
- **Concurrency**: Each document has a per-document `et::mutex` (strand) to serialize compilation and feature queries. Heavy work (compilation, feature extraction) runs on a thread pool via `et::queue`.
## Stateless Worker
The stateless worker (`src/server/stateless_worker.cpp`) handles one-shot requests that don't benefit from cached ASTs:
- **Completion**: Creates a fresh compilation with `CompilationKind::Completion` and invokes `feature::code_complete`
- **Signature help**: Similar to completion, using `feature::signature_help`
- **Build PCH**: Compiles a precompiled header to a temporary file
- **Build PCM**: Compiles a C++20 module interface to a temporary file
- **Index**: Compiles a file for indexing (TUIndex generation — currently a stub)
All requests are dispatched to a thread pool via `et::queue`.
## Compile Graph
The compile graph (`src/server/compile_graph.cpp`) tracks compilation unit dependencies as a DAG. It handles:
- **Registration**: Each file registers its included dependencies
- **Cascade invalidation**: When a file changes, all transitive dependents are marked dirty and their ongoing compilations are cancelled
- **Dependency compilation**: Before compiling a file, `compile_deps` ensures all dependencies (PCH, PCMs) are built first
- **Cancellation**: Uses `et::cancellation_source` to abort in-flight compilations when files are invalidated
## Configuration
The server reads configuration from `clice.toml` (or `.clice/config.toml`) in the workspace root. If no config file exists, sensible defaults are computed from system resources:
| Setting | Default | Description |
| ------------------------ | --------------------- | ------------------------------------------- |
| `stateful_worker_count` | CPU cores / 4 | Number of stateful worker processes |
| `stateless_worker_count` | CPU cores / 4 | Number of stateless worker processes |
| `worker_memory_limit` | 4 GB | Memory limit per stateful worker |
| `compile_commands_path` | auto-detect | Path to `compile_commands.json` |
| `cache_dir` | `<workspace>/.clice/` | Cache directory for PCH/PCM files |
| `debounce_ms` | 200 | Debounce interval for recompilation |
| `enable_indexing` | true | Enable background indexing |
| `idle_timeout_ms` | 3000 | Idle time before background indexing starts |
String values support `${workspace}` substitution.
## IPC Protocol
The master and workers communicate using custom RPC messages defined in `src/server/protocol.h`. Each message type has a `RequestTraits` or `NotificationTraits` specialization that defines the method name and result type.
### Stateful Worker Messages
| Method | Direction | Purpose |
| ----------------------------- | ------------ | ------------------------------------- |
| `clice/worker/compile` | Request | Compile source and return diagnostics |
| `clice/worker/hover` | Request | Get hover info at position |
| `clice/worker/semanticTokens` | Request | Get semantic tokens for file |
| `clice/worker/inlayHints` | Request | Get inlay hints for range |
| `clice/worker/foldingRange` | Request | Get folding ranges |
| `clice/worker/documentSymbol` | Request | Get document symbols |
| `clice/worker/documentLink` | Request | Get document links |
| `clice/worker/codeAction` | Request | Get code actions for range |
| `clice/worker/goToDefinition` | Request | Go to definition at position |
| `clice/worker/documentUpdate` | Notification | Update document text (marks dirty) |
| `clice/worker/evict` | Notification | Master → Worker: evict a document |
| `clice/worker/evicted` | Notification | Worker → Master: document was evicted |
### Stateless Worker Messages
| Method | Direction | Purpose |
| ---------------------------- | --------- | ---------------------------- |
| `clice/worker/completion` | Request | Code completion at position |
| `clice/worker/signatureHelp` | Request | Signature help at position |
| `clice/worker/buildPCH` | Request | Build precompiled header |
| `clice/worker/buildPCM` | Request | Build C++20 module interface |
| `clice/worker/index` | Request | Index a translation unit |

View File

@@ -1,82 +1,73 @@
# Build from Source
## Supported Platforms
clice depends on C++23 features and requires a modern C++ toolchain. We also need to link against LLVM/Clang to parse ASTs. To speed up builds, the default configuration downloads our published [clice-llvm](https://github.com/clice-io/clice-llvm) prebuilt package. This assumes your local environment matches the prebuilt environment closely (especially when enabling Address Sanitizer or LTO).
- Windows
- Linux
- MacOS
To simplify setup and keep builds reproducible, we **strongly recommend** [pixi](https://pixi.prefix.dev/latest) to manage the development environment. Dependency versions are pinned in `pixi.toml`.
## Prerequisite
If you prefer not to use pixi, see [Manual Build](#manual-build) below.
- cmake/xmake
- clang, lld >= 20
- c++23 **compatible** standard library
- MSVC STL >= 19.44(VS 2022 17.4)
- GCC libstdc++ >= 14
- Clang libc++ >= 20
## Quick Start
clice uses C++23 as its language standard. Please ensure you have a clang 20 (or higher) compiler and a C++23 compatible standard library available. clice depends on lld as its linker. Please ensure your clang toolchain can find it (clang distributions usually bundle lld, or you may need to install the lld-20 package separately).
Install pixi following the [official guide](https://pixi.prefix.dev/latest/installation).
> clice is currently only guaranteed to compile with clang (as ensured by CI testing). We do our best to maintain compatibility with gcc and msvc, but we do not add corresponding tests in CI. Contributions are welcome if you encounter any issues.
## CMake
Use the following commands to build clice
We ship several tasks; the commands below configure, build, and run tests:
```shell
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build
# configure && build (default RelWithDebInfo)
pixi run build
# unit && integration
pixi run test
```
For finer-grained tasks (first argument sets the build type):
```shell
pixi run cmake-config Debug
pixi run cmake-build Debug
pixi run unit-test Debug
pixi run integration-test Debug
```
> [!TIP]
> If you want to develop directly with `cmake`, `ninja`, `clang++`, etc., run `pixi shell` to enter a shell with all env vars configured.
## Manual Build
If you plan to build manually, first ensure your toolchain matches the versions defined in `pixi.toml`.
> Compatibility: In theory clice does not rely on compiler-specific extensions, so mainstream compilers (GCC/Clang/MSVC) should work. However, CI only guarantees specific versions of Clang. Other compilers or versions are supported on a **best-effort** basis. Please open an issue or PR if you hit problems.
### CMake
```shell
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \
-DCLICE_ENABLE_TEST=ON
```
> Note: `CMAKE_TOOLCHAIN_FILE` is optional. If your toolchain exactly matches ours, you can use the predefined `cmake/toolchain.cmake`; otherwise remove that flag.
Optional build options:
| Option | Default | Description |
| :------------------- | :------ | :----------------------------------------------------------------------------------------------------------------------------- |
| LLVM_INSTALL_PATH | "" | Build clice using llvm libs from a custom path |
| CLICE_ENABLE_TEST | OFF | Whether to build clice's unit tests |
| CLICE_USE_LIBCXX | OFF | Whether to build clice with libc++ (adds `-std=libc++`). If enabled, ensure that the llvm libs were also compiled with libc++. |
| CLICE_CI_ENVIRONMENT | OFF | Whether to enable the `CLICE_CI_ENVIRONMENT` macro. Some tests only run in a CI environment. |
| Option | Default | Effect |
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
| LLVM_INSTALL_PATH | "" | Build clice with LLVM from a custom path |
| CLICE_ENABLE_TEST | OFF | Build clice unit tests |
| CLICE_USE_LIBCXX | OFF | Build clice with libc++ (adds `-std=libc++`); if enabled, ensure the LLVM libs are also built with libc++ |
| CLICE_CI_ENVIRONMENT | OFF | Enable the `CLICE_CI_ENVIRONMENT` macro; some tests only run in CI |
## XMake
## About LLVM
Use the following commands to build clice
clice calls Clang APIs to parse C++ code, so it must link against LLVM/Clang. Because clice uses Clang's private headers (usually absent from distro packages), the system LLVM package cannot be used directly.
```bash
xmake f -c --mode=releasedbg --toolchain=clang
xmake build --all
```
Two ways to satisfy this dependency:
Optional build options:
| Option | Default | Description |
| :------------ | :------ | :--------------------------------------------- |
| --llvm | "" | Build clice using llvm libs from a custom path |
| --enable_test | false | Whether to build clice's unit tests |
| --ci | false | Whether to enable `CLICE_CI_ENVIRONMENT` |
## A Note on LLVM Libs
Due to the complexity of C++ syntax, writing a new parser from scratch is unrealistic. clice calls clang's APIs to parse C++ source files and obtain the AST, which means it needs to link against llvm/clang libs. Because clice uses clang's private headers, which are not included in the binary releases published by LLVM, you cannot use the system's llvm package directly.
1. We publish pre-compiled binaries for the LLVM version we use on [clice-llvm](https://github.com/clice-io/clice-llvm/releases), which are used for CI or release builds. By default, cmake and xmake will download and use the llvm libs from here during the build.
1. We publish prebuilt binaries of the LLVM version we use at [clice-llvm](https://github.com/clice-io/clice-llvm/releases) for CI and release builds. During builds, cmake downloads these LLVM libs by default.
> [!IMPORTANT]
>
> For debug builds of llvm libs, we enable address sanitizer. Address sanitizer depends on compiler-rt, which is highly sensitive to the compiler version.
>
> Therefore, if you use a debug build, please ensure your clang's compiler-rt version is **strictly identical** to the one used in our build.
>
> - Windows does not currently have debug builds for llvm libs, as it does not support building clang as a dynamic library. Related progress is tracked [here](https://github.com/clice-io/clice/issues/42).
> - Linux uses clang20
> - MacOS uses homebrew llvm@20. **Do not use apple clang**.
>
> You can refer to the [cmake](https://github.com/clice-io/clice/blob/main/.github/workflows/cmake.yml) and [xmake](https://github.com/clice-io/clice/blob/main/.github/workflows/xmake.yml) files in our CI as a reference, as they maintain an environment strictly consistent with the pre-compiled llvm libs.
> For debug LLVM builds, we enable address sanitizer, which depends on compiler-rt and is very sensitive to compiler version. If you use a debug build, ensure your clang compiler-rt version matches the one defined in `pixi.toml`.
2. Build llvm/clang yourself to match your current environment. If the default pre-compiled binaries (Method 1) fail to run on your system due to ABI or library version (e.g., glibc) incompatibility, or if you need a custom Debug build, we recommend you use this method to compile llvm libs from scratch. We provide a script to build the llvm libs required by clice: [build-llvm-libs.py](https://github.com/clice-io/clice/blob/main/scripts/build-llvm-libs.py).
```bash
cd llvm-project
python3 <clice>/scripts/build-llvm-libs.py debug
```
You can also refer to LLVM's official build tutorial: [Building LLVM with CMake](https://llvm.org/docs/CMake.html).
2. Build LLVM/Clang yourself to match your environment. If the default prebuilt binaries fail due to ABI or library version mismatches, or you need a custom debug build, use this approach. We provide `scripts/build-llvm.py` to build the required LLVM libs, or refer to LLVM's official guide [Building LLVM with CMake](https://llvm.org/docs/CMake.html).

View File

@@ -7,5 +7,6 @@ Please refer to [build](./build.md) to build clice, refer to [test and debug](./
## Code Style
Naming:
- Variable names: lowercase with underscores
- Class names, enum names: PascalCase

70
docs/en/dev/extension.md Normal file
View File

@@ -0,0 +1,70 @@
# Extension
This section covers development and release workflows for the editor extensions (VSCode / Neovim / Zed).
## VSCode
The VSCode extension uses the Node/PNPM/VSCE toolchain. Work inside the pixi `node` environment for consistent versions.
```shell
# prepare environment (install pixi first)
pixi shell -e node
# install deps (uses pnpm-lock)
pixi run install-vscode
# package the extension; outputs editors/vscode/*.vsix
pixi run build-vscode
```
Publish to the VSCode Marketplace (`VSCE_PAT` env var required):
```shell
pixi run publish-vscode
```
> [!TIP]
> If clice is already built locally, set `clice.executable` in VSCode settings to point the extension to your custom binary.
Develop and debug:
1. `pixi shell -e node`
2. In `editors/vscode`, run `pnpm run watch` for incremental builds
3. In VSCode, use the “Run Extension/Launch Extension” configs, or run `code --extensionDevelopmentPath=$(pwd)/editors/vscode`
Common scripts (inside `pixi shell -e node`):
```bash
pnpm run package # same as pixi run build-vscode
pnpm run publish # same as pixi run publish-vscode
```
If you skip pixi, install node.js >= 20 and pnpm yourself, then in `editors/vscode` run:
```bash
pnpm install
pnpm run package
```
## Neovim
The Neovim extension lives in `editors/nvim` and is written in Lua. It is still evolving.
- Add the repo path to `runtimepath`, e.g. `set rtp+=/path/to/clice/editors/nvim`
- Or create a local symlink: `~/.config/nvim/pack/clice/start/clice` -> `<repo>/editors/nvim`
- Ensure the `clice` executable is discoverable in `$PATH`
Dev tips: the codebase is small—load it directly in Neovim and watch `:messages`/LSP logs; format with `stylua` (config included).
## Zed
The Zed extension lives in `editors/zed` and uses Rust plus `zed_extension_api`.
Suggested local verification:
```bash
cd editors/zed
cargo build --release
```
Then load the local extension per Zed's official guide (Zed CLI required). Make sure `clice` is on `PATH` before launching. Follow the Zed extension publishing flow when releasing.

View File

@@ -18,13 +18,6 @@ We use pytest to run integration tests. Please refer to `pyproject.toml` to inst
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice
```
If you use xmake as your build system, you can run the tests directly with xmake:
```shell
$ xmake run --verbose unit_tests
$ xmake test --verbose integration_tests/default
```
## Debug
If you want to attach a debugger to clice for debugging, it is recommended to first start clice in socket mode independently, and then connect the client to it.
@@ -53,24 +46,24 @@ You can also connect to a running clice service by configuring the clice-vscode
```jsonc
{
// Point this to the clice binary you downloaded.
"clice.executable": "/path/to/your/clice/executable",
// Point this to the clice binary you downloaded.
"clice.executable": "/path/to/your/clice/executable",
// Enable socket mode.
"clice.mode": "socket",
"clice.port": 50051,
// Enable socket mode.
"clice.mode": "socket",
"clice.port": 50051,
// Optional: Set this to an empty string to turn off the clangd.
"clangd.path": "",
// Optional: Set this to an empty string to turn off the clangd.
"clangd.path": "",
}
```
3. Reload Window: After modifying the configuration, execute the `Developer: Reload Window` command in VS Code for the settings to take effect. The extension will automatically connect to the clice instance listening on port 50051.
If you need to modify or debug the clice-vscode extension itself, follow these steps:
1. Clone and install dependencies:
```shell
$ git clone https://github.com/clice-io/clice-vscode
$ cd clice-vscode

View File

@@ -35,7 +35,7 @@ Glob patterns for matching file paths, following LSP's [standard](https://micros
- `{}`: Used for grouping conditions (e.g., `**/*.{ts,js}` matches all TypeScript and JavaScript files).
- `[]`: Declares a character range to match in a path segment (e.g., `example.[0-9]` matches `example.0`, `example.1`, etc.).
- `[!...]`: Excludes a character range to match in a path segment (e.g., `example.[!0-9]` matches `example.a`, `example.b`, but not `example.0`).
<br>
<br>
| Name | Type | Default |
| ---------------- | ------------------- | ------- |

View File

@@ -54,14 +54,73 @@ bazel run @hedron_compile_commands//:refresh_all
### Visual Studio
TODO:
Visual Studio (2019 16.1+) can generate a compilation database via CMake integration. Open your project as a CMake project, then configure the generation in `CMakeSettings.json`:
```json
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"buildRoot": "${projectDir}\\build",
"cmakeCommandArgs": "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
}
]
}
```
Alternatively, for MSBuild-based projects (`.vcxproj`), you can use [compiledb-vs](https://github.com/pjbroad/compiledb-vs) or [catter](https://github.com/clice-io/catter) to generate the compilation database.
### Makefile
TODO:
For Makefile-based projects, use [bear](https://github.com/rizsotto/Bear) to intercept compilation commands:
```bash
bear -- make
```
This will generate a `compile_commands.json` in the current directory. Note that `bear` requires a clean build to capture all commands — run `make clean` before `bear -- make` if needed.
Alternatively, if you use GNU Make, you can use [compiledb](https://github.com/nicktimko/compiledb):
```bash
compiledb make
```
### Meson
Meson generates a compilation database automatically during setup:
```bash
meson setup build
```
The `compile_commands.json` will be in the `build` directory.
### Xmake
Use one of the following approaches to generate a compilation database.
#### Command Line
Run the following command to manually generate a compilation database:
```bash
xmake project -k compile_commands --lsp=clangd build
```
> Compilation database generated manually doesn't automatically update itself. Re-generate if changes are made to the project.
#### VSCode Extension
The Xmake official VSCode extension automatically generates the compilation database when `xmake.lua` is updated. However, it generates the database to the `.vscode` directory by default. Add this setting in `settings.json`:
```json
"xmake.compileCommandsDirectory": "build"
```
to explicitly ask the extension to generate the compilation database in `build`.
### Others
For any other build system, you can try using [bear](https://github.com/rizsotto/Bear) or [scan-build](https://github.com/rizsotto/scan-build) to intercept compilation commands and obtain the compilation database (no guarantee of success). We plan to write a **new tool** in the future that captures compilation commands through a fake compiler approach.
For any other build system, you can use [catter](https://github.com/clice-io/catter) to generate a compilation database. It captures compilation commands through a fake compiler approach and is designed to work reliably with any build system that invokes a compiler executable.

View File

@@ -16,8 +16,8 @@ hero:
text: Contribution
link: /dev/contribution
image:
src: /image.png
alt: clice
src: /image.png
alt: clice
features:
- icon: T

2239
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"private": true,
"type": "module",
"devDependencies": {
"@types/node": "^24.1.0",
"@types/node": "^24.10.4",
"vitepress": "^1.6.4"
},
"scripts": {
@@ -11,9 +11,12 @@
"docs:build": "vitepress build",
"docs:preview": "vitepress preview"
},
"overrides": {
"vite": {
"esbuild": "0.25.9"
}
"pnpm": {
"overrides": {
"esbuild": "~0.25.0"
},
"ignoredBuiltDependencies": [
"esbuild"
]
}
}

1629
docs/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -58,4 +58,5 @@ struct Widget {
template <typename T>
Widget(T) -> Widget<typename T::value_type>;
} // namespace foo

View File

@@ -16,5 +16,4 @@ int main () {
`iostream` 这个头文件大概有 2w 行代码clice 会先把 `#include <iostream>` 这一行代码构建成 PCH 文件,在完成之后在使用这个 PCH 文件来解析后面的代码。这样的话后续重新解析的代码量就只剩 5 行了,而不是原本的 2w 行,速度会变得非常快。除非你修改了 preamble 部分的代码,导致需要构建新的 preamble。
## Cancel Compilation

View File

@@ -28,5 +28,4 @@ void foo(std::vector<std::vector<T>> vec2) {
2. 它只进行名称查找而不进行模板实例化,就算找到了最后的结果,也没法把它和最初的模板参数映射起来
3. 不考虑默认模板参数,无法处理由默认模板参数导致的依赖名
尽管我们可以对标准库的类型开洞来提供相关的支持但是我希望用户的代码能和标准库的代码有相同的地位那么我们就需要一种通用的算法来处理依赖类型。为了解决这个问题我编写了一个伪实例化器pseudo instantiator。它能在没有具体类型的前提下对依赖类型进行实例化从而达到化简的目的。比如上面这个例子里面的`std::vector<std::vector<T>>::reference`就能被化简为`std::vector<T>&`,进一步就能为用户提供代码补全选项。

View File

@@ -1,81 +1,73 @@
# Build from Source
## Supported Platforms
clice 依赖 C++23 特性,需要使用高版本的 C++ 编译器。同时,我们需要链接 LLVM/Clang 库来解析 AST。为了加快构建速度默认配置会下载我们发布的 [clice-llvm](https://github.com/clice-io/clice-llvm) 预编译包。这要求你的本地环境与预编译环境保持较高的一致性(尤其是开启 Address Sanitizer 或 LTO 时)。
- Windows
- Linux
- MacOS
为了简化环境设置并保证可复现性,我们**强烈推荐**使用 [pixi](https://pixi.prefix.dev/latest) 来管理开发环境。所有的依赖版本均严格定义在 `pixi.toml` 中。
## Prerequisite
如果你不想使用 pixi请参考下方的 [Manual Build](#manual-build) 章节。
- cmake/xmake
- clang, lld >= 20
- c++23 **compatible** standard library
- MSVC STL >= 19.44(VS 2022 17.4)
- GCC libstdc++ >= 14
- Clang libc++ >= 20
## Quick Start
clice 使用 C++23 作为语言标准,请确保有可用的 clang 20 以及以上的编译器,以及兼容 C++23 的标准库。clice 依赖 lld 作为链接器。请确保你的 clang 工具链可以找到它(通常 clang 发行版会自带 lld或者你需要单独安装 lld-20 包)
请参考 [pixi](https://pixi.prefix.dev/latest/installation) 官方指南安装 pixi
> clice 目前只保证能使用 clang 编译CI 测试保证)。对于 gcc 和 msvc 的兼容,我们尽力而为,但不会在 CI 中添加对应的测试。如果遇到任何问题,欢迎贡献。
## CMake
使用如下的命令构建 clice
我们内置了一系列任务,以下命令可直接完成编译并运行测试:
```shell
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build
# configure && build (default RelWithDebInfo)
pixi run build
# unit && integration
pixi run test
```
细粒度任务:上述命令由多个子任务组成,你也可以单独运行它们,并支持通过第一个参数指定构建类型:
```shell
pixi run cmake-config Debug
pixi run cmake-build Debug
pixi run unit-test Debug
pixi run integration-test Debug
```
> [!TIP]
> 如果你想直接使用 `cmake`, `ninja`, `clang++` 等命令进行开发,请运行 `pixi shell` 进入已配置好环境变量的终端
## Manual Build
如果你打算手动构建,请务必先确认你的工具链满足 pixi.toml 中定义的版本要求。
> 兼容性说明:理论上 clice 不依赖特定编译器的扩展可以使用主流编译器GCC/Clang/MSVC编译。但我们仅在 CI 中保证特定版本的 Clang 能通过测试。对于其他编译器或版本,我们提供**尽力而为 (Best Effort)** 的支持。如果遇到问题,欢迎提交 Issue 或 PR
### CMake
```shell
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \
-DCLICE_ENABLE_TEST=ON
```
> 注意:`CMAKE_TOOLCHAIN_FILE` 是可选的。如果你使用的工具链与我们完全一致,可以使用预定义的 `cmake/toolchain.cmake`,否则请移除该选项
可选的构建选项:
| 选项 | 默认值 | 效果 |
| -------------------- | ------ | ----------------------------------------------------------------------------------------------------- |
| LLVM_INSTALL_PATH | "" | 使用自定义路径的 llvm libs 来构建 clice |
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice添加 `-std=libc++`),如果开启,请确保 llvm libs 也是使用 libc++ 编译的 |
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
| 选项 | 默认值 | 效果 |
| -------------------- | ------ | -------------------------------------------------------------------------------------------------- |
| LLVM_INSTALL_PATH | "" | 使用自定义路径的 LLVM 库来构建 clice |
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice添加 `-std=libc++`),如果开启,请确保 LLVM 库也是使用 libc++ 编译的 |
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
## XMake
## About LLVM
使用如下的命令即可构建 clice
clice 调用 Clang API 来解析 C++ 代码,因此必须链接 LLVM/Clang 库。由于 clice 使用了 Clang 的私有头文件(这些文件通常不包含在发行版中),不能直接使用系统安装的 LLVM 包。
```bash
xmake f -c --mode=releasedbg --toolchain=clang
xmake build --all
```
主要有两种方式解决这个依赖问题:
可选的构建选项:
| 选项 | 默认值 | 效果 |
| ------------- | ------ | --------------------------------------- |
| --llvm | "" | 使用自定义路径的 llvm libs 来构建 clice |
| --enable_test | false | 是否构建 clice 的单元测试 |
| --ci | false | 是否打开 `CLICE_CI_ENVIRONMENT` |
## A Note on LLVM Libs
由于 C++ 的语法太过复杂,自己编写一个新的 parser 是不现实的。clice 调用 clang 的 API 来 parse C++ 源文件获取 AST这意味它需要链接 llvm/clang libs。由于 clice 使用了 clang 的私有头文件,这些私有头文件在 llvm 发布的 binary release 中是没有的,所以不能直接使用系统的 llvm package。
1. 我们在 [clice-llvm](https://github.com/clice-io/clice-llvm/releases) 上会发布使用的 llvm 版本的预编译二进制,用于 CI 或者 release 构建。在构建时 cmake 和 xmake 默认会从此处下载 llvm libs 然后使用,
1. 我们在 [clice-llvm](https://github.com/clice-io/clice-llvm/releases) 上会发布使用的 LLVM 版本的预编译二进制,用于 CI 或者 release 构建。在构建时 cmake 默认会从此处下载 LLVM 库然后使用。
> [!IMPORTANT]
>
> 对于 debug 版本的 llvm libs,构建的时候我们开启了 address sanitizer而 address sanitizer 依赖于 compiler rt它对编译器版本十分敏感。所以如果使用 debug 版本,请确保你的 clang 的 compiler rt 版本和我们构建的时候**严格一致**
>
> - Windows 暂时没有 debug 构建的 llvm libs因为它不支持将 clang 构建为动态库,相关的进展在 [这里](https://github.com/clice-io/clice/issues/42) 跟踪
> - Linux 使用 clang20
> - MacOS 使用 homebrew llvm@20**不要使用 apple clang**
>
> 可以参考 CI 中的 [cmake](https://github.com/clice-io/clice/blob/main/.github/workflows/cmake.yml) 和 [xmake](https://github.com/clice-io/clice/blob/main/.github/workflows/xmake.yml) 文件作为参考,它们与预编译 llvm libs 的环境保持严格一致。
> 对于 debug 版本的 LLVM 库,构建的时候我们开启了 address sanitizer而 address sanitizer 依赖于 compiler rt它对编译器版本十分敏感。所以如果使用 debug 版本,请确保你的 clang 的 compiler rt 版本与 `pixi.toml` 中的定义严格一致。
2.己重新一个与当前环境一致的 llvm/clang。如果默认的预编译二进制文件(方法 1在你的系统上因 ABI 或库版本(如 glibc不兼容而运行失败,或者你需要一个自定义的 Debug 版本,那么我们推荐你使用此方法从头编译 llvm libs。我们提供了一个脚本,用于构建 clice 所需要的 llvm libs[build-llvm-libs.py](https://github.com/clice-io/clice/blob/main/scripts/build-llvm-libs.py)。
```bash
cd llvm-project
python3 <clice>/scripts/build-llvm-libs.py debug
```
也可以参考 llvm 的官方构建教程 [Building LLVM with CMake](https://llvm.org/docs/CMake.html)。
2.行构建一套与当前环境一致的 LLVM/Clang。如果默认的预编译二进制文件在你的系统上因 ABI 或库版本不兼容而运行失败,或者你需要一个自定义的 Debug 版本,那么我们推荐你使用此方法从头编译 LLVM 库。我们提供了一个脚本 `scripts/build-llvm.py` 用于构建所需要的 LLVM 库,也可以参考 LLVM 的官方构建教程 [Building LLVM with CMake](https://llvm.org/docs/CMake.html)。

View File

@@ -7,5 +7,6 @@
## Code Style
命名
- 变量名:小写下换线
- 类名,枚举名:大驼峰

70
docs/zh/dev/extension.md Normal file
View File

@@ -0,0 +1,70 @@
# Extension
本节汇总各编辑器插件的开发与发布流程。目前包含 VSCode / Neovim / Zed。
## VSCode
VSCode 插件使用 Node/PNPM/VSCE 链路。推荐在 pixi 的 `node` 环境下操作以获得一致的工具链版本。
```shell
# 准备环境(先安装 pixi
pixi shell -e node
# 安装依赖(基于 pnpm-lock
pixi run install-vscode
# 打包扩展,产物位于 editors/vscode/*.vsix
pixi run build-vscode
```
发布到 VSCode Marketplace需要 `VSCE_PAT` 环境变量):
```shell
pixi run publish-vscode
```
> [!TIP]
> 若已编译 clice本地调试时可在 VSCode 设置中填写 `clice.executable`,使扩展指向你的自定义构建。
开发与调试:
1. `pixi shell -e node`
2.`editors/vscode` 下运行 `pnpm run watch`(增量构建)
3. VSCode 中使用”Run Extension/Launch Extension”调试配置或执行 `code --extensionDevelopmentPath=$(pwd)/editors/vscode`
常用脚本(在 `pixi shell -e node` 下):
```bash
pnpm run package # 等价于 pixi run build-vscode
pnpm run publish # 等价于 pixi run publish-vscode
```
如果不使用 pixi请自行准备 node.js >= 20、pnpm然后在 `editors/vscode` 目录执行:
```bash
pnpm install
pnpm run package
```
## Neovim
Neovim 插件位于 `editors/nvim`,使用 Lua 编写。目前功能仍在演进中。
- 将仓库路径加入 `runtimepath`,例如:`set rtp+=/path/to/clice/editors/nvim`
- 或在本地创建软链接:`~/.config/nvim/pack/clice/start/clice` -> `<repo>/editors/nvim`
- 需要 `clice` 可执行文件可在 `$PATH` 中被找到
开发提示:代码量较小,可直接在 Neovim 中加载并通过 `:messages`/LSP 日志观察效果;格式化可使用 `stylua`(仓库中已提供 `stylua.toml`)。
## Zed
Zed 插件位于 `editors/zed`,使用 Rust 和 `zed_extension_api`
建议的本地验证流程:
```bash
cd editors/zed
cargo build --release
```
随后按 Zed 官方指南加载本地扩展(需安装 Zed CLI在启动前确保 `clice` 已在 PATH 中。发布时同样遵循 Zed 扩展发布流程。

View File

@@ -18,13 +18,6 @@ $ ./build/bin/unit_tests --test-dir="./tests/data"
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice
```
如果你使用 xmake 作为构建系统,可以直接通过 xmake 运行测试:
```shell
$ xmake run --verbose unit_tests
$ xmake test --verbose integration_tests/default
```
## Debug
如果想在 clice 上附加调试器并进行调试,推荐先单独以 socket 模式启动 clice然后再将客户端连接到 clice 上
@@ -51,31 +44,31 @@ $ pytest -s --log-cli-level=INFO tests/integration/test_file_operation.py::test_
2. 配置 `settings.json`: 在你的项目根目录下创建 `.vscode/settings.json` 文件,并填入以下内容:
```jsonc
{
// Point this to the clice binary you downloaded.
"clice.executable": "/path/to/your/clice/executable",
```jsonc
{
// Point this to the clice binary you downloaded.
"clice.executable": "/path/to/your/clice/executable",
// Enable socket mode.
"clice.mode": "socket",
"clice.port": 50051,
// Enable socket mode.
"clice.mode": "socket",
"clice.port": 50051,
// Optional: Set this to an empty string to turn off the clangd.
"clangd.path": "",
}
```
// Optional: Set this to an empty string to turn off the clangd.
"clangd.path": "",
}
```
3. 重新加载窗口:修改配置后,在 vscode 中执行 Developer: Reload Window 命令使配置生效。插件会自动连接到正在 50051 端口监听的 clice。
如果你需要修改或调试 clice-vscode 插件本身,可以按以下步骤操作:
1. 克隆并安装依赖:
```shell
$ git clone https://github.com/clice-io/clice-vscode
$ cd clice-vscode
$ npm install
```
```shell
$ git clone https://github.com/clice-io/clice-vscode
$ cd clice-vscode
$ npm install
```
2. 使用 vscode 打开插件项目:用一个新的 vscode 窗口打开 clice-vscode 文件夹

View File

@@ -35,7 +35,7 @@
- `{}`: 用于分组条件 (例如,`**/*.{ts,js}` 匹配所有 TypeScript 和 JavaScript 文件)。
- `[]`: 声明要匹配的路径段中的字符范围 (例如,`example.[0-9]` 匹配 `example.0`, `example.1` 等)。
- `[!...]`: 排除要匹配的路径段中的字符范围 (例如,`example.[!0-9]` 匹配 `example.a`, `example.b`,但不匹配 `example.0`)。
<br>
<br>
| 名称 | 类型 | 默认值 |
| ---------------- | ------------------- | ------ |

View File

@@ -26,7 +26,6 @@ clice 实现了 [Language Server Protocol](https://microsoft.github.io/language-
自己从源码编译 clice具体的步骤参考 [build](../dev/build.md)。
## Project Setup
为了让 clice 能正确理解你的代码(例如找到头文件的位置),需要为 clice 提供一份 `compile_commands.json` 文件,也就说所谓的 [编译数据库](https://clang.llvm.org/docs/JSONCompilationDatabase.html)。编译数据库中提供了每个源文件的编译选项。
@@ -55,14 +54,73 @@ bazel run @hedron_compile_commands//:refresh_all
### Visual Studio
TODO:
Visual Studio2019 16.1+)可以通过 CMake 集成来生成编译数据库。将项目作为 CMake 项目打开,然后在 `CMakeSettings.json` 中配置:
```json
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"buildRoot": "${projectDir}\\build",
"cmakeCommandArgs": "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
}
]
}
```
对于基于 MSBuild 的项目(`.vcxproj`),可以使用 [compiledb-vs](https://github.com/pjbroad/compiledb-vs) 或 [catter](https://github.com/clice-io/catter) 来生成编译数据库。
### Makefile
TODO:
对于基于 Makefile 的项目,使用 [bear](https://github.com/rizsotto/Bear) 来拦截编译命令:
```bash
bear -- make
```
这会在当前目录生成 `compile_commands.json`。注意 `bear` 需要干净的构建来捕获所有命令——如果需要的话,在运行 `bear -- make` 之前先执行 `make clean`
另外,如果使用 GNU Make也可以使用 [compiledb](https://github.com/nicktimko/compiledb)
```bash
compiledb make
```
### Meson
Meson 在配置阶段会自动生成编译数据库:
```bash
meson setup build
```
`compile_commands.json` 会生成在 `build` 目录下。
### Xmake
用下列任意方法生成编译数据库。
#### 命令行手动生成
在命令行中执行以下命令:
```bash
xmake project -k compile_commands --lsp=clangd build
```
> 通过这种方法生成的编译数据库无法自动更新,需要在项目编译配置更改时手动重新生成。
#### VSCode 扩展
Xmake 提供的官方 VSCode 扩展会在 `xmake.lua` 更新时自动生成编译数据库。然而默认情况下,它将编译数据库生成到了 `.vscode` 文件夹。在 `settings.json` 中添加以下配置:
```json
"xmake.compileCommandsDirectory": "build"
```
以将编译数据库的生成目录调整到 `build`,供 clice 使用。
### Others
对于任意其它的构建系统,可以尝试使用 [bear](https://github.com/rizsotto/Bear) 或者 [scan-build](https://github.com/rizsotto/scan-build) 来拦截编译命令并获取到编译数据库(不保证成功)。我们计划在未来编写一个**新的工具**,通过假编译器的方式来实现编译命令的捕获
对于任意其它的构建系统,可以使用 [catter](https://github.com/clice-io/catter) 来生成编译数据库。它通过伪装编译器的方式来捕获编译命令,能够可靠地与任何调用编译器可执行文件的构建系统配合工作

View File

@@ -16,8 +16,8 @@ hero:
text: 参与贡献
link: /zh/dev/contribution
image:
src: /image.png
alt: clice
src: /image.png
alt: clice
features:
- icon: T

3
editors/nvim/README.md Normal file
View File

@@ -0,0 +1,3 @@
# clice-nvim
Provide extended functionality for [clice](https://github.com/clice-io/clice)

View File

@@ -0,0 +1,32 @@
-- Should be placed under config_dir/lsp/
-- Lsp configuration for nvim >= 0.11
local clice = {
filetypes = { 'c', 'cpp' },
root_markers = {
'.git/',
'clice.toml',
'.clang-tidy',
'.clang-format',
'compile_commands.json',
'compile_flags.txt',
'configure.ac', -- AutoTools
},
capabilities = {
textDocument = {
completion = {
editsNearCursor = true,
},
},
offsetEncoding = { 'utf-8' },
},
cmd = {
'clice',
'--mode=pipe',
},
}
return clice

View File

@@ -0,0 +1,3 @@
local augroup = vim.api.nvim_create_augroup('LspClice', { clear = true })
-- TODO: CallHierarchy, TypeHierarchy, LspDump?, ShowHeaderContext?

6
editors/nvim/stylua.toml Normal file
View File

@@ -0,0 +1,6 @@
column_width = 160
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 4
quote_style = "AutoPreferSingle"
call_parentheses = "None"

View File

@@ -0,0 +1,5 @@
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});

View File

@@ -0,0 +1,15 @@
.github/
.vscode/**
.vscode-test/**
out/**
node_modules/**
src/**
.gitignore
.yarnrc
webpack.config.js
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts
**/.vscode-test.*

201
editors/vscode/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

19
editors/vscode/README.md Normal file
View File

@@ -0,0 +1,19 @@
# vscode-clice
This is the vscode extension for [clice](https://github.com/clice-project/clice).
## develop
- install dependencies
```shell
git clone https://github.com/clice-io/clice.git
cd clice/editors/vscode
npm install
```
- package
```shell
npm run package
```

BIN
editors/vscode/clice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

182
editors/vscode/package.json Normal file
View File

@@ -0,0 +1,182 @@
{
"name": "clice-vscode",
"displayName": "clice",
"description": "VSCode extension for clice",
"repository": {
"type": "git",
"url": "https://github.com/clice-project/clice-vscode"
},
"version": "0.1.4",
"publisher": "ykiko",
"icon": "clice.png",
"engines": {
"vscode": "^1.80.0"
},
"categories": [
"Programming Languages",
"Linters",
"Formatters"
],
"keywords": [
"C",
"C++",
"cuda",
"clang"
],
"activationEvents": [
"onLanguage:c",
"onLanguage:cpp",
"onLanguage:cuda-cpp"
],
"contributes": {
"configuration": {
"type": "object",
"title": "C/C++ Language Client",
"properties": {
"clice-client.trace.server": {
"default": "verbose"
},
"clice.executable": {
"type": "string",
"default": "",
"description": "The path of clice executable."
},
"clice.mode": {
"type": "string",
"default": "pipe",
"enum": [
"pipe",
"socket"
],
"description": "How to communicate with clice. pipe or socket. For daily use please use pipe."
},
"clice.host": {
"type": "string",
"default": "127.0.0.1",
"description": "The host to connect to (default: 127.0.0.1)"
},
"clice.port": {
"type": "number",
"default": 50051,
"description": "The port to connect to"
}
}
},
"commands": [
{
"command": "clice.restart",
"title": "Clice: Restart Language Server"
}
],
"semanticTokenTypes": [
{
"id": "character",
"description": "C/C++ character literal (e.g., 'a')",
"superType": "string"
},
{
"id": "directive",
"description": "C/C++ preprocessor directive (e.g., #include)",
"superType": "keyword"
},
{
"id": "header",
"description": "C/C++ header name (e.g., <iostream>)",
"superType": "string"
},
{
"id": "module",
"description": "C++20 module name",
"superType": "namespace"
},
{
"id": "macroParameter",
"description": "C/C++ macro parameter",
"superType": "parameter"
},
{
"id": "union",
"description": "C/C++ union type",
"superType": "struct"
},
{
"id": "field",
"description": "C/C++ field (member variable)",
"superType": "variable"
},
{
"id": "label",
"description": "C/C++ label (for goto)",
"superType": "variable"
},
{
"id": "concept",
"description": "C++20 concept",
"superType": "type"
},
{
"id": "attribute",
"description": "GNU/MSVC/C++11/C23 attribute",
"superType": "macro"
},
{
"id": "paren",
"description": "Parentheses `()`",
"superType": "operator"
},
{
"id": "bracket",
"description": "Brackets `[]`",
"superType": "operator"
},
{
"id": "brace",
"description": "Braces `{}`",
"superType": "operator"
},
{
"id": "angle",
"description": "Angle brackets `<>`",
"superType": "operator"
}
]
},
"main": "./dist/extension.js",
"scripts": {
"compile": "webpack",
"watch": "webpack --watch",
"vscode:prepublish": "webpack --mode production --devtool hidden-source-map",
"package": "vsce package --no-dependencies --baseImagesUrl https://raw.githubusercontent.com/clice-io/clice/main/",
"publish": "vsce publish --no-dependencies --baseImagesUrl https://raw.githubusercontent.com/clice-io/clice/main/",
"pretest": "pnpm run compile",
"test": "vscode-test",
"release:patch": "pnpm version patch -m \"release: v%s\" && git push --follow-tags",
"release:minor": "pnpm version minor -m \"release: v%s\" && git push --follow-tags"
},
"devDependencies": {
"@types/decompress": "^4.2.7",
"@types/mocha": "^10.0.10",
"@types/node": "~25.0.3",
"@types/vscode": "^1.80.0",
"@vscode/test-cli": "^0.0.12",
"@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.7.1",
"ts-loader": "^9.5.4",
"typescript": "~5.5.4",
"webpack": "^5.104.1",
"webpack-cli": "^6.0.1"
},
"dependencies": {
"decompress": "^4.2.1",
"vscode-languageclient": "^9.0.1"
},
"overrides": {
"glob": "^11.1.0"
},
"pnpm": {
"onlyBuiltDependencies": [
"@vscode/vsce-sign",
"keytar"
]
}
}

4373
editors/vscode/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,247 @@
import * as vscode from "vscode";
import * as fs from "fs";
import * as path from "path";
import * as https from "https";
import * as os from "os";
// @ts-ignore
import decompress = require("decompress");
interface GitHubRelease {
tag_name: string;
assets: {
name: string;
browser_download_url: string;
}[];
}
export async function ensureServerBinary(
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
): Promise<string | undefined> {
const storagePath = context.globalStorageUri.fsPath;
const platform = os.platform();
const arch = os.arch();
channel.appendLine(`[Download] Initializing clice downloader...`);
channel.appendLine(`[Download] Platform: ${platform}, Arch: ${arch}, Storage: ${storagePath}`);
let platformKeyword = "";
let archKeyword = "";
let binaryName = "clice";
if (platform === "win32") {
platformKeyword = "windows";
archKeyword = "x64";
binaryName = "clice.exe";
} else if (platform === "darwin") {
platformKeyword = "macos";
archKeyword = arch;
} else if (platform === "linux") {
platformKeyword = "linux";
archKeyword = arch === "x64" ? "x86_64" : arch;
} else {
const msg = `Unsupported platform: ${platform}`;
channel.appendLine(`[Download] Error: ${msg}`);
vscode.window.showErrorMessage(msg);
return undefined;
}
const executablePath = path.join(storagePath, "bin", binaryName);
if (fs.existsSync(executablePath)) {
channel.appendLine(`[Download] Found existing binary at: ${executablePath}`);
// TODO: check tag update
return executablePath;
}
if (!fs.existsSync(storagePath)) {
channel.appendLine(`[Download] Creating storage directory: ${storagePath}`);
fs.mkdirSync(storagePath, { recursive: true });
}
const statusItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
try {
statusItem.text = "$(sync~spin) Checking clice updates...";
statusItem.show();
channel.appendLine(`[Download] Fetching latest release from GitHub...`);
const release = await fetchReleaseInfo(channel);
channel.appendLine(`[Download] Latest tag: ${release.tag_name}`);
const asset = release.assets.find((a) => {
const name = a.name.toLowerCase();
return (
name.includes(platformKeyword) &&
name.includes(archKeyword) &&
!name.includes("symbol")
);
});
if (!asset) {
throw new Error(
`No compatible asset found for ${platform}-${archKeyword} in release ${release.tag_name}`,
);
}
channel.appendLine(`[Download] Found asset: ${asset.name}`);
channel.appendLine(`[Download] Download URL: ${asset.browser_download_url}`);
const tempArchiveName = asset.name;
const tempArchivePath = path.join(storagePath, tempArchiveName);
statusItem.text = `$(cloud-download) Downloading clice...`;
await downloadFile(asset.browser_download_url, tempArchivePath, channel);
statusItem.text = `$(file-zip) Extracting...`;
channel.appendLine(`[Download] Extracting ${tempArchivePath} to ${storagePath}...`);
await decompress(tempArchivePath, storagePath);
channel.appendLine(`[Download] Extraction complete.`);
fs.unlinkSync(tempArchivePath);
if (!fs.existsSync(executablePath)) {
throw new Error(`Executable not found at ${executablePath} after extraction.`);
}
if (platform !== "win32") {
channel.appendLine(`[Download] Setting executable permissions (chmod 755)...`);
fs.chmodSync(executablePath, "755");
}
channel.appendLine(`[Download] Setup successful. Binary ready at: ${executablePath}`);
vscode.window.showInformationMessage(
`Clice language server updated to ${release.tag_name}`,
);
return executablePath;
} catch (error) {
channel.appendLine(`[Download] CRITICAL ERROR during setup:`);
if (error instanceof Error) {
channel.appendLine(`[Download] Message: ${error.message}`);
if (error.stack) {
channel.appendLine(`[Download] Stack: ${error.stack}`);
}
} else {
channel.appendLine(`[Download] Unknown error: ${JSON.stringify(error)}`);
}
vscode.window
.showErrorMessage(
`Failed to download clice server. Check "clice" output channel for details.`,
"Open Output",
)
.then((selection) => {
if (selection === "Open Output") {
channel.show();
}
});
return undefined;
} finally {
statusItem.dispose();
}
}
function downloadFile(
url: string,
destPath: string,
channel: vscode.OutputChannel,
maxRedirects = 5,
): Promise<void> {
return new Promise((resolve, reject) => {
if (maxRedirects <= 0) {
reject(new Error("Too many redirects"));
return;
}
const file = fs.createWriteStream(destPath);
channel.appendLine(`[Download] Start downloading to ${destPath}`);
https
.get(url, { headers: { "User-Agent": "VSCode-Extension" } }, (response) => {
if (response.statusCode === 302 || response.statusCode === 301) {
channel.appendLine(`[Download] Redirecting to ${response.headers.location}`);
file.close();
downloadFile(response.headers.location!, destPath, channel, maxRedirects - 1)
.then(resolve)
.catch(reject);
return;
}
if (response.statusCode !== 200) {
reject(new Error(`Download failed with status code ${response.statusCode}`));
return;
}
response.pipe(file);
file.on("finish", () => {
file.close();
channel.appendLine(`[Download] Download finished.`);
resolve();
});
})
.on("error", (err) => {
file.close();
fs.unlink(destPath, () => {});
reject(err);
});
});
}
async function fetchReleaseInfo(channel: vscode.OutputChannel): Promise<GitHubRelease> {
try {
channel.appendLine("[Download] Attempting to fetch latest stable release...");
const release = await fetchJson<GitHubRelease>("/repos/clice-io/clice/releases/latest");
channel.appendLine(`[Download] Found stable release: ${release.tag_name}`);
return release;
} catch (error: any) {
if (error.message && error.message.includes("404")) {
channel.appendLine(
"[Download] Latest stable release not found (404). Checking for pre-releases...",
);
const releases = await fetchJson<GitHubRelease[]>(
"/repos/clice-io/clice/releases?per_page=1",
);
if (Array.isArray(releases) && releases.length > 0) {
const latestPre = releases[0];
channel.appendLine(`[Download] Found pre-release: ${latestPre.tag_name}`);
return latestPre;
} else {
throw new Error("No releases found in repository.");
}
}
throw error;
}
}
function fetchJson<T>(apiPath: string): Promise<T> {
return new Promise((resolve, reject) => {
const options = {
hostname: "api.github.com",
path: apiPath,
headers: { "User-Agent": "VSCode-Extension" },
};
https
.get(options, (res) => {
let data = "";
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
res.resume();
reject(new Error(`GitHub API returned ${res.statusCode} for ${apiPath}`));
return;
}
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(new Error(`Failed to parse GitHub API response: ${e}`));
}
});
})
.on("error", reject);
});
}

View File

@@ -0,0 +1,95 @@
import * as net from "net";
import * as vscode from "vscode";
import { workspace, window, ExtensionContext } from "vscode";
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
StreamInfo,
} from "vscode-languageclient/node";
import { getSetting } from "./setting";
import { ensureServerBinary } from "./download";
let client: LanguageClient;
export async function registerCommands(client: LanguageClient, context: ExtensionContext) {
context.subscriptions.push(
vscode.commands.registerCommand("clice.restart", async () => {
await client.restart();
}),
);
}
export async function activate(context: ExtensionContext) {
console.log('Congratulations, your extension "clice" is now active!');
const channel = window.createOutputChannel("clice");
const verboseChannel = window.createOutputChannel("clice-verbose");
const setting = getSetting();
if (!setting) {
return;
}
let executable = setting.executable;
let serverOptions: ServerOptions | (() => Promise<StreamInfo>);
if (setting.mode === "pipe") {
if (!executable || executable === "") {
const downloadedPath = await ensureServerBinary(context, channel);
if (downloadedPath) {
executable = downloadedPath;
} else {
window.showErrorMessage("Could not find or download clice executable.");
return;
}
}
let args = ["--mode=pipe"];
serverOptions = {
run: { command: executable, args: args },
debug: { command: executable, args: args },
};
} else if (setting.mode === "socket") {
serverOptions = (): Promise<StreamInfo> => {
return new Promise((resolve, reject) => {
const client = new net.Socket();
client.connect(setting.port, setting.host, () => {
resolve({
reader: client,
writer: client,
});
});
client.on("error", (error) => {
reject(error);
});
});
};
} else {
vscode.window.showErrorMessage("Invalid mode, please set the mode to 'pipe' or 'socket'.");
return;
}
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "cpp" }],
outputChannel: channel,
traceOutputChannel: verboseChannel,
synchronize: {
fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
},
};
client = new LanguageClient("clice", "clice", serverOptions, clientOptions);
await registerCommands(client, context);
await client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
let ret = client.stop();
return ret;
}

View File

@@ -0,0 +1,72 @@
import * as vscode from "vscode";
import { DocumentUri } from "vscode-languageclient/node";
let provider: HeaderContextProvider | undefined = undefined;
export type HeaderContext = {
file: string;
index: number;
include: number;
};
export type HeaderContextSwitchParams = {
header: DocumentUri;
context: HeaderContext;
};
export type IncludeLocation = {
line: number;
filename: string;
};
export class TreeItem extends vscode.TreeItem {
children: Array<TreeItem> = [];
context: HeaderContext | undefined = undefined;
}
export class HeaderContextProvider implements vscode.TreeDataProvider<TreeItem> {
private _onDidChangeTreeData: vscode.EventEmitter<TreeItem | undefined | void> =
new vscode.EventEmitter<TreeItem | undefined | void>();
readonly onDidChangeTreeData: vscode.Event<TreeItem | undefined | void> =
this._onDidChangeTreeData.event;
header: string = "";
items: Array<TreeItem> = [];
update(contexts: Array<Array<HeaderContext>>) {
/// Create groups
this.items = contexts.map((contexts) => {
let item = new TreeItem("", vscode.TreeItemCollapsibleState.Expanded);
item.children = contexts.map((context) => {
const uri = vscode.Uri.file(context.file);
let item = new TreeItem(uri, vscode.TreeItemCollapsibleState.None);
item.context = context;
item.iconPath = vscode.ThemeIcon.File;
item.contextValue = "header-context";
return item;
});
return item;
});
this.refresh();
}
refresh(): void {
this._onDidChangeTreeData.fire();
}
getTreeItem(element: TreeItem) {
return element;
}
getChildren(element?: TreeItem) {
return element ? element.children : this.items;
}
}
export function registerHeaderContextView() {
provider = new HeaderContextProvider();
let treeView = vscode.window.createTreeView("header-contexts", {
treeDataProvider: provider,
});
}

View File

@@ -0,0 +1,63 @@
import * as vscode from "vscode";
const rainbowColors = ["#56B6C2", "#61AFEF", "#C678DD", "#E06C75", "#98C379", "#D19A66", "#E5C07B"];
const textEditorDecorationTypes = rainbowColors.map((color) => {
return vscode.window.createTextEditorDecorationType({
color: color,
});
});
export function highlightDocument(
document: vscode.TextDocument,
legend: vscode.SemanticTokensLegend,
semanticTokens: vscode.SemanticTokens,
) {
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document !== document) {
return;
}
const angleIndex = legend?.tokenTypes.indexOf("angle");
const leftIndex = legend?.tokenModifiers.indexOf("left");
const rightIndex = legend?.tokenModifiers.indexOf("right");
if (leftIndex === undefined || rightIndex === undefined || angleIndex === undefined) {
return;
}
const decorations = new Map<number, vscode.Range[]>();
let level = 0;
let lastLine = 0;
let lastStart = 0;
// [line, startCharacter, length, tokenType, tokenModifiers]
for (let i = 0; i < semanticTokens.data.length; i += 5) {
const [lineDelta, startDelta, length, tokenType, tokenModifiers] =
semanticTokens.data.slice(i, i + 5);
lastLine += lineDelta;
lastStart = lineDelta === 0 ? lastStart + startDelta : startDelta;
const range = new vscode.Range(lastLine, lastStart, lastLine, lastStart + length);
if (tokenType === angleIndex) {
if (tokenModifiers & (1 << rightIndex)) {
level -= 1;
}
if (decorations.has(level % rainbowColors.length)) {
decorations.get(level % rainbowColors.length)?.push(range);
} else {
decorations.set(level % rainbowColors.length, [range]);
}
if (tokenModifiers & (1 << leftIndex)) {
level += 1;
}
}
}
for (const [level, ranges] of decorations) {
editor.setDecorations(textEditorDecorationTypes[level], ranges);
}
}

View File

@@ -0,0 +1,34 @@
import * as vscode from "vscode";
interface Setting {
executable: string | undefined;
mode: string;
host: string;
port: number;
}
export function getSetting(): Setting | undefined {
const setting = vscode.workspace.getConfiguration("clice");
const executable = setting.get<string>("executable");
const mode = process.env.CLICE_MODE || setting.get<string>("mode");
if (mode !== "pipe" && mode !== "socket") {
vscode.window.showErrorMessage(`Unexpected mode: ${mode}`);
return undefined;
}
const host = setting.get<string>("host")!;
const port = setting.get<number>("port")!;
if (mode === "socket" && (!host || !port)) {
vscode.window.showErrorMessage("Socket mode requires both host and port to be configured.");
return undefined;
}
return {
executable,
mode,
host,
port,
};
}

View File

@@ -0,0 +1,15 @@
import * as assert from "assert";
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from "vscode";
// import * as myExtension from '../../extension';
suite("Extension Test Suite", () => {
vscode.window.showInformationMessage("Start all tests.");
test("Sample test", () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});

View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"lib": ["ES2022"],
"sourceMap": true,
"rootDir": "src",
"strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}

View File

@@ -0,0 +1,48 @@
//@ts-check
"use strict";
const path = require("path");
//@ts-check
/** @typedef {import('webpack').Configuration} WebpackConfig **/
/** @type WebpackConfig */
const extensionConfig = {
target: "node", // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
mode: "none", // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
entry: "./src/extension.ts", // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, "dist"),
filename: "extension.js",
libraryTarget: "commonjs2",
},
externals: {
vscode: "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
// modules added here also need to be added in the .vscodeignore file
},
resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
extensions: [".ts", ".js"],
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader",
},
],
},
],
},
devtool: "nosources-source-map",
infrastructureLogging: {
level: "log", // enables logging required for problem matchers
},
};
module.exports = [extensionConfig];

1
editors/zed/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

Some files were not shown because too many files have changed in this diff Show More