diff --git a/404.html b/404.html index 121c24d6..daf4c744 100644 --- a/404.html +++ b/404.html @@ -16,7 +16,7 @@
- + \ No newline at end of file diff --git a/assets/dev_build.md.Dwh2qLHD.js b/assets/dev_build.md.VCy0k9j2.js similarity index 85% rename from assets/dev_build.md.Dwh2qLHD.js rename to assets/dev_build.md.VCy0k9j2.js index e55b0850..7842dda5 100644 --- a/assets/dev_build.md.Dwh2qLHD.js +++ b/assets/dev_build.md.VCy0k9j2.js @@ -13,6 +13,6 @@ import{_ as i,c as a,o as l,ae as e}from"./chunks/framework.U1Gow_7P.js";const c $ 7z x x64-windows-msvc-release.7z "-o.llvm"

IMPORTANT

For debug versions of llvm libs, we enabled address sanitizer during build, and address sanitizer depends on compiler rt, which is very sensitive to compiler versions. So if using debug versions, please ensure your clang's compiler rt version is strictly consistent with what we used during build.

  1. Compile llvm/clang from scratch

This is the most recommended approach, ensuring environment consistency and avoiding crash issues caused by ABI inconsistencies. We provide a script for building the llvm libs required by clice: 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.

Building

After handling the prerequisites, you can start building clice. We provide two build methods: cmake/xmake.

CMake

Below are the cmake parameters supported by clice:

For example:

bash
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DLLVM_INSTALL_PATH="./.llvm" -DCLICE_ENABLE_TEST=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
 $ cmake --build build

Xmake

Use the following command to build clice:

bash
$ xmake f -c --dev=true --mode=debug --toolchain=clang --llvm="./.llvm" --enable_test=true
-$ xmake build --all

--llvm is optional. If not specified, xmake will automatically download our precompiled binary

Run Tests

clice has two forms of tests: unit tests and integration tests.

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"
bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir is clang's built-in header file folder

Or, if you use xmake as the build system, you can directly run tests through xmake:

shell
$ xmake test --verbose
-$ xmake run unit_tests --verbose
-$ xmake test integration_tests/default --verbose
`,43)]))}const F=i(t,[["render",n]]);export{c as __pageData,F as default}; +$ xmake build --all

--llvm is optional. If not specified, xmake will automatically download our precompiled binary

Run Tests

clice has two forms of tests: unit tests and integration tests.

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

We recommend using uv to manage Python dependencies and versions. If you don't want to download uv, please refer to pyproject.toml to download the required Python version and dependencies.

bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir is clang's built-in header file folder

Or, if you use xmake as the build system, you can directly run tests through xmake:

shell
$ xmake test --verbose
+$ xmake run --verbose unit_tests
+$ xmake test --verbose integration_tests/default
`,44)]))}const u=i(t,[["render",n]]);export{c as __pageData,u as default}; diff --git a/assets/dev_build.md.Dwh2qLHD.lean.js b/assets/dev_build.md.VCy0k9j2.lean.js similarity index 83% rename from assets/dev_build.md.Dwh2qLHD.lean.js rename to assets/dev_build.md.VCy0k9j2.lean.js index 548fab76..30b40c9f 100644 --- a/assets/dev_build.md.Dwh2qLHD.lean.js +++ b/assets/dev_build.md.VCy0k9j2.lean.js @@ -1 +1 @@ -import{_ as i,c as a,o as l,ae as e}from"./chunks/framework.U1Gow_7P.js";const c=JSON.parse('{"title":"Build from Source","description":"","frontmatter":{},"headers":[],"relativePath":"dev/build.md","filePath":"en/dev/build.md"}'),t={name:"dev/build.md"};function n(h,s,p,r,k,o){return l(),a("div",null,s[0]||(s[0]=[e("",43)]))}const F=i(t,[["render",n]]);export{c as __pageData,F as default}; +import{_ as i,c as a,o as l,ae as e}from"./chunks/framework.U1Gow_7P.js";const c=JSON.parse('{"title":"Build from Source","description":"","frontmatter":{},"headers":[],"relativePath":"dev/build.md","filePath":"en/dev/build.md"}'),t={name:"dev/build.md"};function n(h,s,p,r,k,o){return l(),a("div",null,s[0]||(s[0]=[e("",44)]))}const u=i(t,[["render",n]]);export{c as __pageData,u as default}; diff --git a/assets/zh_dev_build.md.Fg3sIKlw.js b/assets/zh_dev_build.md.B0g4Cey6.js similarity index 84% rename from assets/zh_dev_build.md.Fg3sIKlw.js rename to assets/zh_dev_build.md.B0g4Cey6.js index 47646e07..87201ca4 100644 --- a/assets/zh_dev_build.md.Fg3sIKlw.js +++ b/assets/zh_dev_build.md.B0g4Cey6.js @@ -13,6 +13,6 @@ import{_ as i,c as a,o as l,ae as e}from"./chunks/framework.U1Gow_7P.js";const o $ 7z x x64-windows-msvc-release.7z "-o.llvm"

IMPORTANT

对于 debug 版本的 llvm libs,构建的时候我们开启了 address sanitizer,而 address sanitizer 依赖于 compiler rt,它对编译器版本十分敏感。所以如果使用 debug 版本,请确保你的 clang 的 compiler rt 版本和我们构建的时候严格一致

  1. 自己从头编译 llvm/clang

这是最推荐的方式,可以保证环境一致性,避免因为 ABI 不一致而导致的崩溃问题。我们提供了一个脚本,用于构建 clice 所需要的 llvm libs:build-llvm-libs.py

bash
$ cd llvm-project
 $ python3 <clice>/scripts/build-llvm-libs.py debug

