Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions cmake/package.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,20 @@ set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)

# cpptrace
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v1.0.4
GIT_SHALLOW TRUE
)
set(CPPTRACE_DISABLE_CXX_20_MODULES ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers)

FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers cpptrace)
if(CLICE_ENABLE_TEST)
# cpptrace
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v1.0.4
GIT_SHALLOW TRUE
)
set(CPPTRACE_DISABLE_CXX_20_MODULES ON CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(cpptrace)
endif()

if(WIN32)
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
Expand Down
74 changes: 74 additions & 0 deletions docs/en/dev/server-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
You can implement a clice server plugin to extend clice's functionality.

## Use case

When you use `clice` as LSP backend of LLM agents, e.g. claude code, you can add plugin to provide some extra features.

## Writing a plugin

When a plugin is loaded by the server, it will call `clice_get_server_plugin_info` to obtain information about this plugin and about how to register its customization points.

This function needs to be implemented by the plugin, see the example below:

```c++
extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
clice_get_server_plugin_info() {
return {
CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.1", CLICE_PLUGIN_DEF_HASH,
[](ServerPluginBuilder builder) { ... }
};
}
```

See [PluginDef.h](/include/Server/PluginDef.h) for more details.

## Compiling a plugin

The plugin must be compiled with the same dependencies and compiler options as clice, otherwise it will cause undefined behavior. [config/llvm-manifest.json](/config/llvm-manifest.json) defines the build information used by clice.

## Loading plugins

For security reasons, clice does not allow loading plugins through configuration files, but must specify the plugin path through command line options.

When `clice` starts, it will load all plugins specified in the command line. You can specify the plugin path through the `--plugin-path` option.

```shell
$ clice --plugin-path /path/to/my-plugin.so
```

## Getting content of `CLICE_PLUGIN_DEF_HASH`

There are two values to return in the `clice_get_server_plugin_info` function.

- `CLICE_PLUGIN_API_VERSION` is used to ensure compability of the `clice_get_server_plugin_info` function between the plugin and the server.
- `CLICE_PLUGIN_DEF_HASH` is used to ensure the consistency of the C++ declarations between the plugin and the server.

To debug the content of `CLICE_PLUGIN_DEF_HASH`, you can run following command:

```shell
$ git clone https://github.com/clice-io/clice.git
$ cd clice
$ git checkout `clice --version --git-describe`
$ python scripts/plugin-def.py content
```

You will get a C source code file, content of which is like this:

```cpp
#if 0
// begin of config/llvm-manifest.json
[
{
"version": "21.1.4+r1",
"filename": "arm64-macos-clang-debug-asan.tar.xz",
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
"lto": false,
"asan": true,
"platform": "macosx",
"build_type": "Debug"
},
...
]
...
#endif
```
74 changes: 74 additions & 0 deletions docs/zh/dev/server-plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
你可以在 clice 中实现一个 server plugin 来扩展 clice 的功能。

## 用例

当你使用 `clice` 作为 LLM 代理的 LSP 后端时,比如 claude code,你可以添加插件来提供一些额外功能。

## 编写插件

当一个插件被服务器加载时,它会调用 `clice_get_server_plugin_info` 来获取关于这个插件的信息以及如何注册它的定制点。

这个函数需要由插件实现,请参考下面的示例:

```cpp
extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
clice_get_server_plugin_info() {
return {
CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.1", CLICE_PLUGIN_DEF_HASH,
[](ServerPluginBuilder builder) { ... }
};
}
```

请参考 [PluginDef.h](/include/Server/PluginDef.h) 了解更多细节。

## 编译插件

插件必须使用与 clice 一致的依赖和编译器选项来编译,否则会导致 undefined behavior。[config/llvm-manifest.json](/config/llvm-manifest.json) 中定义了 clice 使用的构建信息。

## 加载插件

为了安全考虑,clice 不允许通过配置文件来加载插件,而必须通过命令行选项来指定插件的路径。

在 `clice` 启动时,它会加载所有在命令行中指定的插件。你可以通过 `--plugin-path` 选项来指定插件的路径。

```shell
$ clice --plugin-path /path/to/my-plugin.so
```

## 获取 `CLICE_PLUGIN_DEF_HASH` 的内容

在 `clice_get_server_plugin_info` 函数中需要返回两个值。

- `CLICE_PLUGIN_API_VERSION` 用于确保插件和服务器之间的 `clice_get_server_plugin_info` 函数的一致性。
- `CLICE_PLUGIN_DEF_HASH` 用于确保插件和服务器之间的 C++ 声明的一致性。

要调试 `CLICE_PLUGIN_DEF_HASH` 的内容,你可以运行以下命令:

```shell
$ git clone https://github.com/clice-io/clice.git
$ cd clice
$ git checkout `clice --version --git-describe`
$ python scripts/plugin-def.py content > /tmp/plugin-proto.h
```

你将会得到一个 C 源码格式的文件,内容大致如下:

```cpp
#if 0
// begin of config/llvm-manifest.json
[
{
"version": "21.1.4+r1",
"filename": "arm64-macos-clang-debug-asan.tar.xz",
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
"lto": false,
"asan": true,
"platform": "macosx",
"build_type": "Debug"
},
...
]
...
#endif
```
4 changes: 4 additions & 0 deletions include/Protocol/Basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#include <string>
#include <vector>

#include "llvm/Support/JSON.h"

namespace clice::proto {

using integer = std::int32_t;
Expand All @@ -15,6 +17,8 @@ using decimal = double;

using string = std::string;

using any = llvm::json::Value;

template <typename T>
using array = std::vector<T>;

Expand Down
17 changes: 17 additions & 0 deletions include/Protocol/Feature/ExecuteCommand.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include "../Basic.h"

namespace clice::proto {

struct ExecuteCommandParams {
string command;
array<any> arguments;
};

struct TextDocumentParams {
/// The text document.
TextDocumentIdentifier textDocument;
};

} // namespace clice::proto
1 change: 1 addition & 0 deletions include/Protocol/TextDocument.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "Feature/DocumentHighlight.h"
#include "Feature/DocumentLink.h"
#include "Feature/DocumentSymbol.h"
#include "Feature/ExecuteCommand.h"
#include "Feature/FoldingRange.h"
#include "Feature/Formatting.h"
#include "Feature/Hover.h"
Expand Down
13 changes: 13 additions & 0 deletions include/Server/Indexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class Indexer {
if(it2 != project_index.indices.end()) {
auto path = project_index.path_pool.path(it2->second);
it->second = index::MergedIndex::load(path);
} else {
std::println(stderr,
"failed to load project index for path_id: {} {}",
path_id,
project_index.indices.size());
}

return it->second;
Expand All @@ -67,6 +72,14 @@ class Indexer {

/// TODO: Types ...

bool empty() const {
return project_index.indices.empty();
}

size_t size() const {
return project_index.indices.size();
}

private:
CompilationDatabase& database;

Expand Down
59 changes: 59 additions & 0 deletions include/Server/Plugin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once
#include <expected>

#include "PluginProtocol.h"

#include "llvm/ADT/StringRef.h"

// clang-format off
/// Run `python scripts/plugin-def.py update` to update the hash.
#define CLICE_PLUGIN_DEF_HASH "sha256:e7f911c923f9cdcec6c0edea328292bd48771f7139d4489ed694a72e9af33fc1"
// clang-format on

namespace clice {

/// The hash of the definitions exposed to server plugins.
constexpr std::string_view plugin_definition_hash = CLICE_PLUGIN_DEF_HASH;

class Server;

struct ServerPluginBuilder;

/// A loaded server plugin.
///
/// An instance of this class wraps a loaded server plugin and gives access to its interface.
class Plugin {
public:
/// Attempts to load a server plugin from a given file.
///
/// Returns an error if either the library cannot be found or loaded,
/// there is no public entry point, or the plugin implements the wrong API
/// version.
static std::expected<Plugin, std::string> load(const std::string& file_path);

/// Gets the file path of the loaded plugin.
llvm::StringRef file_path() const;

/// Gets the name of the loaded plugin.
llvm::StringRef name() const;

/// Gets the version of the loaded plugin.
llvm::StringRef version() const;

/// Registers the server callbacks for the loaded plugin.
void register_server_callbacks(ServerPluginBuilder& builder) const;

public:
struct Self;

Plugin(Self* self) : self(self) {}

Self* operator->() {
return self;
}

protected:
Self* self;
};

} // namespace clice
Loading
Loading