Compare commits
20 Commits
server-plu
...
openspec-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eddb34e34e | ||
|
|
94bc872cdb | ||
|
|
e43bb14998 | ||
|
|
21a969af27 | ||
|
|
084f3b2d22 | ||
|
|
eb0a6b35ee | ||
|
|
bc04845293 | ||
|
|
0a891d8b4a | ||
|
|
6d3b6acc82 | ||
|
|
7ed558c1e7 | ||
|
|
a536865fca | ||
|
|
f8a39147a7 | ||
|
|
46ba1e4db6 | ||
|
|
498c975042 | ||
|
|
848065265c | ||
|
|
f7a8d104ce | ||
|
|
020c2cb3cc | ||
|
|
73afcfbb58 | ||
|
|
ce2f355988 | ||
|
|
5b016b1317 |
@@ -139,3 +139,15 @@ KeepEmptyLines:
|
|||||||
AtEndOfFile: false
|
AtEndOfFile: false
|
||||||
AtStartOfBlock: false
|
AtStartOfBlock: false
|
||||||
AtStartOfFile: false
|
AtStartOfFile: false
|
||||||
|
|
||||||
|
StatementMacros:
|
||||||
|
- DECO_CFG_START
|
||||||
|
- DECO_CFG
|
||||||
|
- DECO_CFG_END
|
||||||
|
- DecoKV
|
||||||
|
- DecoFlag
|
||||||
|
- DecoComma
|
||||||
|
- DecoInput
|
||||||
|
- DecoPack
|
||||||
|
- DecoKVStyled
|
||||||
|
- DecoMulti
|
||||||
|
|||||||
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -1,2 +1,9 @@
|
|||||||
# SCM syntax highlighting & preventing 3-way merges
|
# SCM syntax highlighting & preventing 3-way merges
|
||||||
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
|
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
|
||||||
|
|
||||||
|
# Force LF line endings for test data so that byte offsets from clang
|
||||||
|
# (which reads from disk) match the content sent by didOpen in tests.
|
||||||
|
tests/data/** text eol=lf
|
||||||
|
|
||||||
|
# Treat trace files as binary to suppress text diffs
|
||||||
|
tests/smoke/*.jsonl linguist-generated=true binary
|
||||||
|
|||||||
45
.github/workflows/benchmark.yml
vendored
Normal file
45
.github/workflows/benchmark.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: benchmark
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
benchmark:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-24.04, macos-15, windows-2025]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-pixi
|
||||||
|
|
||||||
|
- name: Build scan_benchmark
|
||||||
|
run: |
|
||||||
|
pixi run cmake-config RelWithDebInfo ON "-DCLICE_ENABLE_BENCHMARK=ON"
|
||||||
|
cmake --build build/RelWithDebInfo --target scan_benchmark
|
||||||
|
|
||||||
|
- name: Clone LLVM
|
||||||
|
run: git clone --depth 1 https://github.com/llvm/llvm-project.git
|
||||||
|
|
||||||
|
- name: Generate CDB
|
||||||
|
run: |
|
||||||
|
cmake -B llvm-build -G Ninja \
|
||||||
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/cmake/toolchain.cmake" \
|
||||||
|
-DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld;lldb;mlir;polly;flang;bolt" \
|
||||||
|
-DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \
|
||||||
|
llvm-project/llvm
|
||||||
|
|
||||||
|
- name: Run benchmark
|
||||||
|
run: ./build/RelWithDebInfo/bin/scan_benchmark --runs 20 llvm-build/compile_commands.json
|
||||||
|
|
||||||
|
- name: Stop sccache server
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: pixi run -- sccache --stop-server || true
|
||||||
15
.github/workflows/check-format.yml
vendored
15
.github/workflows/check-format.yml
vendored
@@ -24,10 +24,11 @@ jobs:
|
|||||||
args: --lint ./docs
|
args: --lint ./docs
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Suggest changes
|
- name: Check diff
|
||||||
if: github.event_name == 'pull_request'
|
run: |
|
||||||
uses: reviewdog/action-suggester@v1.24.0
|
if ! git diff --quiet; then
|
||||||
with:
|
echo "::error::Formatting changes detected. Please run 'pixi run format' and commit the result."
|
||||||
tool_name: "fmt"
|
git --no-pager diff --stat
|
||||||
fail_level: any
|
git --no-pager diff
|
||||||
filter_mode: nofilter
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
10
.github/workflows/deploy-docs.yml
vendored
10
.github/workflows/deploy-docs.yml
vendored
@@ -20,9 +20,11 @@ jobs:
|
|||||||
run: pixi run build-docs
|
run: pixi run build-docs
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
if: github.ref == 'refs/heads/main'
|
|
||||||
uses: peaceiris/actions-gh-pages@v4
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
personal_token: ${{ secrets.PUBLISH_DOCS }}
|
||||||
publish_dir: docs/.vitepress/dist
|
external_repository: clice-io/docs
|
||||||
cname: clice.io
|
publish_dir: ./docs/.vitepress/dist
|
||||||
|
destination_dir: clice
|
||||||
|
keep_files: true
|
||||||
|
|||||||
26
.github/workflows/main.yml
vendored
26
.github/workflows/main.yml
vendored
@@ -56,11 +56,6 @@ jobs:
|
|||||||
|
|
||||||
format:
|
format:
|
||||||
needs: changes
|
needs: changes
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
checks: write
|
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
if: ${{ needs.changes.outputs.format == 'true' }}
|
if: ${{ needs.changes.outputs.format == 'true' }}
|
||||||
uses: ./.github/workflows/check-format.yml
|
uses: ./.github/workflows/check-format.yml
|
||||||
|
|
||||||
@@ -70,11 +65,12 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
uses: ./.github/workflows/deploy-docs.yml
|
uses: ./.github/workflows/deploy-docs.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
clice:
|
# clice:
|
||||||
needs: changes
|
# needs: changes
|
||||||
if: ${{ needs.changes.outputs.clice == 'true' }}
|
# if: ${{ needs.changes.outputs.clice == 'true' }}
|
||||||
uses: ./.github/workflows/publish-clice.yml
|
# uses: ./.github/workflows/publish-clice.yml
|
||||||
|
|
||||||
vscode:
|
vscode:
|
||||||
needs: changes
|
needs: changes
|
||||||
@@ -86,10 +82,10 @@ jobs:
|
|||||||
if: ${{ needs.changes.outputs.cmake == 'true' }}
|
if: ${{ needs.changes.outputs.cmake == 'true' }}
|
||||||
uses: ./.github/workflows/test-cmake.yml
|
uses: ./.github/workflows/test-cmake.yml
|
||||||
|
|
||||||
xmake:
|
# xmake:
|
||||||
needs: changes
|
# needs: changes
|
||||||
if: ${{ needs.changes.outputs.xmake == 'true' }}
|
# if: ${{ needs.changes.outputs.xmake == 'true' }}
|
||||||
uses: ./.github/workflows/test-xmake.yml
|
# uses: ./.github/workflows/test-xmake.yml
|
||||||
|
|
||||||
release-clice:
|
release-clice:
|
||||||
permissions:
|
permissions:
|
||||||
@@ -110,10 +106,10 @@ jobs:
|
|||||||
needs:
|
needs:
|
||||||
- format
|
- format
|
||||||
- deploy
|
- deploy
|
||||||
- clice
|
# - clice
|
||||||
- vscode
|
- vscode
|
||||||
- cmake
|
- cmake
|
||||||
- xmake
|
# - xmake
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check results
|
- name: Check results
|
||||||
|
|||||||
58
.github/workflows/test-cmake.yml
vendored
58
.github/workflows/test-cmake.yml
vendored
@@ -3,18 +3,22 @@ name: cmake
|
|||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CCACHE_DIR: ${{ github.workspace }}/.cache/ccache
|
||||||
|
SCCACHE_DIR: ${{ github.workspace }}/.cache/sccache
|
||||||
|
CCACHE_BASEDIR: ${{ github.workspace }}
|
||||||
|
SCCACHE_BASEDIRS: ${{ github.workspace }}
|
||||||
|
CCACHE_COMPILERCHECK: content
|
||||||
|
CCACHE_MAXSIZE: 2G
|
||||||
|
SCCACHE_CACHE_SIZE: 2G
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
os: [windows-2025, ubuntu-24.04, macos-15]
|
||||||
- os: windows-2025
|
build_type: [Debug, RelWithDebInfo]
|
||||||
build_type: RelWithDebInfo
|
|
||||||
- os: ubuntu-24.04
|
|
||||||
build_type: Debug
|
|
||||||
- os: macos-15
|
|
||||||
build_type: Debug
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -22,8 +26,44 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/setup-pixi
|
- uses: ./.github/actions/setup-pixi
|
||||||
|
|
||||||
|
- name: Restore compiler cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ runner.os == 'Windows' && '.cache/sccache' || '.cache/ccache' }}
|
||||||
|
key: ${{ runner.os }}-${{ matrix.build_type }}-ccache-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-${{ matrix.build_type }}-ccache-
|
||||||
|
|
||||||
|
- name: Zero cache stats
|
||||||
|
run: |
|
||||||
|
if [ "$RUNNER_OS" = "Windows" ]; then
|
||||||
|
pixi run -- sccache --stop-server || true
|
||||||
|
pixi run -- sccache --zero-stats || true
|
||||||
|
else
|
||||||
|
pixi run -- ccache --zero-stats || true
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: pixi run build ${{ matrix.build_type }} ON
|
run: pixi run build ${{ matrix.build_type }} ON
|
||||||
|
|
||||||
- name: Test
|
- name: Unit Test
|
||||||
run: pixi run test ${{ matrix.build_type }}
|
run: pixi run unit-test ${{ matrix.build_type }}
|
||||||
|
|
||||||
|
- name: Integration Test
|
||||||
|
run: pixi run integration-test ${{ matrix.build_type }}
|
||||||
|
|
||||||
|
- name: Smoke Test
|
||||||
|
if: success() || failure()
|
||||||
|
run: pixi run smoke-test ${{ matrix.build_type }}
|
||||||
|
|
||||||
|
- name: Print cache stats and stop server
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
if [ "$RUNNER_OS" = "Windows" ]; then
|
||||||
|
pixi run -- sccache --show-stats
|
||||||
|
pixi run -- sccache --stop-server || true
|
||||||
|
else
|
||||||
|
pixi run -- ccache --show-stats
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -66,3 +66,7 @@ tests/unit/Local/
|
|||||||
.env
|
.env
|
||||||
.pixi/*
|
.pixi/*
|
||||||
!.pixi/config.toml
|
!.pixi/config.toml
|
||||||
|
|
||||||
|
.codex/
|
||||||
|
.claude/
|
||||||
|
openspec/
|
||||||
240
CMakeLists.txt
240
CMakeLists.txt
@@ -13,14 +13,45 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
|
|||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
|
||||||
|
|
||||||
option(CLICE_ENABLE_LTO "Enable ThinLTO for all targets" OFF)
|
option(CLICE_ENABLE_LTO "Enable ThinLTO for all targets" OFF)
|
||||||
|
option(CLICE_USE_LIBCXX "Use libc++ instead of libstdc++" OFF)
|
||||||
|
option(CLICE_OFFLINE_BUILD "Disable network downloads during configuration" OFF)
|
||||||
|
option(CLICE_ENABLE_TEST "Build unit tests" OFF)
|
||||||
|
option(CLICE_CI_ENVIRONMENT "Enable CI-specific configuration" OFF)
|
||||||
|
option(CLICE_ENABLE_BENCHMARK "Build benchmarks" OFF)
|
||||||
|
option(CLICE_RELEASE "Enable release packaging (LTO + strip + pack)" OFF)
|
||||||
|
|
||||||
|
# Global flags that apply to all targets (including FetchContent dependencies).
|
||||||
|
if(NOT MSVC)
|
||||||
|
add_compile_options(-ffunction-sections -fdata-sections)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
# https://conda-forge.org/docs/maintainer/knowledge_base/#newer-c-features-with-old-sdk
|
||||||
|
add_compile_definitions(_LIBCPP_DISABLE_AVAILABILITY=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
|
||||||
|
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,/OPT:REF")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,/OPT:REF")
|
||||||
|
elseif(APPLE)
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,-dead_strip")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,-dead_strip")
|
||||||
|
else()
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -static-libstdc++ -static-libgcc -Wl,--gc-sections")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,--gc-sections")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Make sure all third libraries are affected by ABI related options
|
|
||||||
if(CLICE_USE_LIBCXX)
|
if(CLICE_USE_LIBCXX)
|
||||||
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
|
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++")
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++")
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -stdlib=libc++")
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CLICE_RELEASE)
|
||||||
|
set(CLICE_ENABLE_LTO ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CLICE_ENABLE_LTO)
|
if(CLICE_ENABLE_LTO)
|
||||||
string(APPEND CMAKE_C_FLAGS " -flto=thin")
|
string(APPEND CMAKE_C_FLAGS " -flto=thin")
|
||||||
string(APPEND CMAKE_CXX_FLAGS " -flto=thin")
|
string(APPEND CMAKE_CXX_FLAGS " -flto=thin")
|
||||||
@@ -29,17 +60,12 @@ if(CLICE_ENABLE_LTO)
|
|||||||
string(APPEND CMAKE_MODULE_LINKER_FLAGS " -flto=thin")
|
string(APPEND CMAKE_MODULE_LINKER_FLAGS " -flto=thin")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
add_compile_options(-fsanitize=address)
|
add_compile_options(-fsanitize=address)
|
||||||
|
|
||||||
if(NOT WIN32)
|
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
|
# clang-cl (MSVC frontend): manually link ASan runtime since clang-cl
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
|
# doesn't handle -fsanitize=address linking automatically.
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND ${CMAKE_CXX_COMPILER} --print-resource-dir
|
COMMAND ${CMAKE_CXX_COMPILER} --print-resource-dir
|
||||||
OUTPUT_VARIABLE CLANG_RESOURCE_DIR
|
OUTPUT_VARIABLE CLANG_RESOURCE_DIR
|
||||||
@@ -49,27 +75,47 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|||||||
link_directories(${ASAN_LIB_PATH})
|
link_directories(${ASAN_LIB_PATH})
|
||||||
|
|
||||||
set(ASAN_LINK_FLAGS "")
|
set(ASAN_LINK_FLAGS "")
|
||||||
|
|
||||||
list(APPEND ASAN_LINK_FLAGS "clang_rt.asan_dynamic-x86_64.lib")
|
list(APPEND ASAN_LINK_FLAGS "clang_rt.asan_dynamic-x86_64.lib")
|
||||||
list(APPEND ASAN_LINK_FLAGS "/wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib")
|
list(APPEND ASAN_LINK_FLAGS "/wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib")
|
||||||
|
|
||||||
foreach(flag ${ASAN_LINK_FLAGS})
|
foreach(flag ${ASAN_LINK_FLAGS})
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${flag}")
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " ${flag}")
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${flag}")
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " ${flag}")
|
||||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${flag}")
|
string(APPEND CMAKE_MODULE_LINKER_FLAGS " ${flag}")
|
||||||
endforeach()
|
endforeach()
|
||||||
|
else()
|
||||||
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
if(APPLE)
|
if(WIN32)
|
||||||
# https://conda-forge.org/docs/maintainer/knowledge_base/#newer-c-features-with-old-sdk
|
# Disable Identical COMDAT Folding in Debug to avoid ASan ODR false positives.
|
||||||
string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_DISABLE_AVAILABILITY=1")
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,/OPT:NOICF")
|
||||||
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,/OPT:NOICF")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
|
include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
|
||||||
|
|
||||||
|
# Project-specific options (not applied to third-party deps).
|
||||||
add_library(clice_options INTERFACE)
|
add_library(clice_options INTERFACE)
|
||||||
|
|
||||||
|
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
|
||||||
|
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
|
||||||
|
target_compile_options(clice_options INTERFACE /GR- /EHs-c- /Zc:preprocessor)
|
||||||
|
else()
|
||||||
|
target_compile_options(clice_options INTERFACE
|
||||||
|
-fno-rtti
|
||||||
|
-fno-exceptions
|
||||||
|
-Wno-deprecated-declarations
|
||||||
|
$<$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>:-Wno-undefined-inline>
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_link_libraries(clice_options INTERFACE version ntdll)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(CLICE_ENABLE_TEST)
|
if(CLICE_ENABLE_TEST)
|
||||||
target_compile_definitions(clice_options INTERFACE CLICE_ENABLE_TEST=1)
|
target_compile_definitions(clice_options INTERFACE CLICE_ENABLE_TEST=1)
|
||||||
endif()
|
endif()
|
||||||
@@ -78,123 +124,47 @@ if(CLICE_CI_ENVIRONMENT)
|
|||||||
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
|
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
set(FBS_SCHEMA_FILE "${PROJECT_SOURCE_DIR}/src/index/schema.fbs")
|
||||||
target_link_libraries(clice_options INTERFACE version ntdll)
|
set(GENERATED_HEADER "${PROJECT_BINARY_DIR}/generated/schema_generated.h")
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
|
|
||||||
target_link_options(clice_options INTERFACE
|
|
||||||
-fuse-ld=lld-link
|
|
||||||
-Wl,/OPT:REF
|
|
||||||
#,/OPT:NOICF
|
|
||||||
)
|
|
||||||
elseif(APPLE)
|
|
||||||
target_link_options(clice_options INTERFACE
|
|
||||||
-fuse-ld=lld
|
|
||||||
-Wl,-dead_strip
|
|
||||||
)
|
|
||||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|
||||||
target_link_options(clice_options INTERFACE
|
|
||||||
-fuse-ld=lld
|
|
||||||
-static-libstdc++ -static-libgcc
|
|
||||||
-Wl,--gc-sections
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
|
|
||||||
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
|
|
||||||
target_compile_options(clice_options INTERFACE
|
|
||||||
/GR-
|
|
||||||
/EHsc-
|
|
||||||
/Zc:preprocessor
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
target_compile_options(clice_options INTERFACE
|
|
||||||
-fno-rtti
|
|
||||||
-fno-exceptions
|
|
||||||
-Wno-deprecated-declarations
|
|
||||||
-Wno-undefined-inline
|
|
||||||
-ffunction-sections
|
|
||||||
-fdata-sections
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(FBS_SCHEMA_FILE "${CMAKE_CURRENT_SOURCE_DIR}/include/Index/schema.fbs")
|
|
||||||
set(GENERATED_HEADER "${CMAKE_CURRENT_BINARY_DIR}/generated/schema_generated.h")
|
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${GENERATED_HEADER}
|
OUTPUT "${GENERATED_HEADER}"
|
||||||
COMMAND $<TARGET_FILE:flatc> --cpp -o ${CMAKE_CURRENT_BINARY_DIR}/generated ${FBS_SCHEMA_FILE}
|
COMMAND $<TARGET_FILE:flatc> --cpp -o "${PROJECT_BINARY_DIR}/generated" "${FBS_SCHEMA_FILE}"
|
||||||
DEPENDS ${FBS_SCHEMA_FILE}
|
DEPENDS "${FBS_SCHEMA_FILE}"
|
||||||
COMMENT "Generating C++ header from ${FBS_SCHEMA_FILE}"
|
COMMENT "Generating C++ header from ${FBS_SCHEMA_FILE}"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(
|
add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}")
|
||||||
generate_flatbuffers_schema
|
|
||||||
DEPENDS ${GENERATED_HEADER}
|
|
||||||
)
|
|
||||||
|
|
||||||
set(CONFIG_SOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/config/clang-tidy-config.h")
|
file(GLOB_RECURSE CLICE_CORE_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/*.cpp")
|
||||||
set(CONFIG_GENERATED_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated/clang-tidy-config.h")
|
add_library(clice-core STATIC ${CLICE_CORE_SOURCES})
|
||||||
|
add_library(clice::core ALIAS clice-core)
|
||||||
|
add_dependencies(clice-core generate_flatbuffers_schema)
|
||||||
|
|
||||||
add_custom_command(
|
target_include_directories(clice-core PUBLIC
|
||||||
OUTPUT ${CONFIG_GENERATED_FILE}
|
"${PROJECT_SOURCE_DIR}/src"
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CONFIG_SOURCE_FILE} ${CONFIG_GENERATED_FILE}
|
"${PROJECT_BINARY_DIR}/generated"
|
||||||
DEPENDS ${CONFIG_SOURCE_FILE}
|
|
||||||
COMMENT "Generating C++ header from ${CONFIG_SOURCE_FILE}"
|
|
||||||
)
|
)
|
||||||
|
target_link_libraries(clice-core PUBLIC
|
||||||
add_custom_target(
|
|
||||||
generate_config
|
|
||||||
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
|
clice_options
|
||||||
libuv::libuv
|
llvm-libs
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
tomlplusplus::tomlplusplus
|
|
||||||
roaring::roaring
|
roaring::roaring
|
||||||
flatbuffers
|
flatbuffers
|
||||||
llvm-libs
|
eventide::ipc::lsp
|
||||||
|
eventide::serde::toml
|
||||||
|
simdjson::simdjson
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/AST/*.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/Async/*.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/Basic/*.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/Compiler/*.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/Index/*.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/Feature/*.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/Server/*.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/src/Support/*.cpp"
|
|
||||||
)
|
|
||||||
add_library(clice-core STATIC "${CLICE_SOURCES}")
|
|
||||||
add_dependencies(clice-core generate_flatbuffers_schema generate_config)
|
|
||||||
target_link_libraries(clice-core PUBLIC clice_core_deps)
|
|
||||||
clice_finalize_builtin_libraries(TARGET clice-core)
|
|
||||||
|
|
||||||
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 eventide::deco)
|
||||||
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
message(STATUS "Copying resource directory for development build")
|
add_custom_target(copy_clang_resource ALL
|
||||||
file(
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
COPY "${LLVM_INSTALL_PATH}/lib/clang"
|
"${LLVM_INSTALL_PATH}/lib/clang"
|
||||||
DESTINATION "${PROJECT_BINARY_DIR}/lib"
|
"${PROJECT_BINARY_DIR}/lib/clang"
|
||||||
|
COMMENT "Copying clang resource directory"
|
||||||
)
|
)
|
||||||
install(
|
install(
|
||||||
DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang"
|
DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang"
|
||||||
@@ -203,11 +173,35 @@ install(
|
|||||||
|
|
||||||
if(CLICE_ENABLE_TEST)
|
if(CLICE_ENABLE_TEST)
|
||||||
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS
|
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS
|
||||||
"${PROJECT_SOURCE_DIR}/tests/unit/*/*.cpp")
|
"${PROJECT_SOURCE_DIR}/tests/unit/*/*_tests.cpp"
|
||||||
add_executable(unit_tests
|
|
||||||
"${CLICE_TEST_SOURCES}"
|
|
||||||
"${PROJECT_SOURCE_DIR}/tests/unit/unit_tests.cc"
|
|
||||||
)
|
)
|
||||||
target_include_directories(unit_tests PUBLIC "${PROJECT_SOURCE_DIR}")
|
set(CLICE_TEST_SUPPORT_SOURCES
|
||||||
target_link_libraries(unit_tests PRIVATE clice-core cpptrace::cpptrace)
|
"${PROJECT_SOURCE_DIR}/tests/unit/test/annotation.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/tests/unit/test/tester.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(unit_tests
|
||||||
|
"${PROJECT_SOURCE_DIR}/tests/unit/unit_tests.cc"
|
||||||
|
${CLICE_TEST_SOURCES}
|
||||||
|
${CLICE_TEST_SUPPORT_SOURCES}
|
||||||
|
)
|
||||||
|
target_include_directories(unit_tests PRIVATE
|
||||||
|
"${PROJECT_SOURCE_DIR}/src"
|
||||||
|
"${PROJECT_SOURCE_DIR}/tests/unit"
|
||||||
|
)
|
||||||
|
target_link_libraries(unit_tests PRIVATE clice::core eventide::zest eventide::deco)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CLICE_ENABLE_BENCHMARK)
|
||||||
|
add_executable(scan_benchmark
|
||||||
|
"${PROJECT_SOURCE_DIR}/benchmarks/scan_benchmark.cpp"
|
||||||
|
)
|
||||||
|
target_include_directories(scan_benchmark PRIVATE
|
||||||
|
"${PROJECT_SOURCE_DIR}/src"
|
||||||
|
)
|
||||||
|
target_link_libraries(scan_benchmark PRIVATE clice::core eventide::deco)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CLICE_RELEASE)
|
||||||
|
include("${PROJECT_SOURCE_DIR}/cmake/release.cmake")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||

|