也可以参考 llvm 的官方构建教程 Building LLVM with CMake

Building

在处理好前置依赖之后,可以开始构建 clice 了,我们提供 cmake/xmake 两种构建方式。

CMake

下面是 clice 支持的 cmake 参数

例如

bach
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DLLVM_INSTALL_PATH="./.llvm" -DCLICE_ENABLE_TEST=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
 $ cmake --build build

Xmake

使用如下的命令即可构建 clice

bash
$ xmake f -c --dev=true --mode=debug --toolchain=clang --llvm="./.llvm" --enable_test=true
-$ xmake build --all

--llvm 是可选的,如果不指定的话,xmake 会自动下载我们编译好的预编译二进制

Run Tests

clice 有两种形式的测试,单元测试和集成测试。

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"
bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir 是 clang 的内置头文件文件夹

或者,如果你使用 xmake 作为构建系统,可以直接通过 xmake 运行测试

shell
$ xmake test --verbose
-$ xmake run unit_tests --verbose
-$ xmake test integration_tests/default --verbose
`,43)]))}const F=i(t,[["render",n]]);export{o as __pageData,F as default}; +$ xmake build --all

--llvm 是可选的,如果不指定的话,xmake 会自动下载我们编译好的预编译二进制

Run Tests

clice 有两种形式的测试,单元测试和集成测试。

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

我们推荐使用 uv 管理 python 依赖和版本。如果不想下载 uv,请参考 pyproject.toml 下载所需的 python 版本和依赖。

bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir 是 clang 的内置头文件文件夹

或者,如果你使用 xmake 作为构建系统,可以直接通过 xmake 运行测试:

shell
$ xmake test --verbose
+$ xmake run --verbose unit_tests
+$ xmake test --verbose integration_tests/default
`,44)]))}const F=i(t,[["render",n]]);export{o as __pageData,F as default}; diff --git a/assets/zh_dev_build.md.Fg3sIKlw.lean.js b/assets/zh_dev_build.md.B0g4Cey6.lean.js similarity index 84% rename from assets/zh_dev_build.md.Fg3sIKlw.lean.js rename to assets/zh_dev_build.md.B0g4Cey6.lean.js index e917fbaa..0aa51320 100644 --- a/assets/zh_dev_build.md.Fg3sIKlw.lean.js +++ b/assets/zh_dev_build.md.B0g4Cey6.lean.js @@ -1 +1 @@ -import{_ as i,c as a,o as l,ae as e}from"./chunks/framework.U1Gow_7P.js";const o=JSON.parse('{"title":"Build from Source","description":"","frontmatter":{},"headers":[],"relativePath":"zh/dev/build.md","filePath":"zh/dev/build.md"}'),t={name:"zh/dev/build.md"};function n(h,s,p,k,r,d){return l(),a("div",null,s[0]||(s[0]=[e("",43)]))}const F=i(t,[["render",n]]);export{o as __pageData,F as default}; +import{_ as i,c as a,o as l,ae as e}from"./chunks/framework.U1Gow_7P.js";const o=JSON.parse('{"title":"Build from Source","description":"","frontmatter":{},"headers":[],"relativePath":"zh/dev/build.md","filePath":"zh/dev/build.md"}'),t={name:"zh/dev/build.md"};function n(h,s,p,k,r,d){return l(),a("div",null,s[0]||(s[0]=[e("",44)]))}const F=i(t,[["render",n]]);export{o as __pageData,F as default}; diff --git a/design/architecture.html b/design/architecture.html index ed4b5c23..58586980 100644 --- a/design/architecture.html +++ b/design/architecture.html @@ -19,7 +19,7 @@
Skip to content

Architecture

Protocol

Use C++ to describe type definitions in the Language Server Protocol.

AST

Some convenient wrappers for clang AST interfaces.

Async

Wrapper for libuv coroutines using C++20 coroutines.

Compiler

Wrapper for clang compilation interfaces, responsible for actual compilation processes and obtaining various compilation information.

Feature

Specific implementations of various LSP features.

Server

clice is a language server, first and foremost a server. It uses libuv as the event library, adopting a common event-driven compilation model. The main thread is responsible for handling requests and dispatching tasks, while the thread pool is responsible for executing time-consuming tasks, such as compilation tasks. Related code is located in the Server directory.

Support

Some other utility libraries.

- + \ No newline at end of file diff --git a/design/compilation.html b/design/compilation.html index 87d29040..4ddc6590 100644 --- a/design/compilation.html +++ b/design/compilation.html @@ -23,7 +23,7 @@ int main () { std::cout << "Hello world!" << std::endl; }

The iostream header file has about 20,000 lines of code. clice will first build the line #include <iostream> into a PCH file, and after completion, use this PCH file to parse the subsequent code. This way, the amount of code that needs to be re-parsed later is reduced to just 5 lines instead of the original 20,000 lines, making it very fast. Unless you modify the code in the preamble section, which requires building a new preamble.

Cancel Compilation

- + \ No newline at end of file diff --git a/design/header-context.html b/design/header-context.html index be8f9c91..87972010 100644 --- a/design/header-context.html +++ b/design/header-context.html @@ -38,7 +38,7 @@ // b.cpp struct X {}; #include "a.h"

a.h itself cannot be compiled, but when embedded in b.cpp, it compiles normally. In this case, clangd will report an error in a.h, unable to find the definition of X. Obviously, this is because it treats a.h as an independent source file. There are many such header files in libstdc++ code, and some popular C++ header-only libraries also have such code, which clangd currently cannot handle.

clice will support header context, supporting automatic and user-initiated switching of header file states, and of course will also support non-self-contained header files. We want to achieve the following effect, using the first piece of code as an example. When you jump from b.cpp to a.h, use b.cpp as the context for a.h. Similarly, when you jump from c.cpp to a.h, use c.cpp as the context for a.h.

