Compare commits
30 Commits
folding-ra
...
server-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8168577b | ||
|
|
60cab39f92 | ||
|
|
67bedd1ae0 | ||
|
|
7ab8ad9513 | ||
|
|
c11178fd77 | ||
|
|
150f47c590 | ||
|
|
b7987fded3 | ||
|
|
44a4bd4107 | ||
|
|
044e4c4b27 | ||
|
|
68eb63ba04 | ||
|
|
39d3648fdd | ||
|
|
6640c05d66 | ||
|
|
69ac764ef2 | ||
|
|
04c6ca5337 | ||
|
|
2408978d2d | ||
|
|
74f75f107f | ||
|
|
0e2e487bc9 | ||
|
|
6be48bccd2 | ||
|
|
4262734d21 | ||
|
|
ed0d7db3db | ||
|
|
824b305f93 | ||
|
|
f89793c66d | ||
|
|
4425fb5244 | ||
|
|
25a85a3b8e | ||
|
|
0f95344abe | ||
|
|
3511915886 | ||
|
|
6e50451c43 | ||
|
|
99d9363b95 | ||
|
|
7533d4d15e | ||
|
|
a118c16e96 |
@@ -150,6 +150,28 @@ add_custom_target(
|
|||||||
DEPENDS ${CONFIG_GENERATED_FILE}
|
DEPENDS ${CONFIG_GENERATED_FILE}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_library(clice_core_deps INTERFACE)
|
||||||
|
target_include_directories(clice_core_deps INTERFACE
|
||||||
|
"${PROJECT_SOURCE_DIR}/include"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated"
|
||||||
|
)
|
||||||
|
target_link_libraries(clice_core_deps INTERFACE
|
||||||
|
clice_options
|
||||||
|
libuv::libuv
|
||||||
|
spdlog::spdlog
|
||||||
|
tomlplusplus::tomlplusplus
|
||||||
|
roaring::roaring
|
||||||
|
flatbuffers
|
||||||
|
llvm-libs
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(clice_builtin_api INTERFACE)
|
||||||
|
target_link_libraries(clice_builtin_api INTERFACE clice_core_deps)
|
||||||
|
|
||||||
|
include("${PROJECT_SOURCE_DIR}/cmake/builtin-libraries.cmake")
|
||||||
|
|
||||||
|
clice_include_builtin_library_modules()
|
||||||
|
|
||||||
file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
|
file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
|
||||||
"${PROJECT_SOURCE_DIR}/src/AST/*.cpp"
|
"${PROJECT_SOURCE_DIR}/src/AST/*.cpp"
|
||||||
"${PROJECT_SOURCE_DIR}/src/Async/*.cpp"
|
"${PROJECT_SOURCE_DIR}/src/Async/*.cpp"
|
||||||
@@ -162,20 +184,8 @@ file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
|
|||||||
)
|
)
|
||||||
add_library(clice-core STATIC "${CLICE_SOURCES}")
|
add_library(clice-core STATIC "${CLICE_SOURCES}")
|
||||||
add_dependencies(clice-core generate_flatbuffers_schema generate_config)
|
add_dependencies(clice-core generate_flatbuffers_schema generate_config)
|
||||||
|
target_link_libraries(clice-core PUBLIC clice_core_deps)
|
||||||
target_include_directories(clice-core PUBLIC
|
clice_finalize_builtin_libraries(TARGET clice-core)
|
||||||
"${PROJECT_SOURCE_DIR}/include"
|
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/generated"
|
|
||||||
)
|
|
||||||
target_link_libraries(clice-core PUBLIC
|
|
||||||
clice_options
|
|
||||||
libuv::libuv
|
|
||||||
spdlog::spdlog
|
|
||||||
tomlplusplus::tomlplusplus
|
|
||||||
roaring::roaring
|
|
||||||
flatbuffers
|
|
||||||
llvm-libs
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
|
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
|
||||||
target_link_libraries(clice PRIVATE clice-core)
|
target_link_libraries(clice PRIVATE clice-core)
|
||||||
|
|||||||
158
cmake/builtin-libraries.cmake
Normal file
158
cmake/builtin-libraries.cmake
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
include_guard(GLOBAL)
|
||||||
|
|
||||||
|
set(
|
||||||
|
CLICE_BUILTIN_LIBRARY_MODULES
|
||||||
|
"${CLICE_BUILTIN_LIBRARY_MODULES}"
|
||||||
|
CACHE STRING
|
||||||
|
"Semicolon-separated list of CMake modules that register extra builtin clice libraries"
|
||||||
|
)
|
||||||
|
|
||||||
|
function(clice_add_builtin_library)
|
||||||
|
set(options)
|
||||||
|
set(oneValueArgs NAME ENTRYPOINT)
|
||||||
|
set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS COMPILE_OPTIONS)
|
||||||
|
cmake_parse_arguments(CBL "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||||
|
|
||||||
|
if(CBL_UNPARSED_ARGUMENTS)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"clice_add_builtin_library got unexpected arguments: ${CBL_UNPARSED_ARGUMENTS}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_NAME)
|
||||||
|
message(FATAL_ERROR "clice_add_builtin_library requires NAME")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_SOURCES)
|
||||||
|
message(FATAL_ERROR "clice_add_builtin_library(${CBL_NAME}) requires SOURCES")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_ENTRYPOINT)
|
||||||
|
message(FATAL_ERROR "clice_add_builtin_library(${CBL_NAME}) requires ENTRYPOINT")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET clice_builtin_api)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"clice_builtin_api must be defined before calling clice_add_builtin_library(${CBL_NAME})"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(target "clice_builtin_${CBL_NAME}")
|
||||||
|
if(TARGET "${target}")
|
||||||
|
message(FATAL_ERROR "builtin library target '${target}' already exists")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library("${target}" OBJECT)
|
||||||
|
target_sources("${target}" PRIVATE ${CBL_SOURCES})
|
||||||
|
target_link_libraries("${target}" PUBLIC clice_builtin_api)
|
||||||
|
add_dependencies("${target}" generate_flatbuffers_schema generate_config)
|
||||||
|
|
||||||
|
if(CBL_INCLUDE_DIRECTORIES)
|
||||||
|
target_include_directories("${target}" PRIVATE ${CBL_INCLUDE_DIRECTORIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CBL_LINK_LIBRARIES)
|
||||||
|
target_link_libraries("${target}" PUBLIC ${CBL_LINK_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CBL_COMPILE_DEFINITIONS)
|
||||||
|
target_compile_definitions("${target}" PRIVATE ${CBL_COMPILE_DEFINITIONS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CBL_COMPILE_OPTIONS)
|
||||||
|
target_compile_options("${target}" PRIVATE ${CBL_COMPILE_OPTIONS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_property(GLOBAL APPEND PROPERTY CLICE_BUILTIN_LIBRARY_TARGETS "${target}")
|
||||||
|
set_property(GLOBAL APPEND PROPERTY CLICE_BUILTIN_LIBRARY_ENTRYPOINTS "${CBL_ENTRYPOINT}")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(clice_include_builtin_library_modules)
|
||||||
|
foreach(module_path IN LISTS CLICE_BUILTIN_LIBRARY_MODULES)
|
||||||
|
cmake_path(
|
||||||
|
ABSOLUTE_PATH module_path
|
||||||
|
BASE_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||||
|
NORMALIZE
|
||||||
|
OUTPUT_VARIABLE module_abs_path
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT EXISTS "${module_abs_path}")
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"builtin library module '${module_path}' does not exist: ${module_abs_path}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include("${module_abs_path}")
|
||||||
|
endforeach()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(clice_finalize_builtin_libraries)
|
||||||
|
set(options)
|
||||||
|
set(oneValueArgs TARGET REGISTRATION_SOURCE)
|
||||||
|
cmake_parse_arguments(CBL "${options}" "${oneValueArgs}" "" ${ARGN})
|
||||||
|
|
||||||
|
if(CBL_UNPARSED_ARGUMENTS)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"clice_finalize_builtin_libraries got unexpected arguments: ${CBL_UNPARSED_ARGUMENTS}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_TARGET)
|
||||||
|
message(FATAL_ERROR "clice_finalize_builtin_libraries requires TARGET")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET "${CBL_TARGET}")
|
||||||
|
message(FATAL_ERROR "target '${CBL_TARGET}' does not exist")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_REGISTRATION_SOURCE)
|
||||||
|
set(CBL_REGISTRATION_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/generated/builtin-libraries.cpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_property(builtin_targets GLOBAL PROPERTY CLICE_BUILTIN_LIBRARY_TARGETS)
|
||||||
|
get_property(builtin_entrypoints GLOBAL PROPERTY CLICE_BUILTIN_LIBRARY_ENTRYPOINTS)
|
||||||
|
|
||||||
|
if(NOT builtin_targets)
|
||||||
|
set(builtin_targets "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT builtin_entrypoints)
|
||||||
|
set(builtin_entrypoints "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(registration_source_content "#include \"Server/Plugin.h\"\n\nnamespace clice {\n\n")
|
||||||
|
|
||||||
|
foreach(entrypoint IN LISTS builtin_entrypoints)
|
||||||
|
string(APPEND registration_source_content "::clice::PluginInfo ${entrypoint}();\n")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
string(APPEND registration_source_content "\nvoid register_builtin_server_plugins(ServerPluginBuilder& builder) {\n")
|
||||||
|
|
||||||
|
foreach(entrypoint IN LISTS builtin_entrypoints)
|
||||||
|
string(
|
||||||
|
APPEND
|
||||||
|
registration_source_content
|
||||||
|
" ${entrypoint}().register_server_callbacks(builder);\n"
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
string(APPEND registration_source_content "}\n\n} // namespace clice\n")
|
||||||
|
|
||||||
|
get_filename_component(registration_source_dir "${CBL_REGISTRATION_SOURCE}" DIRECTORY)
|
||||||
|
file(MAKE_DIRECTORY "${registration_source_dir}")
|
||||||
|
file(CONFIGURE OUTPUT "${CBL_REGISTRATION_SOURCE}" CONTENT "${registration_source_content}" @ONLY)
|
||||||
|
|
||||||
|
target_sources("${CBL_TARGET}" PRIVATE "${CBL_REGISTRATION_SOURCE}")
|
||||||
|
|
||||||
|
if(builtin_targets)
|
||||||
|
foreach(builtin_target IN LISTS builtin_targets)
|
||||||
|
target_sources("${CBL_TARGET}" PRIVATE $<TARGET_OBJECTS:${builtin_target}>)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
target_link_libraries("${CBL_TARGET}" PRIVATE ${builtin_targets})
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
@@ -66,16 +66,20 @@ set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
|
|||||||
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
|
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
# cpptrace
|
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers)
|
||||||
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 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)
|
if(WIN32)
|
||||||
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
|
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ Optional build options:
|
|||||||
| CLICE_ENABLE_TEST | OFF | Build clice unit tests |
|
| 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_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 |
|
| CLICE_CI_ENVIRONMENT | OFF | Enable the `CLICE_CI_ENVIRONMENT` macro; some tests only run in CI |
|
||||||
|
| CLICE_BUILTIN_LIBRARY_MODULES | "" | Semicolon-separated list of CMake modules that register builtin libraries compiled into `clice`; see [Builtin Libraries](./builtin-library.md) |
|
||||||
|
|
||||||
### XMake
|
### XMake
|
||||||
|
|
||||||
|
|||||||
101
docs/en/dev/builtin-library.md
Normal file
101
docs/en/dev/builtin-library.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Builtin Libraries
|
||||||
|
|
||||||
|
Builtin libraries are compiled directly into the `clice` binary instead of being loaded later with `--plugin-path`.
|
||||||
|
|
||||||
|
This is useful when:
|
||||||
|
|
||||||
|
- your plugin sources live outside the `clice` source tree
|
||||||
|
- you want the builtin to be part of the default executable
|
||||||
|
- you need extra include directories, compile definitions, or link dependencies during the main build
|
||||||
|
|
||||||
|
## CMake Entry Point
|
||||||
|
|
||||||
|
`clice` now exposes a small helper module at [cmake/builtin-libraries.cmake](/cmake/builtin-libraries.cmake).
|
||||||
|
|
||||||
|
Extra builtin libraries are registered through the cache variable `CLICE_BUILTIN_LIBRARY_MODULES`.
|
||||||
|
|
||||||
|
Each value in `CLICE_BUILTIN_LIBRARY_MODULES` must be a CMake file. During configure, `clice` includes those files, and each file calls `clice_add_builtin_library(...)`.
|
||||||
|
|
||||||
|
## Minimal Module
|
||||||
|
|
||||||
|
Create a CMake file in your external project, for example `/path/to/my-plugin/clice-builtin.cmake`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
clice_add_builtin_library(
|
||||||
|
NAME my_plugin
|
||||||
|
SOURCES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/src/MyPlugin.cpp"
|
||||||
|
INCLUDE_DIRECTORIES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||||
|
ENTRYPOINT
|
||||||
|
clice_get_my_plugin_server_plugin_info
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then configure `clice` with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/my-plugin/clice-builtin.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
To load multiple modules, pass a semicolon-separated CMake list:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/a.cmake;/path/to/b.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
## `clice_add_builtin_library`
|
||||||
|
|
||||||
|
The helper accepts the following arguments:
|
||||||
|
|
||||||
|
| Argument | Required | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `NAME` | Yes | Logical name used to create an internal object target |
|
||||||
|
| `SOURCES` | Yes | Source files compiled into `clice`; absolute paths and out-of-tree paths are supported |
|
||||||
|
| `ENTRYPOINT` | Yes | Unique function name in namespace `clice` that returns `::clice::PluginInfo` |
|
||||||
|
| `INCLUDE_DIRECTORIES` | No | Extra include directories for this builtin only |
|
||||||
|
| `LINK_LIBRARIES` | No | Extra libraries or targets needed by this builtin |
|
||||||
|
| `COMPILE_DEFINITIONS` | No | Extra compile definitions for this builtin |
|
||||||
|
| `COMPILE_OPTIONS` | No | Extra compile options for this builtin |
|
||||||
|
|
||||||
|
## Entrypoint Requirements
|
||||||
|
|
||||||
|
Builtin libraries share a single final executable, so each builtin must use its own unique entrypoint function inside namespace `clice`.
|
||||||
|
|
||||||
|
Dynamic plugins use:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
Builtin libraries should use a unique name such as:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_my_plugin_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "Server/Plugin.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
::clice::PluginInfo clice_get_my_plugin_server_plugin_info() {
|
||||||
|
return {
|
||||||
|
CLICE_PLUGIN_API_VERSION,
|
||||||
|
"MyPlugin",
|
||||||
|
"v0.0.1",
|
||||||
|
CLICE_PLUGIN_DEF_HASH,
|
||||||
|
[](clice::ServerPluginBuilder& builder) {
|
||||||
|
// register callbacks here
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
|
```
|
||||||
|
|
||||||
|
`clice` generates the static registration glue automatically, so once the module is included, no additional edits to `src/clice.cc` are required.
|
||||||
78
docs/en/dev/server-plugin.md
Normal file
78
docs/en/dev/server-plugin.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
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 [PluginProtocol.h](/include/Server/PluginProtocol.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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Builtin libraries
|
||||||
|
|
||||||
|
If you want to compile a plugin directly into the `clice` binary instead of loading it dynamically, see [Builtin Libraries](./builtin-library.md).
|
||||||
@@ -69,6 +69,7 @@ cmake -B build -G Ninja \
|
|||||||
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
|
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
|
||||||
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice(添加 `-std=libc++`),如果开启,请确保 LLVM 库也是使用 libc++ 编译的 |
|
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice(添加 `-std=libc++`),如果开启,请确保 LLVM 库也是使用 libc++ 编译的 |
|
||||||
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
|
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
|
||||||
|
| CLICE_BUILTIN_LIBRARY_MODULES | "" | 以分号分隔的 CMake 模块列表,用于注册会被编译进 `clice` 的 builtin library;详见 [Builtin Libraries](./builtin-library.md) |
|
||||||
|
|
||||||
### XMake
|
### XMake
|
||||||
|
|
||||||
|
|||||||
101
docs/zh/dev/builtin-library.md
Normal file
101
docs/zh/dev/builtin-library.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Builtin Libraries
|
||||||
|
|
||||||
|
Builtin library 会被直接编译进 `clice` 可执行文件,而不是在运行时通过 `--plugin-path` 动态加载。
|
||||||
|
|
||||||
|
这种方式适合以下场景:
|
||||||
|
|
||||||
|
- 插件源码位于 `clice` 源码树之外
|
||||||
|
- 希望该 builtin 默认随 `clice` 可执行文件一起发布
|
||||||
|
- 需要为该 builtin 单独补充 include 路径、编译宏或链接依赖
|
||||||
|
|
||||||
|
## CMake 入口
|
||||||
|
|
||||||
|
`clice` 现在提供了一个辅助模块:[cmake/builtin-libraries.cmake](/cmake/builtin-libraries.cmake)。
|
||||||
|
|
||||||
|
额外的 builtin library 通过缓存变量 `CLICE_BUILTIN_LIBRARY_MODULES` 注册。
|
||||||
|
|
||||||
|
`CLICE_BUILTIN_LIBRARY_MODULES` 中的每一项都必须是一个 CMake 文件。配置阶段 `clice` 会 `include()` 这些文件,而每个文件都需要调用 `clice_add_builtin_library(...)`。
|
||||||
|
|
||||||
|
## 最小示例
|
||||||
|
|
||||||
|
你可以在外部项目中创建一个 CMake 文件,例如 `/path/to/my-plugin/clice-builtin.cmake`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
clice_add_builtin_library(
|
||||||
|
NAME my_plugin
|
||||||
|
SOURCES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/src/MyPlugin.cpp"
|
||||||
|
INCLUDE_DIRECTORIES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||||
|
ENTRYPOINT
|
||||||
|
clice_get_my_plugin_server_plugin_info
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在配置 `clice` 时传入:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/my-plugin/clice-builtin.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
如果要加载多个模块,可以传入以分号分隔的 CMake 列表:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/a.cmake;/path/to/b.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
## `clice_add_builtin_library`
|
||||||
|
|
||||||
|
该辅助函数支持以下参数:
|
||||||
|
|
||||||
|
| 参数 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `NAME` | 是 | 逻辑名称,会用于创建内部 object target |
|
||||||
|
| `SOURCES` | 是 | 要编译进 `clice` 的源文件,支持绝对路径和源码树外路径 |
|
||||||
|
| `ENTRYPOINT` | 是 | `clice` 命名空间中的唯一函数名,返回值类型为 `::clice::PluginInfo` |
|
||||||
|
| `INCLUDE_DIRECTORIES` | 否 | 仅对当前 builtin 生效的额外头文件目录 |
|
||||||
|
| `LINK_LIBRARIES` | 否 | 当前 builtin 额外需要的库或 target |
|
||||||
|
| `COMPILE_DEFINITIONS` | 否 | 当前 builtin 额外需要的编译宏 |
|
||||||
|
| `COMPILE_OPTIONS` | 否 | 当前 builtin 额外需要的编译选项 |
|
||||||
|
|
||||||
|
## Entrypoint 要求
|
||||||
|
|
||||||
|
所有 builtin library 最终都会被链接进同一个可执行文件,因此每个 builtin 都必须在 `clice` 命名空间中使用唯一的入口函数名。
|
||||||
|
|
||||||
|
动态插件通常使用:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
builtin library 应该改用类似下面的唯一名字:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_my_plugin_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "Server/Plugin.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
::clice::PluginInfo clice_get_my_plugin_server_plugin_info() {
|
||||||
|
return {
|
||||||
|
CLICE_PLUGIN_API_VERSION,
|
||||||
|
"MyPlugin",
|
||||||
|
"v0.0.1",
|
||||||
|
CLICE_PLUGIN_DEF_HASH,
|
||||||
|
[](clice::ServerPluginBuilder& builder) {
|
||||||
|
// 在这里注册回调
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
|
```
|
||||||
|
|
||||||
|
`clice` 会自动生成静态注册代码,因此只要模块被包含进来,就不需要再手动修改 `src/clice.cc`。
|
||||||
78
docs/zh/dev/server-plugin.md
Normal file
78
docs/zh/dev/server-plugin.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
你可以在 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) { ... }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
请参考 [PluginProtocol.h](/include/Server/PluginProtocol.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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Builtin libraries
|
||||||
|
|
||||||
|
如果你希望把插件直接编译进 `clice` 可执行文件,而不是在运行时动态加载,请参考 [Builtin Libraries](./builtin-library.md)。
|
||||||
@@ -37,6 +37,10 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void unset() {
|
||||||
|
ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
ready = false;
|
ready = false;
|
||||||
awaiters.clear();
|
awaiters.clear();
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public:
|
|||||||
static std::optional<std::uint32_t> get_option_id(llvm::StringRef argument);
|
static std::optional<std::uint32_t> get_option_id(llvm::StringRef argument);
|
||||||
|
|
||||||
/// FIXME: bad interface design ...
|
/// FIXME: bad interface design ...
|
||||||
std::vector<const char*> files();
|
std::vector<llvm::StringRef> files();
|
||||||
|
|
||||||
/// FIXME: remove this api?
|
/// FIXME: remove this api?
|
||||||
auto save_string(llvm::StringRef string) -> llvm::StringRef;
|
auto save_string(llvm::StringRef string) -> llvm::StringRef;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "llvm/Support/JSON.h"
|
||||||
|
|
||||||
namespace clice::proto {
|
namespace clice::proto {
|
||||||
|
|
||||||
using integer = std::int32_t;
|
using integer = std::int32_t;
|
||||||
@@ -15,6 +17,8 @@ using decimal = double;
|
|||||||
|
|
||||||
using string = std::string;
|
using string = std::string;
|
||||||
|
|
||||||
|
using any = llvm::json::Value;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using array = std::vector<T>;
|
using array = std::vector<T>;
|
||||||
|
|
||||||
|
|||||||
17
include/Protocol/Feature/ExecuteCommand.h
Normal file
17
include/Protocol/Feature/ExecuteCommand.h
Normal file
@@ -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
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "Feature/DocumentHighlight.h"
|
#include "Feature/DocumentHighlight.h"
|
||||||
#include "Feature/DocumentLink.h"
|
#include "Feature/DocumentLink.h"
|
||||||
#include "Feature/DocumentSymbol.h"
|
#include "Feature/DocumentSymbol.h"
|
||||||
|
#include "Feature/ExecuteCommand.h"
|
||||||
#include "Feature/FoldingRange.h"
|
#include "Feature/FoldingRange.h"
|
||||||
#include "Feature/Formatting.h"
|
#include "Feature/Formatting.h"
|
||||||
#include "Feature/Hover.h"
|
#include "Feature/Hover.h"
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ public:
|
|||||||
if(it2 != project_index.indices.end()) {
|
if(it2 != project_index.indices.end()) {
|
||||||
auto path = project_index.path_pool.path(it2->second);
|
auto path = project_index.path_pool.path(it2->second);
|
||||||
it->second = index::MergedIndex::load(path);
|
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;
|
return it->second;
|
||||||
@@ -67,6 +72,14 @@ public:
|
|||||||
|
|
||||||
/// TODO: Types ...
|
/// TODO: Types ...
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return project_index.indices.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() const {
|
||||||
|
return project_index.indices.size();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
CompilationDatabase& database;
|
CompilationDatabase& database;
|
||||||
|
|
||||||
@@ -87,6 +100,10 @@ private:
|
|||||||
std::deque<std::uint32_t> waitings;
|
std::deque<std::uint32_t> waitings;
|
||||||
|
|
||||||
async::Event update_event;
|
async::Event update_event;
|
||||||
|
|
||||||
|
async::Event finish_event;
|
||||||
|
|
||||||
|
size_t finish_cnt = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clice
|
} // namespace clice
|
||||||
|
|||||||
59
include/Server/Plugin.h
Normal file
59
include/Server/Plugin.h
Normal file
@@ -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:332cd65741dea17d5168dd1ab564ac73ce230f14fc1e34170438d895eb11881c"
|
||||||
|
// 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
|
||||||
113
include/Server/PluginProtocol.h
Normal file
113
include/Server/PluginProtocol.h
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// The API version of the clice plugin.
|
||||||
|
/// Update this version when you change:
|
||||||
|
/// - The definition of struct `PluginInfo`.
|
||||||
|
/// - The definition of function `clice_get_server_plugin_info`.
|
||||||
|
/// Note: you don't have to update this version if you only change other APIs, which is guaranteed
|
||||||
|
/// by the `PluginInfo::definition_hash`.
|
||||||
|
#define CLICE_PLUGIN_API_VERSION 1
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "Async/Async.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/Support/Compiler.h"
|
||||||
|
#include "llvm/Support/JSON.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
class Server;
|
||||||
|
|
||||||
|
struct ServerPluginBuilder;
|
||||||
|
/// Defines the library APIs that loads a plugin.
|
||||||
|
extern "C" {
|
||||||
|
/// A C-compatible struct that contains information about the plugin.
|
||||||
|
struct PluginInfo {
|
||||||
|
/// The clice API version of the plugin.
|
||||||
|
uint32_t api_version;
|
||||||
|
/// The name of the plugin.
|
||||||
|
const char* name;
|
||||||
|
/// The version of the plugin.
|
||||||
|
const char* version;
|
||||||
|
/// The plugin definition hash.
|
||||||
|
const char* definition_hash;
|
||||||
|
/// Registers the server callbacks for the loaded plugin.
|
||||||
|
void (*register_server_callbacks)(ServerPluginBuilder& builder);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The public entry point for a server plugin.
|
||||||
|
///
|
||||||
|
/// When a plugin is loaded by the server, it will call this entry point 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:
|
||||||
|
///
|
||||||
|
/// ```cpp
|
||||||
|
/// #include "Server/Plugin.h"
|
||||||
|
/// extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
|
||||||
|
/// clice_get_server_plugin_info() {
|
||||||
|
/// return {
|
||||||
|
/// CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.0.1", CLICE_PLUGIN_DEF_HASH,
|
||||||
|
/// [](clice::ServerPluginBuilder& builder) { ... }
|
||||||
|
/// };
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
PluginInfo LLVM_ATTRIBUTE_WEAK clice_get_server_plugin_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ServerRef {
|
||||||
|
public:
|
||||||
|
struct Self;
|
||||||
|
|
||||||
|
ServerRef(Server* self) : self(self) {}
|
||||||
|
|
||||||
|
Server* operator->() const {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server& server() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Server* self;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Defines the library APIs to register callbacks for a plugin.
|
||||||
|
struct ServerPluginBuilder {
|
||||||
|
public:
|
||||||
|
ServerPluginBuilder(ServerRef server_ref) : server_ref(server_ref) {}
|
||||||
|
|
||||||
|
/// Gets a reference to the server.
|
||||||
|
auto get_server_ref() const -> ServerRef {
|
||||||
|
return server_ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CliceServerPluginAPI(METHOD, ...) void METHOD(void* plugin_data, __VA_ARGS__)
|
||||||
|
|
||||||
|
using lifecycle_hook_t = async::Task<> (*)(ServerRef server, void* plugin_data);
|
||||||
|
|
||||||
|
/// Registers a callback to be called when the server is initialized.
|
||||||
|
CliceServerPluginAPI(on_initialize, lifecycle_hook_t callback);
|
||||||
|
/// Registers a callback to be called when the server is initialized.
|
||||||
|
CliceServerPluginAPI(on_initialized, lifecycle_hook_t callback);
|
||||||
|
/// Registers a callback to be called when the server is shutdown.
|
||||||
|
CliceServerPluginAPI(on_shutdown, lifecycle_hook_t callback);
|
||||||
|
/// Registers a callback to be called when the server is exiting.
|
||||||
|
CliceServerPluginAPI(on_exit, lifecycle_hook_t callback);
|
||||||
|
/// Registers a callback to be called when the server's configuration is changed.
|
||||||
|
CliceServerPluginAPI(on_did_change_configuration, lifecycle_hook_t callback);
|
||||||
|
using command_handler_t =
|
||||||
|
async::Task<llvm::json::Value> (*)(ServerRef server,
|
||||||
|
void* plugin_data,
|
||||||
|
llvm::ArrayRef<llvm::json::Value> arguments);
|
||||||
|
/// Registers a callback to be called when a command is received from the LSP client.
|
||||||
|
CliceServerPluginAPI(register_commmand_handler,
|
||||||
|
llvm::StringRef command,
|
||||||
|
command_handler_t callback);
|
||||||
|
#undef CliceServerPluginAPI
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ServerRef server_ref;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Convert.h"
|
#include "Convert.h"
|
||||||
#include "Indexer.h"
|
#include "Indexer.h"
|
||||||
|
#include "Plugin.h"
|
||||||
#include "Async/Async.h"
|
#include "Async/Async.h"
|
||||||
#include "Compiler/Command.h"
|
#include "Compiler/Command.h"
|
||||||
#include "Compiler/Diagnostic.h"
|
#include "Compiler/Diagnostic.h"
|
||||||
@@ -10,6 +11,8 @@
|
|||||||
#include "Feature/DocumentLink.h"
|
#include "Feature/DocumentLink.h"
|
||||||
#include "Protocol/Protocol.h"
|
#include "Protocol/Protocol.h"
|
||||||
|
|
||||||
|
#include <llvm/ADT/FunctionExtras.h>
|
||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
|
|
||||||
struct OpenFile {
|
struct OpenFile {
|
||||||
@@ -76,10 +79,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Try get OpenFile from manager, default construct one if not exists.
|
/// Try get OpenFile from manager, default construct one if not exists.
|
||||||
[[nodiscard]] ActiveFile& get_or_add(llvm::StringRef path);
|
[[nodiscard]] ActiveFile get_or_add(llvm::StringRef path);
|
||||||
|
|
||||||
/// Add a OpenFile to the manager.
|
/// Add a OpenFile to the manager.
|
||||||
ActiveFile& add(llvm::StringRef path, OpenFile file);
|
ActiveFile add(llvm::StringRef path, OpenFile file);
|
||||||
|
|
||||||
[[nodiscard]] bool contains(llvm::StringRef path) const {
|
[[nodiscard]] bool contains(llvm::StringRef path) const {
|
||||||
return index.contains(path);
|
return index.contains(path);
|
||||||
@@ -94,7 +97,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ActiveFile& lru_put_impl(llvm::StringRef path, OpenFile file);
|
ActiveFile lru_put_impl(llvm::StringRef path, OpenFile file);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// The maximum size of the cache.
|
/// The maximum size of the cache.
|
||||||
@@ -167,7 +170,7 @@ private:
|
|||||||
|
|
||||||
async::Task<> on_exit(proto::ExitParams params);
|
async::Task<> on_exit(proto::ExitParams params);
|
||||||
|
|
||||||
private:
|
public:
|
||||||
/// Load the cache info from disk.
|
/// Load the cache info from disk.
|
||||||
void load_cache_info();
|
void load_cache_info();
|
||||||
|
|
||||||
@@ -192,6 +195,8 @@ private:
|
|||||||
private:
|
private:
|
||||||
using Result = async::Task<json::Value>;
|
using Result = async::Task<json::Value>;
|
||||||
|
|
||||||
|
auto on_execute_command(proto::ExecuteCommandParams params) -> Result;
|
||||||
|
|
||||||
auto on_completion(proto::CompletionParams params) -> Result;
|
auto on_completion(proto::CompletionParams params) -> Result;
|
||||||
|
|
||||||
auto on_hover(proto::HoverParams params) -> Result;
|
auto on_hover(proto::HoverParams params) -> Result;
|
||||||
@@ -218,7 +223,7 @@ private:
|
|||||||
|
|
||||||
auto on_inlay_hint(proto::InlayHintParams params) -> Result;
|
auto on_inlay_hint(proto::InlayHintParams params) -> Result;
|
||||||
|
|
||||||
private:
|
public:
|
||||||
/// The current request id.
|
/// The current request id.
|
||||||
std::uint32_t server_request_id = 0;
|
std::uint32_t server_request_id = 0;
|
||||||
std::uint32_t client_request_id = 0;
|
std::uint32_t client_request_id = 0;
|
||||||
@@ -241,6 +246,20 @@ private:
|
|||||||
config::Config config;
|
config::Config config;
|
||||||
|
|
||||||
Indexer indexer;
|
Indexer indexer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
friend struct ServerPluginBuilder;
|
||||||
|
using lifecycle_hook_t = llvm::unique_function<async::Task<>()>;
|
||||||
|
using command_handler_t = llvm::unique_function<async::Task<llvm::json::Value>(
|
||||||
|
llvm::ArrayRef<llvm::json::Value> arguments)>;
|
||||||
|
|
||||||
|
std::vector<lifecycle_hook_t> initialize_hooks;
|
||||||
|
std::vector<lifecycle_hook_t> initialized_hooks;
|
||||||
|
std::vector<lifecycle_hook_t> shutdown_hooks;
|
||||||
|
std::vector<lifecycle_hook_t> exit_hooks;
|
||||||
|
std::vector<lifecycle_hook_t> did_change_configuration_hooks;
|
||||||
|
llvm::StringMap<command_handler_t> command_handlers;
|
||||||
|
std::vector<Plugin> plugins;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clice
|
} // namespace clice
|
||||||
|
|||||||
3
include/Server/Utility.h
Normal file
3
include/Server/Utility.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define bail(...) std::unexpected(std::format(__VA_ARGS__))
|
||||||
@@ -149,7 +149,7 @@ inline std::string toPath(llvm::StringRef uri) {
|
|||||||
|
|
||||||
llvm::SmallString<128> result;
|
llvm::SmallString<128> result;
|
||||||
if(auto err = fs::real_path(decoded, result)) {
|
if(auto err = fs::real_path(decoded, result)) {
|
||||||
std::println("Failed to get real path: {}, Input is {}\n", err.message(), decoded);
|
std::println(stderr, "Failed to get real path: {}, Input is {}\n", err.message(), decoded);
|
||||||
std::abort();
|
std::abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,20 @@ struct Serde<std::nullopt_t> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct Serde<std::optional<T>> {
|
||||||
|
static json::Value serialize(const std::optional<T>& v) {
|
||||||
|
return v ? json::serialize(*v) : json::Value(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<T> deserialize(const json::Value& value) {
|
||||||
|
if(value.kind() == json::Value::Null) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return json::deserialize<T>(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct Serde<bool> {
|
struct Serde<bool> {
|
||||||
static json::Value serialize(bool v) {
|
static json::Value serialize(bool v) {
|
||||||
|
|||||||
@@ -22,6 +22,24 @@ def normalize_mode(value: str) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_headers(project_root, install_prefix):
|
||||||
|
print("\nCopying internal Sema headers...")
|
||||||
|
clang_sema_dir = project_root / "clang/lib/Sema"
|
||||||
|
install_sema_dir = install_prefix / "include/clang/Sema"
|
||||||
|
install_sema_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
headers_to_copy = ["CoroutineStmtBuilder.h", "TypeLocBuilder.h", "TreeTransform.h"]
|
||||||
|
|
||||||
|
for header in headers_to_copy:
|
||||||
|
src = clang_sema_dir / header
|
||||||
|
dst = install_sema_dir / header
|
||||||
|
if src.exists():
|
||||||
|
shutil.copy(src, dst)
|
||||||
|
print(f" Copied {header}")
|
||||||
|
else:
|
||||||
|
print(f" Warning: {header} not found in source.")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Build LLVM with specific configurations."
|
description="Build LLVM with specific configurations."
|
||||||
@@ -293,21 +311,7 @@ def main():
|
|||||||
print("Build failed!")
|
print("Build failed!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
print("\nCopying internal Sema headers...")
|
copy_headers(project_root, install_prefix)
|
||||||
clang_sema_dir = project_root / "clang/lib/Sema"
|
|
||||||
install_sema_dir = install_prefix / "include/clang/Sema"
|
|
||||||
install_sema_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
headers_to_copy = ["CoroutineStmtBuilder.h", "TypeLocBuilder.h", "TreeTransform.h"]
|
|
||||||
|
|
||||||
for header in headers_to_copy:
|
|
||||||
src = clang_sema_dir / header
|
|
||||||
dst = install_sema_dir / header
|
|
||||||
if src.exists():
|
|
||||||
shutil.copy(src, dst)
|
|
||||||
print(f" Copied {header}")
|
|
||||||
else:
|
|
||||||
print(f" Warning: {header} not found in source.")
|
|
||||||
|
|
||||||
def human_readable(num: int) -> str:
|
def human_readable(num: int) -> str:
|
||||||
for unit in ["B", "KB", "MB", "GB"]:
|
for unit in ["B", "KB", "MB", "GB"]:
|
||||||
|
|||||||
98
scripts/plugin-def.py
Normal file
98
scripts/plugin-def.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
clice_apis = []
|
||||||
|
|
||||||
|
# all of the files in `include/`, other than `include/Server/Plugin.h`
|
||||||
|
for path in Path("include/").glob("**/*.h"):
|
||||||
|
if path.name == "Plugin.h":
|
||||||
|
continue
|
||||||
|
clice_apis.append(path)
|
||||||
|
|
||||||
|
clice_apis = [
|
||||||
|
# the dependencies of the clice.
|
||||||
|
Path("config/llvm-manifest.json"),
|
||||||
|
# the clice C/C++ sources.
|
||||||
|
*sorted(clice_apis),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def fatal(message: str):
|
||||||
|
print(f"error: {message}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def sha256sum(paths: list[Path]) -> str:
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
for path in paths:
|
||||||
|
with path.open("rb") as handle:
|
||||||
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||||
|
digest.update(chunk)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def clice_api_content():
|
||||||
|
content = []
|
||||||
|
content.append("#if 0")
|
||||||
|
for path in clice_apis:
|
||||||
|
with path.open("r") as file:
|
||||||
|
content.append(f"// begin of {path}")
|
||||||
|
content.append(file.read())
|
||||||
|
content.append(f"// end of {path}")
|
||||||
|
content.append("#endif")
|
||||||
|
return "\n".join(content)
|
||||||
|
|
||||||
|
|
||||||
|
def plugin_def_hash():
|
||||||
|
hash_val = sha256sum(clice_apis)
|
||||||
|
return f"sha256:{hash_val}"
|
||||||
|
|
||||||
|
|
||||||
|
def update():
|
||||||
|
hash_val = plugin_def_hash()
|
||||||
|
with open("include/Server/Plugin.h", "r") as file:
|
||||||
|
content = file.read()
|
||||||
|
content = re.sub(
|
||||||
|
r"#define CLICE_PLUGIN_DEF_HASH .*",
|
||||||
|
f'#define CLICE_PLUGIN_DEF_HASH "{hash_val}"',
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
with open("include/Server/Plugin.h", "w") as file:
|
||||||
|
file.write(content)
|
||||||
|
|
||||||
|
|
||||||
|
def check():
|
||||||
|
hash_val = plugin_def_hash()
|
||||||
|
with open("include/Server/Plugin.h", "r") as file:
|
||||||
|
content = file.read()
|
||||||
|
match = re.search(r'#define CLICE_PLUGIN_DEF_HASH "(.*)"', content)
|
||||||
|
if match is None:
|
||||||
|
fatal("plugin def hash not found in include/Server/Plugin.h")
|
||||||
|
if match.group(1) != hash_val:
|
||||||
|
fatal(
|
||||||
|
f"plugin def hash mismatch in include/Server/Plugin.h, expected: {hash_val}, actual: {match.group(1)}"
|
||||||
|
)
|
||||||
|
print(f"plugin def hash is up to date: {hash_val}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
if sys.argv[1] == "update":
|
||||||
|
update()
|
||||||
|
check()
|
||||||
|
elif sys.argv[1] == "check":
|
||||||
|
check()
|
||||||
|
elif sys.argv[1] == "content":
|
||||||
|
print(clice_api_content())
|
||||||
|
else:
|
||||||
|
fatal(
|
||||||
|
f"invalid command: {sys.argv[1]}, expected: update, check",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
fatal("no command provided, expected: update, check")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -643,11 +643,24 @@ std::vector<UpdateInfo> CompilationDatabase::load_compile_database(llvm::StringR
|
|||||||
llvm::BumpPtrAllocator local;
|
llvm::BumpPtrAllocator local;
|
||||||
llvm::StringSaver saver(local);
|
llvm::StringSaver saver(local);
|
||||||
llvm::SmallVector<const char*, 32> agrs;
|
llvm::SmallVector<const char*, 32> agrs;
|
||||||
|
auto hasCC1 = false;
|
||||||
for(auto& argument: *arguments) {
|
for(auto& argument: *arguments) {
|
||||||
if(argument.kind() == json::Value::String) {
|
if(argument.kind() == json::Value::String) {
|
||||||
agrs.emplace_back(saver.save(*argument.getAsString()).data());
|
agrs.emplace_back(saver.save(*argument.getAsString()).data());
|
||||||
|
if(argument.getAsString() == "-cc1") {
|
||||||
|
hasCC1 = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(hasCC1) {
|
||||||
|
std::println(stderr, "cannot handle arguments: {}", *file);
|
||||||
|
for(auto& arg: *arguments) {
|
||||||
|
std::print(stderr, "{} ", arg);
|
||||||
|
}
|
||||||
|
std::println(stderr, "");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
item.info = self->save_compilation_info(*file, *directory, agrs);
|
item.info = self->save_compilation_info(*file, *directory, agrs);
|
||||||
} else if(command) {
|
} else if(command) {
|
||||||
item.info = self->save_compilation_info(*file, *directory, *command);
|
item.info = self->save_compilation_info(*file, *directory, *command);
|
||||||
@@ -762,10 +775,10 @@ std::optional<std::uint32_t> CompilationDatabase::get_option_id(llvm::StringRef
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const char*> CompilationDatabase::files() {
|
std::vector<llvm::StringRef> CompilationDatabase::files() {
|
||||||
std::vector<const char*> result;
|
std::vector<llvm::StringRef> result;
|
||||||
for(auto& [file, _]: self->files) {
|
for(auto& [file, _]: self->files) {
|
||||||
result.emplace_back(self->strings.get(file).data());
|
result.emplace_back(self->strings.get(file));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ CompilerFamily driver_family(llvm::StringRef driver) {
|
|||||||
|
|
||||||
std::vector<const char*> query_toolchain(const QueryParams& params) {
|
std::vector<const char*> query_toolchain(const QueryParams& params) {
|
||||||
auto arguments = params.arguments;
|
auto arguments = params.arguments;
|
||||||
llvm::StringRef driver = arguments[0];
|
llvm::StringRef driver = "clang"; // arguments[0];
|
||||||
|
|
||||||
/// Note: The name used to invoke the compiler driver affects its behavior.
|
/// Note: The name used to invoke the compiler driver affects its behavior.
|
||||||
/// For example, `/usr/bin/clang++` is often a symbolic link to
|
/// For example, `/usr/bin/clang++` is often a symbolic link to
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
|
|||||||
auto info = database.lookup(file, options);
|
auto info = database.lookup(file, options);
|
||||||
|
|
||||||
auto bound = compute_preamble_bound(content);
|
auto bound = compute_preamble_bound(content);
|
||||||
auto& open_file = opening_files.get_or_add(file);
|
auto open_file = opening_files.get_or_add(file);
|
||||||
|
|
||||||
/// Check update ...
|
/// Check update ...
|
||||||
if(open_file->pch && !check_pch_update(content, bound, info, *open_file->pch)) {
|
if(open_file->pch && !check_pch_update(content, bound, info, *open_file->pch)) {
|
||||||
@@ -347,7 +347,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, std::string content) {
|
async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, std::string content) {
|
||||||
auto& openFile = opening_files.get_or_add(path);
|
auto openFile = opening_files.get_or_add(path);
|
||||||
openFile->version += 1;
|
openFile->version += 1;
|
||||||
openFile->content = content;
|
openFile->content = content;
|
||||||
|
|
||||||
|
|||||||
19
src/Server/Implement.h
Normal file
19
src/Server/Implement.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Server/Plugin.h"
|
||||||
|
#include "Server/Server.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
struct ServerRef::Self {
|
||||||
|
public:
|
||||||
|
Self(Server* server_instance) : server_instance(server_instance) {}
|
||||||
|
|
||||||
|
Server& server() const {
|
||||||
|
return *server_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Server* server_instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -80,13 +80,26 @@ async::Task<> Indexer::schedule_next() {
|
|||||||
|
|
||||||
co_await workings[i];
|
co_await workings[i];
|
||||||
workings[i].release().destroy();
|
workings[i].release().destroy();
|
||||||
|
|
||||||
|
finish_cnt += 1;
|
||||||
|
finish_event.set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async::Task<> Indexer::index_all() {
|
async::Task<> Indexer::index_all() {
|
||||||
|
waitings.clear();
|
||||||
|
workings.clear();
|
||||||
|
update_event.clear();
|
||||||
|
finish_event.clear();
|
||||||
|
finish_cnt = 0;
|
||||||
|
|
||||||
for(auto& file: database.files()) {
|
for(auto& file: database.files()) {
|
||||||
waitings.push_back(project_index.path_pool.path_id(file));
|
waitings.push_back(project_index.path_pool.path_id(file));
|
||||||
}
|
}
|
||||||
|
auto to_finish = waitings.size();
|
||||||
|
if(to_finish == 0) {
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
auto max_count = std::max(std::thread::hardware_concurrency(), 4u);
|
auto max_count = std::max(std::thread::hardware_concurrency(), 4u);
|
||||||
|
|
||||||
@@ -100,6 +113,11 @@ async::Task<> Indexer::index_all() {
|
|||||||
task.dispose();
|
task.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while(finish_cnt < to_finish) {
|
||||||
|
co_await finish_event;
|
||||||
|
finish_event.unset();
|
||||||
|
}
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "Server/Plugin.h"
|
||||||
#include "Server/Server.h"
|
#include "Server/Server.h"
|
||||||
|
|
||||||
#include <llvm/Support/FileSystem.h>
|
#include <llvm/Support/FileSystem.h>
|
||||||
@@ -46,6 +47,11 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run initialize hooks.
|
||||||
|
for(auto& hook: initialize_hooks) {
|
||||||
|
co_await hook();
|
||||||
|
}
|
||||||
|
|
||||||
/// Load cache info.
|
/// Load cache info.
|
||||||
load_cache_info();
|
load_cache_info();
|
||||||
|
|
||||||
@@ -108,18 +114,41 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async::Task<> Server::on_initialized(proto::InitializedParams) {
|
async::Task<> Server::on_initialized(proto::InitializedParams) {
|
||||||
|
// emit indexing notification
|
||||||
|
co_await notify("clice/willIndex", {});
|
||||||
|
|
||||||
|
/// Run initialized hooks.
|
||||||
|
for(auto& hook: initialized_hooks) {
|
||||||
|
co_await hook();
|
||||||
|
}
|
||||||
|
|
||||||
indexer.load_from_disk();
|
indexer.load_from_disk();
|
||||||
co_await indexer.index_all();
|
co_await indexer.index_all();
|
||||||
|
|
||||||
|
// emit indexed notification
|
||||||
|
co_await notify("clice/didIndex", {});
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
async::Task<json::Value> Server::on_shutdown(proto::ShutdownParams params) {
|
async::Task<json::Value> Server::on_shutdown(proto::ShutdownParams params) {
|
||||||
|
/// Run shutdown hooks.
|
||||||
|
for(auto& hook: shutdown_hooks) {
|
||||||
|
co_await hook();
|
||||||
|
}
|
||||||
|
|
||||||
co_return json::Value(nullptr);
|
co_return json::Value(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
async::Task<> Server::on_exit(proto::ExitParams params) {
|
async::Task<> Server::on_exit(proto::ExitParams params) {
|
||||||
|
/// Run exit hooks.
|
||||||
|
for(auto& hook: exit_hooks) {
|
||||||
|
co_await hook();
|
||||||
|
}
|
||||||
|
|
||||||
save_cache_info();
|
save_cache_info();
|
||||||
indexer.save_to_disk();
|
indexer.save_to_disk();
|
||||||
|
|
||||||
async::stop();
|
async::stop();
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|||||||
163
src/Server/Plugin.cpp
Normal file
163
src/Server/Plugin.cpp
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include "Server/Plugin.h"
|
||||||
|
|
||||||
|
#include "Implement.h"
|
||||||
|
#include "Server/Utility.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/Support/DynamicLibrary.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
Server& ServerRef::server() const {
|
||||||
|
return *self;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Plugin::Self {
|
||||||
|
/// The file path of the plugin.
|
||||||
|
std::string file_path;
|
||||||
|
/// The dynamic library data of the plugin.
|
||||||
|
llvm::sys::DynamicLibrary library;
|
||||||
|
/// The name of the plugin.
|
||||||
|
std::string name;
|
||||||
|
/// The version of the plugin.
|
||||||
|
std::string version;
|
||||||
|
/// Registers the server callbacks for the loaded plugin.
|
||||||
|
void (*register_server_callbacks)(ServerPluginBuilder& builder);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::expected<Plugin, std::string> Plugin::load(const std::string& file_path) {
|
||||||
|
std::string err;
|
||||||
|
auto library = llvm::sys::DynamicLibrary::getPermanentLibrary(file_path.c_str(), &err);
|
||||||
|
if(!library.isValid()) {
|
||||||
|
return bail("Could not load library '{}': {}", file_path, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin P{
|
||||||
|
/// We currently never destroy plugins, so this is not a memory leak.
|
||||||
|
new Self{file_path, library}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// `clice_get_server_plugin_info` should be resolved to the definition from the plugin
|
||||||
|
/// we are currently loading.
|
||||||
|
intptr_t get_details_fn = (intptr_t)library.getAddressOfSymbol("clice_get_server_plugin_info");
|
||||||
|
if(!get_details_fn) {
|
||||||
|
return bail(
|
||||||
|
"The symbol `clice_get_server_plugin_info` is not found in '{}'. Is this a clice server plugin?",
|
||||||
|
file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info = reinterpret_cast<decltype(clice_get_server_plugin_info)*>(get_details_fn)();
|
||||||
|
|
||||||
|
/// First, we check whether the plugin is compatible with the clice plugin API.
|
||||||
|
if(info.api_version != CLICE_PLUGIN_API_VERSION) {
|
||||||
|
return bail("Wrong API version on plugin '{}'. Got version {}. Supported version is {}.",
|
||||||
|
file_path,
|
||||||
|
info.api_version,
|
||||||
|
CLICE_PLUGIN_API_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Then, we safely get definition hash from the plugin, and check if it is consistent with
|
||||||
|
/// the expected hash. This ensures that the plugin has consistent declarations with the
|
||||||
|
/// server.
|
||||||
|
std::string definition_hash = info.definition_hash;
|
||||||
|
if(plugin_definition_hash.size() != definition_hash.size()) {
|
||||||
|
return bail("Wrong definition hash size on plugin '{}'. Got {}, expected {} ({}).",
|
||||||
|
file_path,
|
||||||
|
definition_hash.size(),
|
||||||
|
plugin_definition_hash.size(),
|
||||||
|
plugin_definition_hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If there is any non-printable character in the definition hash, this is likely a bug in the
|
||||||
|
/// plugin. We cannot even print the `definition_hash` in this case.
|
||||||
|
if(std::ranges::any_of(definition_hash, [](char c) { return !std::isprint(c); })) {
|
||||||
|
return bail("Corrupt definition hash on plugin '{}'. This is likely a bug in the plugin.",
|
||||||
|
file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(definition_hash != CLICE_PLUGIN_DEF_HASH) {
|
||||||
|
return bail("Wrong definition hash on plugin '{}'. Got '{}', expected '{}'.",
|
||||||
|
file_path,
|
||||||
|
definition_hash,
|
||||||
|
CLICE_PLUGIN_DEF_HASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A plugin must implement the `register_server_callbacks` function.
|
||||||
|
if(!info.register_server_callbacks) {
|
||||||
|
return bail("Empty `register_server_callbacks` function in plugin '{}'.", file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
P->name = info.name;
|
||||||
|
P->version = info.version;
|
||||||
|
P->register_server_callbacks = info.register_server_callbacks;
|
||||||
|
|
||||||
|
return P;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::StringRef Plugin::file_path() const {
|
||||||
|
return self->file_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::StringRef Plugin::name() const {
|
||||||
|
return self->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::StringRef Plugin::version() const {
|
||||||
|
return self->version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers the server callbacks for the loaded plugin.
|
||||||
|
void Plugin::register_server_callbacks(ServerPluginBuilder& builder) const {
|
||||||
|
self->register_server_callbacks(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
using command_handler_t =
|
||||||
|
async::Task<llvm::json::Value> (*)(ServerRef server,
|
||||||
|
const llvm::ArrayRef<llvm::json::Value>& arguments);
|
||||||
|
|
||||||
|
void ServerPluginBuilder::on_initialize(void* plugin_data, lifecycle_hook_t callback) {
|
||||||
|
auto server = server_ref;
|
||||||
|
server_ref.server().initialize_hooks.push_back(
|
||||||
|
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPluginBuilder::on_initialized(void* plugin_data, lifecycle_hook_t callback) {
|
||||||
|
auto server = server_ref;
|
||||||
|
server_ref.server().initialized_hooks.push_back(
|
||||||
|
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPluginBuilder::on_shutdown(void* plugin_data, lifecycle_hook_t callback) {
|
||||||
|
auto server = server_ref;
|
||||||
|
server_ref.server().shutdown_hooks.push_back(
|
||||||
|
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPluginBuilder::on_exit(void* plugin_data, lifecycle_hook_t callback) {
|
||||||
|
auto server = server_ref;
|
||||||
|
server_ref.server().exit_hooks.push_back(
|
||||||
|
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPluginBuilder::on_did_change_configuration(void* plugin_data,
|
||||||
|
lifecycle_hook_t callback) {
|
||||||
|
auto server = server_ref;
|
||||||
|
server_ref.server().did_change_configuration_hooks.push_back(
|
||||||
|
[=]() -> async::Task<> { co_await callback(server, plugin_data); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerPluginBuilder::register_commmand_handler(void* plugin_data,
|
||||||
|
llvm::StringRef command,
|
||||||
|
command_handler_t callback) {
|
||||||
|
auto server = server_ref;
|
||||||
|
auto [_, inserted] = server_ref.server().command_handlers.try_emplace(
|
||||||
|
command,
|
||||||
|
[=](llvm::ArrayRef<llvm::json::Value> arguments) -> async::Task<llvm::json::Value> {
|
||||||
|
return callback(server, plugin_data, arguments);
|
||||||
|
});
|
||||||
|
if(!inserted) {
|
||||||
|
LOG_ERROR("Command handler already registered for command '{}'.", command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
#include "Server/Server.h"
|
#include "Server/Server.h"
|
||||||
|
|
||||||
|
#include "Compiler/CompilationUnit.h"
|
||||||
|
#include "Protocol/Basic.h"
|
||||||
|
#include "Support/FileSystem.h"
|
||||||
#include "Support/Logging.h"
|
#include "Support/Logging.h"
|
||||||
|
|
||||||
|
#include <clang/AST/Decl.h>
|
||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
|
|
||||||
ActiveFileManager::ActiveFile& ActiveFileManager::lru_put_impl(llvm::StringRef path,
|
ActiveFileManager::ActiveFile ActiveFileManager::lru_put_impl(llvm::StringRef path, OpenFile file) {
|
||||||
OpenFile file) {
|
|
||||||
/// If the file is not in the chain, create a new OpenFile.
|
/// If the file is not in the chain, create a new OpenFile.
|
||||||
if(items.size() >= capability) {
|
if(items.size() >= capability) {
|
||||||
/// If the size exceeds the maximum size, remove the last element.
|
/// If the size exceeds the maximum size, remove the last element.
|
||||||
@@ -21,7 +25,7 @@ ActiveFileManager::ActiveFile& ActiveFileManager::lru_put_impl(llvm::StringRef p
|
|||||||
return items.front().second;
|
return items.front().second;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveFileManager::ActiveFile& ActiveFileManager::get_or_add(llvm::StringRef path) {
|
ActiveFileManager::ActiveFile ActiveFileManager::get_or_add(llvm::StringRef path) {
|
||||||
auto iter = index.find(path);
|
auto iter = index.find(path);
|
||||||
if(iter == index.end()) {
|
if(iter == index.end()) {
|
||||||
return lru_put_impl(path, OpenFile{});
|
return lru_put_impl(path, OpenFile{});
|
||||||
@@ -32,7 +36,7 @@ ActiveFileManager::ActiveFile& ActiveFileManager::get_or_add(llvm::StringRef pat
|
|||||||
return iter->second->second;
|
return iter->second->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveFileManager::ActiveFile& ActiveFileManager::add(llvm::StringRef path, OpenFile file) {
|
ActiveFileManager::ActiveFile ActiveFileManager::add(llvm::StringRef path, OpenFile file) {
|
||||||
auto iter = index.find(path);
|
auto iter = index.find(path);
|
||||||
if(iter == index.end()) {
|
if(iter == index.end()) {
|
||||||
return lru_put_impl(path, std::move(file));
|
return lru_put_impl(path, std::move(file));
|
||||||
@@ -102,6 +106,8 @@ Server::Server() : indexer(database, config, kind) {
|
|||||||
register_callback<&Server::on_shutdown>("shutdown");
|
register_callback<&Server::on_shutdown>("shutdown");
|
||||||
register_callback<&Server::on_exit>("exit");
|
register_callback<&Server::on_exit>("exit");
|
||||||
|
|
||||||
|
register_callback<&Server::on_execute_command>("workspace/executeCommand");
|
||||||
|
|
||||||
register_callback<&Server::on_did_open>("textDocument/didOpen");
|
register_callback<&Server::on_did_open>("textDocument/didOpen");
|
||||||
register_callback<&Server::on_did_change>("textDocument/didChange");
|
register_callback<&Server::on_did_change>("textDocument/didChange");
|
||||||
register_callback<&Server::on_did_save>("textDocument/didSave");
|
register_callback<&Server::on_did_save>("textDocument/didSave");
|
||||||
@@ -184,4 +190,18 @@ async::Task<> Server::on_receive(json::Value value) {
|
|||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async::Task<json::Value> Server::on_execute_command(proto::ExecuteCommandParams params) {
|
||||||
|
auto& command = params.command;
|
||||||
|
auto& arguments = params.arguments;
|
||||||
|
|
||||||
|
auto it = command_handlers.find(command);
|
||||||
|
if(it == command_handlers.end()) {
|
||||||
|
LOG_ERROR("Command handler not found for command '{}'.", command);
|
||||||
|
co_return json::Value{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& handler = it->second;
|
||||||
|
co_return co_await handler(arguments);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace clice
|
} // namespace clice
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "spdlog/sinks/ringbuffer_sink.h"
|
#include "spdlog/sinks/ringbuffer_sink.h"
|
||||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||||
#include "spdlog/sinks/stdout_sinks.h"
|
#include "spdlog/sinks/stdout_sinks.h"
|
||||||
|
#include "llvm/Support/raw_ostream.h"
|
||||||
|
|
||||||
namespace clice::logging {
|
namespace clice::logging {
|
||||||
|
|
||||||
@@ -34,26 +35,43 @@ void stderr_logger(std::string_view name, const Options& options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void file_loggger(std::string_view name, std::string_view dir, const Options& options) {
|
void file_loggger(std::string_view name, std::string_view dir, const Options& options) {
|
||||||
auto now = std::chrono::system_clock::now();
|
if(auto err = fs::create_directories(dir)) {
|
||||||
auto filename = std::format("{:%Y-%m-%d_%H-%M-%S}.log", now);
|
spdlog::error("Failed to create log directory {}: {}", std::string(dir), err.message());
|
||||||
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path::join(dir, filename));
|
return;
|
||||||
|
|
||||||
if(options.replay_console && ringbuffer_sink) {
|
|
||||||
sink->set_level(options.level);
|
|
||||||
sink->set_pattern(pattern);
|
|
||||||
|
|
||||||
for(auto& log: ringbuffer_sink->last_raw()) {
|
|
||||||
sink->log(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
ringbuffer_sink.reset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto logger = std::make_shared<spdlog::logger>(std::string(name), std::move(sink));
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto filename = std::format("{:%Y-%m-%d_%H-%M-%S}.log", now);
|
||||||
|
auto filepath = path::join(dir, filename);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::error_code err;
|
||||||
|
llvm::raw_fd_ostream test(filepath, err, fs::OF_Append);
|
||||||
|
if(err) {
|
||||||
|
spdlog::error("Failed to open log file {}: {}", filepath, err.message());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(filepath);
|
||||||
|
auto console_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>(options.color);
|
||||||
|
std::array<spdlog::sink_ptr, 2> sinks = {file_sink, console_sink};
|
||||||
|
auto logger = std::make_shared<spdlog::logger>(std::string(name), sinks.begin(), sinks.end());
|
||||||
logger->set_level(options.level);
|
logger->set_level(options.level);
|
||||||
logger->set_pattern(pattern);
|
logger->set_pattern(pattern);
|
||||||
logger->flush_on(Level::trace);
|
logger->flush_on(Level::trace);
|
||||||
spdlog::set_default_logger(std::move(logger));
|
spdlog::set_default_logger(std::move(logger));
|
||||||
|
|
||||||
|
if(options.replay_console && ringbuffer_sink) {
|
||||||
|
file_sink->set_level(options.level);
|
||||||
|
file_sink->set_pattern(pattern);
|
||||||
|
|
||||||
|
for(auto& log: ringbuffer_sink->last_raw()) {
|
||||||
|
file_sink->log(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
ringbuffer_sink.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace clice::logging
|
} // namespace clice::logging
|
||||||
|
|||||||
37
src/clice.cc
37
src/clice.cc
@@ -1,3 +1,5 @@
|
|||||||
|
#include "Server/Implement.h"
|
||||||
|
#include "Server/Plugin.h"
|
||||||
#include "Server/Server.h"
|
#include "Server/Server.h"
|
||||||
#include "Server/Version.h"
|
#include "Server/Version.h"
|
||||||
#include "Support/Format.h"
|
#include "Support/Format.h"
|
||||||
@@ -11,6 +13,12 @@
|
|||||||
namespace cl = llvm::cl;
|
namespace cl = llvm::cl;
|
||||||
using namespace clice;
|
using namespace clice;
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
void register_builtin_server_plugins(ServerPluginBuilder& builder);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
static cl::OptionCategory category("clice options");
|
static cl::OptionCategory category("clice options");
|
||||||
@@ -73,6 +81,31 @@ cl::opt<logging::Level> log_level{
|
|||||||
cl::desc("The log level, default is info"),
|
cl::desc("The log level, default is info"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
cl::list<std::string> plugin_paths{
|
||||||
|
"plugin-path",
|
||||||
|
cl::cat(category),
|
||||||
|
cl::value_desc("path1,path2,path3,..."),
|
||||||
|
cl::desc("The server plugins to load"),
|
||||||
|
cl::CommaSeparated,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Loads plugins intermediatly.
|
||||||
|
std::vector<Plugin> load_plugins(Server& instance) {
|
||||||
|
ServerPluginBuilder builder{ServerRef{&instance}};
|
||||||
|
std::vector<Plugin> plugins;
|
||||||
|
for(auto& plugin_path: plugin_paths) {
|
||||||
|
auto plugin_instance = Plugin::load(plugin_path);
|
||||||
|
if(!plugin_instance) {
|
||||||
|
LOG_FATAL("Failed to load plugin {}: {}", plugin_path, plugin_instance.error());
|
||||||
|
}
|
||||||
|
plugin_instance->register_server_callbacks(builder);
|
||||||
|
plugins.push_back(std::move(plugin_instance.value()));
|
||||||
|
}
|
||||||
|
::clice::register_builtin_server_plugins(builder);
|
||||||
|
// The llvm::sys::DynamicLibrary will be unloaded when the program exits.
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, const char** argv) {
|
int main(int argc, const char** argv) {
|
||||||
@@ -105,6 +138,10 @@ int main(int argc, const char** argv) {
|
|||||||
|
|
||||||
/// The global server instance.
|
/// The global server instance.
|
||||||
static Server instance;
|
static Server instance;
|
||||||
|
|
||||||
|
/// Loads plugins intermediatly before the server starts.
|
||||||
|
static auto plugins = load_plugins(instance);
|
||||||
|
|
||||||
auto loop = [&](json::Value value) -> async::Task<> {
|
auto loop = [&](json::Value value) -> async::Task<> {
|
||||||
co_await instance.on_receive(value);
|
co_await instance.on_receive(value);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ TEST_CASE(LruAlgorithm) {
|
|||||||
|
|
||||||
ASSERT_EQ(actives.size(), 0U);
|
ASSERT_EQ(actives.size(), 0U);
|
||||||
|
|
||||||
auto& first = actives.add("first", OpenFile{.version = 1});
|
auto first = actives.add("first", OpenFile{.version = 1});
|
||||||
ASSERT_EQ(actives.size(), 1U);
|
ASSERT_EQ(actives.size(), 1U);
|
||||||
ASSERT_TRUE(actives.contains("first"));
|
ASSERT_TRUE(actives.contains("first"));
|
||||||
ASSERT_EQ(first->version, 1U);
|
ASSERT_EQ(first->version, 1U);
|
||||||
|
|
||||||
auto& second = actives.add("second", OpenFile{.version = 2});
|
auto second = actives.add("second", OpenFile{.version = 2});
|
||||||
ASSERT_EQ(actives.size(), 1U);
|
ASSERT_EQ(actives.size(), 1U);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ TEST_CASE(IteratorCheck) {
|
|||||||
std::string fpath = std::format("{}", i);
|
std::string fpath = std::format("{}", i);
|
||||||
OpenFile object{.version = i};
|
OpenFile object{.version = i};
|
||||||
|
|
||||||
auto& inseted = manager.add(fpath, std::move(object));
|
auto inseted = manager.add(fpath, std::move(object));
|
||||||
std::optional new_added_entry = manager.get_or_add(fpath);
|
std::optional new_added_entry = manager.get_or_add(fpath);
|
||||||
ASSERT_TRUE(new_added_entry.has_value());
|
ASSERT_TRUE(new_added_entry.has_value());
|
||||||
auto new_added = std::move(new_added_entry).value();
|
auto new_added = std::move(new_added_entry).value();
|
||||||
|
|||||||
Reference in New Issue
Block a user