|
||||||
[](https://github.com/clice-io/clice/blob/main/LICENSE)
|
[](https://github.com/clice-io/clice/blob/main/LICENSE)
|
||||||
[](https://github.com/clice-io/clice/actions)
|
[](https://github.com/clice-io/clice/actions)
|
||||||
[](https://clice.io)
|
[](https://docs.clice.io/clice/)
|
||||||
[](https://deepwiki.com/clice-io/clice)
|
[](https://deepwiki.com/clice-io/clice)
|
||||||
[](https://discord.gg/PA3UxW2VA3)
|
[](https://discord.gg/PA3UxW2VA3)
|
||||||
|
|
||||||
@@ -35,4 +35,4 @@ Download the latest `clice` binary from the [releases page](https://github.com/c
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
To learn more about building, installing, and configuring clice, or to dive deep into its features and architecture, please visit our official documentation at [**clice.io**](https://clice.io/).
|
To learn more about building, installing, and configuring clice, or to dive deep into its features and architecture, please visit our official documentation at [**clice.io**](https://docs.clice.io/clice/).
|
||||||
|
|||||||
413
benchmarks/scan_benchmark.cpp
Normal file
413
benchmarks/scan_benchmark.cpp
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
/// Benchmark for scan_dependency_graph on a real compilation database.
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// scan_benchmark [OPTIONS] <compile_commands.json>
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ./build/RelWithDebInfo/bin/scan_benchmark \
|
||||||
|
/// /home/ykiko/C++/clice/.llvm/build-debug/compile_commands.json
|
||||||
|
///
|
||||||
|
/// ./build/RelWithDebInfo/bin/scan_benchmark --log-level info --export graph.json \
|
||||||
|
/// /home/ykiko/C++/clice/.llvm/build-debug/compile_commands.json
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
|
#include <numeric>
|
||||||
|
#include <print>
|
||||||
|
#include <set>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include "command/command.h"
|
||||||
|
#include "eventide/deco/deco.h"
|
||||||
|
#include "eventide/serde/json/serializer.h"
|
||||||
|
#include "support/filesystem.h"
|
||||||
|
#include "support/logging.h"
|
||||||
|
#include "support/path_pool.h"
|
||||||
|
#include "syntax/dependency_graph.h"
|
||||||
|
|
||||||
|
#include "llvm/Support/FileSystem.h"
|
||||||
|
|
||||||
|
namespace et = eventide;
|
||||||
|
|
||||||
|
using namespace clice;
|
||||||
|
|
||||||
|
struct BenchmarkOptions {
|
||||||
|
DecoKV(names = {"--log-level"}; help = "Log level: trace, debug, info, warn, error, off";
|
||||||
|
required = false;)
|
||||||
|
<std::string> log_level = "off";
|
||||||
|
|
||||||
|
DecoKV(names = {"--export"}; help = "Export dependency graph as JSON to this path";
|
||||||
|
required = false;)
|
||||||
|
<std::string> export_path;
|
||||||
|
|
||||||
|
DecoKV(names = {"--runs"}; help = "Number of cold start iterations"; required = false;)
|
||||||
|
<int> runs = 20;
|
||||||
|
|
||||||
|
DecoFlag(names = {"-h", "--help"}; help = "Show help message"; required = false;)
|
||||||
|
help;
|
||||||
|
|
||||||
|
DecoInput(meta_var = "CDB"; help = "Path to compile_commands.json"; required = false;)
|
||||||
|
<std::string> cdb_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FileNode {
|
||||||
|
std::string path;
|
||||||
|
std::string module_name;
|
||||||
|
std::vector<std::string> includes;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GraphExport {
|
||||||
|
std::vector<FileNode> files;
|
||||||
|
};
|
||||||
|
|
||||||
|
void export_graph_json(const PathPool& path_pool,
|
||||||
|
const DependencyGraph& graph,
|
||||||
|
llvm::StringRef output_path) {
|
||||||
|
// Build reverse module map: path_id -> module_name.
|
||||||
|
llvm::DenseMap<std::uint32_t, llvm::StringRef> path_to_module;
|
||||||
|
for(auto& [name, path_ids]: graph.modules()) {
|
||||||
|
for(auto path_id: path_ids) {
|
||||||
|
path_to_module[path_id] = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphExport export_data;
|
||||||
|
for(std::uint32_t id = 0; id < path_pool.paths.size(); id++) {
|
||||||
|
auto inc_ids = graph.get_all_includes(id);
|
||||||
|
if(inc_ids.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileNode node;
|
||||||
|
node.path = path_pool.paths[id].str();
|
||||||
|
|
||||||
|
auto mod_it = path_to_module.find(id);
|
||||||
|
if(mod_it != path_to_module.end()) {
|
||||||
|
node.module_name = mod_it->second.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto flagged_id: inc_ids) {
|
||||||
|
auto raw_id = flagged_id & DependencyGraph::PATH_ID_MASK;
|
||||||
|
node.includes.push_back(path_pool.paths[raw_id].str());
|
||||||
|
}
|
||||||
|
|
||||||
|
export_data.files.push_back(std::move(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto json = et::serde::json::to_json(export_data);
|
||||||
|
if(!json) {
|
||||||
|
std::println(stderr, "Failed to serialize dependency graph");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream out(output_path.str());
|
||||||
|
if(!out) {
|
||||||
|
std::println(stderr, "Failed to open output file: {}", output_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
out << *json;
|
||||||
|
std::println("Graph exported to {} ({} files)", output_path, export_data.files.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_report(const ScanReport& report) {
|
||||||
|
std::println("===============================================================");
|
||||||
|
std::println(" Dependency Scan Report");
|
||||||
|
std::println("===============================================================");
|
||||||
|
|
||||||
|
// Timing.
|
||||||
|
std::println("");
|
||||||
|
std::println(" Time: {}ms", report.elapsed_ms);
|
||||||
|
std::println(" Waves: {}", report.waves);
|
||||||
|
|
||||||
|
// File counts.
|
||||||
|
std::println("");
|
||||||
|
std::println(" Files");
|
||||||
|
std::println(" Source files (from CDB): {}", report.source_files);
|
||||||
|
std::println(" Header files (discovered): {}", report.header_files);
|
||||||
|
std::println(" Total: {}", report.total_files);
|
||||||
|
std::println(" Modules: {}", report.modules);
|
||||||
|
|
||||||
|
// Include edges.
|
||||||
|
std::println("");
|
||||||
|
std::println(" Include Edges");
|
||||||
|
std::println(" Total: {}", report.total_edges);
|
||||||
|
std::println(" Unconditional: {}", report.unconditional_edges);
|
||||||
|
std::println(" Conditional: {} (inside #if/#ifdef)", report.conditional_edges);
|
||||||
|
|
||||||
|
// Resolution accuracy.
|
||||||
|
std::println("");
|
||||||
|
std::println(" Resolution");
|
||||||
|
std::println(" #include directives: {}", report.includes_found);
|
||||||
|
std::println(" Resolved: {}", report.includes_resolved);
|
||||||
|
auto unresolved_count = report.includes_found - report.includes_resolved;
|
||||||
|
std::println(" Unresolved: {}", unresolved_count);
|
||||||
|
if(report.includes_found > 0) {
|
||||||
|
double rate = 100.0 * static_cast<double>(report.includes_resolved) /
|
||||||
|
static_cast<double>(report.includes_found);
|
||||||
|
std::println(" Accuracy: {:.1f}%", rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wall-clock phase breakdown.
|
||||||
|
std::println("");
|
||||||
|
std::println(" Phase Breakdown (wall-clock)");
|
||||||
|
std::println(" Config extraction: {}ms (prewarm={}ms, loop={}ms)",
|
||||||
|
report.config_ms,
|
||||||
|
report.prewarm_ms,
|
||||||
|
report.config_loop_ms);
|
||||||
|
std::println(" Dir cache pre-pop: {}ms (overlapped with Phase 1)", report.dir_cache_ms);
|
||||||
|
std::println(" Phase 1 (read+scan, parallel): {}ms", report.phase1_ms);
|
||||||
|
std::println(" Phase 2 (include resolve): {}ms", report.phase2_ms);
|
||||||
|
std::println(" Phase 3 (graph build): {}ms", report.phase3_ms);
|
||||||
|
|
||||||
|
// Per-wave breakdown.
|
||||||
|
if(!report.wave_stats.empty()) {
|
||||||
|
std::println("");
|
||||||
|
std::println(" Per-Wave Breakdown");
|
||||||
|
std::println(" {:>5s} {:>8s} {:>8s} {:>8s} {:>8s} {:>8s} {:>10s} {:>10s}",
|
||||||
|
"Wave",
|
||||||
|
"Files",
|
||||||
|
"P1(ms)",
|
||||||
|
"P2(ms)",
|
||||||
|
"Next",
|
||||||
|
"Prefetch",
|
||||||
|
"DirList",
|
||||||
|
"DirHits");
|
||||||
|
for(std::size_t i = 0; i < report.wave_stats.size(); i++) {
|
||||||
|
auto& ws = report.wave_stats[i];
|
||||||
|
std::println(" {:>5} {:>8} {:>8} {:>8} {:>8} {:>8} {:>10} {:>10}",
|
||||||
|
i,
|
||||||
|
ws.files,
|
||||||
|
ws.phase1_ms,
|
||||||
|
ws.phase2_ms,
|
||||||
|
ws.next_files,
|
||||||
|
ws.prefetch_count,
|
||||||
|
ws.dir_listings,
|
||||||
|
ws.dir_hits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase 2 breakdown.
|
||||||
|
if(report.p2_resolve_us > 0) {
|
||||||
|
auto other_us = report.phase2_ms * 1000 - report.p2_resolve_us;
|
||||||
|
std::println("");
|
||||||
|
std::println(" Phase 2 Breakdown (single-threaded)");
|
||||||
|
std::println(" resolve_include: {:.1f}ms", report.p2_resolve_us / 1000.0);
|
||||||
|
std::println(" Other (cache lookup, intern, graph): {:.1f}ms", other_us / 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cumulative I/O statistics.
|
||||||
|
std::println("");
|
||||||
|
std::println(" I/O Statistics (cumulative across threads)");
|
||||||
|
std::println(" File read: {:.1f}ms (sum of all threads)", report.read_us / 1000.0);
|
||||||
|
std::println(" Lexer scan: {:.1f}ms (sum of all threads)", report.scan_us / 1000.0);
|
||||||
|
std::println(" Filesystem: {:.1f}ms ({} readdir calls, {} dir cache hits)",
|
||||||
|
report.fs_us / 1000.0,
|
||||||
|
report.dir_listings,
|
||||||
|
report.dir_hits);
|
||||||
|
std::println(" File lookups: {}", report.fs_lookups);
|
||||||
|
std::println(" Include cache hits: {}", report.include_cache_hits);
|
||||||
|
std::println(" Scan result cache hits: {}", report.scan_cache_hits);
|
||||||
|
if(report.dir_listings + report.dir_hits > 0) {
|
||||||
|
double hit_rate = 100.0 * static_cast<double>(report.dir_hits) /
|
||||||
|
static_cast<double>(report.dir_listings + report.dir_hits);
|
||||||
|
std::println(" Dir cache hit rate: {:.1f}%", hit_rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::println("");
|
||||||
|
std::println("===============================================================");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char** argv) {
|
||||||
|
auto args = deco::util::argvify(argc, argv);
|
||||||
|
auto result = deco::cli::parse<BenchmarkOptions>(args);
|
||||||
|
|
||||||
|
if(!result.has_value()) {
|
||||||
|
std::println(stderr, "Error: {}", result.error().message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& opts = result->options;
|
||||||
|
|
||||||
|
if(opts.help.value_or(false) || !opts.cdb_path.has_value()) {
|
||||||
|
std::ostringstream oss;
|
||||||
|
deco::cli::write_usage_for<BenchmarkOptions>(oss, "scan_benchmark [OPTIONS] <cdb>");
|
||||||
|
std::print("{}", oss.str());
|
||||||
|
return opts.help.value_or(false) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure logging.
|
||||||
|
auto level = spdlog::level::from_str(*opts.log_level);
|
||||||
|
clice::logging::options.level = level;
|
||||||
|
clice::logging::stderr_logger("scan_benchmark", clice::logging::options);
|
||||||
|
|
||||||
|
// resource_dir() is self-initializing (lazy static) — no setup needed.
|
||||||
|
|
||||||
|
auto& cdb_path = *opts.cdb_path;
|
||||||
|
auto hw_threads = std::thread::hardware_concurrency();
|
||||||
|
auto runs = *opts.runs;
|
||||||
|
if(runs <= 0) {
|
||||||
|
std::println(stderr, "Error: --runs must be positive (got {})", runs);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set UV_THREADPOOL_SIZE if not already set.
|
||||||
|
// Use at least libuv's default (4) so low-core CI runners don't regress.
|
||||||
|
if(!std::getenv("UV_THREADPOOL_SIZE")) {
|
||||||
|
auto pool_size = std::max(hw_threads, 4u);
|
||||||
|
static std::string env = "UV_THREADPOOL_SIZE=" + std::to_string(pool_size);
|
||||||
|
putenv(env.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::println("Hardware threads: {}", hw_threads);
|
||||||
|
std::println("UV_THREADPOOL_SIZE: {}", std::getenv("UV_THREADPOOL_SIZE"));
|
||||||
|
std::println("Log level: {}", *opts.log_level);
|
||||||
|
std::println("CDB: {}", cdb_path);
|
||||||
|
std::println("");
|
||||||
|
|
||||||
|
// Load compilation database.
|
||||||
|
auto t0 = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
CompilationDatabase cdb;
|
||||||
|
auto count = cdb.load(cdb_path);
|
||||||
|
|
||||||
|
auto t1 = std::chrono::steady_clock::now();
|
||||||
|
auto load_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();
|
||||||
|
|
||||||
|
std::println("CDB loaded: {} entries in {}ms", count, load_ms);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::set<const CompilationInfo*> unique_contexts;
|
||||||
|
std::set<const CanonicalCommand*> unique_canonicals;
|
||||||
|
std::map<const CanonicalCommand*, int> canonical_hist;
|
||||||
|
for(auto& entry: cdb.get_entries()) {
|
||||||
|
unique_contexts.insert(entry.info.ptr);
|
||||||
|
unique_canonicals.insert(entry.info->canonical.ptr);
|
||||||
|
canonical_hist[entry.info->canonical.ptr]++;
|
||||||
|
}
|
||||||
|
double dedup_ratio =
|
||||||
|
unique_contexts.empty() ? 0.0 : static_cast<double>(count) / unique_contexts.size();
|
||||||
|
std::println(
|
||||||
|
"Context dedup: {} files -> {} unique contexts ({:.1f}x), {} unique canonicals",
|
||||||
|
count,
|
||||||
|
unique_contexts.size(),
|
||||||
|
dedup_ratio,
|
||||||
|
unique_canonicals.size());
|
||||||
|
|
||||||
|
// If canonical dedup is poor, dump diagnostics.
|
||||||
|
if(unique_canonicals.size() > 200) {
|
||||||
|
// Sort canonicals by frequency (descending).
|
||||||
|
std::vector<std::pair<int, const CanonicalCommand*>> sorted;
|
||||||
|
for(auto& [ptr, cnt]: canonical_hist)
|
||||||
|
sorted.push_back({cnt, ptr});
|
||||||
|
std::ranges::sort(sorted,
|
||||||
|
std::greater{},
|
||||||
|
&std::pair<int, const CanonicalCommand*>::first);
|
||||||
|
|
||||||
|
// Show top-5 canonical commands.
|
||||||
|
for(int i = 0; i < std::min(5, (int)sorted.size()); i++) {
|
||||||
|
auto [cnt, cmd] = sorted[i];
|
||||||
|
std::println(" canonical[{}] ({} files, {} args):", i, cnt, cmd->arguments.size());
|
||||||
|
for(auto arg: cmd->arguments)
|
||||||
|
std::println(" {}", arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show a singleton canonical (count==1) to see what per-file arg leaks in.
|
||||||
|
for(auto& [cnt, cmd]: sorted) {
|
||||||
|
if(cnt == 1) {
|
||||||
|
std::println(" singleton canonical ({} args):", cmd->arguments.size());
|
||||||
|
for(auto arg: cmd->arguments)
|
||||||
|
std::println(" {}", arg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find two canonicals that differ by only a few args.
|
||||||
|
if(sorted.size() >= 2) {
|
||||||
|
auto* a = sorted[0].second;
|
||||||
|
auto* b = sorted[1].second;
|
||||||
|
std::println(" --- Canonical diff (top-1 vs top-2) ---");
|
||||||
|
auto max_len = std::max(a->arguments.size(), b->arguments.size());
|
||||||
|
for(std::size_t i = 0; i < max_len; i++) {
|
||||||
|
llvm::StringRef av = i < a->arguments.size() ? a->arguments[i] : "<missing>";
|
||||||
|
llvm::StringRef bv = i < b->arguments.size() ? b->arguments[i] : "<missing>";
|
||||||
|
if(av != bv)
|
||||||
|
std::println(" DIFF[{}]: '{}' vs '{}'", i, av, bv);
|
||||||
|
else
|
||||||
|
std::println(" SAME[{}]: '{}'", i, av);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::println("\nRunning {} cold start scan(s)...\n", runs);
|
||||||
|
|
||||||
|
PathPool path_pool;
|
||||||
|
DependencyGraph graph;
|
||||||
|
std::vector<std::int64_t> elapsed_times;
|
||||||
|
std::vector<std::int64_t> config_times;
|
||||||
|
std::vector<std::int64_t> phase1_times;
|
||||||
|
std::vector<std::int64_t> phase2_times;
|
||||||
|
elapsed_times.reserve(runs);
|
||||||
|
config_times.reserve(runs);
|
||||||
|
phase1_times.reserve(runs);
|
||||||
|
phase2_times.reserve(runs);
|
||||||
|
|
||||||
|
for(int i = 0; i < runs; i++) {
|
||||||
|
// True cold start: rebuild CDB (clears toolchain & config caches),
|
||||||
|
// reset PathPool and DependencyGraph.
|
||||||
|
cdb = CompilationDatabase{};
|
||||||
|
cdb.load(cdb_path);
|
||||||
|
path_pool = PathPool{};
|
||||||
|
graph = DependencyGraph{};
|
||||||
|
|
||||||
|
auto report = scan_dependency_graph(cdb, path_pool, graph);
|
||||||
|
|
||||||
|
elapsed_times.push_back(report.elapsed_ms);
|
||||||
|
config_times.push_back(report.config_ms);
|
||||||
|
phase1_times.push_back(report.phase1_ms);
|
||||||
|
phase2_times.push_back(report.phase2_ms);
|
||||||
|
|
||||||
|
std::println("[run {:2}] {}ms | config={}ms phase1={}ms phase2={}ms | files={}",
|
||||||
|
i + 1,
|
||||||
|
report.elapsed_ms,
|
||||||
|
report.config_ms,
|
||||||
|
report.phase1_ms,
|
||||||
|
report.phase2_ms,
|
||||||
|
report.total_files);
|
||||||
|
|
||||||
|
// Print detailed report for the first run only.
|
||||||
|
if(i == 0) {
|
||||||
|
std::println("");
|
||||||
|
print_report(report);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary statistics.
|
||||||
|
if(runs > 1) {
|
||||||
|
auto stats = [](std::vector<std::int64_t>& v) {
|
||||||
|
std::ranges::sort(v);
|
||||||
|
auto sum = std::accumulate(v.begin(), v.end(), std::int64_t{0});
|
||||||
|
return std::tuple{v.front(), sum / static_cast<std::int64_t>(v.size()), v.back()};
|
||||||
|
};
|
||||||
|
auto [e_min, e_avg, e_max] = stats(elapsed_times);
|
||||||
|
auto [c_min, c_avg, c_max] = stats(config_times);
|
||||||
|
auto [p1_min, p1_avg, p1_max] = stats(phase1_times);
|
||||||
|
auto [p2_min, p2_avg, p2_max] = stats(phase2_times);
|
||||||
|
|
||||||
|
std::println("\n Summary ({} runs) min avg max", runs);
|
||||||
|
std::println(" Total: {:>7} {:>6} {:>6}", e_min, e_avg, e_max);
|
||||||
|
std::println(" Config extraction: {:>7} {:>6} {:>6}", c_min, c_avg, c_max);
|
||||||
|
std::println(" Phase 1 (read+scan):{:>7} {:>6} {:>6}", p1_min, p1_avg, p1_max);
|
||||||
|
std::println(" Phase 2 (resolve): {:>7} {:>6} {:>6}", p2_min, p2_avg, p2_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export dependency graph as JSON if requested.
|
||||||
|
if(opts.export_path.has_value()) {
|
||||||
|
export_graph_json(path_pool, graph, *opts.export_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
17
cmake/archive.cmake
Normal file
17
cmake/archive.cmake
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
if(OUTPUT MATCHES "\\.tar\\.gz$")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E tar czf "${OUTPUT}" .
|
||||||
|
WORKING_DIRECTORY "${WORK_DIR}"
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
elseif(OUTPUT MATCHES "\\.zip$")
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E tar cf "${OUTPUT}" --format=zip .
|
||||||
|
WORKING_DIRECTORY "${WORK_DIR}"
|
||||||
|
COMMAND_ERROR_IS_FATAL ANY
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Unsupported archive format: ${OUTPUT}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
message(STATUS "Created: ${OUTPUT}")
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
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()
|
|
||||||
@@ -7,50 +7,22 @@ setup_llvm("21.1.4+r1")
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(NULL_DEVICE NUL)
|
|
||||||
else()
|
|
||||||
set(NULL_DEVICE /dev/null)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# libuv
|
|
||||||
FetchContent_Declare(
|
|
||||||
libuv
|
|
||||||
GIT_REPOSITORY https://github.com/libuv/libuv.git
|
|
||||||
GIT_TAG v1.x
|
|
||||||
GIT_SHALLOW TRUE
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
if(NOT WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|
||||||
set(ASAN ON CACHE BOOL "Enable AddressSanitizer for libuv" FORCE)
|
|
||||||
endif()
|
|
||||||
set(LIBUV_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
|
||||||
set(LIBUV_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
|
||||||
|
|
||||||
# spdlog
|
# spdlog
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
spdlog
|
spdlog
|
||||||
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
||||||
GIT_TAG v1.15.3
|
GIT_TAG v1.15.3
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
)
|
|
||||||
|
|
||||||
# tomlplusplus
|
|
||||||
FetchContent_Declare(
|
|
||||||
tomlplusplus
|
|
||||||
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
|
|
||||||
GIT_TAG v3.4.0
|
|
||||||
GIT_SHALLOW TRUE
|
|
||||||
)
|
)
|
||||||
|
set(SPDLOG_USE_STD_FORMAT ON CACHE BOOL "" FORCE)
|
||||||
|
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
# croaring
|
# croaring
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
croaring
|
croaring
|
||||||
GIT_REPOSITORY https://github.com/RoaringBitmap/CRoaring.git
|
GIT_REPOSITORY https://github.com/RoaringBitmap/CRoaring.git
|
||||||
GIT_TAG v4.4.2
|
GIT_TAG v4.4.2
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
)
|
)
|
||||||
set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "" FORCE)
|
set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "" FORCE)
|
||||||
set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
||||||
@@ -59,43 +31,26 @@ set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
flatbuffers
|
flatbuffers
|
||||||
GIT_REPOSITORY https://github.com/google/flatbuffers.git
|
GIT_REPOSITORY https://github.com/google/flatbuffers.git
|
||||||
GIT_TAG v25.9.23
|
GIT_TAG v25.9.23
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
)
|
)
|
||||||
set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
|
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)
|
||||||
|
|
||||||
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers)
|
FetchContent_Declare(
|
||||||
|
eventide
|
||||||
if(CLICE_ENABLE_TEST)
|
GIT_REPOSITORY https://github.com/clice-io/eventide
|
||||||
# cpptrace
|
GIT_TAG main
|
||||||
FetchContent_Declare(
|
GIT_SHALLOW TRUE
|
||||||
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)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT MSVC AND TARGET uv_a)
|
|
||||||
target_compile_options(uv_a PRIVATE
|
|
||||||
"-Wno-unused-function"
|
|
||||||
"-Wno-unused-variable"
|
|
||||||
"-Wno-unused-but-set-variable"
|
|
||||||
"-Wno-deprecated-declarations"
|
|
||||||
"-Wno-missing-braces"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_compile_definitions(spdlog PUBLIC
|
|
||||||
SPDLOG_USE_STD_FORMAT=1
|
|
||||||
SPDLOG_NO_EXCEPTIONS=1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set(ETD_ENABLE_ZEST ON)
|
||||||
|
set(ETD_ENABLE_TEST OFF)
|
||||||
|
set(ETD_SERDE_ENABLE_SIMDJSON ON)
|
||||||
|
set(ETD_SERDE_ENABLE_YYJSON ON)
|
||||||
|
set(ETD_SERDE_ENABLE_TOML ON)
|
||||||
|
set(ETD_ENABLE_EXCEPTIONS OFF)
|
||||||
|
set(ETD_ENABLE_RTTI OFF)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(eventide spdlog croaring flatbuffers)
|
||||||
|
|||||||
78
cmake/release.cmake
Normal file
78
cmake/release.cmake
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
include_guard()
|
||||||
|
|
||||||
|
set(CLICE_PACK_DIR "${PROJECT_BINARY_DIR}/pack")
|
||||||
|
set(CLICE_SYMBOL_DIR "${PROJECT_BINARY_DIR}/pack-symbol")
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(CLICE_ARCHIVE_EXT ".zip")
|
||||||
|
set(CLICE_SYMBOL_NAME "clice.pdb")
|
||||||
|
else()
|
||||||
|
set(CLICE_ARCHIVE_EXT ".tar.gz")
|
||||||
|
if(APPLE)
|
||||||
|
set(CLICE_SYMBOL_NAME "clice.dSYM")
|
||||||
|
else()
|
||||||
|
set(CLICE_SYMBOL_NAME "clice.debug")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_target(clice-strip ALL
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"$<TARGET_PDB_FILE:clice>"
|
||||||
|
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
|
||||||
|
DEPENDS clice
|
||||||
|
COMMENT "Collecting PDB for clice"
|
||||||
|
)
|
||||||
|
elseif(APPLE)
|
||||||
|
add_custom_target(clice-strip ALL
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
|
||||||
|
COMMAND dsymutil "$<TARGET_FILE:clice>" -o "${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
|
||||||
|
COMMAND strip -x "$<TARGET_FILE:clice>"
|
||||||
|
DEPENDS clice
|
||||||
|
COMMENT "Extracting dSYM and stripping clice"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
add_custom_target(clice-strip ALL
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} --only-keep-debug "$<TARGET_FILE:clice>" "${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
|
||||||
|
COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded "$<TARGET_FILE:clice>"
|
||||||
|
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink="${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "$<TARGET_FILE:clice>"
|
||||||
|
DEPENDS clice
|
||||||
|
COMMENT "Extracting debug symbols and stripping clice"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_target(clice-pack ALL
|
||||||
|
DEPENDS clice-strip copy_clang_resource
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -rf "${CLICE_PACK_DIR}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_PACK_DIR}/clice/bin"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:clice>" "${CLICE_PACK_DIR}/clice/bin/"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory "${LLVM_INSTALL_PATH}/lib/clang" "${CLICE_PACK_DIR}/clice/lib/clang"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/docs/clice.toml" "${CLICE_PACK_DIR}/clice/"
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DOUTPUT="${PROJECT_BINARY_DIR}/clice${CLICE_ARCHIVE_EXT}"
|
||||||
|
-DWORK_DIR="${CLICE_PACK_DIR}"
|
||||||
|
-P "${PROJECT_SOURCE_DIR}/cmake/archive.cmake"
|
||||||
|
COMMENT "Packaging clice distribution"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
set(CLICE_COPY_SYMBOL_CMD ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "${CLICE_SYMBOL_DIR}/pack/${CLICE_SYMBOL_NAME}")
|
||||||
|
else()
|
||||||
|
set(CLICE_COPY_SYMBOL_CMD ${CMAKE_COMMAND} -E copy
|
||||||
|
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "${CLICE_SYMBOL_DIR}/pack/")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_target(clice-pack-symbol ALL
|
||||||
|
DEPENDS clice-strip
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -rf "${CLICE_SYMBOL_DIR}/pack"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}/pack"
|
||||||
|
COMMAND ${CLICE_COPY_SYMBOL_CMD}
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DOUTPUT="${PROJECT_BINARY_DIR}/clice-symbol${CLICE_ARCHIVE_EXT}"
|
||||||
|
-DWORK_DIR="${CLICE_SYMBOL_DIR}/pack"
|
||||||
|
-P "${PROJECT_SOURCE_DIR}/cmake/archive.cmake"
|
||||||
|
COMMENT "Packaging clice debug symbols"
|
||||||
|
)
|
||||||
@@ -1,44 +1,49 @@
|
|||||||
cmake_minimum_required(VERSION 3.30)
|
cmake_minimum_required(VERSION 3.30)
|
||||||
|
|
||||||
if(WIN32)
|
set(CMAKE_C_COMPILER clang CACHE STRING "")
|
||||||
set(CMAKE_C_COMPILER clang-cl CACHE STRING "C compiler")
|
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "")
|
||||||
set(CMAKE_CXX_COMPILER clang-cl CACHE STRING "C++ compiler")
|
|
||||||
set(AR_PROGRAM_NAME "llvm-lib")
|
|
||||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "MSVC runtime")
|
|
||||||
|
|
||||||
find_program(LLVM_LLD_LINK_PATH "lld-link")
|
find_program(LLVM_AR_PATH "llvm-ar")
|
||||||
if(LLVM_LLD_LINK_PATH)
|
|
||||||
set(CMAKE_LINKER "${LLVM_LLD_LINK_PATH}" CACHE FILEPATH "Linker")
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
set(CMAKE_C_COMPILER clang CACHE STRING "C compiler")
|
|
||||||
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "C++ compiler")
|
|
||||||
set(AR_PROGRAM_NAME "llvm-ar")
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld" CACHE STRING "Executable linker flags")
|
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS "-fuse-ld=lld" CACHE STRING "Shared library linker flags")
|
|
||||||
set(CMAKE_MODULE_LINKER_FLAGS "-fuse-ld=lld" CACHE STRING "Module linker flags")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_program(LLVM_AR_PATH ${AR_PROGRAM_NAME})
|
|
||||||
if(LLVM_AR_PATH)
|
if(LLVM_AR_PATH)
|
||||||
set(CMAKE_AR "${LLVM_AR_PATH}" CACHE FILEPATH "Archiver")
|
set(CMAKE_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
||||||
set(CMAKE_C_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "C archiver")
|
set(CMAKE_C_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
||||||
set(CMAKE_CXX_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "C++ archiver")
|
set(CMAKE_CXX_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(LLVM_RANLIB_PATH "llvm-ranlib")
|
find_program(LLVM_RANLIB_PATH "llvm-ranlib")
|
||||||
if(LLVM_RANLIB_PATH)
|
if(LLVM_RANLIB_PATH)
|
||||||
set(CMAKE_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "Ranlib")
|
set(CMAKE_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
||||||
set(CMAKE_C_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "C ranlib")
|
set(CMAKE_C_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
||||||
set(CMAKE_CXX_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "C++ ranlib")
|
set(CMAKE_CXX_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(LLVM_NM_PATH "llvm-nm")
|
find_program(LLVM_NM_PATH "llvm-nm")
|
||||||
if(LLVM_NM_PATH)
|
if(LLVM_NM_PATH)
|
||||||
set(CMAKE_NM "${LLVM_NM_PATH}" CACHE FILEPATH "Symbol lister")
|
set(CMAKE_NM "${LLVM_NM_PATH}" CACHE FILEPATH "")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(LLVM_RC_PATH "llvm-rc")
|
find_program(LLVM_RC_PATH "llvm-rc")
|
||||||
if(LLVM_RC_PATH)
|
if(LLVM_RC_PATH)
|
||||||
set(CMAKE_RC_COMPILER "${LLVM_RC_PATH}" CACHE FILEPATH "Resource compiler")
|
set(CMAKE_RC_COMPILER "${LLVM_RC_PATH}" CACHE FILEPATH "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
find_program(SCCACHE_PATH "sccache")
|
||||||
|
if(SCCACHE_PATH)
|
||||||
|
set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "")
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
|
||||||
|
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
|
||||||
|
else()
|
||||||
|
find_program(CCACHE_PATH "ccache")
|
||||||
|
if(CCACHE_PATH)
|
||||||
|
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PATH}" CACHE FILEPATH "")
|
||||||
|
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}" CACHE FILEPATH "")
|
||||||
|
endif()
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
||||||
|
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
|||||||
title: "clice",
|
title: "clice",
|
||||||
description: "a powerful and modern C++ language server",
|
description: "a powerful and modern C++ language server",
|
||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
base: "/",
|
base: "/clice/",
|
||||||
rewrites: {
|
rewrites: {
|
||||||
"en/:rest*": ":rest*",
|
"en/:rest*": ":rest*",
|
||||||
},
|
},
|
||||||
|
|||||||
183
docs/en/architecture.md
Normal file
183
docs/en/architecture.md
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
# Server Architecture
|
||||||
|
|
||||||
|
clice uses a **multi-process architecture** where a single **Master Server** coordinates multiple **Worker** processes. This design isolates Clang AST operations (which are memory-heavy and may crash) from the main LSP event loop.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────┐ JSON/LSP ┌────────────────┐ Bincode/IPC ┌──────────────────┐
|
||||||
|
│ LSP Client │ ◄──────────► │ Master Server │ ◄─────────────► │ Stateful Workers │
|
||||||
|
│ (Editor) │ (stdio) │ │ (stdio) │ (AST cache) │
|
||||||
|
└──────────────┘ │ - Lifecycle │ └──────────────────┘
|
||||||
|
│ - Documents │
|
||||||
|
│ - CDB │ Bincode/IPC ┌──────────────────┐
|
||||||
|
│ - Build drain │ ◄─────────────► │ Stateless Workers│
|
||||||
|
│ - Indexing │ (stdio) │ (one-shot tasks)│
|
||||||
|
└────────────────┘ └──────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Master Server
|
||||||
|
|
||||||
|
The master server (`src/server/master_server.cpp`) is the central coordinator. It runs a single-threaded async event loop and never touches Clang directly. Its responsibilities:
|
||||||
|
|
||||||
|
### LSP Lifecycle
|
||||||
|
|
||||||
|
The server progresses through these states:
|
||||||
|
|
||||||
|
1. **Uninitialized** — waiting for `initialize` request
|
||||||
|
2. **Initialized** — capabilities exchanged, waiting for `initialized` notification
|
||||||
|
3. **Ready** — workers spawned, workspace loaded, accepting requests
|
||||||
|
4. **ShuttingDown** — `shutdown` received, draining work
|
||||||
|
5. **Exited** — `exit` received, stopping the event loop
|
||||||
|
|
||||||
|
On `initialized`, the master:
|
||||||
|
|
||||||
|
- Loads configuration from `clice.toml` (or uses defaults)
|
||||||
|
- Starts the worker pool (spawns stateful + stateless processes)
|
||||||
|
- Loads `compile_commands.json` and builds an include graph
|
||||||
|
- Starts the background indexer coroutine (if enabled)
|
||||||
|
|
||||||
|
### Document Management
|
||||||
|
|
||||||
|
Each open document is tracked in a `DocumentState` with:
|
||||||
|
|
||||||
|
- Current `version` and `text` (kept in sync via `didOpen`/`didChange`)
|
||||||
|
- A `generation` counter to detect stale compile results
|
||||||
|
- Build state flags (`build_running`, `build_requested`, `drain_scheduled`)
|
||||||
|
|
||||||
|
When a document is opened or changed:
|
||||||
|
|
||||||
|
1. The include graph is re-scanned (via dependency directives)
|
||||||
|
2. The compile unit is registered/updated in the `CompileGraph`
|
||||||
|
3. A debounced build is scheduled
|
||||||
|
|
||||||
|
### Build Drain
|
||||||
|
|
||||||
|
The `run_build_drain` coroutine implements debounced compilation:
|
||||||
|
|
||||||
|
1. Wait for the debounce timer (default 200ms) to expire
|
||||||
|
2. Ensure PCH/PCM dependencies are ready via `CompileGraph`
|
||||||
|
3. Send a `compile` request to the assigned stateful worker
|
||||||
|
4. Publish diagnostics from the result (or clear them on failure)
|
||||||
|
5. If more edits arrived during compilation (`build_requested`), loop back to step 2
|
||||||
|
|
||||||
|
This ensures rapid typing doesn't trigger a compile per keystroke.
|
||||||
|
|
||||||
|
### Request Routing
|
||||||
|
|
||||||
|
Feature requests are split between two worker types:
|
||||||
|
|
||||||
|
**Stateful workers** (affinity-routed by file path):
|
||||||
|
|
||||||
|
- `textDocument/hover`
|
||||||
|
- `textDocument/semanticTokens/full`
|
||||||
|
- `textDocument/inlayHint`
|
||||||
|
- `textDocument/foldingRange`
|
||||||
|
- `textDocument/documentSymbol`
|
||||||
|
- `textDocument/documentLink`
|
||||||
|
- `textDocument/codeAction`
|
||||||
|
- `textDocument/definition`
|
||||||
|
|
||||||
|
**Stateless workers** (round-robin):
|
||||||
|
|
||||||
|
- `textDocument/completion`
|
||||||
|
- `textDocument/signatureHelp`
|
||||||
|
|
||||||
|
All feature responses use `RawValue` passthrough — the worker serializes the LSP result to JSON, and the master forwards the raw JSON bytes to the client without deserializing. This avoids bincode↔JSON conversion overhead and serde annotation conflicts.
|
||||||
|
|
||||||
|
## Worker Pool
|
||||||
|
|
||||||
|
The worker pool (`src/server/worker_pool.cpp`) manages spawning and communicating with worker processes. Each worker is a child process of the same `clice` binary, launched with `--mode stateful-worker` or `--mode stateless-worker`.
|
||||||
|
|
||||||
|
### Communication
|
||||||
|
|
||||||
|
Workers communicate with the master via **stdio pipes** using a **bincode** serialization format (via `eventide::ipc::BincodePeer`). This is more compact and faster than JSON for internal IPC, while the master handles JSON for the external LSP protocol.
|
||||||
|
|
||||||
|
### Stateful Worker Routing
|
||||||
|
|
||||||
|
Stateful workers use **affinity routing**: each file is consistently assigned to the same worker so that the worker retains the cached AST. Assignment uses a **least-loaded** strategy for new files, with **LRU tracking** to manage ownership.
|
||||||
|
|
||||||
|
When a worker exceeds its document capacity (currently hardcoded at 16 documents), it evicts the least-recently-used document and notifies the master via an `evicted` notification.
|
||||||
|
|
||||||
|
### Stateless Worker Routing
|
||||||
|
|
||||||
|
Stateless workers use simple **round-robin** dispatch. Each request includes the full source text and compilation arguments, so any worker can handle it independently.
|
||||||
|
|
||||||
|
## Stateful Worker
|
||||||
|
|
||||||
|
The stateful worker (`src/server/stateful_worker.cpp`) caches compiled ASTs in memory. Key behavior:
|
||||||
|
|
||||||
|
- **Compile**: Parses source code into a `CompilationUnit`, caches the AST, and returns diagnostics as a `RawValue` (JSON bytes)
|
||||||
|
- **Feature queries**: Look up the cached AST and invoke the corresponding `feature::*` function (hover, semantic tokens, etc.), serializing the result to JSON
|
||||||
|
- **Document updates**: Received as notifications — the worker updates the stored text and marks the document as `dirty`, causing feature queries to return `null` until recompilation
|
||||||
|
- **Eviction**: LRU-based; evicts the oldest document when capacity is exceeded, notifying the master
|
||||||
|
- **Concurrency**: Each document has a per-document `et::mutex` (strand) to serialize compilation and feature queries. Heavy work (compilation, feature extraction) runs on a thread pool via `et::queue`.
|
||||||
|
|
||||||
|
## Stateless Worker
|
||||||
|
|
||||||
|
The stateless worker (`src/server/stateless_worker.cpp`) handles one-shot requests that don't benefit from cached ASTs:
|
||||||
|
|
||||||
|
- **Completion**: Creates a fresh compilation with `CompilationKind::Completion` and invokes `feature::code_complete`
|
||||||
|
- **Signature help**: Similar to completion, using `feature::signature_help`
|
||||||
|
- **Build PCH**: Compiles a precompiled header to a temporary file
|
||||||
|
- **Build PCM**: Compiles a C++20 module interface to a temporary file
|
||||||
|
- **Index**: Compiles a file for indexing (TUIndex generation — currently a stub)
|
||||||
|
|
||||||
|
All requests are dispatched to a thread pool via `et::queue`.
|
||||||
|
|
||||||
|
## Compile Graph
|
||||||
|
|
||||||
|
The compile graph (`src/server/compile_graph.cpp`) tracks compilation unit dependencies as a DAG. It handles:
|
||||||
|
|
||||||
|
- **Registration**: Each file registers its included dependencies
|
||||||
|
- **Cascade invalidation**: When a file changes, all transitive dependents are marked dirty and their ongoing compilations are cancelled
|
||||||
|
- **Dependency compilation**: Before compiling a file, `compile_deps` ensures all dependencies (PCH, PCMs) are built first
|
||||||
|
- **Cancellation**: Uses `et::cancellation_source` to abort in-flight compilations when files are invalidated
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The server reads configuration from `clice.toml` (or `.clice/config.toml`) in the workspace root. If no config file exists, sensible defaults are computed from system resources:
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
| ------------------------ | --------------------- | ------------------------------------------- |
|
||||||
|
| `stateful_worker_count` | CPU cores / 4 | Number of stateful worker processes |
|
||||||
|
| `stateless_worker_count` | CPU cores / 4 | Number of stateless worker processes |
|
||||||
|
| `worker_memory_limit` | 4 GB | Memory limit per stateful worker |
|
||||||
|
| `compile_commands_path` | auto-detect | Path to `compile_commands.json` |
|
||||||
|
| `cache_dir` | `<workspace>/.clice/` | Cache directory for PCH/PCM files |
|
||||||
|
| `debounce_ms` | 200 | Debounce interval for recompilation |
|
||||||
|
| `enable_indexing` | true | Enable background indexing |
|
||||||
|
| `idle_timeout_ms` | 3000 | Idle time before background indexing starts |
|
||||||
|
|
||||||
|
String values support `${workspace}` substitution.
|
||||||
|
|
||||||
|
## IPC Protocol
|
||||||
|
|
||||||
|
The master and workers communicate using custom RPC messages defined in `src/server/protocol.h`. Each message type has a `RequestTraits` or `NotificationTraits` specialization that defines the method name and result type.
|
||||||
|
|
||||||
|
### Stateful Worker Messages
|
||||||
|
|
||||||
|
| Method | Direction | Purpose |
|
||||||
|
| ----------------------------- | ------------ | ------------------------------------- |
|
||||||
|
| `clice/worker/compile` | Request | Compile source and return diagnostics |
|
||||||
|
| `clice/worker/hover` | Request | Get hover info at position |
|
||||||
|
| `clice/worker/semanticTokens` | Request | Get semantic tokens for file |
|
||||||
|
| `clice/worker/inlayHints` | Request | Get inlay hints for range |
|
||||||
|
| `clice/worker/foldingRange` | Request | Get folding ranges |
|
||||||
|
| `clice/worker/documentSymbol` | Request | Get document symbols |
|
||||||
|
| `clice/worker/documentLink` | Request | Get document links |
|
||||||
|
| `clice/worker/codeAction` | Request | Get code actions for range |
|
||||||
|
| `clice/worker/goToDefinition` | Request | Go to definition at position |
|
||||||
|
| `clice/worker/documentUpdate` | Notification | Update document text (marks dirty) |
|
||||||
|
| `clice/worker/evict` | Notification | Master → Worker: evict a document |
|
||||||
|
| `clice/worker/evicted` | Notification | Worker → Master: document was evicted |
|
||||||
|
|
||||||
|
### Stateless Worker Messages
|
||||||
|
|
||||||
|
| Method | Direction | Purpose |
|
||||||
|
| ---------------------------- | --------- | ---------------------------- |
|
||||||
|
| `clice/worker/completion` | Request | Code completion at position |
|
||||||
|
| `clice/worker/signatureHelp` | Request | Signature help at position |
|
||||||
|
| `clice/worker/buildPCH` | Request | Build precompiled header |
|
||||||
|
| `clice/worker/buildPCM` | Request | Build C++20 module interface |
|
||||||
|
| `clice/worker/index` | Request | Index a translation unit |
|
||||||
@@ -69,7 +69,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
# 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.
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
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,7 +69,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
# 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`。
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
你可以在 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)。
|
|
||||||
18
editors/vscode/.vscode/launch.json
vendored
18
editors/vscode/.vscode/launch.json
vendored
@@ -1,15 +1,21 @@
|
|||||||
// A launch configuration that compiles the extension and then opens it inside a new window
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
{
|
{
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Run Extension",
|
"name": "Run Extension (socket)",
|
||||||
"type": "extensionHost",
|
"type": "extensionHost",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
"args": ["--disable-extensions", "--extensionDevelopmentPath=${workspaceFolder}"],
|
||||||
|
"env": { "CLICE_MODE": "socket" },
|
||||||
|
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||||
|
"preLaunchTask": "${defaultBuildTask}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Run Extension (pipe)",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"args": ["--disable-extensions", "--extensionDevelopmentPath=${workspaceFolder}"],
|
||||||
|
"env": { "CLICE_MODE": "pipe" },
|
||||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||||
"preLaunchTask": "${defaultBuildTask}"
|
"preLaunchTask": "${defaultBuildTask}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commands": [],
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "clice.restart",
|
||||||
|
"title": "Clice: Restart Language Server"
|
||||||
|
}
|
||||||
|
],
|
||||||
"semanticTokenTypes": [
|
"semanticTokenTypes": [
|
||||||
{
|
{
|
||||||
"id": "character",
|
"id": "character",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface Setting {
|
|||||||
export function getSetting(): Setting | undefined {
|
export function getSetting(): Setting | undefined {
|
||||||
const setting = vscode.workspace.getConfiguration("clice");
|
const setting = vscode.workspace.getConfiguration("clice");
|
||||||
const executable = setting.get<string>("executable");
|
const executable = setting.get<string>("executable");
|
||||||
const mode = setting.get<string>("mode");
|
const mode = process.env.CLICE_MODE || setting.get<string>("mode");
|
||||||
|
|
||||||
if (mode !== "pipe" && mode !== "socket") {
|
if (mode !== "pipe" && mode !== "socket") {
|
||||||
vscode.window.showErrorMessage(`Unexpected mode: ${mode}`);
|
vscode.window.showErrorMessage(`Unexpected mode: ${mode}`);
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Support/Enum.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
struct RelationKind : refl::Enum<RelationKind, true, uint32_t> {
|
|
||||||
enum Kind : uint32_t {
|
|
||||||
Invalid,
|
|
||||||
Declaration,
|
|
||||||
Definition,
|
|
||||||
Reference,
|
|
||||||
WeakReference,
|
|
||||||
// Write Relation.
|
|
||||||
Read,
|
|
||||||
Write,
|
|
||||||
Interface,
|
|
||||||
Implementation,
|
|
||||||
/// When target is a type definition of source, source is possible type or constructor.
|
|
||||||
TypeDefinition,
|
|
||||||
|
|
||||||
/// When target is a base class of source.
|
|
||||||
Base,
|
|
||||||
/// When target is a derived class of source.
|
|
||||||
Derived,
|
|
||||||
|
|
||||||
/// When target is a constructor of source.
|
|
||||||
Constructor,
|
|
||||||
/// When target is a destructor of source.
|
|
||||||
Destructor,
|
|
||||||
|
|
||||||
// When target is a caller of source.
|
|
||||||
Caller,
|
|
||||||
// When target is a callee of source.
|
|
||||||
Callee,
|
|
||||||
};
|
|
||||||
|
|
||||||
using Enum::Enum;
|
|
||||||
|
|
||||||
constexpr bool isDeclOrDef() {
|
|
||||||
return is_one_of(Declaration, Definition);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool isReference() {
|
|
||||||
return is_one_of(Reference, WeakReference);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool isBetweenSymbol() {
|
|
||||||
return is_one_of(Interface,
|
|
||||||
Implementation,
|
|
||||||
TypeDefinition,
|
|
||||||
Base,
|
|
||||||
Derived,
|
|
||||||
Constructor,
|
|
||||||
Destructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool isCall() {
|
|
||||||
return is_one_of(Caller, Callee);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
#include "clang/Basic/SourceLocation.h"
|
|
||||||
#include "clang/Lex/Token.h"
|
|
||||||
|
|
||||||
namespace std {
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct tuple_size<clang::SourceRange> : std::integral_constant<std::size_t, 2> {};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct tuple_element<0, clang::SourceRange> {
|
|
||||||
using type = clang::SourceLocation;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct tuple_element<1, clang::SourceRange> {
|
|
||||||
using type = clang::SourceLocation;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
namespace clang {
|
|
||||||
|
|
||||||
/// Through ADL, make `clang::SourceRange` could be destructured.
|
|
||||||
template <std::size_t I>
|
|
||||||
clang::SourceLocation get(clang::SourceRange range) {
|
|
||||||
if constexpr(I == 0) {
|
|
||||||
return range.getBegin();
|
|
||||||
} else {
|
|
||||||
return range.getEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Lexer;
|
|
||||||
|
|
||||||
} // namespace clang
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
struct LocalSourceRange {
|
|
||||||
/// The begin position offset to the source file.
|
|
||||||
uint32_t begin = static_cast<uint32_t>(-1);
|
|
||||||
|
|
||||||
/// The end position offset to the source file.
|
|
||||||
uint32_t end = static_cast<uint32_t>(-1);
|
|
||||||
|
|
||||||
constexpr bool operator==(const LocalSourceRange& other) const = default;
|
|
||||||
|
|
||||||
constexpr auto length() {
|
|
||||||
return end - begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool contains(uint32_t offset) const {
|
|
||||||
return offset >= begin && offset <= end;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool intersects(const LocalSourceRange& other) const {
|
|
||||||
return begin <= other.end && end >= other.begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool valid() const {
|
|
||||||
return begin != -1 && end != -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using TokenKind = clang::tok::TokenKind;
|
|
||||||
|
|
||||||
struct Token {
|
|
||||||
/// Whether this token is at the start of line.
|
|
||||||
bool is_at_start_of_line = false;
|
|
||||||
|
|
||||||
/// Whether this token is a preprocessor directive.
|
|
||||||
bool is_pp_keyword = false;
|
|
||||||
|
|
||||||
/// The kind of this token.
|
|
||||||
TokenKind kind;
|
|
||||||
|
|
||||||
/// The source range of this token.
|
|
||||||
LocalSourceRange range;
|
|
||||||
|
|
||||||
bool valid() {
|
|
||||||
return range.valid();
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::StringRef name() const {
|
|
||||||
return clang::tok::getTokenName(kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
llvm::StringRef text(llvm::StringRef content) const {
|
|
||||||
assert(range.valid() && "Invalid source range");
|
|
||||||
return content.substr(range.begin, range.end - range.begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_eod() const {
|
|
||||||
return kind == clang::tok::eod;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_eof() const {
|
|
||||||
return kind == clang::tok::eof;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_identifier() const {
|
|
||||||
return kind == clang::tok::raw_identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_directive_hash() const {
|
|
||||||
return is_at_start_of_line && kind == clang::tok::hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The tokens after the include directive are regarded as
|
|
||||||
/// a whole token, whose kind is `header_name`. For example
|
|
||||||
/// `<iostream>` and `"test.h"` are both header name.
|
|
||||||
bool is_header_name() const {
|
|
||||||
return kind == clang::tok::header_name;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class Lexer {
|
|
||||||
public:
|
|
||||||
Lexer(llvm::StringRef content,
|
|
||||||
bool ignore_comments = true,
|
|
||||||
const clang::LangOptions* lang_opts = nullptr,
|
|
||||||
bool ignore_end_of_directive = true);
|
|
||||||
|
|
||||||
Lexer(const Lexer&) = delete;
|
|
||||||
|
|
||||||
Lexer(Lexer&&) = delete;
|
|
||||||
|
|
||||||
Lexer& operator=(const Lexer&) = delete;
|
|
||||||
|
|
||||||
Lexer& operator=(Lexer&&) = delete;
|
|
||||||
|
|
||||||
~Lexer();
|
|
||||||
|
|
||||||
void lex(Token& token);
|
|
||||||
|
|
||||||
/// Get the token before this token without moving the lexer.
|
|
||||||
Token last();
|
|
||||||
|
|
||||||
/// Get the token after this token without moving the lexer.
|
|
||||||
Token next();
|
|
||||||
|
|
||||||
/// Advance the lexer and return the next token.
|
|
||||||
Token advance();
|
|
||||||
|
|
||||||
/// Advance the lexer if the next token kind is the param.
|
|
||||||
std::optional<Token> advance_if(llvm::function_ref<bool(const Token&)> callback);
|
|
||||||
|
|
||||||
std::optional<Token> advance_if(llvm::StringRef spelling) {
|
|
||||||
return advance_if([&](const Token& token) {
|
|
||||||
return token.is_identifier() && token.text(content) == spelling;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Token> advance_if(TokenKind kind) {
|
|
||||||
return advance_if([&](const Token& token) { return token.kind == kind; });
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance the lexer until meet the specific kind token.
|
|
||||||
Token advance_until(TokenKind kind);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// If this is set to false, the lexer will emit tok::eod at the end
|
|
||||||
/// of directive.
|
|
||||||
bool ignore_end_of_directive = true;
|
|
||||||
|
|
||||||
/// Whether we are lexing the preprocessor directive.
|
|
||||||
bool parse_pp_keyword = false;
|
|
||||||
|
|
||||||
/// Whether we are lexing the header name.
|
|
||||||
bool parse_header_name = false;
|
|
||||||
|
|
||||||
bool module_declaration_context = true;
|
|
||||||
|
|
||||||
/// The cache of last token.
|
|
||||||
Token last_token;
|
|
||||||
|
|
||||||
/// The cache of current token.
|
|
||||||
Token current_token;
|
|
||||||
|
|
||||||
/// The cache of next token.
|
|
||||||
std::optional<Token> next_token;
|
|
||||||
|
|
||||||
/// The lexed content.
|
|
||||||
llvm::StringRef content;
|
|
||||||
|
|
||||||
std::unique_ptr<clang::Lexer> lexer;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Event.h"
|
|
||||||
#include "FileSystem.h"
|
|
||||||
#include "Gather.h"
|
|
||||||
#include "Lock.h"
|
|
||||||
#include "Network.h"
|
|
||||||
#include "Sleep.h"
|
|
||||||
#include "ThreadPool.h"
|
|
||||||
#include "libuv.h"
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <expected>
|
|
||||||
|
|
||||||
#include "Task.h"
|
|
||||||
#include "libuv.h"
|
|
||||||
|
|
||||||
namespace clice::async::awaiter {
|
|
||||||
|
|
||||||
template <typename Request>
|
|
||||||
struct uv_base;
|
|
||||||
|
|
||||||
template <typename Request>
|
|
||||||
requires (is_uv_handle_v<Request>)
|
|
||||||
struct uv_base<Request> {
|
|
||||||
/// For libuv handles, `uv_close` must be called to release resources.
|
|
||||||
/// However, `uv_close` can only be async operation. When close is actually called,
|
|
||||||
/// the promise object may have already been destroyed, leading to undefined behavior
|
|
||||||
/// such as use-after-free. To avoid this situation, we allocate memory separately
|
|
||||||
/// for the handle and destroy it in the callback function.
|
|
||||||
Request& request;
|
|
||||||
|
|
||||||
uv_base() : request(*static_cast<Request*>(std::malloc(sizeof(Request)))) {}
|
|
||||||
|
|
||||||
~uv_base() {
|
|
||||||
uv_close(reinterpret_cast<uv_handle_t*>(&request),
|
|
||||||
[](uv_handle_t* handle) { std::free(handle); });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Request>
|
|
||||||
requires (is_uv_req_v<Request>)
|
|
||||||
struct uv_base<Request> {
|
|
||||||
/// For libuv requests, they don't need to be closed. We can lay them on the promise
|
|
||||||
/// object directly.
|
|
||||||
Request request;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The CRTP base class for the awaiter of libuv async operations. The Derived should
|
|
||||||
/// implement the `start` and `cleanup` functions.
|
|
||||||
template <typename Derived, typename Request, typename Ret, typename... Extras>
|
|
||||||
struct uv : uv_base<Request> {
|
|
||||||
int error = 0;
|
|
||||||
promise_base* continuation;
|
|
||||||
|
|
||||||
bool await_ready() const noexcept {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The callback function to handle the async operation. This should always called
|
|
||||||
/// in the main thread.
|
|
||||||
static void callback(Request* request, Extras... extras) {
|
|
||||||
auto& self = *static_cast<Derived*>(request->data);
|
|
||||||
|
|
||||||
/// The derived should implement the cleanup function to release resources or set
|
|
||||||
/// the error code if the async operation fails.
|
|
||||||
self.cleanup(extras...);
|
|
||||||
|
|
||||||
/// Then we resume the coroutine. It may destroy the current task,
|
|
||||||
/// If the task is cancelled and disposable.
|
|
||||||
self.continuation->resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Promise>
|
|
||||||
std::coroutine_handle<> await_suspend(std::coroutine_handle<Promise> waiting) noexcept {
|
|
||||||
continuation = &waiting.promise();
|
|
||||||
this->request.data = static_cast<Derived*>(this);
|
|
||||||
|
|
||||||
auto& self = *static_cast<Derived*>(this);
|
|
||||||
|
|
||||||
/// Start the async operation.
|
|
||||||
error = self.start(callback);
|
|
||||||
|
|
||||||
/// If the async operation fails, resume the coroutine immediately.
|
|
||||||
if(error < 0) {
|
|
||||||
return continuation->resume_handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Otherwise, return the coroutine handle to resume later.
|
|
||||||
return std::noop_coroutine();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::expected<Ret, std::error_code> await_resume() noexcept {
|
|
||||||
if(error < 0) {
|
|
||||||
return std::unexpected(std::error_code(error, category()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr(!std::is_void_v<Ret>) {
|
|
||||||
return static_cast<Derived*>(this)->result();
|
|
||||||
} else {
|
|
||||||
return std::expected<void, std::error_code>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::async::awaiter
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Task.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
namespace awaiter {
|
|
||||||
|
|
||||||
struct event {
|
|
||||||
bool ready;
|
|
||||||
llvm::SmallVectorImpl<promise_base*>& awaiters;
|
|
||||||
|
|
||||||
bool await_ready() const noexcept {
|
|
||||||
return ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Promise>
|
|
||||||
void await_suspend(std::coroutine_handle<Promise> handle) const noexcept {
|
|
||||||
awaiters.emplace_back(&handle.promise());
|
|
||||||
}
|
|
||||||
|
|
||||||
void await_resume() const noexcept {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace awaiter
|
|
||||||
|
|
||||||
class Event {
|
|
||||||
public:
|
|
||||||
Event() = default;
|
|
||||||
|
|
||||||
void set() {
|
|
||||||
ready = true;
|
|
||||||
for(auto* awaiter: awaiters) {
|
|
||||||
awaiter->schedule();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void unset() {
|
|
||||||
ready = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
ready = false;
|
|
||||||
awaiters.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator co_await() {
|
|
||||||
return awaiter::event{ready, awaiters};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool ready = false;
|
|
||||||
llvm::SmallVector<promise_base*, 4> awaiters;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::async
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#include "Awaiter.h"
|
|
||||||
#include "Task.h"
|
|
||||||
#include "libuv.h"
|
|
||||||
#include "Support/Enum.h"
|
|
||||||
#include "Support/JSON.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/FunctionExtras.h"
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
namespace fs {
|
|
||||||
|
|
||||||
struct Mode : refl::Enum<Mode, true> {
|
|
||||||
enum Kind {
|
|
||||||
/// Open the file for reading.
|
|
||||||
Read = 0,
|
|
||||||
|
|
||||||
/// Open the file for writing.
|
|
||||||
Write,
|
|
||||||
|
|
||||||
/// Open the file for reading and writing.
|
|
||||||
ReadWrite,
|
|
||||||
|
|
||||||
/// If the file does not exist, create it.
|
|
||||||
Create,
|
|
||||||
|
|
||||||
/// If the file exists, append the data to the end of the file.
|
|
||||||
Append,
|
|
||||||
|
|
||||||
/// If the file exists, truncate the file to zero length.
|
|
||||||
Truncate,
|
|
||||||
|
|
||||||
/// If the file exists, fail the open.
|
|
||||||
Exclusive,
|
|
||||||
};
|
|
||||||
|
|
||||||
using Enum::Enum;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct handle {
|
|
||||||
public:
|
|
||||||
handle(uv_file file) : file(file) {}
|
|
||||||
|
|
||||||
handle(const handle&) = delete;
|
|
||||||
|
|
||||||
handle(handle&& other) noexcept : file(other.file) {
|
|
||||||
other.file = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
~handle();
|
|
||||||
|
|
||||||
handle& operator=(const handle&) = delete;
|
|
||||||
|
|
||||||
handle& operator=(handle&& other) noexcept = delete;
|
|
||||||
|
|
||||||
int value() const {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uv_file file;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Open the file asynchronously.
|
|
||||||
Result<handle> open(std::string path, Mode mode);
|
|
||||||
|
|
||||||
/// Read the file asynchronously, make sure the buffer is valid until the task is done.
|
|
||||||
Result<ssize_t> read(const handle& handle, char* buffer, std::size_t size);
|
|
||||||
|
|
||||||
Result<std::string> read(std::string path, Mode mode = Mode::Read);
|
|
||||||
|
|
||||||
/// Write the file asynchronously, make sure the buffer is valid until the task is done.
|
|
||||||
Result<void> write(const handle& handle, char* buffer, std::size_t size);
|
|
||||||
|
|
||||||
Result<void> write(std::string path,
|
|
||||||
char* buffer,
|
|
||||||
std::size_t size,
|
|
||||||
Mode mode = Mode(Mode::Write, Mode::Create, Mode::Truncate));
|
|
||||||
|
|
||||||
struct Stats {
|
|
||||||
std::chrono::milliseconds mtime;
|
|
||||||
size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
Result<Stats> stat(std::string path);
|
|
||||||
|
|
||||||
} // namespace fs
|
|
||||||
|
|
||||||
} // namespace clice::async
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
#include "Event.h"
|
|
||||||
#include "Task.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
struct none {};
|
|
||||||
|
|
||||||
template <typename Task, typename V = typename std::remove_cvref_t<Task>::value_type>
|
|
||||||
using task_value_t = std::conditional_t<std::is_void_v<V>, none, V>;
|
|
||||||
|
|
||||||
template <typename... Tasks>
|
|
||||||
auto gather(Tasks&&... tasks) -> Task<std::tuple<task_value_t<Tasks>...>> {
|
|
||||||
constexpr static std::size_t count = sizeof...(Tasks);
|
|
||||||
|
|
||||||
Event event;
|
|
||||||
std::size_t finished = 0;
|
|
||||||
|
|
||||||
auto run_task = [&](auto& task) -> Task<task_value_t<decltype(task)>> {
|
|
||||||
using V = typename std::remove_cvref_t<decltype(task)>::value_type;
|
|
||||||
if constexpr(std::is_void_v<V>) {
|
|
||||||
co_await task;
|
|
||||||
/// Check if all tasks are finished. If so, set the event to
|
|
||||||
/// resume the gather handle.
|
|
||||||
finished += 1;
|
|
||||||
if(finished == count) {
|
|
||||||
event.set();
|
|
||||||
}
|
|
||||||
co_return none{};
|
|
||||||
} else {
|
|
||||||
auto result = co_await task;
|
|
||||||
finished += 1;
|
|
||||||
if(finished == count) {
|
|
||||||
event.set();
|
|
||||||
}
|
|
||||||
co_return std::move(result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto schedule_task = [&](auto& task) {
|
|
||||||
auto core = run_task(task);
|
|
||||||
core.schedule();
|
|
||||||
return core;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::tuple all = {schedule_task(tasks)...};
|
|
||||||
|
|
||||||
/// Wait for all tasks to finish.
|
|
||||||
co_await event;
|
|
||||||
|
|
||||||
/// Return the results of all tasks.
|
|
||||||
co_return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
|
||||||
return std::make_tuple(std::get<Is>(all).result()...);
|
|
||||||
}(std::make_index_sequence<count>{});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run the tasks in parallel and return the results.
|
|
||||||
template <typename... Tasks>
|
|
||||||
auto run(Tasks&&... tasks) {
|
|
||||||
auto core = gather(std::forward<Tasks>(tasks)...);
|
|
||||||
core.schedule();
|
|
||||||
async::run();
|
|
||||||
assert(core.done() && "run: not done");
|
|
||||||
return core.result();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <ranges::input_range Range, typename Coroutine>
|
|
||||||
requires requires(Coroutine coroutine, ranges::range_value_t<Range> value) {
|
|
||||||
{ coroutine(value) } -> std::same_as<Task<bool>>;
|
|
||||||
}
|
|
||||||
Task<bool> gather(Range&& range,
|
|
||||||
Coroutine&& coroutine,
|
|
||||||
std::size_t concurrency = std::thread::hardware_concurrency()) {
|
|
||||||
std::vector<Task<>> tasks;
|
|
||||||
tasks.reserve(concurrency);
|
|
||||||
|
|
||||||
auto iter = ranges::begin(range);
|
|
||||||
auto end = ranges::end(range);
|
|
||||||
|
|
||||||
Event event;
|
|
||||||
std::size_t finished = 0;
|
|
||||||
bool cancelled = false;
|
|
||||||
|
|
||||||
auto run_task = [&](auto& value) -> async::Task<> {
|
|
||||||
/// Execute the first task.
|
|
||||||
auto task = coroutine(value);
|
|
||||||
|
|
||||||
/// If any task fails, cancel all tasks and return false.
|
|
||||||
if(auto result = co_await task; !result) {
|
|
||||||
for(auto& task: tasks) {
|
|
||||||
task.cancel();
|
|
||||||
task.dispose();
|
|
||||||
}
|
|
||||||
cancelled = true;
|
|
||||||
event.set();
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
|
|
||||||
finished += 1;
|
|
||||||
|
|
||||||
/// Check if still have tasks to run. If so, run the next task.
|
|
||||||
while(iter != end) {
|
|
||||||
auto task = coroutine(*iter);
|
|
||||||
iter++;
|
|
||||||
finished -= 1;
|
|
||||||
|
|
||||||
if(auto result = co_await task; !result) {
|
|
||||||
for(auto& task: tasks) {
|
|
||||||
task.cancel();
|
|
||||||
task.dispose();
|
|
||||||
}
|
|
||||||
cancelled = true;
|
|
||||||
event.set();
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
|
|
||||||
finished += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if all tasks are finished. If so, set the event to
|
|
||||||
/// resume the gather handle.
|
|
||||||
if(finished == tasks.size()) {
|
|
||||||
event.set();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Fill tasks.
|
|
||||||
while(iter != end && tasks.size() < concurrency) {
|
|
||||||
tasks.emplace_back(run_task(*iter));
|
|
||||||
tasks.back().schedule();
|
|
||||||
iter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
co_await event;
|
|
||||||
|
|
||||||
co_return !cancelled;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace clice::async
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Event.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
namespace awaiter {
|
|
||||||
|
|
||||||
struct lock {
|
|
||||||
llvm::SmallVectorImpl<promise_base*>& awaiters;
|
|
||||||
|
|
||||||
bool await_ready() const noexcept {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Promise>
|
|
||||||
void await_suspend(std::coroutine_handle<Promise> handle) const noexcept {
|
|
||||||
awaiters.emplace_back(&handle.promise());
|
|
||||||
}
|
|
||||||
|
|
||||||
void await_resume() const noexcept {}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace awaiter
|
|
||||||
|
|
||||||
class Lock {
|
|
||||||
friend class guard;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Lock() = default;
|
|
||||||
|
|
||||||
class Guard {
|
|
||||||
public:
|
|
||||||
Guard(Lock* lock) : lock(lock) {}
|
|
||||||
|
|
||||||
Guard(Guard&& other) : lock(other.lock) {
|
|
||||||
other.lock = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Guard() {
|
|
||||||
if(!lock) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock->locked = false;
|
|
||||||
if(!lock->awaiters.empty()) {
|
|
||||||
lock->awaiters.front()->schedule();
|
|
||||||
lock->awaiters.erase(lock->awaiters.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Lock* lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Try to get the lock. If the lock is locked, the current coroutine will be
|
|
||||||
/// suspended and wait for the lock to be released.
|
|
||||||
Task<Guard> try_lock() {
|
|
||||||
/// Note that this task also may be canceled, we make sure
|
|
||||||
/// even cancel, it can resume one task(through destructor).
|
|
||||||
Guard guard(this);
|
|
||||||
|
|
||||||
if(locked) {
|
|
||||||
co_await awaiter::lock{awaiters};
|
|
||||||
}
|
|
||||||
|
|
||||||
locked = true;
|
|
||||||
|
|
||||||
/// Use `std::move` to make sure it will not resume
|
|
||||||
/// the awaiter here.
|
|
||||||
co_return std::move(guard);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool locked = false;
|
|
||||||
llvm::SmallVector<promise_base*, 4> awaiters;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::async
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Task.h"
|
|
||||||
#include "libuv.h"
|
|
||||||
#include "Support/JSON.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/FunctionExtras.h"
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice::async::net {
|
|
||||||
|
|
||||||
using Callback = llvm::unique_function<Task<void>(json::Value)>;
|
|
||||||
|
|
||||||
/// Listen on stdin/stdout, callback is called when there is a LSP message available.
|
|
||||||
void listen(Callback callback);
|
|
||||||
|
|
||||||
/// Listen on the given host and port, callback is called when there is a LSP message available.
|
|
||||||
void listen(const char* host, unsigned int port, Callback callback);
|
|
||||||
|
|
||||||
/// Write a JSON value to the client.
|
|
||||||
Task<> write(json::Value value);
|
|
||||||
|
|
||||||
} // namespace clice::async::net
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "Awaiter.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
namespace awaiter {
|
|
||||||
|
|
||||||
struct sleep : uv<sleep, uv_timer_t, void> {
|
|
||||||
std::chrono::milliseconds duration;
|
|
||||||
|
|
||||||
int start(auto callback) {
|
|
||||||
int err = uv_timer_init(async::loop, &request);
|
|
||||||
if(err < 0) {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return uv_timer_start(&request, callback, duration.count(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanup() {
|
|
||||||
error = uv_timer_stop(&request);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace awaiter
|
|
||||||
|
|
||||||
inline auto sleep(std::chrono::milliseconds duration) {
|
|
||||||
return awaiter::sleep{{}, duration};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline auto sleep(std::size_t milliseconds) {
|
|
||||||
return sleep(std::chrono::milliseconds(milliseconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
}; // namespace clice::async
|
|
||||||
@@ -1,334 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <coroutine>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <optional>
|
|
||||||
#include <source_location>
|
|
||||||
|
|
||||||
#include "Support/Format.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class Task;
|
|
||||||
|
|
||||||
struct promise_base {
|
|
||||||
enum Flags : uint8_t {
|
|
||||||
Empty = 0,
|
|
||||||
|
|
||||||
/// The task is cancelled.
|
|
||||||
Cancelled = 1,
|
|
||||||
|
|
||||||
/// The coroutine handle will be destroyed when the task is done or cancelled.
|
|
||||||
Disposable = 1 << 1,
|
|
||||||
|
|
||||||
/// The coroutine is done or is cancelled and resumed, means it will never
|
|
||||||
/// scheduled again.
|
|
||||||
Finished = 1 << 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
uint8_t flags;
|
|
||||||
|
|
||||||
void* data;
|
|
||||||
|
|
||||||
/// The coroutine handle that is waiting for the task to complete.
|
|
||||||
/// If this is a top-level coroutine, it is empty.
|
|
||||||
promise_base* continuation = nullptr;
|
|
||||||
|
|
||||||
promise_base* next = nullptr;
|
|
||||||
|
|
||||||
std::source_location location;
|
|
||||||
|
|
||||||
template <typename Promise>
|
|
||||||
void set(std::coroutine_handle<Promise> handle) {
|
|
||||||
flags = Empty;
|
|
||||||
data = handle.address();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto handle() const noexcept {
|
|
||||||
return std::coroutine_handle<>::from_address(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void schedule();
|
|
||||||
|
|
||||||
bool done() const noexcept {
|
|
||||||
return handle().done();
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy() {
|
|
||||||
handle().destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cancel() {
|
|
||||||
auto p = this;
|
|
||||||
while(p) {
|
|
||||||
p->flags |= Flags::Cancelled;
|
|
||||||
p = p->next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cancelled() const noexcept {
|
|
||||||
return flags & Flags::Cancelled;
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
flags |= Flags::Disposable;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool disposable() const noexcept {
|
|
||||||
return flags & Flags::Disposable;
|
|
||||||
}
|
|
||||||
|
|
||||||
void finish() {
|
|
||||||
flags |= Flags::Finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool finished() {
|
|
||||||
return flags & Flags::Finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::coroutine_handle<> resume_handle() {
|
|
||||||
if(cancelled()) {
|
|
||||||
/// If the task is cancelled and disposable, destroy the coroutine handle.
|
|
||||||
auto p = this;
|
|
||||||
while(p && p->cancelled()) {
|
|
||||||
auto con = p->continuation;
|
|
||||||
|
|
||||||
if(p->disposable()) {
|
|
||||||
p->destroy();
|
|
||||||
} else {
|
|
||||||
p->finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
p = con;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::noop_coroutine();
|
|
||||||
} else {
|
|
||||||
/// Otherwise, resume the coroutine handle.
|
|
||||||
return handle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void resume() {
|
|
||||||
resume_handle().resume();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace awaiter {
|
|
||||||
|
|
||||||
/// The awaiter for the final suspend point of `Task`.
|
|
||||||
struct final {
|
|
||||||
promise_base* continuation;
|
|
||||||
|
|
||||||
bool await_ready() noexcept {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Promise>
|
|
||||||
std::coroutine_handle<> await_suspend(std::coroutine_handle<Promise> current) noexcept {
|
|
||||||
std::coroutine_handle<> handle = std::noop_coroutine();
|
|
||||||
|
|
||||||
/// In the final suspend point, this coroutine is already done.
|
|
||||||
/// So try to resume the waiting coroutine if it exists.
|
|
||||||
if(continuation) {
|
|
||||||
continuation->next = nullptr;
|
|
||||||
handle = continuation->resume_handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark current coroutine as finished.
|
|
||||||
current.promise().finish();
|
|
||||||
|
|
||||||
if(current.promise().disposable()) {
|
|
||||||
/// If this task is disposable, destroy the coroutine handle.
|
|
||||||
current.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
void await_resume() noexcept {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The awaiter for the `Task` type.
|
|
||||||
template <typename T, typename P>
|
|
||||||
struct task {
|
|
||||||
std::coroutine_handle<P> handle;
|
|
||||||
|
|
||||||
bool await_ready() noexcept {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Promise>
|
|
||||||
auto await_suspend(std::coroutine_handle<Promise> waiting) noexcept {
|
|
||||||
/// Store the waiting coroutine in the promise for later scheduling.
|
|
||||||
/// It will be scheduled in the final suspend point.
|
|
||||||
assert(!handle.promise().continuation && "await_suspend: already waiting");
|
|
||||||
handle.promise().continuation = &waiting.promise();
|
|
||||||
waiting.promise().next = &handle.promise();
|
|
||||||
|
|
||||||
/// If this `Task` is awaited from another coroutine, we should schedule
|
|
||||||
/// the this task first.
|
|
||||||
return handle.promise().resume_handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
T await_resume() noexcept {
|
|
||||||
if constexpr(!std::is_void_v<T>) {
|
|
||||||
assert(handle.promise().value.has_value() && "await_resume: value not set");
|
|
||||||
return std::move(*handle.promise().value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace awaiter
|
|
||||||
|
|
||||||
template <typename T = void>
|
|
||||||
class Task {
|
|
||||||
public:
|
|
||||||
template <typename V>
|
|
||||||
struct promise_result {
|
|
||||||
std::optional<V> value;
|
|
||||||
|
|
||||||
template <typename U>
|
|
||||||
void return_value(U&& val) noexcept {
|
|
||||||
assert(!value.has_value() && "return_value: value already set");
|
|
||||||
value.emplace(std::forward<U>(val));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// WORKAROUND: GCC bug - full specialization in non-namespace scope not supported
|
|
||||||
// see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282
|
|
||||||
template <std::same_as<void> V>
|
|
||||||
struct promise_result<V> {
|
|
||||||
void return_void() noexcept {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct promise_type : promise_base, promise_result<T> {
|
|
||||||
promise_type(std::source_location location = std::source_location::current()) {
|
|
||||||
set(handle());
|
|
||||||
this->location = location;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto get_return_object() {
|
|
||||||
return Task<T>(handle());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto initial_suspend() {
|
|
||||||
return std::suspend_always();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto final_suspend() noexcept {
|
|
||||||
return awaiter::final{continuation};
|
|
||||||
}
|
|
||||||
|
|
||||||
void unhandled_exception() {
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto handle() {
|
|
||||||
return std::coroutine_handle<promise_type>::from_promise(*this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using coroutine_handle = std::coroutine_handle<promise_type>;
|
|
||||||
|
|
||||||
using value_type = T;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Task() = default;
|
|
||||||
|
|
||||||
Task(coroutine_handle handle) : core(handle) {}
|
|
||||||
|
|
||||||
Task(const Task&) = delete;
|
|
||||||
|
|
||||||
Task(Task&& other) noexcept : core(other.core) {
|
|
||||||
other.core = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Task& operator=(const Task&) = delete;
|
|
||||||
|
|
||||||
Task& operator=(Task&& other) noexcept {
|
|
||||||
if(core) {
|
|
||||||
core.destroy();
|
|
||||||
}
|
|
||||||
core = other.core;
|
|
||||||
other.core = nullptr;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Task() {
|
|
||||||
if(core) {
|
|
||||||
core.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
coroutine_handle handle() const noexcept {
|
|
||||||
return core;
|
|
||||||
}
|
|
||||||
|
|
||||||
coroutine_handle release() noexcept {
|
|
||||||
auto handle = core;
|
|
||||||
core = nullptr;
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool empty() const noexcept {
|
|
||||||
return !core;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool done() const noexcept {
|
|
||||||
return core.done();
|
|
||||||
}
|
|
||||||
|
|
||||||
void schedule() {
|
|
||||||
core.promise().schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cancel the task, the suspend point after the current one will be skipped.
|
|
||||||
void cancel() {
|
|
||||||
core.promise().cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool cancelled() {
|
|
||||||
return core.promise().cancelled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dispose the task, it will be destroyed when finished or cancelled.
|
|
||||||
void dispose() {
|
|
||||||
core.promise().dispose();
|
|
||||||
core = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool finished() {
|
|
||||||
return core.promise().finished();
|
|
||||||
}
|
|
||||||
|
|
||||||
T result() {
|
|
||||||
if constexpr(!std::is_void_v<T>) {
|
|
||||||
return std::move(core.promise().value.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator co_await() const noexcept {
|
|
||||||
return awaiter::task<T, promise_type>{core};
|
|
||||||
}
|
|
||||||
|
|
||||||
void stacktrace() {
|
|
||||||
promise_base* handle = core;
|
|
||||||
while(handle) {
|
|
||||||
std::println("{}:{}:{}",
|
|
||||||
handle->location.file_name(),
|
|
||||||
handle->location.line(),
|
|
||||||
handle->location.function_name());
|
|
||||||
handle = handle->continuation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
coroutine_handle core;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::async
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Awaiter.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
namespace awaiter {
|
|
||||||
|
|
||||||
template <typename Ret>
|
|
||||||
struct value {
|
|
||||||
std::optional<Ret> value;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct value<void> {};
|
|
||||||
|
|
||||||
template <typename Work, typename Ret>
|
|
||||||
struct thread_pool : value<Ret>, uv<thread_pool<Work, Ret>, uv_work_t, Ret, int> {
|
|
||||||
Work work;
|
|
||||||
|
|
||||||
/// `uv_work_t` has two callback functions, `work_cb` is executed in the thread pool,
|
|
||||||
/// and `after_work_cb` is executed in the main thread.
|
|
||||||
static void work_cb(uv_work_t* work) {
|
|
||||||
auto& awaiter = uv_cast<thread_pool>(work);
|
|
||||||
if constexpr(!std::is_void_v<Ret>) {
|
|
||||||
awaiter.value.emplace(awaiter.work());
|
|
||||||
} else {
|
|
||||||
awaiter.work();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int start(auto callback) {
|
|
||||||
return uv_queue_work(async::loop, &this->request, work_cb, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanup(int status) {
|
|
||||||
this->error = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ret result() {
|
|
||||||
if constexpr(!std::is_void_v<Ret>) {
|
|
||||||
return std::move(*this->value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace awaiter
|
|
||||||
|
|
||||||
template <typename Work, typename Ret = std::invoke_result_t<Work>>
|
|
||||||
async::Task<Ret> submit(Work&& work) {
|
|
||||||
using W = std::remove_cvref_t<Work>;
|
|
||||||
auto result = co_await awaiter::thread_pool<W, Ret>{{}, {}, std::forward<Work>(work)};
|
|
||||||
if(!result) {
|
|
||||||
/// Thread pool task should never fails.
|
|
||||||
std::abort();
|
|
||||||
}
|
|
||||||
co_return std::move(*result);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace clice::async
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#define NOMINMAX
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "uv.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#undef THIS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <expected>
|
|
||||||
#include <system_error>
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
#include "Support/Logging.h"
|
|
||||||
#include "Support/TypeTraits.h"
|
|
||||||
|
|
||||||
namespace clice::async {
|
|
||||||
|
|
||||||
/// The default event loop.
|
|
||||||
extern uv_loop_t* loop;
|
|
||||||
|
|
||||||
template <typename T, typename U>
|
|
||||||
T& uv_cast(U* u) {
|
|
||||||
assert(u && u->data && "uv_cast: invalid uv handle");
|
|
||||||
return *static_cast<std::remove_cvref_t<T>*>(u->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define UV_TYPE_ITER(_, name) || std::is_same_v<T, uv_##name##_t>
|
|
||||||
|
|
||||||
/// Check if the type `T` is a libuv handle.
|
|
||||||
template <typename T>
|
|
||||||
constexpr bool is_uv_handle_v = false UV_HANDLE_TYPE_MAP(UV_TYPE_ITER);
|
|
||||||
|
|
||||||
/// Check if the type `T` is a libuv request.
|
|
||||||
template <typename T>
|
|
||||||
constexpr bool is_uv_req_v = false UV_REQ_TYPE_MAP(UV_TYPE_ITER);
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
constexpr bool is_uv_stream_v = std::is_same_v<T, uv_stream_t> || std::is_same_v<T, uv_tcp_t> ||
|
|
||||||
std::is_same_v<T, uv_pipe_t> || std::is_same_v<T, uv_tty_t>;
|
|
||||||
|
|
||||||
#undef UV_TYPE_ITER
|
|
||||||
|
|
||||||
template <typename T, typename U>
|
|
||||||
T* uv_cast(U& u) {
|
|
||||||
if constexpr(std::is_same_v<T, uv_handle_t>) {
|
|
||||||
static_assert(is_uv_handle_v<std::remove_cvref_t<U>>, "uv_cast: invalid uv handle");
|
|
||||||
} else if constexpr(std::is_same_v<T, uv_req_t>) {
|
|
||||||
static_assert(is_uv_req_v<std::remove_cvref_t<U>>, "uv_cast: invalid uv request");
|
|
||||||
} else if constexpr(std::is_same_v<T, uv_stream_t>) {
|
|
||||||
static_assert(is_uv_stream_v<std::remove_cvref_t<U>>, "uv_cast: invalid uv stream");
|
|
||||||
} else {
|
|
||||||
static_assert(dependent_false<U>, "uv_cast: invalid type");
|
|
||||||
}
|
|
||||||
return reinterpret_cast<T*>(&u);
|
|
||||||
}
|
|
||||||
|
|
||||||
void uv_check_result(const int result,
|
|
||||||
const std::source_location location = std::source_location::current());
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
class Task;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using Result = Task<std::expected<T, std::error_code>>;
|
|
||||||
|
|
||||||
const std::error_category& category();
|
|
||||||
|
|
||||||
void init();
|
|
||||||
|
|
||||||
void run();
|
|
||||||
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
} // namespace clice::async
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <expected>
|
|
||||||
|
|
||||||
#include "Toolchain.h"
|
|
||||||
#include "Support/Enum.h"
|
|
||||||
#include "Support/Format.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
#include "llvm/ADT/DenseSet.h"
|
|
||||||
#include "llvm/ADT/StringMap.h"
|
|
||||||
#include "llvm/Support/Allocator.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
struct CommandOptions {
|
|
||||||
/// Ignore unknown commands arguments.
|
|
||||||
bool ignore_unknown = true;
|
|
||||||
|
|
||||||
/// Inject resource directory to the command.
|
|
||||||
bool resource_dir = false;
|
|
||||||
|
|
||||||
/// Query the compiler driver for additional information, such as system includes and target.
|
|
||||||
bool query_toolchain = false;
|
|
||||||
|
|
||||||
/// Suppress the warning log if failed to query driver info.
|
|
||||||
/// Set true in unittests to avoid cluttering test output.
|
|
||||||
bool suppress_logging = false;
|
|
||||||
|
|
||||||
/// The commands that you want to remove from original commands list.
|
|
||||||
llvm::ArrayRef<std::string> remove;
|
|
||||||
|
|
||||||
/// The commands that you want to add to original commands list.
|
|
||||||
llvm::ArrayRef<std::string> append;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class UpdateKind : std::uint8_t {
|
|
||||||
Unchanged,
|
|
||||||
Inserted,
|
|
||||||
Deleted,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct UpdateInfo {
|
|
||||||
/// The kind of update.
|
|
||||||
UpdateKind kind;
|
|
||||||
|
|
||||||
/// The updated file.
|
|
||||||
std::uint32_t path_id;
|
|
||||||
|
|
||||||
/// The compilation context of this file command, which could
|
|
||||||
/// be used to identity the same file with different compilation
|
|
||||||
/// contexts.
|
|
||||||
const void* context;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompilationContext {
|
|
||||||
/// The working directory of compilation.
|
|
||||||
llvm::StringRef directory;
|
|
||||||
|
|
||||||
/// The compilation arguments.
|
|
||||||
std::vector<const char*> arguments;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string print_argv(llvm::ArrayRef<const char*> args);
|
|
||||||
|
|
||||||
class CompilationDatabase {
|
|
||||||
public:
|
|
||||||
CompilationDatabase();
|
|
||||||
|
|
||||||
CompilationDatabase(const CompilationDatabase&) = delete;
|
|
||||||
|
|
||||||
CompilationDatabase(CompilationDatabase&& other);
|
|
||||||
|
|
||||||
CompilationDatabase& operator=(const CompilationDatabase&) = delete;
|
|
||||||
|
|
||||||
CompilationDatabase& operator=(CompilationDatabase&& other);
|
|
||||||
|
|
||||||
~CompilationDatabase();
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Read the compilation database on the give file and return the
|
|
||||||
/// incremental update infos.
|
|
||||||
std::vector<UpdateInfo> load_compile_database(llvm::StringRef file);
|
|
||||||
|
|
||||||
/// Lookup the compilation context of specific file. If the context
|
|
||||||
/// param is provided, we will return the compilation context corresponding
|
|
||||||
/// to the handle. Otherwise we just return the first one(if the file have)
|
|
||||||
/// multiple compilation contexts.
|
|
||||||
CompilationContext lookup(llvm::StringRef file,
|
|
||||||
const CommandOptions& options = {},
|
|
||||||
const void* context = nullptr);
|
|
||||||
|
|
||||||
/// TODO: list all compilation context of the file, this is useful to show
|
|
||||||
/// all contexts and let user choose one.
|
|
||||||
/// std::vector<CompilationContext> fetch_all(llvm::StringRef file);
|
|
||||||
|
|
||||||
/// Get an the option for specific argument.
|
|
||||||
static std::optional<std::uint32_t> get_option_id(llvm::StringRef argument);
|
|
||||||
|
|
||||||
/// FIXME: bad interface design ...
|
|
||||||
std::vector<llvm::StringRef> files();
|
|
||||||
|
|
||||||
/// FIXME: remove this api?
|
|
||||||
auto save_string(llvm::StringRef string) -> llvm::StringRef;
|
|
||||||
|
|
||||||
#ifdef CLICE_ENABLE_TEST
|
|
||||||
|
|
||||||
void add_command(llvm::StringRef directory,
|
|
||||||
llvm::StringRef file,
|
|
||||||
llvm::ArrayRef<const char*> arguments);
|
|
||||||
|
|
||||||
void add_command(llvm::StringRef directory, llvm::StringRef file, llvm::StringRef command);
|
|
||||||
|
|
||||||
/// FIXME: remove this
|
|
||||||
/// Update commands from json file and return all updated file.
|
|
||||||
std::expected<std::vector<UpdateInfo>, std::string> load_commands(llvm::StringRef json_content,
|
|
||||||
llvm::StringRef workspace);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Impl;
|
|
||||||
std::unique_ptr<Impl> self;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <expected>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Support/Struct.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
class CompilationUnit;
|
|
||||||
|
|
||||||
struct CompilationParams;
|
|
||||||
|
|
||||||
struct ModuleInfo {
|
|
||||||
/// Whether this module is an interface unit.
|
|
||||||
/// i.e. has export module declaration.
|
|
||||||
bool isInterfaceUnit = false;
|
|
||||||
|
|
||||||
/// Module name.
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
/// Dependent modules of this module.
|
|
||||||
std::vector<std::string> mods;
|
|
||||||
};
|
|
||||||
|
|
||||||
inherited_struct(PCMInfo, ModuleInfo) {
|
|
||||||
/// PCM file path.
|
|
||||||
std::string path;
|
|
||||||
|
|
||||||
/// Source file path.
|
|
||||||
std::string srcPath;
|
|
||||||
|
|
||||||
/// Files involved in building this PCM(not include module).
|
|
||||||
std::vector<std::string> deps;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// If input file is module interface unit, return its module name.
|
|
||||||
/// Otherwise, return an empty string.
|
|
||||||
std::string scanModuleName(CompilationParams& params);
|
|
||||||
|
|
||||||
/// Run the preprocessor to scan the given module unit to
|
|
||||||
/// collect its module name and dependencies.
|
|
||||||
std::expected<ModuleInfo, std::string> scanModule(CompilationParams& params);
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <expected>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
class CompilationUnit;
|
|
||||||
|
|
||||||
struct CompilationParams;
|
|
||||||
|
|
||||||
struct PCHInfo {
|
|
||||||
/// The path of the output PCH file.
|
|
||||||
std::string path;
|
|
||||||
|
|
||||||
/// The building time of this PCH.
|
|
||||||
std::int64_t mtime;
|
|
||||||
|
|
||||||
/// The content used to build this PCH.
|
|
||||||
std::string preamble;
|
|
||||||
|
|
||||||
/// All files involved in building this PCH.
|
|
||||||
std::vector<std::string> deps;
|
|
||||||
|
|
||||||
/// The command arguments used to build this PCH.
|
|
||||||
std::vector<const char*> arguments;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Compute the preamble bound of given content. We just
|
|
||||||
/// run lex until we find first not directive.
|
|
||||||
std::uint32_t compute_preamble_bound(llvm::StringRef content);
|
|
||||||
|
|
||||||
/// Same as above, but return a group of bounds for chained PCH
|
|
||||||
/// building.
|
|
||||||
std::vector<uint32_t> compute_preamble_bounds(llvm::StringRef content);
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
struct Inclusion {
|
|
||||||
/// Whether this file is braced angles.
|
|
||||||
bool angled;
|
|
||||||
|
|
||||||
/// The line of this inclusion(zero based).
|
|
||||||
/// std::uint32_t line;
|
|
||||||
|
|
||||||
/// The included file.
|
|
||||||
llvm::StringRef file;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ScanResult {
|
|
||||||
/// The module file of this file(may be empty).
|
|
||||||
std::vector<Token> module_name;
|
|
||||||
|
|
||||||
/// The includes of file.
|
|
||||||
std::vector<Inclusion> includes;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Scan the file and return necessary info.
|
|
||||||
ScanResult scan(llvm::StringRef content);
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
struct CompilationParams;
|
|
||||||
|
|
||||||
namespace config {
|
|
||||||
|
|
||||||
struct CodeCompletionOption {
|
|
||||||
/// Insert placeholder for keywords? function call parameters? template arguments?
|
|
||||||
bool enable_keyword_snippet = false;
|
|
||||||
|
|
||||||
/// Also apply for lambda ...
|
|
||||||
bool enable_function_arguments_snippet = false;
|
|
||||||
bool enable_template_arguments_snippet = false;
|
|
||||||
|
|
||||||
bool insert_paren_in_function_call = false;
|
|
||||||
/// TODO: Add more detailed option, see
|
|
||||||
/// https://github.com/llvm/llvm-project/issues/63565
|
|
||||||
|
|
||||||
bool bundle_overloads = true;
|
|
||||||
|
|
||||||
/// The limits of code completion, 0 is non limit.
|
|
||||||
std::uint32_t limit = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
}; // namespace config
|
|
||||||
|
|
||||||
namespace feature {
|
|
||||||
|
|
||||||
enum class CompletionItemKind {
|
|
||||||
None = 0,
|
|
||||||
Text,
|
|
||||||
Method,
|
|
||||||
Function,
|
|
||||||
Constructor,
|
|
||||||
Field,
|
|
||||||
Variable,
|
|
||||||
Class,
|
|
||||||
Interface,
|
|
||||||
Module,
|
|
||||||
Property,
|
|
||||||
Unit,
|
|
||||||
Value,
|
|
||||||
Enum,
|
|
||||||
Keyword,
|
|
||||||
Snippet,
|
|
||||||
Color,
|
|
||||||
File,
|
|
||||||
Reference,
|
|
||||||
Folder,
|
|
||||||
EnumMember,
|
|
||||||
Constant,
|
|
||||||
Struct,
|
|
||||||
Event,
|
|
||||||
Operator,
|
|
||||||
TypeParameter
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Represents a single code completion item to be presented to the user.
|
|
||||||
struct CompletionItem {
|
|
||||||
/// The primary label displayed in the completion list.
|
|
||||||
std::string label;
|
|
||||||
|
|
||||||
/// Additional details, like a function signature, shown next to the label.
|
|
||||||
std::string detail;
|
|
||||||
|
|
||||||
/// A short description of the item, typically its type or namespace.
|
|
||||||
std::string description;
|
|
||||||
|
|
||||||
/// Full documentation for the item, shown on selection or hover.
|
|
||||||
std::string document;
|
|
||||||
|
|
||||||
/// The kind of item (function, class, etc.), used for an icon.
|
|
||||||
CompletionItemKind kind;
|
|
||||||
|
|
||||||
/// A score for ranking this item against others. Higher is better.
|
|
||||||
float score;
|
|
||||||
|
|
||||||
/// Whether this item is deprecated (often rendered with a strikethrough).
|
|
||||||
bool deprecated;
|
|
||||||
|
|
||||||
/// The text edit to be applied when this item is accepted.
|
|
||||||
struct Edit {
|
|
||||||
/// The new text to insert, which may be a snippet.
|
|
||||||
std::string text;
|
|
||||||
|
|
||||||
/// The source range to be replaced by the new text.
|
|
||||||
LocalSourceRange range;
|
|
||||||
} edit;
|
|
||||||
};
|
|
||||||
|
|
||||||
using CodeCompletionResult = std::vector<CompletionItem>;
|
|
||||||
|
|
||||||
std::vector<CompletionItem> code_complete(CompilationParams& params,
|
|
||||||
const config::CodeCompletionOption& option);
|
|
||||||
|
|
||||||
} // namespace feature
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Compiler/Diagnostic.h"
|
|
||||||
#include "Server/Convert.h"
|
|
||||||
#include "Support/JSON.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
class CompilationUnitRef;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
/// FIXME: This is not correct way, we don't want to couple
|
|
||||||
/// `Feature with Protocol`? Return an array of LSP diagnostic.
|
|
||||||
json::Value diagnostics(PositionEncodingKind kind, PathMapping mapping, CompilationUnitRef unit);
|
|
||||||
|
|
||||||
} // namespace clice::feature
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace clice::proto {}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
#include "Index/Shared.h"
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
struct DocumentLink {
|
|
||||||
/// The range of the whole link.
|
|
||||||
LocalSourceRange range;
|
|
||||||
|
|
||||||
/// The target string path.
|
|
||||||
std::string file;
|
|
||||||
};
|
|
||||||
|
|
||||||
using DocumentLinks = std::vector<DocumentLink>;
|
|
||||||
|
|
||||||
/// Generate document link for main file.
|
|
||||||
DocumentLinks document_links(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
/// Generate document link for all source file.
|
|
||||||
index::Shared<DocumentLinks> index_document_link(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
} // namespace clice::feature
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
#include "AST/SymbolKind.h"
|
|
||||||
#include "Index/Shared.h"
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
struct DocumentSymbol {
|
|
||||||
/// The range of symbol name in source code.
|
|
||||||
LocalSourceRange selectionRange;
|
|
||||||
|
|
||||||
/// The range of whole symbol.
|
|
||||||
LocalSourceRange range;
|
|
||||||
|
|
||||||
/// The symbol kind of this document symbol.
|
|
||||||
SymbolKind kind;
|
|
||||||
|
|
||||||
/// The symbol name.
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
/// Extra information about this symbol.
|
|
||||||
std::string detail;
|
|
||||||
|
|
||||||
/// The symbols that this symbol contains
|
|
||||||
std::vector<DocumentSymbol> children;
|
|
||||||
};
|
|
||||||
|
|
||||||
using DocumentSymbols = std::vector<DocumentSymbol>;
|
|
||||||
|
|
||||||
/// Generate document symbols for only interested file.
|
|
||||||
DocumentSymbols document_symbols(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
/// Generate document symbols for all file in unit.
|
|
||||||
index::Shared<DocumentSymbols> index_document_symbol(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
} // namespace clice::feature
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
#include "Index/Shared.h"
|
|
||||||
#include "Support/Enum.h"
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
struct FoldingRangeKind : refl::Enum<FoldingRangeKind> {
|
|
||||||
enum Kind : uint8_t {
|
|
||||||
Invalid = 0,
|
|
||||||
Comment,
|
|
||||||
Imports,
|
|
||||||
Region,
|
|
||||||
Namespace,
|
|
||||||
Class,
|
|
||||||
Enum,
|
|
||||||
Struct,
|
|
||||||
Union,
|
|
||||||
LambdaCapture,
|
|
||||||
FunctionParams,
|
|
||||||
FunctionBody,
|
|
||||||
FunctionCall,
|
|
||||||
CompoundStmt,
|
|
||||||
AccessSpecifier,
|
|
||||||
ConditionDirective,
|
|
||||||
Initializer,
|
|
||||||
};
|
|
||||||
|
|
||||||
using Enum::Enum;
|
|
||||||
|
|
||||||
constexpr static auto InvalidEnum = Invalid;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// We don't record the coalesced text for a range, because it's rarely useful.
|
|
||||||
struct FoldingRange {
|
|
||||||
/// The range to fold.
|
|
||||||
LocalSourceRange range;
|
|
||||||
|
|
||||||
/// Describes the kind of the folding range.
|
|
||||||
FoldingRangeKind kind;
|
|
||||||
|
|
||||||
/// The text to display when the folding range is collapsed.
|
|
||||||
std::string text;
|
|
||||||
};
|
|
||||||
|
|
||||||
using FoldingRanges = std::vector<FoldingRange>;
|
|
||||||
|
|
||||||
/// Generate folding range for interested file only.
|
|
||||||
FoldingRanges folding_ranges(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
/// Generate folding range for all files.
|
|
||||||
index::Shared<FoldingRanges> index_folding_range(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
} // namespace clice::feature
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
#include "Protocol/Feature/Formatting.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
std::vector<proto::TextEdit> document_format(llvm::StringRef file,
|
|
||||||
llvm::StringRef content,
|
|
||||||
std::optional<LocalSourceRange>);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
#include "AST/SymbolKind.h"
|
|
||||||
#include "Index/Shared.h"
|
|
||||||
|
|
||||||
namespace clice::config {
|
|
||||||
|
|
||||||
struct HoverOptions {
|
|
||||||
/// Strip doxygen info and merge with lsp info
|
|
||||||
bool enable_doxygen_parsing = true;
|
|
||||||
/// If set `false`, the comment will be wrapped
|
|
||||||
/// in code block and keep ascii typesetting
|
|
||||||
bool parse_comment_as_markdown = true;
|
|
||||||
/// Show sugar type
|
|
||||||
bool show_aka = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::config
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
struct HoverItem {
|
|
||||||
enum class HoverKind : uint8_t {
|
|
||||||
/// The typename of a variable or a type alias.
|
|
||||||
Type,
|
|
||||||
/// Size of type or variable.
|
|
||||||
Size,
|
|
||||||
/// Align of type or variable.
|
|
||||||
Align,
|
|
||||||
/// Offset of field in a class/struct.
|
|
||||||
Offset,
|
|
||||||
/// Bit width of a bit field.
|
|
||||||
BitWidth,
|
|
||||||
/// The index of a field in a class/struct.
|
|
||||||
FieldIndex,
|
|
||||||
/// The value of an enum item.
|
|
||||||
EnumValue,
|
|
||||||
};
|
|
||||||
|
|
||||||
using enum HoverKind;
|
|
||||||
|
|
||||||
HoverKind kind;
|
|
||||||
|
|
||||||
std::string value;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Hover information for a symbol.
|
|
||||||
struct Hover {
|
|
||||||
/// Title
|
|
||||||
SymbolKind kind;
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
/// Extra information.
|
|
||||||
std::vector<HoverItem> items;
|
|
||||||
|
|
||||||
/// Raw document in the source code.
|
|
||||||
std::string document;
|
|
||||||
|
|
||||||
/// The full qualified name of the declaration.
|
|
||||||
std::string qualifier;
|
|
||||||
|
|
||||||
/// The source code of the declaration.
|
|
||||||
std::string source;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Generate the hover information for the given declaration(for test).
|
|
||||||
Hover hover(CompilationUnitRef unit, const clang::NamedDecl* decl);
|
|
||||||
|
|
||||||
/// Generate the hover information for the symbol at the given offset.
|
|
||||||
Hover hover(CompilationUnitRef unit, std::uint32_t offset);
|
|
||||||
|
|
||||||
} // namespace clice::feature
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
#include "AST/SymbolID.h"
|
|
||||||
#include "Index/Shared.h"
|
|
||||||
#include "Support/JSON.h"
|
|
||||||
|
|
||||||
namespace clice::config {
|
|
||||||
|
|
||||||
struct InlayHintsOptions {
|
|
||||||
/// If false, inlay hints are completely disabled.
|
|
||||||
bool enabled = true;
|
|
||||||
|
|
||||||
// Whether specific categories of hints are enabled.
|
|
||||||
bool parameters = true;
|
|
||||||
bool deduced_types = true;
|
|
||||||
bool designators = true;
|
|
||||||
bool block_end = false;
|
|
||||||
bool default_arguments = false;
|
|
||||||
|
|
||||||
// Limit the length of type names in inlay hints. (0 means no limit)
|
|
||||||
uint32_t type_name_limit = 32;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::config
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
enum class InlayHintKind {
|
|
||||||
Parameter,
|
|
||||||
InvalidEnum,
|
|
||||||
DefaultArgument,
|
|
||||||
Type,
|
|
||||||
Designator,
|
|
||||||
BlockEnd,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InlayHint {
|
|
||||||
/// The position offset of the inlay hint in the source code.
|
|
||||||
std::uint32_t offset;
|
|
||||||
|
|
||||||
/// The kind/category of the inlay hint.
|
|
||||||
InlayHintKind kind;
|
|
||||||
|
|
||||||
/// The label parts of the inlay hint.
|
|
||||||
/// Each SymbolID consists of two parts: the symbol name and its USR hash.
|
|
||||||
/// For symbols without a USR (e.g., built-in types or function parameters),
|
|
||||||
/// the symbol hash will be empty.
|
|
||||||
/// Otherwise, the symbol hash is non-empty and can be used for "go-to-definition".
|
|
||||||
std::vector<index::SymbolID> parts;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto inlay_hints(CompilationUnitRef unit,
|
|
||||||
LocalSourceRange target,
|
|
||||||
const config::InlayHintsOptions& options) -> std::vector<InlayHint>;
|
|
||||||
|
|
||||||
} // namespace clice::feature
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AST/SymbolKind.h"
|
|
||||||
#include "Server/Protocol.h"
|
|
||||||
#include "Support/Struct.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct WorkDoneProgressOptions {
|
|
||||||
/// Report on work done progress.
|
|
||||||
bool workDoneProgress = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PartialResultParams {};
|
|
||||||
|
|
||||||
/// The options of the all lookup.
|
|
||||||
using LookupOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
/// The parameters of the simple lookup(definition, declaration,
|
|
||||||
/// type definition, implementation and reference).
|
|
||||||
inherited_struct(ReferenceParams, TextDocumentPositionParams, PartialResultParams){};
|
|
||||||
|
|
||||||
/// The result of the simple lookup.
|
|
||||||
using ReferenceResult = std::vector<Location>;
|
|
||||||
|
|
||||||
/// The parameters of the all hierarchy resolve(call hierarchy and type hierarchy)
|
|
||||||
using HierarchyPrepareParams = TextDocumentPositionParams;
|
|
||||||
|
|
||||||
struct HierarchyItem {
|
|
||||||
/// The name of the item.
|
|
||||||
string name;
|
|
||||||
|
|
||||||
/// The kind of the item.
|
|
||||||
SymbolKind kind;
|
|
||||||
|
|
||||||
/// The resource identifier of this item.
|
|
||||||
DocumentUri uri;
|
|
||||||
|
|
||||||
/// The range enclosing this symbol not including leading/trailing whitespace
|
|
||||||
/// but everything else, e.g. comments and code.
|
|
||||||
Range range;
|
|
||||||
|
|
||||||
/// The range that should be selected and revealed when this symbol is being
|
|
||||||
/// picked, e.g. the name of a function. Must be contained by the
|
|
||||||
/// [`range`](#CallHierarchyItem.range).
|
|
||||||
Range selectionRange;
|
|
||||||
|
|
||||||
/// A customized data of the item. We use it to store
|
|
||||||
/// the USR hash of the item.
|
|
||||||
uint64_t data = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
using HierarchyPrepareResult = std::vector<HierarchyItem>;
|
|
||||||
|
|
||||||
/// The parameters of the both call hierarchy and type hierarchy.
|
|
||||||
inherited_struct(HierarchyParams, TextDocumentPositionParams, PartialResultParams) {
|
|
||||||
HierarchyItem item;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CallHierarchyIncomingCall {
|
|
||||||
/// The item that makes the call.
|
|
||||||
HierarchyItem from;
|
|
||||||
|
|
||||||
/// The ranges at which the calls appear. This is relative to the caller
|
|
||||||
/// denoted by [`this.from`](#CallHierarchyIncomingCall.from).
|
|
||||||
std::vector<Range> fromRanges;
|
|
||||||
};
|
|
||||||
|
|
||||||
using CallHierarchyIncomingCallsResult = std::vector<CallHierarchyIncomingCall>;
|
|
||||||
|
|
||||||
struct CallHierarchyOutgoingCall {
|
|
||||||
/// The item that is called.
|
|
||||||
HierarchyItem to;
|
|
||||||
|
|
||||||
/// The range at which this item is called. This is the range relative to
|
|
||||||
/// the caller, e.g the item passed to `callHierarchy/outgoingCalls` request.
|
|
||||||
std::vector<Range> fromRanges;
|
|
||||||
};
|
|
||||||
|
|
||||||
using CallHierarchyOutgoingCallsResult = std::vector<CallHierarchyOutgoingCall>;
|
|
||||||
|
|
||||||
/// The result of the both super and sub type hierarchy.
|
|
||||||
using TypeHierarchyResult = std::vector<HierarchyItem>;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AST/SourceCode.h"
|
|
||||||
#include "AST/SymbolKind.h"
|
|
||||||
#include "Index/Shared.h"
|
|
||||||
|
|
||||||
namespace clice::config {
|
|
||||||
|
|
||||||
struct SemanticTokensOption {};
|
|
||||||
|
|
||||||
}; // namespace clice::config
|
|
||||||
|
|
||||||
namespace clice::feature {
|
|
||||||
|
|
||||||
struct SemanticToken {
|
|
||||||
LocalSourceRange range;
|
|
||||||
SymbolKind kind;
|
|
||||||
SymbolModifiers modifiers;
|
|
||||||
};
|
|
||||||
|
|
||||||
using SemanticTokens = std::vector<SemanticToken>;
|
|
||||||
|
|
||||||
/// Generate semantic tokens for the interested file only.
|
|
||||||
SemanticTokens semantic_tokens(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
/// Generate semantic tokens for all files.
|
|
||||||
index::Shared<SemanticTokens> index_semantic_token(CompilationUnitRef unit);
|
|
||||||
|
|
||||||
} // namespace clice::feature
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Protocol/Feature/SignatureHelp.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
struct CompilationParams;
|
|
||||||
|
|
||||||
namespace config {
|
|
||||||
|
|
||||||
struct SignatureHelpOption {};
|
|
||||||
|
|
||||||
} // namespace config
|
|
||||||
|
|
||||||
namespace feature {
|
|
||||||
|
|
||||||
proto::SignatureHelp signature_help(CompilationParams& params,
|
|
||||||
const config::SignatureHelpOption& option);
|
|
||||||
|
|
||||||
} // namespace feature
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
namespace clice.index.binary;
|
|
||||||
|
|
||||||
struct Range {
|
|
||||||
begin: uint;
|
|
||||||
end: uint;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Occurrence {
|
|
||||||
range: Range;
|
|
||||||
target: ulong;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Relation {
|
|
||||||
kind: uint;
|
|
||||||
padding: uint;
|
|
||||||
range: Range;
|
|
||||||
target_symbol: ulong;
|
|
||||||
}
|
|
||||||
|
|
||||||
table CacheEntry {
|
|
||||||
sha256: string;
|
|
||||||
canonical_id: uint;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IncludeContext {
|
|
||||||
include_id: uint;
|
|
||||||
canonical_id: uint;
|
|
||||||
}
|
|
||||||
|
|
||||||
table HeaderContextEntry {
|
|
||||||
path_id: uint;
|
|
||||||
version: uint;
|
|
||||||
includes: [IncludeContext];
|
|
||||||
}
|
|
||||||
|
|
||||||
struct IncludeLocation {
|
|
||||||
path_id: uint;
|
|
||||||
line: uint;
|
|
||||||
include_id: uint;
|
|
||||||
}
|
|
||||||
|
|
||||||
table CompilationContextEntry {
|
|
||||||
path_id: uint;
|
|
||||||
version: uint;
|
|
||||||
canonical_id: uint;
|
|
||||||
build_at: ulong;
|
|
||||||
include_locations: [IncludeLocation];
|
|
||||||
}
|
|
||||||
|
|
||||||
table OccurrenceEntry {
|
|
||||||
occurrence: Occurrence;
|
|
||||||
context: [ubyte];
|
|
||||||
}
|
|
||||||
|
|
||||||
table RelationEntry {
|
|
||||||
relation: Relation;
|
|
||||||
context: [ubyte];
|
|
||||||
}
|
|
||||||
|
|
||||||
table SymbolRelationsEntry {
|
|
||||||
symbol: ulong;
|
|
||||||
relations: [RelationEntry];
|
|
||||||
}
|
|
||||||
|
|
||||||
table Symbol {
|
|
||||||
kind: ubyte;
|
|
||||||
refs: [ubyte];
|
|
||||||
}
|
|
||||||
|
|
||||||
table SymbolEntry {
|
|
||||||
symbol_id: ulong;
|
|
||||||
symbol: Symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
table MergedIndex {
|
|
||||||
max_canonical_id: uint;
|
|
||||||
|
|
||||||
canonical_cache: [CacheEntry];
|
|
||||||
|
|
||||||
header_contexts: [HeaderContextEntry];
|
|
||||||
|
|
||||||
compilation_contexts: [CompilationContextEntry];
|
|
||||||
|
|
||||||
occurrences: [OccurrenceEntry];
|
|
||||||
|
|
||||||
relations: [SymbolRelationsEntry];
|
|
||||||
}
|
|
||||||
|
|
||||||
table PathEntry {
|
|
||||||
path: string;
|
|
||||||
id: uint;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PathMapEntry {
|
|
||||||
source: uint;
|
|
||||||
index: uint;
|
|
||||||
}
|
|
||||||
|
|
||||||
table ProjectIndex {
|
|
||||||
paths: [PathEntry];
|
|
||||||
indices: [PathMapEntry];
|
|
||||||
symbols: [SymbolEntry];
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "llvm/Support/JSON.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
using integer = std::int32_t;
|
|
||||||
/// range in [0, 2^31- 1]
|
|
||||||
using uinteger = std::uint32_t;
|
|
||||||
using decimal = double;
|
|
||||||
|
|
||||||
using string = std::string;
|
|
||||||
|
|
||||||
using any = llvm::json::Value;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using array = std::vector<T>;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using optional = std::optional<T>;
|
|
||||||
|
|
||||||
using PositionEncodingKind = string;
|
|
||||||
|
|
||||||
struct WorkDoneProgressOptions {
|
|
||||||
bool workDoneProgress;
|
|
||||||
};
|
|
||||||
|
|
||||||
using URI = string;
|
|
||||||
using DocumentUri = string;
|
|
||||||
|
|
||||||
enum class ErrorCodes : integer {
|
|
||||||
/// Defined by JSON-RPC.
|
|
||||||
ParseError = -32700,
|
|
||||||
InvalidRequest = -32600,
|
|
||||||
MethodNotFound = -32601,
|
|
||||||
InvalidParams = -32602,
|
|
||||||
InternalError = -32603,
|
|
||||||
/// JSON-RPC error code indicating a server error.
|
|
||||||
serverErrorStart = -32099,
|
|
||||||
serverErrorEnd = -32000,
|
|
||||||
ServerNotInitialized = -32002,
|
|
||||||
UnknownErrorCode = -32001,
|
|
||||||
|
|
||||||
/// Defined by the protocol.
|
|
||||||
RequestFailed = -32803,
|
|
||||||
ServerCancelled = -32802,
|
|
||||||
ContentModified = -32801,
|
|
||||||
RequestCancelled = -32800
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Position {
|
|
||||||
/// Line position in a document (zero-based).
|
|
||||||
uinteger line;
|
|
||||||
|
|
||||||
/// Character offset on a line in a document (zero-based).
|
|
||||||
/// The meaning of this offset is determined by the negotiated
|
|
||||||
/// `PositionEncodingKind`.
|
|
||||||
uinteger character;
|
|
||||||
|
|
||||||
constexpr friend bool operator==(const Position&, const Position&) = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Range {
|
|
||||||
/// The range's start position.
|
|
||||||
Position start;
|
|
||||||
|
|
||||||
/// The range's end position.
|
|
||||||
Position end;
|
|
||||||
|
|
||||||
constexpr friend bool operator==(const Range&, const Range&) = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Location {
|
|
||||||
DocumentUri uri;
|
|
||||||
|
|
||||||
Range range;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextEdit {
|
|
||||||
/// The range of the text document to be manipulated. To insert
|
|
||||||
/// text into a document create a range where start === end.
|
|
||||||
Range range;
|
|
||||||
|
|
||||||
// The string to be inserted. For delete operations use an
|
|
||||||
// empty string.
|
|
||||||
string newText;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextDocumentItem {
|
|
||||||
/// The text document's URI.
|
|
||||||
DocumentUri uri;
|
|
||||||
|
|
||||||
/// The text document's language identifier.
|
|
||||||
string languageId;
|
|
||||||
|
|
||||||
/// The version number of this document (it will strictly increase after each
|
|
||||||
/// change, including undo/redo).
|
|
||||||
uinteger version;
|
|
||||||
|
|
||||||
/// The content of the opened text document.
|
|
||||||
string text;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextDocumentIdentifier {
|
|
||||||
/// The text document's URI.
|
|
||||||
DocumentUri uri;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VersionedTextDocumentIdentifier {
|
|
||||||
/// The text document's URI.
|
|
||||||
DocumentUri uri;
|
|
||||||
|
|
||||||
/// The version of document.
|
|
||||||
integer version;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextDocumentPositionParams {
|
|
||||||
/// The text document.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
|
|
||||||
/// The position inside the text document.
|
|
||||||
Position position;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MarkupContent {
|
|
||||||
/// The type of the Markup.
|
|
||||||
string kind;
|
|
||||||
|
|
||||||
/// The content itself.
|
|
||||||
string value;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct CallHierarchyClientCapabilities {};
|
|
||||||
|
|
||||||
using CallHierarchyOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct CodeActionClientCapabilities {};
|
|
||||||
|
|
||||||
struct CodeActionOptions {};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct CompletionClientCapabilities {};
|
|
||||||
|
|
||||||
struct CompletionOptions {
|
|
||||||
/// The additional characters, beyond the defaults provided by the client (typically
|
|
||||||
/// [a-zA-Z]), that should automatically trigger a completion request. For example
|
|
||||||
///`.` in JavaScript represents the beginning of an object property or method and is
|
|
||||||
/// thus a good candidate for triggering a completion request.
|
|
||||||
//
|
|
||||||
/// Most tools trigger a completion request automatically without explicitly
|
|
||||||
/// requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they
|
|
||||||
/// do so when the user starts to type an identifier. For example if the user
|
|
||||||
/// types `c` in a JavaScript file code complete will automatically pop up
|
|
||||||
/// present `console` besides others as a completion item. Characters that
|
|
||||||
/// make up identifiers don't need to be listed here.
|
|
||||||
array<string> triggerCharacters;
|
|
||||||
|
|
||||||
/// The server provides support to resolve additional information for a completion item.
|
|
||||||
bool resolveProvider;
|
|
||||||
|
|
||||||
struct CompletionItemCapabilities {
|
|
||||||
/// The server has support for completion item label
|
|
||||||
/// details (see also `CompletionItemLabelDetails`) when receiving
|
|
||||||
/// a completion item in a resolve call.
|
|
||||||
bool labelDetailsSupport;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The server supports the following `CompletionItem` specific capabilities.
|
|
||||||
CompletionItemCapabilities completionItem;
|
|
||||||
};
|
|
||||||
|
|
||||||
using CompletionParams = TextDocumentPositionParams;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct CodeLensClientCapabilities {};
|
|
||||||
|
|
||||||
struct CodeLensOptions {};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct DeclarationClientCapabilities {};
|
|
||||||
|
|
||||||
using DeclarationOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
using DeclarationParams = TextDocumentPositionParams;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct DefinitionClientCapabilities {};
|
|
||||||
|
|
||||||
using DefinitionOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
using DefinitionParams = TextDocumentPositionParams;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct PublishDiagnosticsClientCapabilities {};
|
|
||||||
|
|
||||||
struct DiagnosticClientCapabilities {};
|
|
||||||
|
|
||||||
enum class DiagnosticSeverity : std::uint8_t {
|
|
||||||
/// Reports an error.
|
|
||||||
Error = 1,
|
|
||||||
|
|
||||||
/// Reports a warning.
|
|
||||||
Warning = 2,
|
|
||||||
|
|
||||||
/// Reports an information.
|
|
||||||
Information = 3,
|
|
||||||
|
|
||||||
/// Reports a hint.
|
|
||||||
Hint = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class DiagnosticTag : std::uint8_t {
|
|
||||||
/// Unused or unnecessary code. Clients are allowed to render diagnostics
|
|
||||||
/// with this tag faded out instead of having an error squiggle.
|
|
||||||
Unnecessary = 1,
|
|
||||||
|
|
||||||
/// Deprecated or obsolete code. Clients are allowed to rendered
|
|
||||||
/// diagnostics with this tag strike through.
|
|
||||||
Deprecated = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CodeDescription {
|
|
||||||
/// An URI to open with more information about the diagnostic error.
|
|
||||||
URI uri;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Represents a related message and source code location for a diagnostic.
|
|
||||||
/// This should be used to point to code locations that cause or are related to
|
|
||||||
/// a diagnostics, e.g when duplicating a symbol in a scope.
|
|
||||||
struct DiagnosticRelatedInformation {
|
|
||||||
/// The location of this related diagnostic information.
|
|
||||||
Location location;
|
|
||||||
|
|
||||||
/// The message of this related diagnostic information.
|
|
||||||
string message;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Diagnostic {
|
|
||||||
/// The range at which the message applies.
|
|
||||||
Range range;
|
|
||||||
|
|
||||||
/// The diagnostic's severity. To avoid interpretation mismatches when a
|
|
||||||
/// server is used with different clients it is highly recommended that
|
|
||||||
/// servers always provide a severity value. If omitted, it’s recommended
|
|
||||||
/// for the client to interpret it as an Error severity.
|
|
||||||
DiagnosticSeverity severity;
|
|
||||||
|
|
||||||
/// The diagnostic's code, which might appear in the user interface.
|
|
||||||
string code;
|
|
||||||
|
|
||||||
/// An optional property to describe the error code.
|
|
||||||
optional<CodeDescription> codeDescription;
|
|
||||||
|
|
||||||
/// A human-readable string describing the source of this
|
|
||||||
/// diagnostic, e.g. 'typescript' or 'super lint'.
|
|
||||||
string source;
|
|
||||||
|
|
||||||
/// The diagnostic's message.
|
|
||||||
string message;
|
|
||||||
|
|
||||||
/// Additional metadata about the diagnostic.
|
|
||||||
array<DiagnosticTag> tags;
|
|
||||||
|
|
||||||
/// An array of related diagnostic information, e.g. when symbol-names within
|
|
||||||
/// a scope collide all definitions can be marked via this property.
|
|
||||||
array<DiagnosticRelatedInformation> relatedInformation;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct DocumentHighlightClientCapabilities {};
|
|
||||||
|
|
||||||
using DocumentHighlightOptions = bool;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct DocumentLinkClientCapabilities {
|
|
||||||
/// Whether the client supports the `tooltip` property on `DocumentLink`.
|
|
||||||
bool tooltipSupport = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DocumentLinkOptions {
|
|
||||||
/// Document links have a resolve provider as well.
|
|
||||||
bool resolveProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DocumentLinkParams {
|
|
||||||
/// The document to provide document links for.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A document link is a range in a text document that links to an internal or
|
|
||||||
/// external resource, like another text document or a web site.
|
|
||||||
struct DocumentLink {
|
|
||||||
/// The range this link applies to.
|
|
||||||
Range range;
|
|
||||||
|
|
||||||
/// The uri this link points to. If missing a resolve request is sent later.
|
|
||||||
URI target;
|
|
||||||
|
|
||||||
/// The tooltip text when you hover over this link.
|
|
||||||
///
|
|
||||||
/// If a tooltip is provided, is will be displayed in a string that includes
|
|
||||||
/// instructions on how to trigger the link, such as `{0} (ctrl + click)`.
|
|
||||||
/// The specific instructions vary depending on OS, user settings, and
|
|
||||||
/// localization.
|
|
||||||
/// FIXME: string tooltip;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
enum class SymbolKind : std::uint8_t {
|
|
||||||
File = 1,
|
|
||||||
Module = 2,
|
|
||||||
Namespace = 3,
|
|
||||||
Package = 4,
|
|
||||||
Class = 5,
|
|
||||||
Method = 6,
|
|
||||||
Property = 7,
|
|
||||||
Field = 8,
|
|
||||||
Constructor = 9,
|
|
||||||
Enum = 10,
|
|
||||||
Interface = 11,
|
|
||||||
Function = 12,
|
|
||||||
Variable = 13,
|
|
||||||
Constant = 14,
|
|
||||||
String = 15,
|
|
||||||
Number = 16,
|
|
||||||
Boolean = 17,
|
|
||||||
Array = 18,
|
|
||||||
Object = 19,
|
|
||||||
Key = 20,
|
|
||||||
Null = 21,
|
|
||||||
EnumMember = 22,
|
|
||||||
Struct = 23,
|
|
||||||
Event = 24,
|
|
||||||
Operator = 25,
|
|
||||||
TypeParameter = 26,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class SymbolTag {
|
|
||||||
/// Render a symbol as obsolete, usually using a strike-out.
|
|
||||||
Deprecated = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DocumentSymbolClientCapabilities {
|
|
||||||
/// Specific capabilities for the `SymbolKind` in the
|
|
||||||
/// `textDocument/documentSymbol` request.
|
|
||||||
struct {
|
|
||||||
/// The symbol kind values the client supports. When this
|
|
||||||
/// property exists the client also guarantees that it will
|
|
||||||
/// handle values outside its set gracefully and falls back
|
|
||||||
/// to a default value when unknown.
|
|
||||||
//
|
|
||||||
/// If this property is not present the client only supports
|
|
||||||
/// the symbol kinds from `File` to `Array` as defined in
|
|
||||||
/// the initial version of the protocol.
|
|
||||||
array<SymbolKind> valueSet;
|
|
||||||
} symbolKind;
|
|
||||||
|
|
||||||
/// The client supports hierarchical document symbols.
|
|
||||||
bool hierarchicalDocumentSymbolSupport;
|
|
||||||
|
|
||||||
/// The client supports tags on `SymbolInformation`. Tags are supported on
|
|
||||||
/// `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true.
|
|
||||||
/// Clients supporting tags have to handle unknown tags gracefully.
|
|
||||||
struct {
|
|
||||||
/// The tags supported by the client.
|
|
||||||
array<SymbolTag> valueSet;
|
|
||||||
} tagSupport;
|
|
||||||
|
|
||||||
/// The client supports an additional label presented in the UI when
|
|
||||||
/// registering a document symbol provider.
|
|
||||||
bool labelSupport;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DocumentSymbolOptions {};
|
|
||||||
|
|
||||||
struct DocumentSymbolParams {
|
|
||||||
/// The text document.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Represents programming constructs like variables, classes, interfaces etc.
|
|
||||||
/// that appear in a document. Document symbols can be hierarchical and they
|
|
||||||
/// have two ranges: one that encloses its definition and one that points to its
|
|
||||||
/// most interesting range, e.g. the range of an identifier.
|
|
||||||
struct DocumentSymbol {
|
|
||||||
/// The name of this symbol. Will be displayed in the user interface and
|
|
||||||
/// therefore must not be an empty string or a string only consisting of
|
|
||||||
/// white spaces.
|
|
||||||
string name;
|
|
||||||
|
|
||||||
/// More detail for this symbol, e.g the signature of a function.
|
|
||||||
string detail;
|
|
||||||
|
|
||||||
/// The kind of this symbol.
|
|
||||||
SymbolKind kind;
|
|
||||||
|
|
||||||
/// Tags for this document symbol.
|
|
||||||
array<SymbolTag> tags;
|
|
||||||
|
|
||||||
/// The range enclosing this symbol not including leading/trailing whitespace
|
|
||||||
/// but everything else like comments. This information is typically used to
|
|
||||||
/// determine if the clients cursor is inside the symbol to reveal it in the
|
|
||||||
/// UI.
|
|
||||||
Range range;
|
|
||||||
|
|
||||||
/// The range that should be selected and revealed when this symbol is being
|
|
||||||
/// picked, e.g. the name of a function. Must be contained by the `range`.
|
|
||||||
Range selectionRange;
|
|
||||||
|
|
||||||
/// Children of this symbol, e.g. properties of a class.
|
|
||||||
array<DocumentSymbol> children;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#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,75 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct FoldingRangeClientCapabilities {
|
|
||||||
/// The maximum number of folding ranges that the client prefers to receive
|
|
||||||
/// per document. The value serves as a hint, servers are free to follow the
|
|
||||||
/// limit.
|
|
||||||
optional<uinteger> rangeLimit;
|
|
||||||
|
|
||||||
/// If set, the client signals that it only supports folding complete lines.
|
|
||||||
/// If set, client will ignore specified `startCharacter` and `endCharacter`
|
|
||||||
/// properties in a FoldingRange.
|
|
||||||
bool lineFoldingOnly = false;
|
|
||||||
|
|
||||||
/// Specific options for the folding range kind.
|
|
||||||
struct {
|
|
||||||
/// The folding range kind values the client supports. When this
|
|
||||||
/// property exists the client also guarantees that it will
|
|
||||||
/// handle values outside its set gracefully and falls back
|
|
||||||
/// to a default value when unknown.
|
|
||||||
array<string> valueSet;
|
|
||||||
} foldingRangeKind;
|
|
||||||
|
|
||||||
/// Specific options for the folding range.
|
|
||||||
struct {
|
|
||||||
/// If set, the client signals that it supports setting collapsedText on
|
|
||||||
/// folding ranges to display custom labels instead of the default text.
|
|
||||||
bool collapsedText = false;
|
|
||||||
} foldingRange;
|
|
||||||
};
|
|
||||||
|
|
||||||
using FoldingRangeOptions = bool;
|
|
||||||
|
|
||||||
struct FoldingRangeParams {
|
|
||||||
/// The text document.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
using FoldingRangeKind = string;
|
|
||||||
|
|
||||||
struct FoldingRange {
|
|
||||||
/// The zero-based start line of the range to fold. The folded area starts
|
|
||||||
/// after the line's last character. To be valid, the end must be zero or
|
|
||||||
/// larger and smaller than the number of lines in the document.
|
|
||||||
uinteger startLine;
|
|
||||||
|
|
||||||
/// The zero-based character offset from where the folded range starts. If
|
|
||||||
/// not defined, defaults to the length of the start line.
|
|
||||||
uinteger startCharacter;
|
|
||||||
|
|
||||||
/// The zero-based end line of the range to fold. The folded area ends with
|
|
||||||
/// the line's last character. To be valid, the end must be zero or larger
|
|
||||||
/// and smaller than the number of lines in the document.
|
|
||||||
uinteger endLine;
|
|
||||||
|
|
||||||
/// The zero-based character offset before the folded range ends. If not
|
|
||||||
/// defined, defaults to the length of the end line.
|
|
||||||
uinteger endCharacter;
|
|
||||||
|
|
||||||
/// Describes the kind of the folding range such as `comment` or `region`.
|
|
||||||
/// The kind is used to categorize folding ranges and used by commands like
|
|
||||||
/// 'Fold all comments'. See [FoldingRangeKind](#FoldingRangeKind) for an
|
|
||||||
/// enumeration of standardized kinds.
|
|
||||||
FoldingRangeKind kind;
|
|
||||||
|
|
||||||
/// The text that the client should show when the specified range is
|
|
||||||
/// collapsed. If not defined or not supported by the client, a default
|
|
||||||
/// will be chosen by the client.
|
|
||||||
string collapsedText;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct DocumentFormattingClientCapabilities {};
|
|
||||||
|
|
||||||
using DocumentFormattingOptions = bool;
|
|
||||||
|
|
||||||
struct DocumentFormattingParams {
|
|
||||||
/// The document to format.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DocumentRangeFormattingParams {
|
|
||||||
/// The document to format.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
|
|
||||||
/// The range to format
|
|
||||||
Range range;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DocumentRangeFormattingClientCapabilities {};
|
|
||||||
|
|
||||||
using DocumentRangeFormattingOptions = bool;
|
|
||||||
|
|
||||||
struct DocumentOnTypeFormattingClientCapabilities {};
|
|
||||||
|
|
||||||
struct DocumentOnTypeFormattingOptions {};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct HoverClientCapabilities {};
|
|
||||||
|
|
||||||
using HoverOptions = bool;
|
|
||||||
|
|
||||||
using HoverParams = TextDocumentPositionParams;
|
|
||||||
|
|
||||||
struct Hover {
|
|
||||||
/// The hover's content
|
|
||||||
MarkupContent contents;
|
|
||||||
|
|
||||||
/// An optional range is a range inside a text document
|
|
||||||
/// that is used to visualize a hover, e.g. by changing the background color.
|
|
||||||
/// FIXME: Range range;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct ImplementationClientCapabilities {};
|
|
||||||
|
|
||||||
using ImplementationOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct InlayHintClientCapabilities {
|
|
||||||
/// Indicates which properties a client can resolve lazily on an inlay hint.
|
|
||||||
struct {
|
|
||||||
/// The properties that a client can resolve lazily.
|
|
||||||
array<string> properties;
|
|
||||||
} resolveSupport;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InlayHintOptions {
|
|
||||||
/// The server provides support to resolve additional
|
|
||||||
/// information for an inlay hint item.
|
|
||||||
bool resolveProvider;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InlayHintParams {
|
|
||||||
/// The text document.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
|
|
||||||
/// The visible document range for which inlay hints should be computed.
|
|
||||||
Range range;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class InlayHintKind {
|
|
||||||
/// An inlay hint that for a type annotation.
|
|
||||||
Type = 1,
|
|
||||||
|
|
||||||
/// An inlay hint that is for a parameter.
|
|
||||||
Parameter = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InlayHintLabelPart {
|
|
||||||
/// The value of this label part.
|
|
||||||
string value;
|
|
||||||
|
|
||||||
/// An optional source code location that represents this
|
|
||||||
/// label part.
|
|
||||||
///
|
|
||||||
/// The editor will use this location for the hover and for code navigation
|
|
||||||
/// features: This part will become a clickable link that resolves to the
|
|
||||||
/// definition of the symbol at the given location (not necessarily the
|
|
||||||
/// location itself), it shows the hover that shows at the given location,
|
|
||||||
/// and it shows a context menu with further code navigation commands.
|
|
||||||
///
|
|
||||||
/// Depending on the client capability `inlayHint.resolveSupport` clients
|
|
||||||
/// might resolve this property late using the resolve request.
|
|
||||||
/// FIXME: Location location;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InlayHint {
|
|
||||||
/// The position of this hint.
|
|
||||||
///
|
|
||||||
/// If multiple hints have the same position, they will be shown in the order
|
|
||||||
/// they appear in the response.
|
|
||||||
Position position;
|
|
||||||
|
|
||||||
/// The label of this hint. A human readable string or an array of
|
|
||||||
/// InlayHintLabelPart label parts.
|
|
||||||
///
|
|
||||||
/// *Note* that neither the string nor the label part can be empty.
|
|
||||||
/// TODO: Use label
|
|
||||||
array<InlayHintLabelPart> label;
|
|
||||||
|
|
||||||
/// The kind of this hint. Can be omitted in which case the client
|
|
||||||
/// should fall back to a reasonable default.
|
|
||||||
InlayHintKind kind;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct ReferenceClientCapabilities {};
|
|
||||||
|
|
||||||
using ReferenceOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
using ReferenceParams = TextDocumentPositionParams;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct RenameClientCapabilities {};
|
|
||||||
|
|
||||||
struct RenameOptions {};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct SemanticTokensClientCapabilities {};
|
|
||||||
|
|
||||||
struct SemanticTokensLegend {
|
|
||||||
/// The token types a server uses.
|
|
||||||
array<string> tokenTypes;
|
|
||||||
|
|
||||||
/// The token modifiers a server uses.
|
|
||||||
array<string> tokenModifiers;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SemanticTokensOptions {
|
|
||||||
/// The legend used by the server.
|
|
||||||
SemanticTokensLegend legend;
|
|
||||||
|
|
||||||
/// Server supports providing semantic tokens for a specific
|
|
||||||
/// range of a document.
|
|
||||||
bool range = false;
|
|
||||||
|
|
||||||
/// Server supports providing semantic tokens for a full document.
|
|
||||||
bool full = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SemanticTokensParams {
|
|
||||||
/// The text document.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SemanticTokens {};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct SignatureHelpClientCapabilities {
|
|
||||||
/**
|
|
||||||
* The client supports the `activeParameter` property on
|
|
||||||
* `SignatureInformation` literal.
|
|
||||||
*
|
|
||||||
* @since 3.16.0
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SignatureHelpOptions {
|
|
||||||
/// The characters that trigger signature help automatically.
|
|
||||||
array<string> triggerCharacters;
|
|
||||||
|
|
||||||
/// List of characters that re-trigger signature help.
|
|
||||||
///
|
|
||||||
/// These trigger characters are only active when signature help is already
|
|
||||||
/// showing. All trigger characters are also counted as re-trigger
|
|
||||||
/// characters.
|
|
||||||
array<string> retriggerCharacters;
|
|
||||||
};
|
|
||||||
|
|
||||||
using SignatureHelpParams = TextDocumentPositionParams;
|
|
||||||
|
|
||||||
struct ParameterInformation {
|
|
||||||
std::array<uinteger, 2> label;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SignatureInformation {
|
|
||||||
string label;
|
|
||||||
|
|
||||||
MarkupContent document;
|
|
||||||
|
|
||||||
array<ParameterInformation> parameters;
|
|
||||||
|
|
||||||
uinteger activeParameter;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SignatureHelp {
|
|
||||||
array<SignatureInformation> signatures;
|
|
||||||
|
|
||||||
uinteger activeSignature;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct TypeDefinitionClientCapabilities {};
|
|
||||||
|
|
||||||
using TypeDefinitionOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct TypeHierarchyClientCapabilities {};
|
|
||||||
|
|
||||||
using TypeHierarchyOptions = WorkDoneProgressOptions;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Basic.h"
|
|
||||||
#include "Notebook.h"
|
|
||||||
#include "TextDocument.h"
|
|
||||||
#include "Workspace.h"
|
|
||||||
|
|
||||||
/// clice currently ignores all `dynamicRegistration` field in LSP specification.
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct LSPInfo {
|
|
||||||
/// The name of server or client.
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
/// The version of server or client.
|
|
||||||
std::string version;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WindowCapacities {};
|
|
||||||
|
|
||||||
struct RegularExpressionsClientCapabilities {};
|
|
||||||
|
|
||||||
struct MarkdownClientCapabilities {};
|
|
||||||
|
|
||||||
struct GeneralCapacities {
|
|
||||||
/// FIXME: staleRequestSupport
|
|
||||||
|
|
||||||
/// Client capabilities specific to regular expressions.
|
|
||||||
optional<RegularExpressionsClientCapabilities> regularExpressions;
|
|
||||||
|
|
||||||
/// Client capabilities specific to the client's markdown parser.
|
|
||||||
optional<MarkdownClientCapabilities> markdown;
|
|
||||||
|
|
||||||
/// The position encodings supported by the client.
|
|
||||||
optional<array<PositionEncodingKind>> positionEncodings;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ClientCapabilities {
|
|
||||||
/// Workspace specific client capabilities.
|
|
||||||
WorkspaceClientCapabilities workspace;
|
|
||||||
|
|
||||||
/// Text document specific client capabilities.
|
|
||||||
TextDocumentClientCapabilities textDocument;
|
|
||||||
|
|
||||||
/// Capabilities specific to the notebook document support.
|
|
||||||
NotebookDocumentClientCapabilities notebookDocument;
|
|
||||||
|
|
||||||
/// Window specific client capabilities.
|
|
||||||
WindowCapacities window;
|
|
||||||
|
|
||||||
/// General client capabilities.
|
|
||||||
GeneralCapacities general;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InitializeParams {
|
|
||||||
/// Information about client.
|
|
||||||
LSPInfo clientInfo;
|
|
||||||
|
|
||||||
/// The capabilities provided by the client (editor or tool).
|
|
||||||
ClientCapabilities capabilities;
|
|
||||||
|
|
||||||
/// The workspace folders configured in the client when the server starts.
|
|
||||||
/// This property is only available if the client supports workspace folders.
|
|
||||||
/// It can be `null` if the client supports workspace folders but none are
|
|
||||||
/// configured.
|
|
||||||
optional<array<WorkspaceFolder>> workspaceFolders;
|
|
||||||
|
|
||||||
/// The rootUri of the workspace. Is null if no
|
|
||||||
/// folder is open. If both `rootPath` and `rootUri` are set
|
|
||||||
/// `rootUri` wins.
|
|
||||||
///
|
|
||||||
/// Deprecated in favour of `workspaceFolders`
|
|
||||||
optional<URI> rootUri;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ServerCapabilities {
|
|
||||||
/// The position encoding the server picked from the encodings offered
|
|
||||||
/// by the client via the client capability `general.positionEncodings`.
|
|
||||||
PositionEncodingKind positionEncoding;
|
|
||||||
|
|
||||||
/// Defines how text documents are synced.
|
|
||||||
TextDocumentSyncOptions textDocumentSync;
|
|
||||||
|
|
||||||
/// Defines how notebook documents are synced.
|
|
||||||
/// FIXME: NotebookDocumentSyncOptions notebookDocumentSync;
|
|
||||||
|
|
||||||
/// The server provides completion support.
|
|
||||||
CompletionOptions completionProvider;
|
|
||||||
|
|
||||||
/// The server provides hover support.
|
|
||||||
HoverOptions hoverProvider;
|
|
||||||
|
|
||||||
/// The server provides signature help support.
|
|
||||||
SignatureHelpOptions signatureHelpProvider;
|
|
||||||
|
|
||||||
/// The server provides go to declaration support.
|
|
||||||
DeclarationOptions declarationProvider;
|
|
||||||
|
|
||||||
/// The server provides goto definition support.
|
|
||||||
DefinitionOptions definitionProvider;
|
|
||||||
|
|
||||||
/// The server provides goto type definition support.
|
|
||||||
/// FIXME: TypeDefinitionOptions typeDefinitionProvider;
|
|
||||||
|
|
||||||
/// The server provides goto implementation support.
|
|
||||||
/// FIXME: ImplementationOptions implementationProvider;
|
|
||||||
|
|
||||||
/// The server provides find references support.
|
|
||||||
ReferenceOptions referencesProvider;
|
|
||||||
|
|
||||||
/// The server provides document highlight support.
|
|
||||||
/// FIXME: DocumentHighlightOptions documentHighlightProvider;
|
|
||||||
|
|
||||||
/// The server provides document symbol support.
|
|
||||||
DocumentSymbolOptions documentSymbolProvider;
|
|
||||||
|
|
||||||
/// The server provides code actions. The `CodeActionOptions` return type is
|
|
||||||
/// only valid if the client signals code action literal support via the
|
|
||||||
/// property `textDocument.codeAction.codeActionLiteralSupport`.
|
|
||||||
/// FIXME: CodeActionOptions codeActionProvider;
|
|
||||||
|
|
||||||
/// The server provides code lens.
|
|
||||||
/// FIXME: CodeLensOptions codeLensProvider;
|
|
||||||
|
|
||||||
/// The server provides document link support.
|
|
||||||
DocumentLinkOptions documentLinkProvider;
|
|
||||||
|
|
||||||
/// The server provides color provider support.
|
|
||||||
/// FIXME: DocumentColorOptions colorProvider;
|
|
||||||
|
|
||||||
/// The server provides document formatting.
|
|
||||||
DocumentFormattingOptions documentFormattingProvider;
|
|
||||||
|
|
||||||
/// The server provides document range formatting.
|
|
||||||
DocumentRangeFormattingOptions documentRangeFormattingProvider;
|
|
||||||
|
|
||||||
/// The server provides document formatting on typing.
|
|
||||||
/// FIXME: DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
|
|
||||||
|
|
||||||
/// The server provides rename support. RenameOptions may only be specified if the client
|
|
||||||
/// states that it supports `prepareSupport` in its initial `initialize` request.
|
|
||||||
/// FIXME: RenameOptions renameProvider;
|
|
||||||
|
|
||||||
/// The server provides folding provider support.
|
|
||||||
FoldingRangeOptions foldingRangeProvider;
|
|
||||||
|
|
||||||
/// The server provides execute command support.
|
|
||||||
/// FIXME: ExecuteCommandOptions executeCommandProvider;
|
|
||||||
|
|
||||||
/// The server provides selection range support.
|
|
||||||
/// FIXME: SelectionRangeOptions selectionRangeProvider;
|
|
||||||
|
|
||||||
/// The server provides linked editing range support.
|
|
||||||
/// FIXME: LinkedEditingRangeOptions linkedEditingRangeProvider;
|
|
||||||
|
|
||||||
/// The server provides call hierarchy support.
|
|
||||||
/// FIXME: CallHierarchyOptions callHierarchyProvider;
|
|
||||||
|
|
||||||
/// The server provides semantic tokens support.
|
|
||||||
SemanticTokensOptions semanticTokensProvider;
|
|
||||||
|
|
||||||
/// Whether server provides moniker support.
|
|
||||||
/// FIXME: MonikerOptions monikerProvider;
|
|
||||||
|
|
||||||
/// The server provides type hierarchy support.
|
|
||||||
/// FIXME: TypeHierarchyOptions typeHierarchyProvider;
|
|
||||||
|
|
||||||
/// The server provides inline values.
|
|
||||||
/// FIXME: InlineValueOptions inlineValueProvider;
|
|
||||||
|
|
||||||
/// The server provides inlay hints.
|
|
||||||
InlayHintOptions inlayHintProvider;
|
|
||||||
|
|
||||||
/// The server has support for pull model diagnostics.
|
|
||||||
/// FIXME: DiagnosticOptions diagnosticProvider;
|
|
||||||
|
|
||||||
/// The server provides workspace symbol support.
|
|
||||||
WorkspaceSymbolOptions workspaceSymbolProvider;
|
|
||||||
|
|
||||||
/// Workspace specific server capabilities.
|
|
||||||
WorkspaceServerCapabilities workspace;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InitializeResult {
|
|
||||||
/// Information about the server.
|
|
||||||
LSPInfo serverInfo;
|
|
||||||
|
|
||||||
/// The capabilities the language server provides.
|
|
||||||
ServerCapabilities capabilities;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Empty {};
|
|
||||||
|
|
||||||
using InitializedParams = Empty;
|
|
||||||
|
|
||||||
using ShutdownParams = Empty;
|
|
||||||
|
|
||||||
using ShutdownResult = Empty;
|
|
||||||
|
|
||||||
using ExitParams = Empty;
|
|
||||||
|
|
||||||
using ExitResult = Empty;
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct NotebookDocumentClientCapabilities {};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Lifecycle.h"
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Basic.h"
|
|
||||||
#include "Feature/CallHierarchy.h"
|
|
||||||
#include "Feature/CodeAction.h"
|
|
||||||
#include "Feature/CodeCompletion.h"
|
|
||||||
#include "Feature/CodeLens.h"
|
|
||||||
#include "Feature/Declaration.h"
|
|
||||||
#include "Feature/Definition.h"
|
|
||||||
#include "Feature/Diagnostic.h"
|
|
||||||
#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"
|
|
||||||
#include "Feature/Implementation.h"
|
|
||||||
#include "Feature/InlayHint.h"
|
|
||||||
#include "Feature/Reference.h"
|
|
||||||
#include "Feature/Rename.h"
|
|
||||||
#include "Feature/SemanticTokens.h"
|
|
||||||
#include "Feature/SignatureHelp.h"
|
|
||||||
#include "Feature/TypeDefinition.h"
|
|
||||||
#include "Feature/TypeHierarchy.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct TextDocumentSyncClientCapabilities {};
|
|
||||||
|
|
||||||
struct TextDocumentClientCapabilities {
|
|
||||||
optional<TextDocumentSyncClientCapabilities> synchronization;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/completion` request.
|
|
||||||
optional<CompletionClientCapabilities> completion;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/hover` request.
|
|
||||||
optional<HoverClientCapabilities> hover;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/signatureHelp` request.
|
|
||||||
optional<SignatureHelpClientCapabilities> signatureHelp;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/declaration` request.
|
|
||||||
optional<DeclarationClientCapabilities> declaration;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/definition` request.
|
|
||||||
optional<DefinitionClientCapabilities> definition;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/typeDefinition` request.
|
|
||||||
optional<TypeDefinitionClientCapabilities> typeDefinition;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/implementation` request.
|
|
||||||
optional<ImplementationClientCapabilities> implementation;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/references` request.
|
|
||||||
optional<ReferenceClientCapabilities> references;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/documentHighlight` request.
|
|
||||||
optional<DocumentHighlightClientCapabilities> documentHighlight;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/documentSymbol` request.
|
|
||||||
optional<DocumentSymbolClientCapabilities> documentSymbol;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/codeAction` request.
|
|
||||||
optional<CodeActionClientCapabilities> codeAction;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/codeLens` request.
|
|
||||||
optional<CodeLensClientCapabilities> codeLens;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/documentLink` request.
|
|
||||||
optional<DocumentLinkClientCapabilities> documentLink;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/documentColor` and the
|
|
||||||
/// `textDocument/colorPresentation` request.
|
|
||||||
/// FIXME: optional<DocumentColorClientCapabilities> colorProvider;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/formatting` request.
|
|
||||||
optional<DocumentFormattingClientCapabilities> formatting;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/rangeFormatting` request.
|
|
||||||
optional<DocumentRangeFormattingClientCapabilities> rangeFormatting;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/onTypeFormatting` request.
|
|
||||||
optional<DocumentOnTypeFormattingClientCapabilities> onTypeFormatting;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/rename` request.
|
|
||||||
optional<RenameClientCapabilities> rename;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/publishDiagnostics` notification.
|
|
||||||
optional<PublishDiagnosticsClientCapabilities> publishDiagnostics;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/foldingRange` request.
|
|
||||||
optional<FoldingRangeClientCapabilities> foldingRange;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/selectionRange` request.
|
|
||||||
/// FIXME: optional<SelectionRangeClientCapabilities> selectionRange;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/linkedEditingRange` request.
|
|
||||||
/// FIXME: optional<LinkedEditingRangeClientCapabilities> linkedEditingRange;
|
|
||||||
|
|
||||||
/// Capabilities specific to the various call hierarchy requests.
|
|
||||||
optional<CallHierarchyClientCapabilities> callHierarchy;
|
|
||||||
|
|
||||||
/// Capabilities specific to the various semantic token requests.
|
|
||||||
optional<SemanticTokensClientCapabilities> semanticTokens;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/moniker` request.
|
|
||||||
/// FIXME: optional<MonikerClientCapabilities> moniker;
|
|
||||||
|
|
||||||
/// Capabilities specific to the various type hierarchy requests.
|
|
||||||
optional<TypeHierarchyClientCapabilities> typeHierarchy;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/inlineValue` request.
|
|
||||||
/// FIXME: optional<InlineValueClientCapabilities> inlineValue;
|
|
||||||
|
|
||||||
/// Capabilities specific to the `textDocument/inlayHint` request.
|
|
||||||
optional<InlayHintClientCapabilities> inlayHint;
|
|
||||||
|
|
||||||
/// Capabilities specific to the diagnostic pull model.
|
|
||||||
optional<DiagnosticClientCapabilities> diagnostic;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class TextDocumentSyncKind : std::uint8_t {
|
|
||||||
/// Documents should not be synced at all.
|
|
||||||
None = 0,
|
|
||||||
|
|
||||||
/// Documents are synced by always sending the full content of the document.
|
|
||||||
Full = 1,
|
|
||||||
|
|
||||||
/// Documents are synced by sending the full content on open. After that
|
|
||||||
/// only incremental updates to the document are sent.
|
|
||||||
Incremental = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextDocumentSyncOptions {
|
|
||||||
/// Open and close notifications are sent to the server. If omitted open
|
|
||||||
/// close notifications should not be sent.
|
|
||||||
bool openClose = true;
|
|
||||||
|
|
||||||
/// Change notifications are sent to the server.
|
|
||||||
TextDocumentSyncKind change = TextDocumentSyncKind::Incremental;
|
|
||||||
|
|
||||||
/// If present will save notifications are sent to the server. If omitted
|
|
||||||
/// the notification should not be sent.
|
|
||||||
/// FIXME: bool willSave;
|
|
||||||
|
|
||||||
/// If present will save wait until requests are sent to the server. If
|
|
||||||
/// omitted the request should not be sent.
|
|
||||||
/// FIXME: bool willSaveWaitUntil;
|
|
||||||
|
|
||||||
/// If present save notifications are sent to the server. If omitted the
|
|
||||||
/// notification should not be sent.
|
|
||||||
bool save = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DidOpenTextDocumentParams {
|
|
||||||
/// The document that was opened.
|
|
||||||
TextDocumentItem textDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TextDocumentContentChangeEvent {
|
|
||||||
/// The new text of the whole document.
|
|
||||||
string text;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DidChangeTextDocumentParams {
|
|
||||||
/// The document that did change. The version number points
|
|
||||||
/// to the version after all provided content changes have
|
|
||||||
/// been applied.
|
|
||||||
VersionedTextDocumentIdentifier textDocument;
|
|
||||||
|
|
||||||
/// The actual content changes. The content changes describe single state
|
|
||||||
/// changes to the document. So if there are two content changes c1 (at
|
|
||||||
/// array index 0) and c2 (at array index 1) for a document in state S then
|
|
||||||
/// c1 moves the document from S to S' and c2 from S' to S''. So c1 is
|
|
||||||
/// computed on the state S and c2 is computed on the state S'.
|
|
||||||
//
|
|
||||||
/// To mirror the content of a document using change events use the following
|
|
||||||
/// approach:
|
|
||||||
/// - start with the same initial content
|
|
||||||
/// - apply the 'textDocument/didChange' notifications in the order you
|
|
||||||
/// receive them.
|
|
||||||
/// - apply the `TextDocumentContentChangeEvent`s in a single notification
|
|
||||||
/// in the order you receive them.
|
|
||||||
array<TextDocumentContentChangeEvent> contentChanges;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DidSaveTextDocumentParams {
|
|
||||||
/// The document that was saved.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
|
|
||||||
/// Optional the content when saved. Depends on the includeText value
|
|
||||||
/// when the save notification was requested.
|
|
||||||
string text;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DidCloseTextDocumentParams {
|
|
||||||
/// The document that was closed.
|
|
||||||
TextDocumentIdentifier textDocument;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Basic.h"
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
struct WorkspaceFolder {
|
|
||||||
/// The associated URI for this workspace folder.
|
|
||||||
URI uri;
|
|
||||||
|
|
||||||
/// The name of the workspace folder. Used to refer to this
|
|
||||||
/// workspace folder in the user interface.
|
|
||||||
string name;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WorkspaceClientCapabilities {};
|
|
||||||
|
|
||||||
struct WorkspaceSymbolOptions {};
|
|
||||||
|
|
||||||
struct WorkspaceFoldersServerCapabilities {
|
|
||||||
/// The server has support for workspace folders.
|
|
||||||
bool supported = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WorkspaceServerCapabilities {
|
|
||||||
/// The server supports workspace folder.
|
|
||||||
WorkspaceFoldersServerCapabilities workspaceFolders;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <expected>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice::config {
|
|
||||||
|
|
||||||
struct ProjectOptions {
|
|
||||||
bool root = true;
|
|
||||||
|
|
||||||
bool clang_tidy = false;
|
|
||||||
|
|
||||||
std::size_t max_active_file = 8;
|
|
||||||
|
|
||||||
std::string cache_dir = "${workspace}/.clice/cache";
|
|
||||||
|
|
||||||
std::string index_dir = "${workspace}/.clice/index";
|
|
||||||
|
|
||||||
std::string logging_dir = "${workspace}/.clice/logging";
|
|
||||||
|
|
||||||
std::vector<std::string> compile_commands_paths = {"${workspace}/build"};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Rule {
|
|
||||||
/// All patterns of the rule.
|
|
||||||
llvm::SmallVector<std::string> patterns;
|
|
||||||
|
|
||||||
/// The commands that you want to remove from original command.
|
|
||||||
llvm::SmallVector<std::string> remove;
|
|
||||||
|
|
||||||
/// The commands that you want to append from original command.
|
|
||||||
llvm::SmallVector<std::string> append;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Config {
|
|
||||||
/// The workspace of this config file.
|
|
||||||
std::string workspace;
|
|
||||||
|
|
||||||
/// Project level configs.
|
|
||||||
ProjectOptions project;
|
|
||||||
|
|
||||||
/// All rules used for specific files.
|
|
||||||
llvm::SmallVector<Rule> rules;
|
|
||||||
|
|
||||||
auto parse(llvm::StringRef workspace) -> std::expected<void, std::string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
}; // namespace clice::config
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Compiler/Diagnostic.h"
|
|
||||||
#include "Feature/CodeCompletion.h"
|
|
||||||
#include "Feature/SemanticToken.h"
|
|
||||||
#include "Protocol/Protocol.h"
|
|
||||||
#include "Support/FileSystem.h"
|
|
||||||
#include "Support/JSON.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
enum class PositionEncodingKind {
|
|
||||||
UTF8,
|
|
||||||
UTF16,
|
|
||||||
UTF32,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PathMapping {
|
|
||||||
std::string to_path(llvm::StringRef uri) {
|
|
||||||
/// FIXME: Path mapping.
|
|
||||||
return fs::toPath(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string to_uri(llvm::StringRef path) {
|
|
||||||
/// FIXME: Path mapping.
|
|
||||||
return fs::toURI(path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Iterates over Unicode codepoints in a UTF-8 encoded string and invokes a callback for
|
|
||||||
/// each codepoint.
|
|
||||||
///
|
|
||||||
/// Processes the input UTF-8 string, calculating the length of each Unicode codepoint in both
|
|
||||||
/// UTF-8 (bytes) and UTF-16 (code units), and passes these lengths to the callback.
|
|
||||||
/// Iteration stops early if the callback returns `false`.
|
|
||||||
///
|
|
||||||
/// ASCII characters are treated as 1-byte UTF-8 codepoints with a UTF-16 length of 1.
|
|
||||||
/// Non-ASCII characters are processed based on their leading byte to determine UTF-8 length:
|
|
||||||
/// - Valid lengths are 2 to 4 bytes.
|
|
||||||
/// - Astral codepoints (UTF-8 length of 4) have a UTF-16 length of 2 code units.
|
|
||||||
/// Invalid UTF-8 sequences are treated as single-byte ASCII characters.
|
|
||||||
///
|
|
||||||
/// Returns `false` if the callback stops the iteration.
|
|
||||||
template <typename Callback>
|
|
||||||
bool iterateCodepoints(llvm::StringRef content, const Callback& callback) {
|
|
||||||
// Iterate over the input string, processing each codepoint.
|
|
||||||
for(size_t index = 0; index < content.size();) {
|
|
||||||
unsigned char c = static_cast<unsigned char>(content[index]);
|
|
||||||
|
|
||||||
// Handle ASCII characters (1-byte UTF-8, 1-code-unit UTF-16).
|
|
||||||
if(!(c & 0x80)) [[likely]] {
|
|
||||||
if(!callback(1, 1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
++index;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the length of the codepoint in UTF-8 by counting the leading 1s.
|
|
||||||
size_t length = llvm::countl_one(c);
|
|
||||||
|
|
||||||
// Validate UTF-8 encoding: length must be between 2 and 4.
|
|
||||||
if(length < 2 || length > 4) [[unlikely]] {
|
|
||||||
assert(false && "Invalid UTF-8 sequence");
|
|
||||||
|
|
||||||
// Treat the byte as an ASCII character.
|
|
||||||
if(!callback(1, 1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
++index;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance the index by the length of the current UTF-8 codepoint.
|
|
||||||
index += length;
|
|
||||||
|
|
||||||
// Calculate the UTF-16 length: astral codepoints (4-byte UTF-8) take 2 code units.
|
|
||||||
if(!callback(length, length == 4 ? 2 : 1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remeasure the length (character count) of the content with the specified encoding kind.
|
|
||||||
inline std::uint32_t remeasure(llvm::StringRef content, PositionEncodingKind kind) {
|
|
||||||
if(kind == PositionEncodingKind::UTF8) {
|
|
||||||
return content.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kind == PositionEncodingKind::UTF16) {
|
|
||||||
std::uint32_t length = 0;
|
|
||||||
iterateCodepoints(content, [&](std::uint32_t, std::uint32_t utf16Length) {
|
|
||||||
length += utf16Length;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kind == PositionEncodingKind::UTF32) {
|
|
||||||
std::uint32_t length = 0;
|
|
||||||
iterateCodepoints(content, [&](std::uint32_t, std::uint32_t) {
|
|
||||||
length += 1;
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
class PositionConverter {
|
|
||||||
public:
|
|
||||||
PositionConverter(llvm::StringRef content, PositionEncodingKind encoding) :
|
|
||||||
content(content), encoding(encoding) {}
|
|
||||||
|
|
||||||
/// Convert a offset to a proto::Position with given encoding.
|
|
||||||
/// The input offset must be UTF-8 encoded and in order.
|
|
||||||
proto::Position toPosition(uint32_t offset) {
|
|
||||||
assert(offset <= content.size() && "Offset is out of range");
|
|
||||||
assert(offset >= lastInput && "Offset must be in order");
|
|
||||||
|
|
||||||
/// Fast path: return the last output.
|
|
||||||
if(offset == lastInput) [[unlikely]] {
|
|
||||||
return lastOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The length of the current line.
|
|
||||||
std::uint32_t lineLength = 0;
|
|
||||||
|
|
||||||
/// Move the line offset to the current line.
|
|
||||||
for(std::uint32_t i = lastLineOffset; i < offset; i++) {
|
|
||||||
lineLength += 1;
|
|
||||||
if(content[i] == '\n') {
|
|
||||||
line += 1;
|
|
||||||
lastLineOffset += lineLength;
|
|
||||||
lineLength = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the content of the current line.
|
|
||||||
auto lineContent = content.substr(lastLineOffset, lineLength);
|
|
||||||
auto position = proto::Position{
|
|
||||||
.line = line,
|
|
||||||
.character = remeasure(lineContent, encoding),
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Cache the result.
|
|
||||||
lastInput = offset;
|
|
||||||
lastOutput = position;
|
|
||||||
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Range, typename Proj = std::identity>
|
|
||||||
void to_positions(Range&& range, const Proj&& proj = {}) {
|
|
||||||
std::vector<uint32_t> offsets;
|
|
||||||
for(auto&& item: range) {
|
|
||||||
auto [begin, end] = proj(item);
|
|
||||||
offsets.emplace_back(begin);
|
|
||||||
offsets.emplace_back(end);
|
|
||||||
}
|
|
||||||
|
|
||||||
ranges::sort(offsets);
|
|
||||||
|
|
||||||
for(auto&& offset: offsets) {
|
|
||||||
if(auto it = cache.find(offset); it == cache.end()) {
|
|
||||||
cache.try_emplace(offset, toPosition(offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proto::Position lookup(uint32_t offset) {
|
|
||||||
auto it = cache.find(offset);
|
|
||||||
assert(it != cache.end() && "Offset is not cached");
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
proto::Range lookup(LocalSourceRange range) {
|
|
||||||
auto it = cache.find(range.begin);
|
|
||||||
assert(it != cache.end() && "Offset is not cached");
|
|
||||||
auto begin = it->second;
|
|
||||||
it = cache.find(range.end);
|
|
||||||
assert(it != cache.end() && "Offset is not cached");
|
|
||||||
auto end = it->second;
|
|
||||||
return proto::Range{begin, end};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::uint32_t line = 0;
|
|
||||||
/// The offset of the last line end.
|
|
||||||
std::uint32_t lastLineOffset = 0;
|
|
||||||
|
|
||||||
/// The input offset of last call.
|
|
||||||
std::uint32_t lastInput = 0;
|
|
||||||
proto::Position lastOutput = {0, 0};
|
|
||||||
|
|
||||||
llvm::DenseMap<std::uint32_t, proto::Position> cache;
|
|
||||||
|
|
||||||
llvm::StringRef content;
|
|
||||||
PositionEncodingKind encoding;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline std::uint32_t to_offset(clice::PositionEncodingKind kind,
|
|
||||||
llvm::StringRef content,
|
|
||||||
proto::Position position) {
|
|
||||||
std::uint32_t offset = 0;
|
|
||||||
for(auto i = 0; i < position.line; i++) {
|
|
||||||
auto pos = content.find('\n');
|
|
||||||
assert(pos != llvm::StringRef::npos && "Line value is out of range");
|
|
||||||
|
|
||||||
offset += pos + 1;
|
|
||||||
content = content.substr(pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drop the content after the line.
|
|
||||||
content = content.take_until([](char c) { return c == '\n'; });
|
|
||||||
assert(position.character <= content.size() && "Character value is out of range");
|
|
||||||
|
|
||||||
if(position.character == 0) {
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kind == PositionEncodingKind::UTF8) {
|
|
||||||
offset += position.character;
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kind == PositionEncodingKind::UTF16) {
|
|
||||||
iterateCodepoints(content, [&](std::uint32_t utf8Length, std::uint32_t utf16Length) {
|
|
||||||
assert(position.character >= utf16Length && "Character value is out of range");
|
|
||||||
position.character -= utf16Length;
|
|
||||||
offset += utf8Length;
|
|
||||||
return position.character != 0;
|
|
||||||
});
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kind == PositionEncodingKind::UTF32) {
|
|
||||||
iterateCodepoints(content, [&](std::uint32_t utf8Length, std::uint32_t) {
|
|
||||||
assert(position.character >= 1 && "Character value is out of range");
|
|
||||||
position.character -= 1;
|
|
||||||
offset += utf8Length;
|
|
||||||
return position.character != 0;
|
|
||||||
});
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
|
|
||||||
namespace clice::proto {
|
|
||||||
|
|
||||||
inline SymbolKind kind_map(clice::SymbolKind kind) {
|
|
||||||
switch(kind.kind()) {
|
|
||||||
case clice::SymbolKind::Comment: return SymbolKind::String;
|
|
||||||
case clice::SymbolKind::Number: return SymbolKind::Number;
|
|
||||||
case clice::SymbolKind::Character: return SymbolKind::String;
|
|
||||||
case clice::SymbolKind::String: return SymbolKind::String;
|
|
||||||
case clice::SymbolKind::Keyword: return SymbolKind::Variable;
|
|
||||||
case clice::SymbolKind::Directive: return SymbolKind::Variable;
|
|
||||||
case clice::SymbolKind::Header: return SymbolKind::String;
|
|
||||||
case clice::SymbolKind::Module: return SymbolKind::Module;
|
|
||||||
case clice::SymbolKind::Macro: return SymbolKind::Function;
|
|
||||||
case clice::SymbolKind::MacroParameter: return SymbolKind::Variable;
|
|
||||||
case clice::SymbolKind::Namespace: return SymbolKind::Namespace;
|
|
||||||
case clice::SymbolKind::Class: return SymbolKind::Class;
|
|
||||||
case clice::SymbolKind::Struct: return SymbolKind::Struct;
|
|
||||||
case clice::SymbolKind::Union: return SymbolKind::Class;
|
|
||||||
case clice::SymbolKind::Enum: return SymbolKind::Enum;
|
|
||||||
case clice::SymbolKind::Type: return SymbolKind::TypeParameter;
|
|
||||||
case clice::SymbolKind::Field: return SymbolKind::Field;
|
|
||||||
case clice::SymbolKind::EnumMember: return SymbolKind::EnumMember;
|
|
||||||
case clice::SymbolKind::Function: return SymbolKind::Function;
|
|
||||||
case clice::SymbolKind::Method: return SymbolKind::Method;
|
|
||||||
case clice::SymbolKind::Variable: return SymbolKind::Variable;
|
|
||||||
case clice::SymbolKind::Parameter: return SymbolKind::Variable;
|
|
||||||
case clice::SymbolKind::Label: return SymbolKind::Variable;
|
|
||||||
case clice::SymbolKind::Concept: return SymbolKind::TypeParameter;
|
|
||||||
case clice::SymbolKind::Attribute: return SymbolKind::Variable;
|
|
||||||
case clice::SymbolKind::Operator:
|
|
||||||
case clice::SymbolKind::Paren:
|
|
||||||
case clice::SymbolKind::Bracket:
|
|
||||||
case clice::SymbolKind::Brace:
|
|
||||||
case clice::SymbolKind::Angle: return SymbolKind::Operator;
|
|
||||||
case clice::SymbolKind::Conflict:
|
|
||||||
case clice::SymbolKind::Invalid:
|
|
||||||
default: return SymbolKind::Null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
json::Value to_json(clice::PositionEncodingKind kind,
|
|
||||||
llvm::StringRef content,
|
|
||||||
llvm::ArrayRef<feature::SemanticToken> tokens);
|
|
||||||
|
|
||||||
json::Value to_json(clice::PositionEncodingKind kind,
|
|
||||||
llvm::StringRef content,
|
|
||||||
llvm::ArrayRef<feature::CompletionItem> items);
|
|
||||||
|
|
||||||
} // namespace clice::proto
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <deque>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Config.h"
|
|
||||||
#include "Convert.h"
|
|
||||||
#include "Async/Async.h"
|
|
||||||
#include "Compiler/Command.h"
|
|
||||||
#include "Index/MergedIndex.h"
|
|
||||||
#include "Index/ProjectIndex.h"
|
|
||||||
#include "Protocol/Protocol.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/DenseSet.h"
|
|
||||||
#include "llvm/ADT/StringMap.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
class CompilationUnit;
|
|
||||||
|
|
||||||
class Indexer {
|
|
||||||
public:
|
|
||||||
Indexer(CompilationDatabase& database,
|
|
||||||
config::Config& config,
|
|
||||||
const PositionEncodingKind& kind) :
|
|
||||||
database(database), config(config), encoding_kind(kind) {}
|
|
||||||
|
|
||||||
async::Task<> index(llvm::StringRef path);
|
|
||||||
|
|
||||||
async::Task<> index(llvm::StringRef path, llvm::StringRef content);
|
|
||||||
|
|
||||||
async::Task<> schedule_next();
|
|
||||||
|
|
||||||
async::Task<> index_all();
|
|
||||||
|
|
||||||
index::MergedIndex& get_index(std::uint32_t path_id) {
|
|
||||||
auto [it, success] = in_memory_indices.try_emplace(path_id);
|
|
||||||
if(!success) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it2 = project_index.indices.find(path_id);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
using Result = async::Task<std::vector<proto::Location>>;
|
|
||||||
|
|
||||||
void load_from_disk();
|
|
||||||
|
|
||||||
void save_to_disk();
|
|
||||||
|
|
||||||
auto lookup(llvm::StringRef path, std::uint32_t offset, RelationKind kind) -> Result;
|
|
||||||
|
|
||||||
auto declaration(llvm::StringRef path, std::uint32_t offset) -> Result;
|
|
||||||
|
|
||||||
auto definition(llvm::StringRef path, std::uint32_t offset) -> Result;
|
|
||||||
|
|
||||||
auto references(llvm::StringRef path, std::uint32_t offset) -> Result;
|
|
||||||
|
|
||||||
/// TODO: Calls ...
|
|
||||||
|
|
||||||
/// TODO: Types ...
|
|
||||||
|
|
||||||
bool empty() const {
|
|
||||||
return project_index.indices.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t size() const {
|
|
||||||
return project_index.indices.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
CompilationDatabase& database;
|
|
||||||
|
|
||||||
config::Config& config;
|
|
||||||
|
|
||||||
const PositionEncodingKind& encoding_kind;
|
|
||||||
|
|
||||||
index::ProjectIndex project_index;
|
|
||||||
|
|
||||||
PathMapping mapping;
|
|
||||||
|
|
||||||
llvm::DenseMap<std::uint32_t, index::MergedIndex> in_memory_indices;
|
|
||||||
|
|
||||||
/// Currently indexes tasks ...
|
|
||||||
std::vector<async::Task<>> workings;
|
|
||||||
|
|
||||||
/// FIXME: Use a LRU to make sure we won't index a file twice ...
|
|
||||||
std::deque<std::uint32_t> waitings;
|
|
||||||
|
|
||||||
async::Event update_event;
|
|
||||||
|
|
||||||
async::Event finish_event;
|
|
||||||
|
|
||||||
size_t finish_cnt = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Config.h"
|
|
||||||
#include "Convert.h"
|
|
||||||
#include "Indexer.h"
|
|
||||||
#include "Plugin.h"
|
|
||||||
#include "Async/Async.h"
|
|
||||||
#include "Compiler/Command.h"
|
|
||||||
#include "Compiler/Diagnostic.h"
|
|
||||||
#include "Compiler/Preamble.h"
|
|
||||||
#include "Feature/DocumentLink.h"
|
|
||||||
#include "Protocol/Protocol.h"
|
|
||||||
|
|
||||||
#include <llvm/ADT/FunctionExtras.h>
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
struct OpenFile {
|
|
||||||
/// The file version, every edition will increase it.
|
|
||||||
std::uint32_t version = 0;
|
|
||||||
|
|
||||||
/// The file content.
|
|
||||||
std::string content;
|
|
||||||
|
|
||||||
/// We build PCH for every opened file.
|
|
||||||
std::optional<PCHInfo> pch;
|
|
||||||
async::Task<bool> pch_build_task;
|
|
||||||
async::Event pch_built_event;
|
|
||||||
std::vector<feature::DocumentLink> pch_includes;
|
|
||||||
|
|
||||||
/// For each opened file, we would like to build an AST for it.
|
|
||||||
std::shared_ptr<CompilationUnit> ast;
|
|
||||||
async::Task<> ast_build_task;
|
|
||||||
async::Lock ast_built_lock;
|
|
||||||
|
|
||||||
/// For header with context, it may have multiple ASTs, use
|
|
||||||
/// an chain to store them.
|
|
||||||
std::unique_ptr<OpenFile> next;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A manager for all OpenFile with LRU cache.
|
|
||||||
class ActiveFileManager {
|
|
||||||
public:
|
|
||||||
/// Use shared_ptr to manage the lifetime of OpenFile object in async function.
|
|
||||||
using ActiveFile = std::shared_ptr<OpenFile>;
|
|
||||||
|
|
||||||
/// A double-linked list to store all opened files. While the `first` field of pair (each node
|
|
||||||
/// of list) refers to a key in `index`, the `second` field refers to the OpenFile object.
|
|
||||||
/// In another word, the `index` holds the ownership of path and the `items` holds the
|
|
||||||
/// ownership of OpenFile object.
|
|
||||||
using ListContainer = std::list<std::pair<llvm::StringRef, ActiveFile>>;
|
|
||||||
|
|
||||||
struct ActiveFileIterator : public ListContainer::const_iterator {};
|
|
||||||
|
|
||||||
constexpr static size_t DefaultMaxActiveFileNum = 8;
|
|
||||||
constexpr static size_t UnlimitedActiveFileNum = 512;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Create an ActiveFileManager with a default size.
|
|
||||||
ActiveFileManager() : capability(DefaultMaxActiveFileNum) {}
|
|
||||||
|
|
||||||
ActiveFileManager(const ActiveFileManager&) = delete;
|
|
||||||
ActiveFileManager& operator=(const ActiveFileManager&) = delete;
|
|
||||||
|
|
||||||
/// Set the maximum active file count and it will be clamped to [1, UnlimitedActiveFileNum].
|
|
||||||
void set_capability(size_t size) {
|
|
||||||
// Use static_cast to make MSVC happy.
|
|
||||||
capability = std::clamp(size, static_cast<size_t>(1), UnlimitedActiveFileNum);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the maximum size of the cache.
|
|
||||||
size_t max_size() const {
|
|
||||||
return capability;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current size of the cache.
|
|
||||||
size_t size() const {
|
|
||||||
return index.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try get OpenFile from manager, default construct one if not exists.
|
|
||||||
[[nodiscard]] ActiveFile get_or_add(llvm::StringRef path);
|
|
||||||
|
|
||||||
/// Add a OpenFile to the manager.
|
|
||||||
ActiveFile add(llvm::StringRef path, OpenFile file);
|
|
||||||
|
|
||||||
[[nodiscard]] bool contains(llvm::StringRef path) const {
|
|
||||||
return index.contains(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveFileIterator begin() const {
|
|
||||||
return ActiveFileIterator(items.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveFileIterator end() const {
|
|
||||||
return ActiveFileIterator(items.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
ActiveFile lru_put_impl(llvm::StringRef path, OpenFile file);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// The maximum size of the cache.
|
|
||||||
size_t capability;
|
|
||||||
|
|
||||||
/// The first element is the most recently used, and the last
|
|
||||||
/// element is the least recently used.
|
|
||||||
/// When a file is accessed, it will be moved to the front of the list.
|
|
||||||
/// When a new file is added, if the size exceeds the maximum size,
|
|
||||||
/// the last element will be removed.
|
|
||||||
ListContainer items;
|
|
||||||
|
|
||||||
/// A map from path to the iterator of the list.
|
|
||||||
llvm::StringMap<ListContainer::iterator> index;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Server {
|
|
||||||
public:
|
|
||||||
Server();
|
|
||||||
|
|
||||||
using Self = Server;
|
|
||||||
|
|
||||||
using Callback = async::Task<json::Value> (*)(Server&, json::Value);
|
|
||||||
|
|
||||||
template <auto method>
|
|
||||||
void register_callback(llvm::StringRef name) {
|
|
||||||
using MF = decltype(method);
|
|
||||||
static_assert(std::is_member_function_pointer_v<MF>, "");
|
|
||||||
using F = member_type_t<MF>;
|
|
||||||
using Ret = function_return_t<F>;
|
|
||||||
using Params = std::tuple_element_t<0, function_args_t<F>>;
|
|
||||||
|
|
||||||
Callback callback = [](Server& server, json::Value value) -> async::Task<json::Value> {
|
|
||||||
if constexpr(std::is_same_v<Ret, async::Task<>>) {
|
|
||||||
co_await (server.*method)(json::deserialize<Params>(value));
|
|
||||||
co_return json::Value(nullptr);
|
|
||||||
} else {
|
|
||||||
co_return co_await (server.*method)(json::deserialize<Params>(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
callbacks.try_emplace(name, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
async::Task<> on_receive(json::Value value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// Send a request to the client.
|
|
||||||
async::Task<> request(llvm::StringRef method, json::Value params);
|
|
||||||
|
|
||||||
/// Send a notification to the client.
|
|
||||||
async::Task<> notify(llvm::StringRef method, json::Value params);
|
|
||||||
|
|
||||||
/// Send a response to the client.
|
|
||||||
async::Task<> response(json::Value id, json::Value result);
|
|
||||||
|
|
||||||
async::Task<> response(json::Value id, proto::ErrorCodes code, llvm::StringRef message = "");
|
|
||||||
|
|
||||||
/// Send an register capability to the client.
|
|
||||||
async::Task<> registerCapacity(llvm::StringRef id,
|
|
||||||
llvm::StringRef method,
|
|
||||||
json::Value registerOptions);
|
|
||||||
|
|
||||||
private:
|
|
||||||
async::Task<json::Value> on_initialize(proto::InitializeParams params);
|
|
||||||
|
|
||||||
async::Task<> on_initialized(proto::InitializedParams);
|
|
||||||
|
|
||||||
async::Task<json::Value> on_shutdown(proto::ShutdownParams params);
|
|
||||||
|
|
||||||
async::Task<> on_exit(proto::ExitParams params);
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Load the cache info from disk.
|
|
||||||
void load_cache_info();
|
|
||||||
|
|
||||||
/// Save the cache info to disk.
|
|
||||||
void save_cache_info();
|
|
||||||
|
|
||||||
async::Task<bool> build_pch(std::string file, std::string preamble);
|
|
||||||
|
|
||||||
async::Task<> build_ast(std::string file, std::string content);
|
|
||||||
|
|
||||||
async::Task<std::shared_ptr<OpenFile>> add_document(std::string path, std::string content);
|
|
||||||
|
|
||||||
private:
|
|
||||||
async::Task<> on_did_open(proto::DidOpenTextDocumentParams params);
|
|
||||||
|
|
||||||
async::Task<> on_did_change(proto::DidChangeTextDocumentParams params);
|
|
||||||
|
|
||||||
async::Task<> on_did_save(proto::DidSaveTextDocumentParams params);
|
|
||||||
|
|
||||||
async::Task<> on_did_close(proto::DidCloseTextDocumentParams params);
|
|
||||||
|
|
||||||
private:
|
|
||||||
using Result = async::Task<json::Value>;
|
|
||||||
|
|
||||||
auto on_execute_command(proto::ExecuteCommandParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_completion(proto::CompletionParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_hover(proto::HoverParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_signature_help(proto::SignatureHelpParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_go_to_declaration(proto::DeclarationParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_go_to_definition(proto::DefinitionParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_find_references(proto::ReferenceParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_document_symbol(proto::DocumentSymbolParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_document_link(proto::DocumentLinkParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_document_format(proto::DocumentFormattingParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_document_range_format(proto::DocumentRangeFormattingParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_folding_range(proto::FoldingRangeParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_semantic_token(proto::SemanticTokensParams params) -> Result;
|
|
||||||
|
|
||||||
auto on_inlay_hint(proto::InlayHintParams params) -> Result;
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// The current request id.
|
|
||||||
std::uint32_t server_request_id = 0;
|
|
||||||
std::uint32_t client_request_id = 0;
|
|
||||||
|
|
||||||
/// All registered LSP callbacks.
|
|
||||||
llvm::StringMap<Callback> callbacks;
|
|
||||||
|
|
||||||
PositionEncodingKind kind = PositionEncodingKind::UTF16;
|
|
||||||
|
|
||||||
std::string workspace;
|
|
||||||
|
|
||||||
/// The compilation database.
|
|
||||||
CompilationDatabase database;
|
|
||||||
|
|
||||||
/// All opening files.
|
|
||||||
ActiveFileManager opening_files;
|
|
||||||
|
|
||||||
PathMapping mapping;
|
|
||||||
|
|
||||||
config::Config config;
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#define bail(...) std::unexpected(std::format(__VA_ARGS__))
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace clice::config {
|
|
||||||
|
|
||||||
constexpr inline std::string_view version = "0.0.1";
|
|
||||||
constexpr inline std::string_view llvm_version = "20.1.5";
|
|
||||||
|
|
||||||
} // namespace clice::config
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "Format.h"
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define ASSERT(expr, message, ...) \
|
|
||||||
if(!(expr)) { \
|
|
||||||
llvm::errs() << "ASSERT FAIL: " << std::format(message, ##__VA_ARGS__); \
|
|
||||||
std::abort(); \
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#define ASSERT(expr, message, ...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
@@ -1,312 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Enum.h"
|
|
||||||
#include "FixedString.h"
|
|
||||||
#include "Format.h"
|
|
||||||
#include "Struct.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
#include "llvm/Support/MemoryBuffer.h"
|
|
||||||
|
|
||||||
namespace clice::binary {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct array {
|
|
||||||
/// The offset to the beginning of binary buffer.
|
|
||||||
uint32_t offset;
|
|
||||||
|
|
||||||
/// The size of array.
|
|
||||||
uint32_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
using string = array<char>;
|
|
||||||
|
|
||||||
/// Check whether a type can be directly binarized.
|
|
||||||
template <typename T>
|
|
||||||
constexpr inline bool is_directly_binarizable_v = [] {
|
|
||||||
if constexpr(std::is_integral_v<T> || refl::reflectable_enum<T>) {
|
|
||||||
return true;
|
|
||||||
} else if constexpr(refl::reflectable_struct<T>) {
|
|
||||||
return refl::member_types<T>::apply(
|
|
||||||
[]<typename... Ts>() { return (is_directly_binarizable_v<Ts> && ...); });
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
consteval auto binarify();
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
using binarify_t = typename decltype(binarify<T>())::type;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
consteval auto binarify() {
|
|
||||||
if constexpr(is_directly_binarizable_v<T>) {
|
|
||||||
return identity<T>();
|
|
||||||
} else if constexpr(std::is_same_v<T, std::string>) {
|
|
||||||
return identity<binary::string>();
|
|
||||||
} else if constexpr(is_specialization_of<T, std::vector>) {
|
|
||||||
return identity<binary::array<typename T::value_type>>();
|
|
||||||
} else if constexpr(is_specialization_of<T, std::tuple>) {
|
|
||||||
return tuple_to_list_t<T>::apply(
|
|
||||||
[]<typename... Ts> { return identity<std::tuple<binarify_t<Ts>...>>(); });
|
|
||||||
} else if constexpr(refl::reflectable_struct<T>) {
|
|
||||||
return refl::member_types<T>::apply(
|
|
||||||
[]<typename... Ts> { return identity<std::tuple<binarify_t<Ts>...>>(); });
|
|
||||||
} else {
|
|
||||||
static_assert(dependent_false<T>, "unsupported type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A section in the binary data.
|
|
||||||
template <typename T>
|
|
||||||
struct Section {
|
|
||||||
/// Current count of elements.
|
|
||||||
uint32_t count = 0;
|
|
||||||
|
|
||||||
/// Total count of elements in the section.
|
|
||||||
uint32_t total = 0;
|
|
||||||
|
|
||||||
/// Offset of the section.
|
|
||||||
uint32_t offset = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T, typename Primary = void>
|
|
||||||
consteval auto layout() {
|
|
||||||
if constexpr(is_directly_binarizable_v<T>) {
|
|
||||||
return std::tuple<>();
|
|
||||||
} else if constexpr(std::is_same_v<T, std::string>) {
|
|
||||||
return std::tuple<Section<char>>();
|
|
||||||
} else if constexpr(is_specialization_of<T, std::vector>) {
|
|
||||||
using V = typename T::value_type;
|
|
||||||
if constexpr(std::is_same_v<V, Primary>) {
|
|
||||||
return std::tuple<Section<V>>();
|
|
||||||
} else {
|
|
||||||
return std::tuple_cat(std::tuple<Section<V>>(), layout<V>());
|
|
||||||
}
|
|
||||||
} else if constexpr(is_specialization_of<T, std::tuple>) {
|
|
||||||
return tuple_to_list_t<T>::apply(
|
|
||||||
[]<typename... Ts> { return std::tuple_cat(layout<Ts>()...); });
|
|
||||||
} else if constexpr(refl::reflectable_struct<T>) {
|
|
||||||
return refl::member_types<T>::apply(
|
|
||||||
[]<typename... Ts> { return std::tuple_cat(layout<Ts, T>()...); });
|
|
||||||
} else {
|
|
||||||
static_assert(dependent_false<T>, "unsupported type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the binary layout of a type. Make sure every type in the
|
|
||||||
/// layout is unique.
|
|
||||||
template <typename T>
|
|
||||||
using layout_t = tuple_uniuqe_t<decltype(layout<T>())>;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct Packer {
|
|
||||||
/// The layout of the binary data.
|
|
||||||
layout_t<T> layout = {};
|
|
||||||
|
|
||||||
/// The total size of the binary data.
|
|
||||||
uint32_t size = 0;
|
|
||||||
|
|
||||||
/// The buffer to store the binary data.
|
|
||||||
std::vector<char> buffer;
|
|
||||||
|
|
||||||
/// Recursively traverse the object and calculate the size of each section.
|
|
||||||
template <typename Object>
|
|
||||||
void init(const Object& object) {
|
|
||||||
if constexpr(std::same_as<Object, std::string>) {
|
|
||||||
std::get<Section<char>>(layout).total += object.size() + 1;
|
|
||||||
} else if constexpr(requires { typename Object::value_type; }) {
|
|
||||||
std::get<Section<typename Object::value_type>>(layout).total += object.size();
|
|
||||||
for(const auto& element: object) {
|
|
||||||
init(element);
|
|
||||||
}
|
|
||||||
} else if constexpr(refl::reflectable_struct<Object>) {
|
|
||||||
refl::foreach(object, [&](auto, auto& field) { init(field); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Object>
|
|
||||||
requires (is_directly_binarizable_v<Object> && !refl::reflectable_struct<Object>)
|
|
||||||
Object write(const Object& object) {
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Object>
|
|
||||||
requires (std::is_same_v<Object, std::string>)
|
|
||||||
string write(const Object& object) {
|
|
||||||
auto& section = std::get<Section<char>>(layout);
|
|
||||||
uint32_t size = object.size();
|
|
||||||
uint32_t offset = section.offset + section.count;
|
|
||||||
section.count += size + 1;
|
|
||||||
|
|
||||||
std::memcpy(buffer.data() + offset, object.data(), size);
|
|
||||||
buffer[offset + size] = '\0';
|
|
||||||
|
|
||||||
return string{offset, size};
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Object, typename V = typename Object::value_type>
|
|
||||||
requires (is_specialization_of<Object, std::vector>)
|
|
||||||
array<V> write(const Object& object) {
|
|
||||||
auto& section = std::get<Section<V>>(layout);
|
|
||||||
uint32_t size = object.size();
|
|
||||||
uint32_t offset = section.offset + section.count * sizeof(binarify_t<V>);
|
|
||||||
section.count += size;
|
|
||||||
|
|
||||||
for(std::size_t i = 0; i < size; ++i) {
|
|
||||||
::new (buffer.data() + offset + i * sizeof(binarify_t<V>)) auto{write(object[i])};
|
|
||||||
}
|
|
||||||
|
|
||||||
return array<V>{offset, size};
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Object>
|
|
||||||
requires (refl::reflectable_struct<Object>)
|
|
||||||
std::array<char, sizeof(binarify_t<Object>)> write(const Object& object) {
|
|
||||||
std::array<char, sizeof(binarify_t<Object>)> buffer;
|
|
||||||
std::memset(buffer.data(), 0, sizeof(buffer));
|
|
||||||
|
|
||||||
binarify_t<Object> result;
|
|
||||||
refl::foreach(result, object, [&](auto& lhs, auto& rhs) {
|
|
||||||
auto offset = reinterpret_cast<char*>(&lhs) - reinterpret_cast<char*>(&result);
|
|
||||||
::new (buffer.data() + offset) auto{write(rhs)};
|
|
||||||
});
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<char> pack(const auto& object) {
|
|
||||||
/// First initialize the layout.
|
|
||||||
init(object);
|
|
||||||
|
|
||||||
/// Calculate the total size of the binary data and
|
|
||||||
/// the offset of each section.
|
|
||||||
size = sizeof(binarify_t<T>);
|
|
||||||
|
|
||||||
auto try_each = [&]<typename V>(auto, Section<V>& field) {
|
|
||||||
static_assert(alignof(binarify_t<V>) <= 8, "Alignment not supported.");
|
|
||||||
|
|
||||||
/// Make sure each section is aligned to 8 bytes.
|
|
||||||
if(size % 8 != 0) {
|
|
||||||
size += 8 - size % 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
field.offset = size;
|
|
||||||
size += field.total * sizeof(binarify_t<V>);
|
|
||||||
};
|
|
||||||
|
|
||||||
refl::foreach(layout, try_each);
|
|
||||||
|
|
||||||
/// Make sure the buffer is clean. So we can compare the result.
|
|
||||||
/// Every padding in the struct should be filled with 0.
|
|
||||||
buffer.resize(size, 0);
|
|
||||||
|
|
||||||
/// Write the object to the buffer.
|
|
||||||
auto result = write(object);
|
|
||||||
std::memcpy(buffer.data(), &result, sizeof(result));
|
|
||||||
|
|
||||||
return std::move(buffer);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A helper class to access the binary data.
|
|
||||||
template <typename T>
|
|
||||||
struct Proxy {
|
|
||||||
using underlying_type = binarify_t<T>;
|
|
||||||
const void* base;
|
|
||||||
const void* data;
|
|
||||||
|
|
||||||
const auto& value() const {
|
|
||||||
return *reinterpret_cast<const underlying_type*>(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::size_t I>
|
|
||||||
auto get() const {
|
|
||||||
return Proxy<refl::member_type<T, I>>{base, &std::get<I>(value())};
|
|
||||||
}
|
|
||||||
|
|
||||||
template <fixed_string name>
|
|
||||||
auto get() const {
|
|
||||||
constexpr auto& names = refl::member_names<T>();
|
|
||||||
|
|
||||||
constexpr auto index = []() {
|
|
||||||
for(std::size_t i = 0; i < names.size(); ++i) {
|
|
||||||
if(names[i] == name) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return names.size();
|
|
||||||
}();
|
|
||||||
|
|
||||||
return this->template get<index>();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto as_string() const {
|
|
||||||
auto [offset, size] = value();
|
|
||||||
return llvm::StringRef{reinterpret_cast<const char*>(base) + offset, size};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto as_array() const {
|
|
||||||
auto [offset, size] = value();
|
|
||||||
using U = binarify_t<typename T::value_type>;
|
|
||||||
return llvm::ArrayRef<U>{
|
|
||||||
reinterpret_cast<const U*>(static_cast<const char*>(base) + offset),
|
|
||||||
size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator[](std::size_t index) const {
|
|
||||||
return Proxy<typename T::value_type>{base, &as_array()[index]};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto size() const {
|
|
||||||
return value().size;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto operator->() const {
|
|
||||||
return &value();
|
|
||||||
}
|
|
||||||
|
|
||||||
operator const underlying_type&() const {
|
|
||||||
return value();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Binirize an object.
|
|
||||||
template <typename Object>
|
|
||||||
auto serialize(const Object& object) {
|
|
||||||
auto buffer = Packer<Object>().pack(object);
|
|
||||||
auto proxy = Proxy<Object>{buffer.data(), buffer.data()};
|
|
||||||
return std::tuple(std::move(buffer), proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Object>
|
|
||||||
Object deserialize(Proxy<Object> proxy) {
|
|
||||||
if constexpr(is_directly_binarizable_v<Object>) {
|
|
||||||
return proxy.value();
|
|
||||||
} else if constexpr(std::is_same_v<Object, std::string>) {
|
|
||||||
return proxy.as_string().str();
|
|
||||||
} else if constexpr(is_specialization_of<Object, std::vector>) {
|
|
||||||
Object result;
|
|
||||||
for(std::size_t i = 0; i < proxy.size(); i++) {
|
|
||||||
result.emplace_back(deserialize(proxy[i]));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else if constexpr(refl::reflectable_struct<Object>) {
|
|
||||||
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
|
||||||
return Object{deserialize(proxy.template get<Is>())...};
|
|
||||||
}(std::make_index_sequence<refl::member_count<Object>()>());
|
|
||||||
} else {
|
|
||||||
static_assert(dependent_false<Object>, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace clice::binary
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Enum.h"
|
|
||||||
#include "Struct.h"
|
|
||||||
|
|
||||||
namespace clice::refl {
|
|
||||||
|
|
||||||
template <typename LHS, typename RHS = LHS>
|
|
||||||
struct Equal {
|
|
||||||
constexpr static bool equal(const LHS& lhs, const RHS& rhs) {
|
|
||||||
return lhs == rhs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct equal_t {
|
|
||||||
template <typename LHS, typename RHS = LHS>
|
|
||||||
constexpr static bool operator()(const LHS& lhs, const RHS& rhs) {
|
|
||||||
return Equal<LHS, RHS>::equal(lhs, rhs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr inline equal_t equal;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct Equal<std::vector<T>> {
|
|
||||||
constexpr static bool equal(const std::vector<T>& lhs, const std::vector<T>& rhs) {
|
|
||||||
if(lhs.size() != rhs.size()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(std::size_t i = 0; i < lhs.size(); ++i) {
|
|
||||||
if(!refl::equal(lhs[i], rhs[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <reflectable_enum E>
|
|
||||||
struct Equal<E> {
|
|
||||||
constexpr static bool equal(E lhs, E rhs) {
|
|
||||||
return lhs.value() == rhs.value();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <reflectable_struct T>
|
|
||||||
requires (!requires(T lhs, T rhs) {
|
|
||||||
{ lhs == rhs } -> std::convertible_to<bool>;
|
|
||||||
})
|
|
||||||
struct Equal<T> {
|
|
||||||
constexpr static bool equal(const T& lhs, const T& rhs) {
|
|
||||||
return foreach(lhs, rhs, [](const auto& lhs, const auto& rhs) {
|
|
||||||
return refl::equal(lhs, rhs);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename RHS, typename LHS = RHS>
|
|
||||||
struct Less {
|
|
||||||
constexpr static bool less(const LHS& lhs, const RHS& rhs) {
|
|
||||||
return lhs < rhs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct less_t {
|
|
||||||
template <typename RHS, typename LHS = RHS>
|
|
||||||
constexpr static bool operator()(const LHS& lhs, const RHS& rhs) {
|
|
||||||
return Less<RHS, LHS>::less(lhs, rhs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr inline less_t less;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
struct Less<std::vector<T>> {
|
|
||||||
constexpr static bool less(const std::vector<T>& lhs, const std::vector<T>& rhs) {
|
|
||||||
if(lhs.size() != rhs.size()) {
|
|
||||||
return lhs.size() < rhs.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(std::size_t i = 0; i < lhs.size(); ++i) {
|
|
||||||
if(refl::less(lhs[i], rhs[i])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <reflectable_enum E>
|
|
||||||
struct Less<E> {
|
|
||||||
constexpr static bool less(E lhs, E rhs) {
|
|
||||||
return lhs.value() < rhs.value();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <reflectable_struct T>
|
|
||||||
requires (!requires(T lhs, T rhs) {
|
|
||||||
{ lhs < rhs } -> std::convertible_to<bool>;
|
|
||||||
})
|
|
||||||
struct Less<T> {
|
|
||||||
constexpr static bool less(const T& lhs, const T& rhs) {
|
|
||||||
bool result = false;
|
|
||||||
foreach(lhs, rhs, [&](const auto& lhs, const auto& rhs) {
|
|
||||||
/// return false to break the loop.
|
|
||||||
if(refl::less(lhs, rhs)) {
|
|
||||||
result = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(refl::less(rhs, lhs)) {
|
|
||||||
result = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// continue the loop.
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct less_equal_t {
|
|
||||||
template <typename LHS, typename RHS = LHS>
|
|
||||||
constexpr static bool operator()(const LHS& lhs, const RHS& rhs) {
|
|
||||||
return equal(lhs, rhs) || less(lhs, rhs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr inline less_equal_t less_equal;
|
|
||||||
|
|
||||||
} // namespace clice::refl
|
|
||||||
@@ -1,301 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <bit>
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <source_location>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "Support/TypeTraits.h"
|
|
||||||
|
|
||||||
namespace clice::refl {
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
requires std::is_enum_v<T>
|
|
||||||
constexpr auto underlying_value(T value) {
|
|
||||||
return static_cast<std::underlying_type_t<T>>(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <auto value>
|
|
||||||
requires std::is_enum_v<decltype(value)>
|
|
||||||
consteval auto enum_name() {
|
|
||||||
std::string_view name = std::source_location::current().function_name();
|
|
||||||
#if __GNUC__ || __clang__
|
|
||||||
std::size_t start = name.find('=') + 2;
|
|
||||||
std::size_t end = name.size() - 1;
|
|
||||||
#elif _MSC_VER
|
|
||||||
std::size_t start = name.find('<') + 1;
|
|
||||||
std::size_t end = name.rfind(">(");
|
|
||||||
#else
|
|
||||||
static_assert(false, "Not supported compiler");
|
|
||||||
#endif
|
|
||||||
name = name.substr(start, end - start);
|
|
||||||
start = name.rfind("::");
|
|
||||||
return start == std::string_view::npos ? name : name.substr(start + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename E, std::size_t N = 0>
|
|
||||||
consteval auto enum_max() {
|
|
||||||
constexpr auto value = std::bit_cast<E>(static_cast<std::underlying_type_t<E>>(N));
|
|
||||||
if constexpr(enum_name<value>().find(")") == std::string_view::npos)
|
|
||||||
return enum_max<E, N + 1>();
|
|
||||||
else
|
|
||||||
return N;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename E, std::size_t count>
|
|
||||||
struct enum_table {
|
|
||||||
constexpr static std::array<std::string_view, count> table =
|
|
||||||
[]<std::size_t... Is>(std::index_sequence<Is...>) {
|
|
||||||
return std::array{enum_name<static_cast<E>(Is)>()...};
|
|
||||||
}(std::make_index_sequence<count>{});
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename E, std::size_t begin = 0, std::size_t end = enum_max<E, begin>()>
|
|
||||||
constexpr std::string_view enum_name(E value) {
|
|
||||||
return enum_table<E, end - begin>::table[static_cast<std::underlying_type_t<E>>(value) - begin];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A helper class to define enum.
|
|
||||||
template <typename Derived, bool is_bitmask = false, typename underlying = uint8_t>
|
|
||||||
class Enum {
|
|
||||||
public:
|
|
||||||
/// Tag to indicate this is a special enum.
|
|
||||||
constexpr inline static bool reflectable_enum = true;
|
|
||||||
|
|
||||||
using underlying_type = underlying;
|
|
||||||
|
|
||||||
constexpr Enum() : m_Value(invalid()) {}
|
|
||||||
|
|
||||||
/// A integral must explicitly convert to the enum.
|
|
||||||
explicit constexpr Enum(underlying value) : m_Value(value) {}
|
|
||||||
|
|
||||||
/// Allow the enum to be constructed from the enum value.
|
|
||||||
template <std::same_as<typename Derived::Kind> Kind>
|
|
||||||
constexpr Enum(Kind kind) : m_Value(kind) {
|
|
||||||
static_assert(sizeof(underlying) >= sizeof(typename Derived::Kind),
|
|
||||||
"Underlying type is too small to hold all enum values.");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Enum(const Enum&) = default;
|
|
||||||
|
|
||||||
constexpr Enum& operator=(const Enum&) = default;
|
|
||||||
|
|
||||||
/// Get the underlying value of the enum.
|
|
||||||
constexpr underlying value() const {
|
|
||||||
return m_Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the enum value.
|
|
||||||
constexpr auto kind() const {
|
|
||||||
return static_cast<typename Derived::Kind>(m_Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the name of the enum.
|
|
||||||
constexpr std::string_view name() const {
|
|
||||||
using E = typename Derived::Kind;
|
|
||||||
return refl::enum_name<E, begin(), end()>(static_cast<E>(m_Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::same_as<typename Derived::Kind>... Kinds>
|
|
||||||
constexpr bool is_one_of(Kinds... kinds) const {
|
|
||||||
return ((m_Value == underlying_value(kinds)) || ...);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr explicit operator bool() const {
|
|
||||||
return m_Value != invalid();
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr friend bool operator==(Enum lhs, Enum rhs) = default;
|
|
||||||
|
|
||||||
constexpr static auto& all() {
|
|
||||||
return enum_table<typename Derived::Kind, end() - begin()>::table;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
consteval static underlying begin() {
|
|
||||||
if constexpr(requires { Derived::FirstEnum; }) {
|
|
||||||
return Derived::FirstEnum;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
consteval static underlying end() {
|
|
||||||
if constexpr(requires { Derived::LastEnum; }) {
|
|
||||||
return Derived::LastEnum;
|
|
||||||
} else {
|
|
||||||
return refl::enum_max<typename Derived::Kind, begin()>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
consteval static underlying invalid() {
|
|
||||||
if constexpr(requires { Derived::InvalidEnum; }) {
|
|
||||||
return Derived::InvalidEnum;
|
|
||||||
} else {
|
|
||||||
static_assert(dependent_false<Derived>, "Invalid enum value is not defined.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
underlying m_Value;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Derived, typename underlying>
|
|
||||||
class Enum<Derived, true, underlying> {
|
|
||||||
public:
|
|
||||||
/// Tag to indicate this is a special enum.
|
|
||||||
constexpr inline static bool reflectable_enum = true;
|
|
||||||
|
|
||||||
using underlying_type = underlying;
|
|
||||||
|
|
||||||
Enum() = default;
|
|
||||||
|
|
||||||
/// A integral must explicitly convert to the enum.
|
|
||||||
explicit constexpr Enum(underlying value) : m_Value(value) {}
|
|
||||||
|
|
||||||
/// Allow the enum to be constructed from the enum value.
|
|
||||||
template <std::same_as<typename Derived::Kind>... Kinds>
|
|
||||||
constexpr Enum(Kinds... kind) : m_Value(((1 << underlying_value(kind)) | ...)) {
|
|
||||||
static_assert(sizeof(underlying) * 8 >= end(),
|
|
||||||
"Underlying type is too small to hold all enum values.");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Enum(const Enum&) = default;
|
|
||||||
|
|
||||||
constexpr Enum& operator=(const Enum&) = default;
|
|
||||||
|
|
||||||
/// Get the underlying value of the enum.
|
|
||||||
constexpr underlying value() const {
|
|
||||||
return m_Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the name of the enum.
|
|
||||||
constexpr std::string name() const {
|
|
||||||
std::string masks;
|
|
||||||
bool isFirst = true;
|
|
||||||
for(std::size_t i = 0; i < sizeof(underlying) * 8; i++) {
|
|
||||||
bool hasBit = m_Value & (1 << i);
|
|
||||||
|
|
||||||
if(!hasBit) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isFirst) {
|
|
||||||
isFirst = false;
|
|
||||||
} else {
|
|
||||||
masks += " | ";
|
|
||||||
}
|
|
||||||
|
|
||||||
using E = typename Derived::Kind;
|
|
||||||
masks += refl::enum_name<E, begin(), end()>(static_cast<E>(i));
|
|
||||||
}
|
|
||||||
return masks;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr static auto& all() {
|
|
||||||
return enum_table<typename Derived::Kind, end() - begin()>::table;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr explicit operator bool() const {
|
|
||||||
return m_Value != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr friend bool operator==(Enum lhs, Enum rhs) = default;
|
|
||||||
|
|
||||||
template <std::same_as<typename Derived::Kind> Kind>
|
|
||||||
constexpr Enum operator|(Kind kind) const {
|
|
||||||
return Enum(m_Value | (1 << underlying_value(kind)));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::same_as<typename Derived::Kind> Kind>
|
|
||||||
constexpr Enum operator&(Kind kind) const {
|
|
||||||
return Enum(m_Value & (1 << underlying_value(kind)));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Enum operator&(Enum e) const {
|
|
||||||
return Enum(m_Value & e.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::same_as<typename Derived::Kind> Kind>
|
|
||||||
constexpr Enum& operator|=(Kind kind) {
|
|
||||||
m_Value |= (1 << underlying_value(kind));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::same_as<typename Derived::Kind> Kind>
|
|
||||||
constexpr Enum& operator&=(Kind kind) {
|
|
||||||
m_Value &= (1 << underlying_value(kind));
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::same_as<typename Derived::Kind>... Kinds>
|
|
||||||
constexpr bool is_one_of(Kinds... kinds) const {
|
|
||||||
return (((*this) & (kinds)) || ...);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
consteval static std::size_t begin() {
|
|
||||||
if constexpr(requires { Derived::FirstEnum; }) {
|
|
||||||
return Derived::FirstEnum;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
consteval static std::size_t end() {
|
|
||||||
if constexpr(requires { Derived::LastEnum; }) {
|
|
||||||
return Derived::LastEnum;
|
|
||||||
} else {
|
|
||||||
return refl::enum_max<typename Derived::Kind, begin()>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
underlying m_Value = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Derived, typename underlying>
|
|
||||||
requires (!integral<underlying>)
|
|
||||||
class Enum<Derived, false, underlying> {
|
|
||||||
public:
|
|
||||||
/// Tag to indicate this is a special enum.
|
|
||||||
constexpr inline static bool reflectable_enum = true;
|
|
||||||
|
|
||||||
using underlying_type = underlying;
|
|
||||||
|
|
||||||
constexpr Enum(underlying value) {
|
|
||||||
static_assert(
|
|
||||||
requires { Derived::All; },
|
|
||||||
"Derived enum must define all possible enum values.");
|
|
||||||
|
|
||||||
for(auto& element: Derived::All) {
|
|
||||||
if(element == value) {
|
|
||||||
m_Value = element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(!m_Value.empty() && "Invalid enum value.");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Enum(const Enum&) = default;
|
|
||||||
|
|
||||||
constexpr friend bool operator==(Enum lhs, Enum rhs) = default;
|
|
||||||
|
|
||||||
constexpr underlying value() const {
|
|
||||||
return m_Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
underlying m_Value;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
concept reflectable_enum = requires {
|
|
||||||
T::reflectable_enum;
|
|
||||||
requires T::reflectable_enum;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace clice::refl
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace clice {
|
|
||||||
|
|
||||||
template <std::size_t N>
|
|
||||||
struct fixed_string : std::array<char, N + 1> {
|
|
||||||
template <std::size_t M>
|
|
||||||
constexpr fixed_string(const char (&str)[M]) {
|
|
||||||
for(std::size_t i = 0; i < N; ++i) {
|
|
||||||
this->data()[i] = str[i];
|
|
||||||
}
|
|
||||||
this->data()[N] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr fixed_string(const char* str) {
|
|
||||||
for(std::size_t i = 0; i < N; ++i) {
|
|
||||||
this->data()[i] = str[i];
|
|
||||||
}
|
|
||||||
this->data()[N] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr auto size() const {
|
|
||||||
return N;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr operator std::string_view() const {
|
|
||||||
return {this->data(), N};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <std::size_t M>
|
|
||||||
fixed_string(const char (&)[M]) -> fixed_string<M - 1>;
|
|
||||||
|
|
||||||
} // namespace clice
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user