- + \ No newline at end of file diff --git a/design/index.html b/design/index.html index 7f8edb42..64b84744 100644 --- a/design/index.html +++ b/design/index.html @@ -19,7 +19,7 @@
Skip to content
- + \ No newline at end of file diff --git a/design/template-resolver.html b/design/template-resolver.html index 0c8f1800..188f53a4 100644 --- a/design/template-resolver.html +++ b/design/template-resolver.html @@ -25,7 +25,7 @@ void foo(std::vector<std::vector<T>> vec2) { vec2[0].^ }

From the user's perspective, completion should also be provided here, since the type of vec2[0] is also vector<T>, right? Same as the previous example. But clangd won't provide any completion here. What's the problem? According to the C++ standard, the return type of std::vector<T>::operator[] is std::vector<T>::reference, which is actually a dependent name. Its result seems quite direct - it's T&. But in libstdc++, its definition is nested in dozens of layers of templates, seemingly for compatibility with old standards? So why can't clangd handle this situation?

  1. It's based on primary template assumptions, not considering that partial specializations might make lookup impossible to proceed
  2. It only performs name lookup without template instantiation, so even if it finds the final result, it can't map it back to the original template parameters
  3. It doesn't consider default template parameters, unable to handle dependent names caused by default template parameters

Although we can make exceptions for standard library types to provide related support, I hope that user code can have the same status as standard library code, so we need a universal algorithm to handle dependent types. To solve this problem, I wrote a pseudo-instantiation (pseudo instantiator). It can instantiate dependent types without specific types, achieving the purpose of simplification. For example, in the above example, std::vector<std::vector<T>>::reference can be simplified to std::vector<T>&, and further provide code completion options for users.

- + \ No newline at end of file diff --git a/dev/build.html b/dev/build.html index 993e1620..97c9ebf2 100644 --- a/dev/build.html +++ b/dev/build.html @@ -13,7 +13,7 @@ - + @@ -33,10 +33,10 @@ $ 7z x x64-windows-msvc-release.7z "-o.llvm"

IMPORTANT

For debug versions of llvm libs, we enabled address sanitizer during build, and address sanitizer depends on compiler rt, which is very sensitive to compiler versions. So if using debug versions, please ensure your clang's compiler rt version is strictly consistent with what we used during build.

  1. Compile llvm/clang from scratch

This is the most recommended approach, ensuring environment consistency and avoiding crash issues caused by ABI inconsistencies. We provide a script for building the llvm libs required by clice: 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.

Building

After handling the prerequisites, you can start building clice. We provide two build methods: cmake/xmake.

CMake

Below are the cmake parameters supported by clice:

For example:

bash
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DLLVM_INSTALL_PATH="./.llvm" -DCLICE_ENABLE_TEST=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
 $ cmake --build build

Xmake

Use the following command to build clice:

bash
$ xmake f -c --dev=true --mode=debug --toolchain=clang --llvm="./.llvm" --enable_test=true
-$ xmake build --all

--llvm is optional. If not specified, xmake will automatically download our precompiled binary

Run Tests

clice has two forms of tests: unit tests and integration tests.

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"
bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir is clang's built-in header file folder

Or, if you use xmake as the build system, you can directly run tests through xmake:

shell
$ xmake test --verbose
-$ xmake run unit_tests --verbose
-$ xmake test integration_tests/default --verbose
- +$ xmake build --all

--llvm is optional. If not specified, xmake will automatically download our precompiled binary

Run Tests

clice has two forms of tests: unit tests and integration tests.

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

We recommend using uv to manage Python dependencies and versions. If you don't want to download uv, please refer to pyproject.toml to download the required Python version and dependencies.

bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir is clang's built-in header file folder

Or, if you use xmake as the build system, you can directly run tests through xmake:

shell
$ xmake test --verbose
+$ xmake run --verbose unit_tests
+$ xmake test --verbose integration_tests/default
+ \ No newline at end of file diff --git a/dev/contribution.html b/dev/contribution.html index b0df4fd4..df70a29d 100644 --- a/dev/contribution.html +++ b/dev/contribution.html @@ -19,7 +19,7 @@
Skip to content

Contribution

We welcome any contributions!

Please refer to build to build clice.

Code Style

Naming:

  • Variable names: lowercase with underscores
  • Class names, enum names: PascalCase
- + \ No newline at end of file diff --git a/guide/configuration.html b/guide/configuration.html index d8e0d3dc..babc2fb2 100644 --- a/guide/configuration.html +++ b/guide/configuration.html @@ -19,7 +19,7 @@
Skip to content

Configuration

This is the documentation for clice.toml.

Server

Rule

[[rules]] represents an array of objects, where each object has the following properties:

NameType
[rules].patternarray of string

Glob patterns for matching file paths, following LSP's standard.

  • *: Matches one or more characters in a path segment.
  • ?: Matches a single character in a path segment.
  • **: Matches any number of path segments, including zero.
  • {}: 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).
NameTypeDefault
[rules].appendarray of string[]

Commands to append to the original command list. For example, append = ["-std=c++17"].

NameTypeDefault
[rules].removearray of string[]

Commands to remove from the original command list. For example, remove = ["-std=c++11"].

NameTypeDefault
[rules].readonlystring"auto"

Controls whether the file is treated as read-only. Values can be one of "auto", "always", and "never".

  • "auto": The file is treated as read-only before you edit it.
  • "always": Always treat the file as read-only.
  • "never": Always treat the file as non-read-only.

Read-only means the file is not editable, and LSP requests like code actions or completions won't be triggered on it. This avoids dynamic computation and allows direct loading of pre-indexed results, improving performance.

NameTypeDefault
[rules].headerstring"auto"

Controls how to handle header files. Values can be one of "auto", "always", and "never".

  • "auto": First try to infer header file context. If no header file context is found, the file will be treated as a regular source file.
  • "always": Always treat the file as a header file. If no header file context is found, an error will be reported.
  • "never": Always treat the file as a source file.

Header file context refers to the source files or other metadata associated with that header file.

NameTypeDefault
[rules].contextsarray of string[]

Specify additional header file contexts (file paths) for the file.

Usually, once a file is indexed, header file context is automatically inferred. However, if you need immediate context before indexing is complete, you can manually provide it using this field.

Cache

NameTypeDefault
cache.dirstring"${workspace}/.clice/cache"

Folder for storing PCH and PCM caches.

Index

NameTypeDefault
index.dirstring"${workspace}/.clice/index"

Folder for storing index files.

Feature

- + \ No newline at end of file diff --git a/guide/quick-start.html b/guide/quick-start.html index 5cce303c..6c8784a2 100644 --- a/guide/quick-start.html +++ b/guide/quick-start.html @@ -19,7 +19,7 @@
Skip to content

Quick Start

Editor Plugins

clice implements the Language Server Protocol. Any editor that supports this protocol can theoretically work with clice to provide features like code completion, diagnostics, go-to-definition, and more.

However, beyond the standard protocol, clice also supports some protocol extensions. For better handling of these protocol extensions and better integration with editors, using clice plugins in specific editors is often a better choice. Most of them work out of the box and support clice's protocol extensions.

Visual Studio Code

Vim/Neovim

Others

Other editors don't have available clice plugins yet (contributions welcome!). To use clice in them, please install clice yourself and refer to the specific editor's documentation on how to use a language server.

Installation

If your editor plugin handles clice's download, you can skip this step.

Download Prebuilt Binary

Download clice binary version through the Release page.

Build from Source

Build clice from source yourself. For specific steps, refer to build.

Project Setup

For clice to correctly understand your code (e.g., find header file locations), you need to provide clice with a compile_commands.json file, also known as a compilation database. The compilation database provides compilation options for each source file.

CMake

For build systems using cmake, add the -DCMAKE_EXPORT_COMPILE_COMMANDS=ON option during build, for example:

cmake
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

This will generate a compile_commands.json file in the build directory.

WARNING

Note: This option only works when cmake's generator is set to makefile and ninja. For other generators, this option will be ignored, meaning the compilation database cannot be generated.

Bazel

TODO:

Visual Studio

TODO:

Makefile

TODO:

Xmake

Others

For any other build system, you can try using bear or 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.

- + \ No newline at end of file diff --git a/guide/what-is-clice.html b/guide/what-is-clice.html index 121b224a..f2b0db19 100644 --- a/guide/what-is-clice.html +++ b/guide/what-is-clice.html @@ -19,7 +19,7 @@
Skip to content

What is clice?

clice is a completely new C++ language server designed to address the shortcomings of existing C++ language servers. It provides code navigation and intelligent suggestions for your editor.

Why a New Language Server?

So the first question is, why develop a new language server? Is it necessary to reinvent the wheel?

This question deserves a serious answer. Before this project, I had written many projects, both large and small. But the vast majority of them were toy projects, written only to verify some idea or for personal learning, without solving any real problems. clice is not like that - it genuinely intends to solve existing problems (specific problems will be discussed later), rather than rewriting for the sake of rewriting.

At the beginning of this year, I wanted to participate in the development of the LLVM project. I wanted to start from what I'm more familiar with - C++, specifically clang. But without requirements, I couldn't just stare at the source code. The normal process in such cases is to start with some "first issues" and gradually get familiar with the project. But I found this boring - I wanted to do something big right from the start, like implementing some new C++ standard feature. However, I found there was almost no place for me to get involved here, as new feature implementations are almost always completed by a few core clang developers. Well, since there's no opportunity here, let's look elsewhere. My attention naturally shifted to clangd, since I mainly use VSCode for development, and the best C++ language server on VSCode is clangd.

At the time, I knew nothing about clangd, except that I found it seemed to render keyword highlighting incorrectly. So I started reading clangd's source code while browsing through clangd's numerous issues to see if there was anything I could solve. After going through hundreds of issues, I found there were quite a few problems here. At the time, I was particularly interested in an issue about code completion within templates. Why was I interested in this? Readers familiar with me might know that I'm quite an experienced metaprogramming player, having written many related articles before. Naturally, I'm not only curious about how template metaprogramming itself works, but also curious about how clang, as a compiler, implements related features. This issue was a good entry point for me. After spending several weeks exploring prototype implementations, I initially solved that issue, but then I found that there was no one to review the related code!

After some investigation, I found that clangd's current situation is quite bad. Let's go through the timeline: clangd was originally just a simple small project within LLVM, not outstanding in terms of functionality and usability. As MaskRay mentioned in this blog post about ccls, clangd at the time could only handle single compilation units, and cross-compilation unit requests couldn't be processed. This blog post was published in 2017, which is also one reason why MaskRay chose to write ccls. ccls is also a C/C++ language server that was stronger than clangd at that point. However, later, Google started sending people to improve clangd to meet their internal large codebase needs. At the same time, the LSP standard content was constantly expanding, and clangd was continuously following up on new standard content, but ccls's author seemed to gradually become busy with other things and didn't have much time to maintain ccls. So in the end, clangd had surpassed ccls overall. The turning point occurred around 2023, when clangd seemed to have reached a highly usable state for Google internally, and the original employees responsible for clangd were transferred to do other things. Currently, clangd's issues are mainly handled by only one person, HighCommander4, purely out of passion, not employed by anyone. Since he's not specifically employed to maintain clangd, he can only handle issues in his limited free time, and only limited to answering questions and very limited reviews. As he mentioned in this comment:

The other part of the reason is lack of resources to pursue the ideas we do have, such as the idea mentioned above of trying to shift more of the burden to disk usage through more aggressive preamble caching. I'm a casual contributor, and the limited time I have to spend on clangd is mostly taken up by answering questions, some code reviews, and the occasional small fix / improvement; I haven't had the bandwidth to drive this type of performance-related experimentation.

Given this situation, it's not surprising that large PRs like preliminary support for C++20 modules for clangd have been delayed for nearly a year. After realizing this current state, I had the idea of writing a language server myself. I estimated the project size - removing test code, it would take about 20,000 lines to complete, which is a workload that one person can accomplish over a period of time, and there are precedents like ccls and rust-analyzer. Another point is that clangd's code is already dated - despite having many comments, the related logic is still very convoluted, and the time spent on large-scale modifications might not be faster than rewriting.

So I got to work. I categorized clangd's hundreds of issues to see if there were some problems that were difficult to solve due to clangd's initial architectural design errors and were therefore shelved. If so, could these be considered and solved during redesign? I found that there were indeed some! So in the following time, I spent about two months learning and researching the related mechanisms in clang, exploring solutions to related problems, and exploring prototype implementations. After confirming that the related problems could basically be solved, I officially started the development of clice.

- + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json index e2ec5e5b..65198840 100644 --- a/hashmap.json +++ b/hashmap.json @@ -1 +1 @@ -{"design_architecture.md":"D8TKjkhM","design_compilation.md":"DgqTpBDl","design_header-context.md":"D86T7s_L","design_index.md":"BgicbTS7","design_template-resolver.md":"IYEsm7Dh","dev_build.md":"Dwh2qLHD","dev_contribution.md":"D-EaJs3t","guide_configuration.md":"BSfrI2TP","guide_quick-start.md":"CAVsOW0R","guide_what-is-clice.md":"q1sqlvBr","index.md":"B80amafG","zh_design_architecture.md":"BVcDWyLW","zh_design_compilation.md":"DfE77o6C","zh_design_header-context.md":"Cr4H3x27","zh_design_index.md":"DHNbht8F","zh_design_template-resolver.md":"BdV6FoQY","zh_dev_build.md":"Fg3sIKlw","zh_dev_contribution.md":"UxN4TV1f","zh_guide_configuration.md":"DM65IMG5","zh_guide_quick-start.md":"CcQPc5x5","zh_guide_what-is-clice.md":"DFkS8WKr","zh_index.md":"DOAh-Isq"} +{"design_architecture.md":"D8TKjkhM","design_compilation.md":"DgqTpBDl","design_header-context.md":"D86T7s_L","design_index.md":"BgicbTS7","design_template-resolver.md":"IYEsm7Dh","dev_build.md":"VCy0k9j2","dev_contribution.md":"D-EaJs3t","guide_configuration.md":"BSfrI2TP","guide_quick-start.md":"CAVsOW0R","guide_what-is-clice.md":"q1sqlvBr","index.md":"B80amafG","zh_design_architecture.md":"BVcDWyLW","zh_design_compilation.md":"DfE77o6C","zh_design_header-context.md":"Cr4H3x27","zh_design_index.md":"DHNbht8F","zh_design_template-resolver.md":"BdV6FoQY","zh_dev_build.md":"B0g4Cey6","zh_dev_contribution.md":"UxN4TV1f","zh_guide_configuration.md":"DM65IMG5","zh_guide_quick-start.md":"CcQPc5x5","zh_guide_what-is-clice.md":"DFkS8WKr","zh_index.md":"DOAh-Isq"} diff --git a/index.html b/index.html index 7a67b870..1e5d6900 100644 --- a/index.html +++ b/index.html @@ -19,7 +19,7 @@
Skip to content

cliceNext Generation C++ Language Server

Development is actively in progress

clice
- + \ No newline at end of file diff --git a/zh/design/architecture.html b/zh/design/architecture.html index 88c05216..1e6eb70d 100644 --- a/zh/design/architecture.html +++ b/zh/design/architecture.html @@ -19,7 +19,7 @@
Skip to content

Architecture

Protocol

使用 C++ 来描述 Language Server Protocol 中的类型定义。

AST

对 clang AST 接口的一些方便的封装

Async

使用 C++20 coroutine 对 libuv 协程的封装

Compiler

对 clang 编译接口的封装,负责实际的编译过程,以及各种编译信息的获取。

Feature

各种 LSP 特性的具体实现。

Server

clice 是一个语言服务器,首先是一个服务器。它使用 libuv 作为事件库,采用常见的事件驱动的编译模型。主线程负责处理请求以及分发任务,线程池负责执行耗时的任务,比如编译任务。相关的代码位于 Server 目录下。

Support

一些其它的工具库。

- + \ No newline at end of file diff --git a/zh/design/compilation.html b/zh/design/compilation.html index 3cd71337..a9402271 100644 --- a/zh/design/compilation.html +++ b/zh/design/compilation.html @@ -23,7 +23,7 @@ int main () { std::cout << "Hello world!" << std::endl; }

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

Cancel Compilation

- + \ No newline at end of file diff --git a/zh/design/header-context.html b/zh/design/header-context.html index 1c8d34c5..737abe98 100644 --- a/zh/design/header-context.html +++ b/zh/design/header-context.html @@ -38,7 +38,7 @@ // b.cpp struct X {}; #include "a.h"

a.h自身不能被编译,但是嵌入到b.cpp中的时候就编译正常了。这种情况下 clangd 会在a.h中报错,找不到X的定义。显然这是因为它把a.h当成一个独立的源文件了。在 libstdc++ 中的代码中就有很多这样的头文件,现在流行的一些 C++ 的 header-only 的库也有些有这样的代码,clangd 目前无法处理它们。

clice 将会支持头文件上下文 (header context),支持自动和用户主动切换头文件的状态,当然也会支持非自包含的头文件。我们想要实现如下的效果,以最开始那份代码为例。当你从b.cpp跳转到a.h的时候使用b.cpp作为a.h的上下文。同理,当你从c.cpp跳转到a.h的时候则使用c.cpp作为a.h的上下文。

- + \ No newline at end of file diff --git a/zh/design/index.html b/zh/design/index.html index 6d29b53c..c61b4289 100644 --- a/zh/design/index.html +++ b/zh/design/index.html @@ -19,7 +19,7 @@
Skip to content
- + \ No newline at end of file diff --git a/zh/design/template-resolver.html b/zh/design/template-resolver.html index 8541f46e..51f02335 100644 --- a/zh/design/template-resolver.html +++ b/zh/design/template-resolver.html @@ -25,7 +25,7 @@ void foo(std::vector<std::vector<T>> vec2) { vec2[0].^ }

从用户的角度来说,这里也应该提供补全,毕竟vec2[0]的类型不也是vector<T>吗?和前面一个例子一样。但是 clangd 在这里却不会为你提供任何补全,问题出在哪里?根据 C++ 标准,std::vector<T>operator[]返回的类型是std::vector<T>::reference,这其实是一个 dependent name,它的结果似乎相当直接,就是T&。但是 libstdc++ 中它的定义却嵌套了十几层模板,似乎是为了兼容旧标准?那为什么 clangd 不能处理这种情况呢?

  1. 它基于主模板假设,不考虑偏特化可能会使查找无法进行下去
  2. 它只进行名称查找而不进行模板实例化,就算找到了最后的结果,也没法把它和最初的模板参数映射起来
  3. 不考虑默认模板参数,无法处理由默认模板参数导致的依赖名

尽管我们可以对标准库的类型开洞来提供相关的支持,但是我希望用户的代码能和标准库的代码有相同的地位,那么我们就需要一种通用的算法来处理依赖类型。为了解决这个问题,我编写了一个伪实例化器(pseudo instantiator)。它能在没有具体类型的前提下对依赖类型进行实例化,从而达到化简的目的。比如上面这个例子里面的std::vector<std::vector<T>>::reference就能被化简为std::vector<T>&,进一步就能为用户提供代码补全选项。

- + \ No newline at end of file diff --git a/zh/dev/build.html b/zh/dev/build.html index 66ccd14e..07dafd9d 100644 --- a/zh/dev/build.html +++ b/zh/dev/build.html @@ -13,7 +13,7 @@ - + @@ -33,10 +33,10 @@ $ 7z x x64-windows-msvc-release.7z "-o.llvm"

IMPORTANT

对于 debug 版本的 llvm libs,构建的时候我们开启了 address sanitizer,而 address sanitizer 依赖于 compiler rt,它对编译器版本十分敏感。所以如果使用 debug 版本,请确保你的 clang 的 compiler rt 版本和我们构建的时候严格一致

  1. 自己从头编译 llvm/clang

这是最推荐的方式,可以保证环境一致性,避免因为 ABI 不一致而导致的崩溃问题。我们提供了一个脚本,用于构建 clice 所需要的 llvm libs:build-llvm-libs.py

bash
$ cd llvm-project
 $ python3 <clice>/scripts/build-llvm-libs.py debug

也可以参考 llvm 的官方构建教程 Building LLVM with CMake

Building

在处理好前置依赖之后,可以开始构建 clice 了,我们提供 cmake/xmake 两种构建方式。

CMake

下面是 clice 支持的 cmake 参数

例如

bach
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DLLVM_INSTALL_PATH="./.llvm" -DCLICE_ENABLE_TEST=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
 $ cmake --build build

Xmake

使用如下的命令即可构建 clice

bash
$ xmake f -c --dev=true --mode=debug --toolchain=clang --llvm="./.llvm" --enable_test=true
-$ xmake build --all

--llvm 是可选的,如果不指定的话,xmake 会自动下载我们编译好的预编译二进制

Run Tests

clice 有两种形式的测试,单元测试和集成测试。

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"
bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir 是 clang 的内置头文件文件夹

或者,如果你使用 xmake 作为构建系统,可以直接通过 xmake 运行测试

shell
$ xmake test --verbose
-$ xmake run unit_tests --verbose
-$ xmake test integration_tests/default --verbose
- +$ xmake build --all

--llvm 是可选的,如果不指定的话,xmake 会自动下载我们编译好的预编译二进制

Run Tests

clice 有两种形式的测试,单元测试和集成测试。

bash
$ ./build/bin/unit_tests --test-dir="./tests/data" --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

我们推荐使用 uv 管理 python 依赖和版本。如果不想下载 uv,请参考 pyproject.toml 下载所需的 python 版本和依赖。

bash
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice --resource-dir="<LLVM_INSTALL_PATH>/lib/clang/20"

resource-dir 是 clang 的内置头文件文件夹

或者,如果你使用 xmake 作为构建系统,可以直接通过 xmake 运行测试:

shell
$ xmake test --verbose
+$ xmake run --verbose unit_tests
+$ xmake test --verbose integration_tests/default
+ \ No newline at end of file diff --git a/zh/dev/contribution.html b/zh/dev/contribution.html index 96ed910d..dca4ba79 100644 --- a/zh/dev/contribution.html +++ b/zh/dev/contribution.html @@ -19,7 +19,7 @@
Skip to content

Contribution

我们欢迎任何贡献!

请参考 build 来构建 clice

Code Style

命名

  • 变量名:小写下换线
  • 类名,枚举名:大驼峰
- + \ No newline at end of file diff --git a/zh/guide/configuration.html b/zh/guide/configuration.html index 32c59148..e276ab98 100644 --- a/zh/guide/configuration.html +++ b/zh/guide/configuration.html @@ -19,7 +19,7 @@
Skip to content

Configuration

这是 clice.toml 的文档。

Server

Rule

[[rules]] 表示一个对象数组,其中每个对象都拥有下面这些属性

名称类型
[rules].patternarray of string

用于匹配文件路径的 glob patterns,遵循 LSP 的 标准

  • *: 匹配路径段中的一个或多个字符。
  • ?: 匹配路径段中的单个字符。
  • **: 匹配任意数量的路径段,包括零个。
  • {}: 用于分组条件 (例如, **/*.{ts,js} 匹配所有 TypeScript 和 JavaScript 文件)。
  • []: 声明要匹配的路径段中的字符范围 (例如, example.[0-9] 匹配 example.0, example.1 等)。
  • [!...]: 排除要匹配的路径段中的字符范围 (例如, example.[!0-9] 匹配 example.a, example.b,但不匹配 example.0)。
名称类型默认值
[rules].appendarray of string[]

追加到原始命令列表中的命令。例如,append = ["-std=c++17"]

名称类型默认值
[rules].removearray of string[]

从原始命令列表中移除的命令。例如,remove = ["-std=c++11"]

名称类型默认值
[rules].readonlystring"auto"

控制文件是否被视为只读。值可以是 "auto""always""never" 中的一个。

  • "auto": 在你编辑文件之前,文件被视为只读。
  • "always": 始终将文件视为只读。
  • "never": 始终将文件视为非只读。

只读意味着文件不可编辑,并且像代码操作 (code actions) 或补全 (completions) 这样的 LSP 请求不会在其上触发。这避免了动态计算,并允许直接加载预先索引的结果,从而提高性能。

名称类型默认值
[rules].headerstring"auto"

控制如何处理头文件。值可以是 "auto""always""never" 中的一个。

  • "auto": 首先尝试推断头文件上下文。如果未找到头文件上下文,文件将被视为普通的源文件。
  • "always": 始终将文件视为头文件。如果未找到头文件上下文,将会报告错误。
  • "never": 始终将文件视为源文件。

头文件上下文指的是与该头文件相关联的源文件或其他元数据。

名称类型默认值
[rules].contextsarray of string[]

为文件指定额外的头文件上下文 (文件路径)。

通常,一旦文件被索引,头文件上下文会自动推断。但是,如果你需要在索引完成前获得即时上下文,可以使用此字段手动提供。

Cache

名称类型默认值
cache.dirstring"${workspace}/.clice/cache"

用于储存 PCH 和 PCM 缓存的文件夹。

Index

名称类型默认值
index.dirstring"${workspace}/.clice/index"

用于储存索引文件的文件夹。

Feature

- + \ No newline at end of file diff --git a/zh/guide/quick-start.html b/zh/guide/quick-start.html index 4df8d8b0..21726409 100644 --- a/zh/guide/quick-start.html +++ b/zh/guide/quick-start.html @@ -19,7 +19,7 @@
Skip to content

Quick Start

Editor Plugins

clice 实现了 Language Server Protocol,任何支持该协议的编辑器原则上均可以与 clice 一起使用,提供像 code completion, diagnostics, go-to-definition, 等等。

但是除了标准协议之外,clice 还支持一些协议扩展,为了更好的处理这些协议扩展以及更好的与编辑器集成。使用特定编辑器中的 clice 插件往往是更好的选择,它们大多数都是开箱即用的,并且支持 clice 的协议扩展。

Visual Studio Code

Vim/Neovim

Others

其它的编辑器还没有可用的 clice 插件(欢迎贡献!),为了在它们中使用 clice,请自行安装 clice 并参考特定编辑器的文档关于如何使用一个语言服务器。

Installation

如果你的编辑器插件负责了 clice 的下载,可以跳过这一步。

Download Prebuilt Binary

通过 Release 界面下载 clice 二进制版本。

Build from Source

自己从源码编译 clice,具体的步骤参考 build

Project Setup

为了让 clice 能正确理解你的代码(例如找到头文件的位置),需要为 clice 提供一份 compile_commands.json 文件,也就说所谓的 编译数据库。编译数据库中提供了每个源文件的编译选项。

CMake

对于使用 cmake 的构建系统来说,在构建的时候添加 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 选项即可,例如

cmake
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON

这会在 build 目录下生成一份 compile_commands.json 文件。

WARNING

注意:只有当 cmake 的生成器选择为 makefile 和 ninja 的时候,这个选项才有作用。对于其它生成器会忽略这个选项,也就是说无法生成编译数据库。

Bazel

TODO:

Visual Studio

TODO:

Makefile

TODO:

Xmake

Others

对于任意其它的构建系统,可以尝试使用 bear 或者 scan-build 来拦截编译命令并获取到编译数据库(不保证成功)。我们计划在未来编写一个新的工具,通过假编译器的方式来实现编译命令的捕获。

- + \ No newline at end of file diff --git a/zh/guide/what-is-clice.html b/zh/guide/what-is-clice.html index 00ee3f1f..4e2761ee 100644 --- a/zh/guide/what-is-clice.html +++ b/zh/guide/what-is-clice.html @@ -19,7 +19,7 @@
Skip to content

What is clice?

clice 是一个全新的 C++ 的语言服务器,旨在解决现存 C++ 语言服务器的不足,它可以为你的编辑器提供代码导航和智能提示等服务。

Why a New Language Server?

那么第一个问题,为什么要去开发一个新的语言服务器?重复造轮子有必要吗?

这个问题值得去认真回答。在这个项目之前,我自己也编写过或大或小的很多项目。但它们绝大多数都是 toy project,只是为了验证某个想法或者个人学习而编写的,并没有解决任何的实际问题。clice 并不是这样的,它确实打算解决现有的问题(具体的问题后面再说),而不是为了重写而重写。

在今年年初,我想参与到 llvm 项目的开发中。我想从我较为熟悉的地方,C++,也就是 clang 开始。但是,没需求的话,总不能干瞪源码吧。一般这种时候的正常流程是从一些 first issue 开始,一点点熟悉项目。但是我觉得这样很无聊,一上来我就想来干点大的,比如实现某个 C++ 新标准的特性。但是,我发现这里几乎没有我能插手的地方,新特性的实现几乎总是由几位 clang 的核心开发者完成。好吧,既然这里没什么机会,那就看看别的地方吧。注意力自然而然的转移到了 clangd 身上去了,毕竟我主要使用 vscode 进行开发,而 vscode 上最好用的 C++ 语言服务器就是 clangd 了。

当时我对 clangd 一无所知,只是发现它似乎对关键字的高亮渲染并不正确。然后呢我就开始一边阅读 clangd 的源码,一边翻翻 clangd 数量众多的 issue 看看有没有什么我能解决的。在翻了几百个 issue 过后,我发现这里的问题还真不少。当时我特别感兴趣的是一个有关模板内代码补全的 issue,为什么我对这个感兴趣呢?熟悉我的读者可能知道,我算是一个资深的元编程玩家了,在这之前也写过很多相关的文章。那很自然的,我不仅仅好奇模板元它本身是如何运作的,也好奇 clang 作为一个编译器是如何实现相关的特性的,这个 issue 对我来说是一个很好的切入点。在花了几个星期摸索原型实现后,我初步解决了那个 issue,但是,这时我发现根本没有人可以 review 相关的代码!

一番调查过后,我发现 clangd 目前的情况很糟糕。让我们来捋一捋时间线,clangd 最初只是 llvm 内部一个简单的小项目,在功能性和易用性上都不出色。正如 MaskRay 在 ccls 中的这篇 博客 中所说,clangd 在当时只能处理单个编译单元,跨编译单元的请求无法处理。这篇博客发布的时间是 2017 年,这也是为什么 MaskRay 选择编写 ccls 的一个原因。ccls 也是一个 C/C++ 语言服务器,在当时那个节点上是强于 clangd 的。但是,后来,Google 开始派人对 clangd 进行改进以满足他们内部的大型代码库需求。与此同时,LSP 标准的内容也在不断地扩充,clangd 在不断地跟进新标准的内容,但是 ccls 的作者似乎逐渐忙于其它事情没有太多的时间去维护 ccls。于是最后,总体上 clangd 已经超越了 ccls。事情的转折发生在大概 2023 年,clangd 对 Google 内部来说,似乎已经达到了一个高度可用的状态,原先负责 clangd 的几位员工也被调离去做其他事情了。目前来说,clangd 的 issue 主要只有 HighCommander4 一个人在负责处理,纯粹出于热爱,并没有被任何人雇佣。由于并没有被专门雇佣来维护 clangd,所以他只能在有限的空闲时间来处理 issue,而且也仅限于答疑和十分有限的 review。正如他在这条 评论 中提到的一样:

The other part of the reason is lack of resources to pursue the ideas we do have, such as the idea mentioned above of trying to shift more of the burden to disk usage through more aggressive preamble caching. I'm a casual contributor, and the limited time I have to spend on clangd is mostly taken up by answering questions, some code reviews, and the occasional small fix / improvement; I haven't had the bandwidth to drive this type of performance-related experimentation.

另一部分原因是缺乏资源去实践我们已有的一些想法,例如上面提到的通过更积极的预编译缓存,将更多的负担转移到磁盘使用上的想法。我只是一个非正式的贡献者,我能投入到 clangd 上的时间非常有限,主要用于回答问题、进行一些代码审查以及偶尔的小修复或改进;我没有足够的精力来推动这种与性能相关的实验。

既然如此,那么像为 clangd 初步支持 C++20 module 这样的大型 PR 被拖了将近一年也就不奇怪了。意识到这个现状之后,我萌生了自己编写一个语言服务器的想法。我估计了一下项目大小,去除测试代码,大概 2w 行就能完成,是一个人花一段时间能完成的工作量,而且也有先例,例如 ccls 和 rust analyzer。另外一点就是 clangd 的代码已经上了年代了,尽管有非常多的注释,但是相关的逻辑仍然很绕,进行大范围修改所花费的时间可能还不如重写来得快。

于是说干就干,我对 clangd 的几百个 issue 进行了分类,看看有没有一些问题是因为 clangd 一开始的架构设计错误而导致很难解决,然后被搁置的。如果有的话,是否能在重新设计的时候就考虑这个问题来解决呢?我发现,确实有一些!于是接下来的时间里,我花了大概两个月的时间来学习研究 clang 里面相关的机制,摸索相关问题的解决方法,探索原型实现,在确定相关的问题基本都可以解决之后,正式开始了 clice 的开发。

- + \ No newline at end of file diff --git a/zh/index.html b/zh/index.html index 917faf59..e708564b 100644 --- a/zh/index.html +++ b/zh/index.html @@ -19,7 +19,7 @@
Skip to content

clice下一代 C++ 语言服务器

开发正在活跃进行中

clice
- + \ No newline at end of file