diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 0000000..d95a701 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,209 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "dap/implement" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: true + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + # ToDo: Fix macos build + # ToDo: Find alternative for std::chrono::clock_cast on non-msvc compilers + # os: [ubuntu-latest, windows-latest, macos-latest] + os: [windows-latest] + build_type: [Release] + c_compiler: [clang, cl] + arch: [x86, x64] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + vcpkg_triplet: x86-windows + platform: windows + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + vcpkg_triplet: x64-windows + platform: windows + #- os: macos-latest + # c_compiler: clang + # cpp_compiler: clang++ + # vcpkg_triplet: x64-osx-release + # platform: macos + #- os: ubuntu-latest + # c_compiler: clang + # cpp_compiler: clang++ + # vcpkg_triplet: x64-linux-release + # platform: linux + exclude: + # windows-latest + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + + ## ubuntu-latest + #- os: ubuntu-latest + # c_compiler: clang + # arch: x86 + #- os: ubuntu-latest + # c_compiler: cl + # arch: x86 + #- os: ubuntu-latest + # c_compiler: cl + # arch: x64 + + ## macos-latest + #- os: macos-latest + # c_compiler: clang + # arch: x86 + #- os: macos-latest + # c_compiler: cl + # arch: x86 + #- os: macos-latest + # c_compiler: cl + # arch: x64 + + steps: + - uses: actions/checkout@v3 + with: + submodules: true + + - name: vcpkg build + uses: johnwason/vcpkg-action@v5 + id: vcpkg + with: + manifest-dir: ${{ github.workspace }}/server + triplet: ${{ matrix.vcpkg_triplet }} + cache-key: ${{ matrix.config.os }} + revision: 8b04a7bd93bef991818fc372bb83ce00ec1c1c16 #master + token: ${{ github.token }} + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + if: ${{ matrix.os != 'windows.latest' }} + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + ${{ steps.vcpkg.outputs.vcpkg-cmake-config }} + -S ${{ github.workspace }}/server + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + if: ${{ matrix.os == 'windows.latest' }} + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -A ${{ matrix.arch == 'x86' && 'Win32' || matrix.arch }} + ${{ steps.vcpkg.outputs.vcpkg-cmake-config }} + -S ${{ github.workspace }}/server + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + # - name: Test + # working-directory: ${{ steps.strings.outputs.build-output-dir }} + # # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + # run: ctest --build-config ${{ matrix.build_type }} + + - name: Upload binary + uses: actions/upload-artifact@v3 + if: ${{ matrix.os == 'windows-latest' }} + with: + name: ${{ matrix.platform }}-${{ matrix.arch }} + if-no-files-found: error + path: | + .\build\sqfvm_language_server\Release\*.dll + .\build\sqfvm_language_server\Release\*.exe + + - name: Upload binary + uses: actions/upload-artifact@v3 + if: ${{ matrix.os == 'ubuntu-latest' }} + with: + name: ${{ matrix.platform }} + if-no-files-found: error + path: | + ./build/sqfvm_language_server/sqfvm_language_server + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Download binaries + uses: actions/download-artifact@v3 + with: + path: ./clients/vscode/bin/ + - uses: actions/setup-node@v1 + with: + node-version: 16 + + - name: Install YARN + run: npm install -g yarn + - name: Install VCSE + run: npm install -g @vscode/vsce + + - name: NPM Install + working-directory: ./clients/vscode + run: npm install + + - name: YARN Compile + working-directory: ./clients/vscode + run: yarn run compile + + - name: VCSE Package + working-directory: ./clients/vscode + run: vsce package --yarn + + - name: Upload extension + uses: actions/upload-artifact@v3 + with: + name: VSCode extension + if-no-files-found: error + path: | + ./clients/vscode/*.vsix +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - uses: actions/setup-node@v1 +# with: +# node-version: 16 +# - run: npm ci +# - name: Publish to Open VSX Registry +# uses: HaaLeo/publish-vscode-extension@v1 +# with: +# pat: ${{ secrets.OPEN_VSX_TOKEN }} +# - name: Publish to Visual Studio Marketplace +# uses: HaaLeo/publish-vscode-extension@v1 +# with: +# pat: ${{ secrets.VS_MARKETPLACE_TOKEN }} +# registryUrl: https://marketplace.visualstudio.com diff --git a/.gitignore b/.gitignore index 7974a96..ba0990c 100644 --- a/.gitignore +++ b/.gitignore @@ -327,6 +327,7 @@ cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake +cmake-build-*/ /DLL2 /sqfvm-cpp/DLL2 diff --git a/.gitmodules b/.gitmodules index d2fafb9..c63d2af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "json"] - path = server/extern/json - url = https://github.com/nlohmann/json.git [submodule "vm"] - path = server/extern/vm + path = server/extern/runtime url = https://github.com/SQFvm/vm.git +[submodule "server/extern/json"] + path = server/extern/json + url = https://github.com/nlohmann/json.git diff --git a/clients/vscode/.vscode/tasks.json b/clients/vscode/.vscode/tasks.json index 1e37eb7..55b4e53 100644 --- a/clients/vscode/.vscode/tasks.json +++ b/clients/vscode/.vscode/tasks.json @@ -8,17 +8,11 @@ // A task runner that calls a custom npm script that compiles the extension. { - "version": "0.1.0", + "version": "2.0.0", // we want to run npm "command": "npm", - // the command is a shell script - "isShellCommand": true, - - // show the output window only if unrecognized errors occur. - "showOutput": "silent", - // we run the custom script "compile" as defined in package.json "args": ["run", "compile", "--loglevel", "silent"], @@ -26,5 +20,24 @@ "isBackground": true, // use the standard tsc in watch mode problem matcher to find compile problems in the output. - "problemMatcher": "$tsc-watch" + "problemMatcher": "$tsc-watch", + "tasks": [ + { + "label": "npm", + "type": "shell", + "command": "npm", + "args": [ + "run", + "compile", + "--loglevel", + "silent" + ], + "isBackground": true, + "problemMatcher": "$tsc-watch", + "group": { + "_id": "build", + "isDefault": false + } + } + ] } \ No newline at end of file diff --git a/clients/vscode/.vscode/tasks.json.old b/clients/vscode/.vscode/tasks.json.old new file mode 100644 index 0000000..1e37eb7 --- /dev/null +++ b/clients/vscode/.vscode/tasks.json.old @@ -0,0 +1,30 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process + +// A task runner that calls a custom npm script that compiles the extension. +{ + "version": "0.1.0", + + // we want to run npm + "command": "npm", + + // the command is a shell script + "isShellCommand": true, + + // show the output window only if unrecognized errors occur. + "showOutput": "silent", + + // we run the custom script "compile" as defined in package.json + "args": ["run", "compile", "--loglevel", "silent"], + + // The tsc compiler is started in watching mode + "isBackground": true, + + // use the standard tsc in watch mode problem matcher to find compile problems in the output. + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/clients/vscode/README.md b/clients/vscode/README.md index 87fa2ac..c6f3b77 100644 --- a/clients/vscode/README.md +++ b/clients/vscode/README.md @@ -1,28 +1,37 @@ -***UNIX Support pending. (Windows Only as of now)*** - -- - - - - Provides a Language Server for the Scripting Language SQF, that is used in the `Arma` Series and `Virtual Battle Space` Simulation. + +* [Features](#features) +* [Planned Features](#planned-features) +* [How to report errors](#how-to-report-errors) +* [How to use](#how-to-use) +* [Social Channels](#social-channels) +* [FAQ](#faq) + * [Enable/Disable a problem](#enabledisable-a-problem) + ![](https://raw.githubusercontent.com/SQFvm/vscode/master/clients/vscode/assets/readme/variable_not_defined.gif) +![](https://raw.githubusercontent.com/SQFvm/vscode/master/clients/vscode/assets/readme/symbol_lookup.gif) # Features * Linting * PreProcessor *Note that the preprocessor expects files to exist. Recommended to setup a `$PBOPREFIX$`* * Syntax Checking * Code Analysis - * Variable Hiding Detection - * Unused Variable Warning + * Unused variable warning + * Unused value warning * Syntax Highlighting +* Reference (Symbol) lookup +* Disable reported problems and informations *Note that disabling syntactical errors will not fix the file* + # Planned Features * Basic Auto Completion (Macros, Variables, Operators, ...) * Extended Auto Completion (Read params of methods and automagically complete on call, ...) -* Symbol Lookup * Debugger (SQF-VM powered) * Resolve macro on hover * PreProcess file and display it +* Official Linux support (Deployment related lack of, you can compile your own from the sources) # How to report errors Head over to https://github.com/SQFvm/vscode/issues and create a new issue. @@ -31,4 +40,30 @@ Head over to https://github.com/SQFvm/vscode/issues and create a new issue. Just install the extension and you should be able to run it from the get-go. # Social Channels -* [Discord](https://discord.gg/5uQYWQu) \ No newline at end of file +* [Discord](https://discord.gg/5uQYWQu) + +# FAQ +## Enable/Disable a problem +Note: `ERROR-CODE` in the following samples is the code reported by the language server (eg. `VV-001`) + +To disable a warning for the line following, use +`#pragma sls disable line ERROR-CODE` +This will surpress the error code for the next line. +To disable (and later enable) an error code on a long span, use +```sqf +#pragma sls disable ERROR-CODE +// [...] +#pragma sls enable ERROR-CODE +``` + +## Scripted analyzers +The language server supports scripted analyzers. +A scripted analyzer is a script that is called by the language server on every file, +allowing you to implement your own analyzers in SQF. + +To enable scripted analyzers, you have to create an empty file called `use_scripted_analyzers` +at `/.vscode/sqfvm-lsp/use_scripted_analyzers` (where `` is the workspace root). + +After touching any other file managed by the language server, the language server will +automatically generate template files for the scripted analyzers, including a documentation +on how to use them. \ No newline at end of file diff --git a/clients/vscode/assets/readme/symbol_lookup.gif b/clients/vscode/assets/readme/symbol_lookup.gif new file mode 100644 index 0000000..2ddd59a Binary files /dev/null and b/clients/vscode/assets/readme/symbol_lookup.gif differ diff --git a/clients/vscode/package-lock.json b/clients/vscode/package-lock.json index 5aa3b30..185bc32 100644 --- a/clients/vscode/package-lock.json +++ b/clients/vscode/package-lock.json @@ -1,8 +1,1561 @@ { "name": "sqf-vm-language-server", - "version": "0.1.0", - "lockfileVersion": 1, + "version": "0.1.16", + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "sqf-vm-language-server", + "version": "0.1.16", + "license": "LGPL-3.0-only", + "dependencies": { + "fast-glob": "^3.2.4", + "vscode-languageclient": "^6.1.3", + "yarn": "^1.22.4" + }, + "devDependencies": { + "@types/glob": "^7.1.3", + "@types/mocha": "^7.0.2", + "@types/node": "^13.9.2", + "@types/vscode": "^1.43.0", + "mocha": "^7.1.1", + "tslint": "^6.1.0", + "typescript": "^3.8.3" + }, + "engines": { + "vscode": "^1.43.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dependencies": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", + "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "13.13.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz", + "integrity": "sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw==", + "dev": true + }, + "node_modules/@types/vscode": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.48.0.tgz", + "integrity": "sha512-sZJKzsJz1gSoFXcOJWw3fnKl2sseUgZmvB4AJZS+Fea+bC/jfGPVhmFL/FfQHld/TKtukVONsmoD3Pkyx9iadg==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.1" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-glob": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz", + "integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/node-environment-flags/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz", + "integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==", + "engines": { + "node": ">=8.0.0 || >=10.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz", + "integrity": "sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA==", + "dependencies": { + "semver": "^6.3.0", + "vscode-languageserver-protocol": "^3.15.3" + }, + "engines": { + "vscode": "^1.41.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.15.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz", + "integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==", + "dependencies": { + "vscode-jsonrpc": "^5.0.1", + "vscode-languageserver-types": "3.15.1" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz", + "integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==" + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yarn": { + "version": "1.22.4", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.4.tgz", + "integrity": "sha512-oYM7hi/lIWm9bCoDMEWgffW8aiNZXCWeZ1/tGy0DWrN6vmzjCXIKu2Y21o8DYVBUtiktwKcNoxyGl/2iKLUNGA==", + "bin": { + "yarn": "bin/yarn.js", + "yarnpkg": "bin/yarn.js" + }, + "engines": { + "node": ">=4.0.0" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.10.4", @@ -94,9 +1647,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true }, "ansi-styles": { @@ -225,9 +1778,9 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true }, "string-width": { @@ -396,9 +1949,9 @@ } }, "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", "dev": true, "requires": { "is-buffer": "~2.0.3" @@ -444,9 +1997,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { "is-glob": "^4.0.1" } @@ -602,9 +2155,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "log-symbols": { @@ -640,9 +2193,9 @@ } }, "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true }, "mkdirp": { @@ -698,6 +2251,17 @@ "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } } } } @@ -719,9 +2283,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -812,9 +2376,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "picomatch": { @@ -863,9 +2427,9 @@ "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" }, "set-blocking": { "version": "2.0.0", @@ -975,9 +2539,9 @@ "dev": true }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -1061,9 +2625,9 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true }, "string-width": { @@ -1095,9 +2659,9 @@ "dev": true }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "yargs": { @@ -1119,9 +2683,9 @@ }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", "dev": true }, "string-width": { diff --git a/clients/vscode/package.json b/clients/vscode/package.json index aa0215f..4a15789 100644 --- a/clients/vscode/package.json +++ b/clients/vscode/package.json @@ -3,7 +3,7 @@ "displayName": "SQF-VM Language Server", "description": "", "icon": "assets/icon.png", - "version": "0.1.16", + "version": "0.2.13", "publisher": "SQF-VM", "repository": { "type": "git", @@ -11,18 +11,20 @@ }, "license": "LGPL-3.0-only", "engines": { - "vscode": "^1.43.0" + "vscode": "^1.63.0" }, "categories": [ "Programming Languages", "Linters" ], + "activationEvents-docs": "https://code.visualstudio.com/api/references/activation-events#onFileSystem", "activationEvents": [ "onLanguage:sqf", "onLanguage:sqc", "onCommand:sqfVmLanguageServer.openFilePath", "onCommand:sqfVmLanguageServer.openRpt", - "onCommand:sqfVmLanguageServer.alignEquals" + "onCommand:sqfVmLanguageServer.alignEquals", + "workspaceContains:*.sqf" ], "main": "./out/src/extension", "contributes": { @@ -86,59 +88,25 @@ } ], "configuration": { - "type": "object", - "title": "SQF VM Language server", + "title": "%sqfVmLanguageServer.Executable.Title%", "properties": { - "sqfVmLanguageServer.mode": { - "enum": [ - "Arma2", - "Arma3", - "VBS3", - "VBS4" - ], - "default": "Arma3", - "description": "Controls which game version to run the LS for." - }, - "sqfVmLanguageServer.maxNumberOfProblems": { - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server." - }, - "sqfVmLanguageServer.trace.server": { - "scope": "window", - "type": "string", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "Traces the communication between VSCode and the sqfVmLanguageServer service." - }, - "sqfVmLanguageServer.workdrive.path": { - "type": "string", - "default": "P:\\", - "description": "Set the partition name for your workdrive (Used to open files from). Used by sqfVmLanguageServer.openFilePath Task. Language server will map the drive-path to `/`." - }, - "sqfVmLanguageServer.ls.additionalMappings": { + "sqfVmLanguageServer.Executable.PathMappings": { "type": "array", - "default": [], - "markdownDescription": "Allows to declare additional mappings. The array is expected to have children of the following structure: \n\n`{ \"virtual path\": \"physical path\" }`.\n\nExample:\n\n```\n\n\"sqfVmLanguageServer.ls.additionalMappings\": [\n\n { \"/x/tag/addon\": \"C:/sqfdev/extern/unpacked/tag_addon\" }\n\n]\n```" - }, - "sqfVmLanguageServer.ls.sqcSupport": { - "type": "boolean", - "default": false, - "markdownDescription": "Enables (or disables) SQC support. This involves auto-compilation upon typing inside a SQC file, outputting a compiled SQF file with the same name. Note that the 'compile on change' is done due to vscode not supplying textDocument/didSave to Language Servers." - }, - "sqfVmLanguageServer.ls.logLevel.verbose": { - "type": "boolean", - "default": false, - "markdownDescription": "Enables (or disables) verbose logging." - }, - "sqfVmLanguageServer.ls.logLevel.trace": { - "type": "boolean", - "default": false, - "markdownDescription": "Enables (or disables) trace logging." + "items": { + "type": "object", + "properties": { + "physical": { + "type": "string" + }, + "virtual": { + "type": "string" + } + } + }, + "uniqueItems": true, + "title": "%sqfVmLanguageServer.Executable.PathMappings.Title%", + "markdownDescription": "%sqfVmLanguageServer.Executable.PathMappings.MarkdownDescription%", + "scope": "machine-overridable" } } } diff --git a/clients/vscode/package.nls.de.json b/clients/vscode/package.nls.de.json new file mode 100644 index 0000000..4ecc136 --- /dev/null +++ b/clients/vscode/package.nls.de.json @@ -0,0 +1,5 @@ +{ + "sqfVmLanguageServer.Executable.Title": "SQF-VM Sprachserver", + "sqfVmLanguageServer.Executable.PathMappings.Title": "Pfadzuordnung", + "sqfVmLanguageServer.Executable.PathMappings.MarkdownDescription": "Die physischen -> virtuellen Pfadzuordnungen für den zu verwendenden Sprachserver.\n\nBeispiel:\n```json\n\"sqfVmLanguageServer.Executable.PathMappings\": [\n {\"physical\": \"C:/Physischer/Pfad\", \"virtual\": \"/Virtueller/Pfad\"}\n]\n```" +} \ No newline at end of file diff --git a/clients/vscode/package.nls.json b/clients/vscode/package.nls.json new file mode 100644 index 0000000..130f89c --- /dev/null +++ b/clients/vscode/package.nls.json @@ -0,0 +1,5 @@ +{ + "sqfVmLanguageServer.Executable.Title": "SQF-VM Language Server", + "sqfVmLanguageServer.Executable.PathMappings.Title": "Path mappings", + "sqfVmLanguageServer.Executable.PathMappings.MarkdownDescription": "The physical -> virtual path mappings for the language server to use.\n\nSample:\n```json\n\"sqfVmLanguageServer.Executable.PathMappings\": [\n {\"physical\": \"C:/Physical/Path\", \"virtual\": \"/Virtual/Path\"}\n]\n```" +} \ No newline at end of file diff --git a/clients/vscode/publish.cmd b/clients/vscode/publish.cmd new file mode 100644 index 0000000..2993ab4 --- /dev/null +++ b/clients/vscode/publish.cmd @@ -0,0 +1,4 @@ +yarn run compile +vsce package --yarn +vsce publish --pre-release --yarn +pause \ No newline at end of file diff --git a/clients/vscode/sample/.vscode/settings.json b/clients/vscode/sample/.vscode/settings.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/clients/vscode/sample/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/ReadMe.md b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/ReadMe.md new file mode 100644 index 0000000..6d9421b --- /dev/null +++ b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/ReadMe.md @@ -0,0 +1,209 @@ +# Welcome to scripted analyzers + +This is a short introduction to scripted analyzers. +It will cover the basics of how to write your own analyzer and how to use it. + +# What are scripted analyzers? + +Scripted analyzers are a way to extend the functionality of the language server +by writing your own analyzers in SQF. This allows you to write e.g. your own +diagnostics. The analyzers are run on the server and the results are sent to +the client, which will display them in the editor. + +# Data structures + +The following are the data structures that are available to the analyzers. +They will be referred in the documentation by writing `` where type is +the name of the data structure. + +## severity + +```sqf +// One of the following: +"FATAL"; +"ERROR"; +"WARNING"; +"INFO"; +"VERBOSE"; +"TRACE"; +``` + +The severity of a diagnostic. The higher the severity, the more important the +diagnostic is. +FATAL is the highest severity and TRACE is the lowest. + +| Severity | Description | Problems | Editor | +|----------|-----------------------------------------------------------------------------------------------------------------------------------|----------|--------| +| FATAL | The highest severity, should be used sparingly. | ERROR | RED | +| ERROR | The second-highest severity, should be used for problems that prevent the code from running. | ERROR | RED | +| WARNING | The third-highest severity, should be used for problems that might cause unexpected behavior. | WARNING | YELLOW | +| INFO | The fourth-highest severity, should be used for problems that are not necessarily problems, but might be interesting to the user. | INFO | WHITE | +| VERBOSE | The second-lowest severity, should be used for when INFO is too noisy. | | GRAY | +| TRACE | The lowest severity. | | GRAY | + +## diagnostic + +```sqf +[ + severity, // + error_code, // string + content, // string + message, // string + line, // scalar + column, // scalar + offset, // scalar + length, // scalar + file_id, // scalar +] +``` + +A diagnostic is a problem that is found in the code. It is represented by an +array of the above structure. The fields are as follows: + +| Field | Description | Type | +|------------|-----------------------------------------|-----------------------| +| severity | The severity of the diagnostic. | [severity](#severity) | +| error_code | A unique identifier for the diagnostic. | string | +| content | The content of the diagnostic. | string | +| message | The message of the diagnostic. | string | +| line | The line of the diagnostic. | scalar | +| column | The column of the diagnostic. | scalar | +| offset | The offset of the diagnostic. | scalar | +| length | The length of the diagnostic. | scalar | +| file_id | The file id of the diagnostic. | scalar | + +## file + +```sqf +[ + file_id, // scalar + file_name, // string + file_contents, // string +] +``` + +A file is a file that is being analyzed. It is represented by an array of the +above structure. The fields are as follows: + +| Field | Description | Type | +|---------------|--------------------------------|--------| +| file_id | The file id of the file. | scalar | +| file_name | The file name of the file. | string | +| file_contents | The file contents of the file. | string | + +## ast_node_type + +```sqf +// One of the following: +"ENDOFFILE" +"INVALID" +"__TOKEN" +"NA" +"STATEMENTS" +"STATEMENT" +"IDENT" +"NUMBER" +"HEXNUMBER" +"STRING" +"BOOLEAN_TRUE" +"BOOLEAN_FALSE" +"EXPRESSION_LIST" +"CODE" +"ARRAY" +"ASSIGNMENT" +"ASSIGNMENT_LOCAL" +"EXPN" +"EXP0" +"EXP1" +"EXP2" +"EXP3" +"EXP4" +"EXP5" +"EXP6" +"EXP7" +"EXP8" +"EXP9" +"EXPU" +"EXP_GROUP" +``` + +The type of AST node. The AST is a tree representation of the code. It is +used to analyze the code. The type of node is represented by one of the above +strings. + +## ast_node + +```sqf +[ + + reference, // scalar + line, // scalar + column, // scalar + offset, // scalar + path, // string + type, // +] +``` + +An AST node is a node in the AST. It is represented by an array of the above +structure. +To get the children of an AST node, use the `SLS_fnc_getChildren` function. +To get the content of an AST node, use the `SLS_fnc_getContent` function. + +| Field | Description | Type | +|-----------|--------------------------------|---------------------------------| +| reference | The reference of the AST node. | scalar | +| line | The line of the AST node. | scalar | +| column | The column of the AST node. | scalar | +| offset | The offset of the AST node. | scalar | +| path | The path of the AST node. | string | +| type | The type of the AST node. | [ast_node_type](#ast_node_type) | + +# Functions + +The following are the functions that are available to the analyzers. +They will be referred in the documentation by writing `SLS_fnc_` +where `` is the name of the function. + +Note that the functions listed here expect the arguments to always be passed in as an array +and call directly into native functions. + +## SLS_fnc_getFile + +```sqf +[ + path // string +] call SLS_fnc_getFile // -> +``` + +Gets the file with the given path. + +## SLS_fnc_getChildren + +```sqf +[ + node // +] call SLS_fnc_getChildren // -> [] +``` + +Gets the children of the given AST node. + +## SLS_fnc_getContent + +```sqf +[ + node // +] call SLS_fnc_getContent // -> string +``` + +Gets the content of the given AST node. + +## SLS_fnc_reportDiagnostic + +```sqf +[ + diagnostic // +] call SLS_fnc_reportDiagnostic // -> nil +``` + +Reports the given diagnostic to the client. diff --git a/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/analyze.sqf b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/analyze.sqf new file mode 100644 index 0000000..023080d --- /dev/null +++ b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/analyze.sqf @@ -0,0 +1,11 @@ +reportDiagnostic [ + "INFO", + "SQF-001", + "Hello from SQF-based intrinsics", + "Hello from SQF-based intrinsics", + 0, + 0, + 0, + 1, + 0 +]; \ No newline at end of file diff --git a/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/end.sqf b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/end.sqf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/end.sqf @@ -0,0 +1 @@ + diff --git a/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/enter.sqf b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/enter.sqf new file mode 100644 index 0000000..e72ec54 --- /dev/null +++ b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/enter.sqf @@ -0,0 +1 @@ +params ["_node", "_parents"]; diff --git a/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/exit.sqf b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/exit.sqf new file mode 100644 index 0000000..e72ec54 --- /dev/null +++ b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/exit.sqf @@ -0,0 +1 @@ +params ["_node", "_parents"]; diff --git a/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/start.sqf b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/start.sqf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/clients/vscode/sample/.vscode/sqfvm-lsp/scripted/analyzers/sqf/start.sqf @@ -0,0 +1 @@ + diff --git a/clients/vscode/sample/.vscode/sqfvm-lsp/use_scripted_analyzers b/clients/vscode/sample/.vscode/sqfvm-lsp/use_scripted_analyzers new file mode 100644 index 0000000..e69de29 diff --git a/clients/vscode/sample/deref-pboprefix-include/deref.hpp b/clients/vscode/sample/deref-pboprefix-include/deref.hpp new file mode 100644 index 0000000..ba21c02 --- /dev/null +++ b/clients/vscode/sample/deref-pboprefix-include/deref.hpp @@ -0,0 +1 @@ +#include "/pboprefix/sample/include.h" \ No newline at end of file diff --git a/clients/vscode/sample/deref-pboprefix-include/deref.sqf b/clients/vscode/sample/deref-pboprefix-include/deref.sqf new file mode 100644 index 0000000..dff0873 --- /dev/null +++ b/clients/vscode/sample/deref-pboprefix-include/deref.sqf @@ -0,0 +1 @@ +#include "deref.hpp" diff --git a/clients/vscode/sample/detachable_scope.sqf b/clients/vscode/sample/detachable_scope.sqf new file mode 100644 index 0000000..7c7acad --- /dev/null +++ b/clients/vscode/sample/detachable_scope.sqf @@ -0,0 +1,17 @@ +private _variable = "test"; +Variable = "test"; + +isNil "_variable"; +diag_log format["getVariable global", missionNamespace getVariable "variable"]; +[] call { + diag_log format["call local", _Variable]; + diag_log format["call global", missionNamespace getVariable "variable"]; +}; +[] spawn { + diag_log format["spawn local", _variable]; + diag_log format["spawn global", missionNamespace getVariable "Variable"]; +}; +isNil { + diag_log format["isNil local", _vaRiable]; + diag_log format["isNil global", missionNamespace getVariable "Variable"]; +}; \ No newline at end of file diff --git a/clients/vscode/sample/example.sqc b/clients/vscode/sample/example.sqc index 35cfd90..c27a69d 100644 --- a/clients/vscode/sample/example.sqc +++ b/clients/vscode/sample/example.sqc @@ -1,3 +1,12 @@ +for (it : arr) +{ + diag_log(it); + for (it2 : it) + { + diag_log(it, it2); + } +} + // Assign local values /* test */ $"test {{{ 1 + 1 }}}"; diff --git a/clients/vscode/sample/example.sqf b/clients/vscode/sample/example.sqf index 4508de1..b73342f 100644 --- a/clients/vscode/sample/example.sqf +++ b/clients/vscode/sample/example.sqf @@ -1 +1 @@ -private _a = 1; private _b = 2; private _c = 3; private _d = 4; private _f = { scopename "___sqc_func"; params ["_arga", "_argb"]; diag_log format ["%1 - %2", _arga, _argb] }; global = { scopename "___sqc_func"; params ["_val"]; if (_val > 10) then { true breakout "___sqc_func" } else { if (_val > 12) then { if (_val < 50) then { false } else { true } breakout "___sqc_func" } } }; ["sqc", "hello world"] call _f; diag_log "test"; player getvariable "TAG_SomeVar"; private _arr = [1, 2, 3]; _arr select { scopename "___sqc_func"; (_x > 2) breakout "___sqc_func" }; for "_i" from 0 to 100 do { diag_log "test" } \ No newline at end of file +{ diag_log _it; { diag_log [_it, _it2] } foreach _it } foreach arr; private _a = 1; private _b = 2; private _c = 3; private _d = 4; private _f = { scopename "___sqc_func"; params ["_arga", "_argb"]; diag_log format ["%1 - %2", _arga, _argb] }; global = { scopename "___sqc_func"; params ["_val"]; if (_val > 10) then { true breakout "___sqc_func" } else { if (_val > 12) then { if (_val < 50) then { false } else { true } breakout "___sqc_func" } } }; ["sqc", "hello world"] call _f; diag_log "test"; player getvariable "TAG_SomeVar"; private _arr = [1, 2, 3]; _arr select { scopename "___sqc_func"; (_x > 2) breakout "___sqc_func" }; for "_i" from 0 to 100 do { diag_log "test" } \ No newline at end of file diff --git a/clients/vscode/sample/get_after_nested_set.sqf b/clients/vscode/sample/get_after_nested_set.sqf new file mode 100644 index 0000000..dee91b0 --- /dev/null +++ b/clients/vscode/sample/get_after_nested_set.sqf @@ -0,0 +1,4 @@ +if (true) then { + private _var = false; +}; +_var \ No newline at end of file diff --git a/clients/vscode/sample/hide_variable.sqf b/clients/vscode/sample/hide_variable.sqf new file mode 100644 index 0000000..2abbded --- /dev/null +++ b/clients/vscode/sample/hide_variable.sqf @@ -0,0 +1,6 @@ +private _foobar = 0; +diag_log _foobar; +if true then { + private _foobar = 1; + diag_log _foobar; +}; \ No newline at end of file diff --git a/clients/vscode/sample/included_sqf_file.txt b/clients/vscode/sample/included_sqf_file.txt new file mode 100644 index 0000000..ec1ae50 --- /dev/null +++ b/clients/vscode/sample/included_sqf_file.txt @@ -0,0 +1,2 @@ +private _unusedVariable = "UnusedValue"; +_unusedVariable = "DifferentValue"; \ No newline at end of file diff --git a/clients/vscode/sample/including_sqf_file.sqf b/clients/vscode/sample/including_sqf_file.sqf new file mode 100644 index 0000000..c6f1397 --- /dev/null +++ b/clients/vscode/sample/including_sqf_file.sqf @@ -0,0 +1,4 @@ +#include "included_sqf_file.txt" + +diag_log _unusedVariable; +_unusedVariable = ""; \ No newline at end of file diff --git a/clients/vscode/sample/magic_variables.sqf b/clients/vscode/sample/magic_variables.sqf new file mode 100644 index 0000000..83eb74e --- /dev/null +++ b/clients/vscode/sample/magic_variables.sqf @@ -0,0 +1,10 @@ +// _x, _y, _forEachIndex +{ + diag_log _x; + diag_log _y; + diag_log _forEachIndex; +} forEach createHashMap; + +// _this +_this params []; +[] spawn { diag_log _this; }; \ No newline at end of file diff --git a/clients/vscode/sample/needless_brackets.sqf b/clients/vscode/sample/needless_brackets.sqf new file mode 100644 index 0000000..70f0232 --- /dev/null +++ b/clients/vscode/sample/needless_brackets.sqf @@ -0,0 +1,6 @@ +#include "include.h" +private _s = nil; +diag_log ( CONCAT(_,s) ); +diag_log ((1 + 1) * (1 + 1)); +diag_log ((1 * 1) + (1 * 1)); +if (true && false) then {}; \ No newline at end of file diff --git a/clients/vscode/sample/new_file_3.sqf b/clients/vscode/sample/new_file_3.sqf new file mode 100644 index 0000000..de037dd --- /dev/null +++ b/clients/vscode/sample/new_file_3.sqf @@ -0,0 +1,7 @@ +#include "include.h" +_codes = missionNamespace getVariable "asd"; +[] call _codes; +isNil "_codes"; +Variable = "this is a cross reference"; + +CONCAT(Var,iable) = "test"; \ No newline at end of file diff --git a/clients/vscode/sample/new_file_5.sqf b/clients/vscode/sample/new_file_5.sqf new file mode 100644 index 0000000..d34436e --- /dev/null +++ b/clients/vscode/sample/new_file_5.sqf @@ -0,0 +1,15 @@ +private _declaration = nil; +_declaration = 1; +diag_log _declaration; + +private _item = 1; +private _notAssigned = _item; +diag_log _notAssigned; + +private ["_tst1"]; +_tst1 = 123; +diag_log _tst1; + +private ["_tst2"]; +_tst2 = 123; +diag_log _tst2; \ No newline at end of file diff --git a/clients/vscode/sample/pragma_disable_line.sqf b/clients/vscode/sample/pragma_disable_line.sqf new file mode 100644 index 0000000..6fef581 --- /dev/null +++ b/clients/vscode/sample/pragma_disable_line.sqf @@ -0,0 +1,2 @@ +#pragma sls disable line VV-001 +private _config = ""; \ No newline at end of file diff --git a/clients/vscode/sample/reference_cross_1.sqf b/clients/vscode/sample/reference_cross_1.sqf new file mode 100644 index 0000000..4d71e37 --- /dev/null +++ b/clients/vscode/sample/reference_cross_1.sqf @@ -0,0 +1,2 @@ +comment "tell me:"; +where_am_i = "?"; \ No newline at end of file diff --git a/clients/vscode/sample/reference_cross_2.sqf b/clients/vscode/sample/reference_cross_2.sqf new file mode 100644 index 0000000..af35e1a --- /dev/null +++ b/clients/vscode/sample/reference_cross_2.sqf @@ -0,0 +1,2 @@ +where_am_i = "Here am i!"; + asda \ No newline at end of file diff --git a/clients/vscode/sample/sample.sqf b/clients/vscode/sample/sample.sqf index 2966ea4..2793aab 100644 --- a/clients/vscode/sample/sample.sqf +++ b/clients/vscode/sample/sample.sqf @@ -1,21 +1,22 @@ #include "include.h" -#include "/pboprefix/sample/include.h" -#define TEST -#define CFUNC(ARG) ARG +#define TEST test +#define CFUNC(ARG) CONCAT(ARG,TEST) testvalue = ""; + + systemChat CONCAT(test,value); private _something = []; private _input = _this select 0; [1, 2, 3] params ["_one", "_two", ["_three", 3]]; - + // Reserved forEach variables { _x; _forEachIndex; } forEach _something; - + path = "a3\air_f\config.cpp"; private "_code"; private ["_state", "_args"]; @@ -23,7 +24,7 @@ private ["_return"]; _state = _state getVariable ["##state", "init"]; _args = _state getVariable ["##args", []]; -_code = _state getVariable [_state, { "init" }]; +_code = _state getVariable [_state, { "init" }]; _return = _args call _code; private _someRandomVar = 1; //noprivate private _anotherVar = 2; diff --git a/clients/vscode/sample/scope_check.sqf b/clients/vscode/sample/scope_check.sqf new file mode 100644 index 0000000..0a7a308 --- /dev/null +++ b/clients/vscode/sample/scope_check.sqf @@ -0,0 +1,19 @@ + +if (true) then { + private _foo = 0; + diag_log _foo; +}; +diag_log _foo; // Should raise warning (not assigned) +private _foo = 1; // Should raise information (not used) +private _bar = 0; +if (true) then { + diag_log _bar; + _bar = 1; +}; +diag_log _bar; + +if (true) then { + FooBarScopeCheck = 0; + diag_log FooBarScopeCheck; +}; +diag_log FooBarScopeCheck; \ No newline at end of file diff --git a/clients/vscode/sample/tickets/19.sqf b/clients/vscode/sample/tickets/19.sqf new file mode 100644 index 0000000..d64dfcc --- /dev/null +++ b/clients/vscode/sample/tickets/19.sqf @@ -0,0 +1,60 @@ +// _x never assigned +private ["_ctrlImport", "_import"]; +{ + _ctrlImport lbAdd (_x select 0); +} forEach _import; + +// _forEachIndex never assigned, _x never assigned +private ["_baseRadios", "_channels", "_volumes", "_spatials", "_radios"]; +acre_api_fnc_getBaseRadio = nil; +acre_api_fnc_getRadioChannel = nil; +acre_api_fnc_getRadioVolume = nil; +acre_api_fnc_getRadioSpatial = nil; +{ + // Loop through maximum 6 radios + if (_forEachIndex >= 6) exitWith {}; + _baseRadio = [_x] call acre_api_fnc_getBaseRadio; + _baseRadios pushBack _baseRadio; + _channel = [_x] call acre_api_fnc_getRadioChannel; + _channels pushBack _channel; + _volume = [_x] call acre_api_fnc_getRadioVolume; + _volumes pushBack _volume; + _spatial = [_x] call acre_api_fnc_getRadioSpatial; + _spatials pushBack _spatial; +} forEach _radios; + +//_this never assigned +KPCF_ace = nil; +KPCF_fnc_manageAceActions = nil; +KPCF_interactRadius = nil; +KPCF_fnc_openDialog = nil; +private ["_cfBase"]; +if (KPCF_ace) then { + [_cfBase] call KPCF_fnc_manageAceActions; +} else { + _cfBase addAction [ + "" + localize "STR_KPCF_ACTIONOPEN" + "", { + [_this] call KPCF_fnc_openDialog; + }, + nil, + 1, + false, + true, + "", + "true", + KPCF_interactRadius + ]; +}; + +// _i should not raise an error +KPCF_fnc_getConfigPath = nil; +KPCF_inventory = nil; +private ["_count", "_cargo", "_config"]; +for "_i" from 0 to (_count - 1) do { + _config = [(_cargo select 0) select _i] call KPCF_fnc_getConfigPath; + KPCF_inventory pushBack [ + (getText (configFile >> _config >> ((_cargo select 0) select _i) >> "displayName")), + (_cargo select 0) select _i, + (_cargo select 1) select _i + ]; +}; \ No newline at end of file diff --git a/clients/vscode/sample/tickets/24.sqf b/clients/vscode/sample/tickets/24.sqf new file mode 100644 index 0000000..a001395 --- /dev/null +++ b/clients/vscode/sample/tickets/24.sqf @@ -0,0 +1,26 @@ +private _item = getItemCargo KPCF_activeStorage; +private _weapon = getWeaponCargo KPCF_activeStorage; +private _magazine = getMagazineCargo KPCF_activeStorage; +private _backpack = getBackpackCargo KPCF_activeStorage; +private _cargo = _item; +(_cargo select 0) append (_weapon select 0); +(_cargo select 1) append (_weapon select 1); +(_cargo select 0) append (_magazine select 0); +(_cargo select 1) append (_magazine select 1); +(_cargo select 0) append (_backpack select 0); +(_cargo select 1) append (_backpack select 1); + +// Count the variable index +private _count = count (_cargo select 0); + +private _config = ""; + +// Adapt the cargo into KPCF variable +for "_i" from 0 to (_count-1) do { + _config = [(_cargo select 0) select _i] call KPCF_fnc_getConfigPath; + KPCF_inventory pushBack [ + (getText (configFile >> _config >> ((_cargo select 0) select _i) >> "displayName")), + (_cargo select 0) select _i, + (_cargo select 1) select _i + ]; +}; \ No newline at end of file diff --git a/clients/vscode/sample/tickets/32.sqf b/clients/vscode/sample/tickets/32.sqf new file mode 100644 index 0000000..513d2f4 --- /dev/null +++ b/clients/vscode/sample/tickets/32.sqf @@ -0,0 +1,20 @@ +// Add units +_valids = allUnits select { + (alive _x) // Alive + && { + (KP_liberation_enemies_zeus && {!(side (group _x) isEqualTo GRLIB_side_civilian)}) // Not civilian side, if enemy adding is enabled + || {side (group _x) isEqualTo GRLIB_side_friendly} // Player side if enemy adding is disabled + } + && {((str _x) find "BIS_SUPP_HQ_") isEqualTo -1} // Not a HQ entity from support module +}; + +// Add vehicles +_valids append (vehicles select { + (alive _x) // Alive + && { + ((toLower (typeOf _x)) in _vehicleClassnames) // In valid classnames + || (_x getVariable ["KPLIB_captured", false]) // or captured + || (_x getVariable ["KPLIB_seized", false]) // or seized + } + && {isNull (attachedTo _x)} // Not attached to something +}); \ No newline at end of file diff --git a/clients/vscode/src/extension.ts b/clients/vscode/src/extension.ts index 3dc8f1f..d17282b 100644 --- a/clients/vscode/src/extension.ts +++ b/clients/vscode/src/extension.ts @@ -11,10 +11,15 @@ import * as OpenSelected from "./commands/open_selected"; import * as OpenRpt from "./commands/open_rpt"; import * as AlignEquals from "./commands/align_equals"; + // Defines the search path of your language server DLL. (.NET Core) const languageServerPaths = [ - "../../server/Debug/sqfvm_language_server.exe", - "./sqfvm_language_server.exe" + "../../server/cmake-build-debug/sqfvm_language_server/sqfvm_language_server.exe", + process.platform === 'win32' + ? process.arch === 'x64' + ? "./bin/windows-x64/sqfvm_language_server.exe" + : "./bin/windows-x86/sqfvm_language_server.exe" + : "./bin/linux/sqfvm_language_server" ] let client: languageClient.LanguageClient | undefined; @@ -30,7 +35,7 @@ async function activateLanguageServer(context: vscode.ExtensionContext) { serverModule = p; break; } catch (err) { - // Skip this path. + // Skip this path. } } if (!serverModule) throw new URIError("Cannot find the language server module."); diff --git a/clients/vscode/yarn.lock b/clients/vscode/yarn.lock index 06ac525..2b3c5d4 100644 --- a/clients/vscode/yarn.lock +++ b/clients/vscode/yarn.lock @@ -3,29 +3,29 @@ "@babel/code-frame@^7.0.0": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" - integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== dependencies: - "@babel/highlight" "^7.8.3" + "@babel/highlight" "^7.10.4" -"@babel/helper-validator-identifier@^7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" - integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== -"@babel/highlight@^7.8.3": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" - integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== dependencies: - "@babel/helper-validator-identifier" "^7.9.0" + "@babel/helper-validator-identifier" "^7.10.4" chalk "^2.0.0" js-tokens "^4.0.0" "@nodelib/fs.scandir@2.1.3": version "2.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz" integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw== dependencies: "@nodelib/fs.stat" "2.0.3" @@ -33,12 +33,12 @@ "@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2": version "2.0.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz" integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA== "@nodelib/fs.walk@^1.2.3": version "1.2.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz" integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ== dependencies: "@nodelib/fs.scandir" "2.1.3" @@ -46,7 +46,7 @@ "@types/glob@^7.1.3": version "7.1.3" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183" + resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz" integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w== dependencies: "@types/minimatch" "*" @@ -54,54 +54,49 @@ "@types/minimatch@*": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/mocha@^7.0.2": version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" + resolved "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz" integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== -"@types/node@*": - version "14.6.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.0.tgz#7d4411bf5157339337d7cff864d9ff45f177b499" - integrity sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA== - -"@types/node@^13.9.2": - version "13.9.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.2.tgz#ace1880c03594cc3e80206d96847157d8e7fa349" - integrity sha512-bnoqK579sAYrQbp73wwglccjJ4sfRdKU7WNEZ5FW4K2U6Kc0/eZ5kvXG0JKsEKFB50zrFmfFt52/cvBbZa7eXg== +"@types/node@*", "@types/node@^13.9.2": + version "13.13.15" + resolved "https://registry.npmjs.org/@types/node/-/node-13.13.15.tgz" + integrity sha512-kwbcs0jySLxzLsa2nWUAGOd/s21WU1jebrEdtzhsj1D4Yps1EOuyI1Qcu+FD56dL7NRNIJtDDjcqIG22NwkgLw== "@types/vscode@^1.43.0": - version "1.43.0" - resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.43.0.tgz#22276e60034c693b33117f1068ffaac0e89522db" - integrity sha512-kIaR9qzd80rJOxePKpCB/mdy00mz8Apt2QA5Y6rdrKFn13QNFNeP3Hzmsf37Bwh/3cS7QjtAeGSK7wSqAU0sYQ== + version "1.48.0" + resolved "https://registry.npmjs.org/@types/vscode/-/vscode-1.48.0.tgz" + integrity sha512-sZJKzsJz1gSoFXcOJWw3fnKl2sseUgZmvB4AJZS+Fea+bC/jfGPVhmFL/FfQHld/TKtukVONsmoD3Pkyx9iadg== ansi-colors@3.2.3: version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz" integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + version "3.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + version "4.1.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" anymatch@~3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz" integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== dependencies: normalize-path "^3.0.0" @@ -109,24 +104,24 @@ anymatch@~3.1.1: argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" balanced-match@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + version "2.1.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz" + integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -134,29 +129,29 @@ brace-expansion@^1.1.7: braces@^3.0.1, braces@~3.0.2: version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" browser-stdout@1.3.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + resolved "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== builtin-modules@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= camelcase@^5.0.0: version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2: version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -165,7 +160,7 @@ chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2: chokidar@3.3.0: version "3.3.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz" integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== dependencies: anymatch "~3.1.1" @@ -180,7 +175,7 @@ chokidar@3.3.0: cliui@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" + resolved "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== dependencies: string-width "^3.1.0" @@ -189,80 +184,80 @@ cliui@^5.0.0: color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-name@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= commander@^2.12.1: version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= debug@3.2.6: version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + resolved "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== dependencies: ms "^2.1.1" decamelize@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== dependencies: object-keys "^1.0.12" diff@3.5.0: version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + resolved "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== diff@^4.0.1: version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== emoji-regex@^7.0.1: version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -es-abstract@^1.17.0-next.1: - version "1.17.4" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" - integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== +es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== dependencies: es-to-primitive "^1.2.1" function-bind "^1.1.1" has "^1.0.3" has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" + is-callable "^1.2.0" + is-regex "^1.1.0" object-inspect "^1.7.0" object-keys "^1.1.1" object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" es-to-primitive@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" @@ -271,17 +266,17 @@ es-to-primitive@^1.2.1: escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= esprima@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== fast-glob@^3.2.4: version "3.2.4" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz" integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -293,69 +288,62 @@ fast-glob@^3.2.4: fastq@^1.6.0: version "1.8.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz" integrity sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q== dependencies: reusify "^1.0.4" fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" find-up@3.0.0, find-up@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + resolved "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz" integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== dependencies: locate-path "^3.0.0" flat@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" - integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== + version "4.1.1" + resolved "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz" + integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== dependencies: is-buffer "~2.0.3" fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" - integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== + version "2.1.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" + integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== get-caller-file@^2.0.1: version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -glob-parent@^5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== - dependencies: - is-glob "^4.0.1" - -glob-parent@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" - integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== +glob-parent@^5.1.0, glob-parent@~5.1.0: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob@7.1.3: version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== dependencies: fs.realpath "^1.0.0" @@ -367,7 +355,7 @@ glob@7.1.3: glob@^7.1.1: version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" @@ -379,34 +367,34 @@ glob@^7.1.1: growl@1.10.5: version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + resolved "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz" integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= has-symbols@^1.0.0, has-symbols@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== has@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" he@1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" @@ -414,80 +402,80 @@ inflight@^1.0.4: inherits@2: version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== is-binary-path@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" is-buffer@~2.0.3: version "2.0.4" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" + resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz" integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== -is-callable@^1.1.4, is-callable@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" - integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== is-date-object@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-fullwidth-code-point@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== dependencies: is-extglob "^2.1.1" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-regex@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" - integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== +is-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== dependencies: - has "^1.0.3" + has-symbols "^1.0.1" is-symbol@^1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz" integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== dependencies: has-symbols "^1.0.1" isexe@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@3.13.1, js-yaml@^3.13.1: version "3.13.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== dependencies: argparse "^1.0.7" @@ -495,32 +483,32 @@ js-yaml@3.13.1, js-yaml@^3.13.1: locate-path@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz" integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== dependencies: p-locate "^3.0.0" path-exists "^3.0.0" lodash@^4.17.15: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz" integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== dependencies: chalk "^2.4.2" merge2@^1.3.0: version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== dependencies: braces "^3.0.1" @@ -528,27 +516,27 @@ micromatch@^4.0.2: minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mkdirp@0.5.3, mkdirp@^0.5.1: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c" - integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg== +mkdirp@0.5.5, mkdirp@^0.5.3: + version "0.5.5" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" mocha@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.1.tgz#89fbb30d09429845b1bb893a830bf5771049a441" - integrity sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA== + version "7.2.0" + resolved "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz" + integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== dependencies: ansi-colors "3.2.3" browser-stdout "1.3.1" @@ -563,7 +551,7 @@ mocha@^7.1.1: js-yaml "3.13.1" log-symbols "3.0.0" minimatch "3.0.4" - mkdirp "0.5.3" + mkdirp "0.5.5" ms "2.1.1" node-environment-flags "1.0.6" object.assign "4.1.0" @@ -575,19 +563,14 @@ mocha@^7.1.1: yargs-parser "13.1.2" yargs-unparser "1.6.0" -ms@2.1.1: +ms@2.1.1, ms@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - node-environment-flags@1.0.6: version "1.0.6" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" + resolved "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz" integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== dependencies: object.getownpropertydescriptors "^2.0.3" @@ -595,22 +578,22 @@ node-environment-flags@1.0.6: normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + version "1.8.0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz" + integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@4.1.0, object.assign@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz" integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== dependencies: define-properties "^1.1.2" @@ -620,7 +603,7 @@ object.assign@4.1.0, object.assign@^4.1.0: object.getownpropertydescriptors@^2.0.3: version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" + resolved "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz" integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== dependencies: define-properties "^1.1.3" @@ -628,107 +611,107 @@ object.getownpropertydescriptors@^2.0.3: once@^1.3.0: version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" p-limit@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" - integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-locate@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz" integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== dependencies: p-limit "^2.0.0" p-try@^2.0.0: version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== path-exists@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== readdirp@~3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz" integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== dependencies: picomatch "^2.0.4" require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= require-main-filename@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== resolve@^1.3.2: - version "1.15.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" - integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + version "1.17.0" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== dependencies: path-parse "^1.0.6" reusify@^1.0.4: version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== run-parallel@^1.1.9: version "1.1.9" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz" integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== semver@^5.3.0, semver@^5.7.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== set-blocking@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= "string-width@^1.0.2 || 2": version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== dependencies: is-fullwidth-code-point "^2.0.0" @@ -736,78 +719,78 @@ sprintf-js@~1.0.2: string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + resolved "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz" integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== dependencies: emoji-regex "^7.0.1" is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string.prototype.trimleft@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" - integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" -string.prototype.trimright@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" - integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== dependencies: define-properties "^1.1.3" - function-bind "^1.1.1" + es-abstract "^1.17.5" strip-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== dependencies: ansi-regex "^4.1.0" strip-json-comments@2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= supports-color@6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz" integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== dependencies: has-flag "^3.0.0" supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" -tslib@^1.10.0, tslib@^1.8.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tslib@^1.13.0, tslib@^1.8.1: + version "1.13.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== tslint@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.0.tgz#c6c611b8ba0eed1549bf5a59ba05a7732133d851" - integrity sha512-fXjYd/61vU6da04E505OZQGb2VCN2Mq3doeWcOIryuG+eqdmFUXTYVwdhnbEu2k46LNLgUYt9bI5icQze/j0bQ== + version "6.1.3" + resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz" + integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg== dependencies: "@babel/code-frame" "^7.0.0" builtin-modules "^1.1.1" @@ -817,32 +800,32 @@ tslint@^6.1.0: glob "^7.1.1" js-yaml "^3.13.1" minimatch "^3.0.4" - mkdirp "^0.5.1" + mkdirp "^0.5.3" resolve "^1.3.2" semver "^5.3.0" - tslib "^1.10.0" + tslib "^1.13.0" tsutils "^2.29.0" tsutils@^2.29.0: version "2.29.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz" integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA== dependencies: tslib "^1.8.1" typescript@^3.8.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" - integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== + version "3.9.7" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== vscode-jsonrpc@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz#9bab9c330d89f43fc8c1e8702b5c36e058a01794" + resolved "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz" integrity sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A== vscode-languageclient@^6.1.3: version "6.1.3" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz#c979c5bb5855714a0307e998c18ca827c1b3953a" + resolved "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-6.1.3.tgz" integrity sha512-YciJxk08iU5LmWu7j5dUt9/1OLjokKET6rME3cI4BRpiF6HZlusm2ZwPt0MYJ0lV5y43sZsQHhyon2xBg4ZJVA== dependencies: semver "^6.3.0" @@ -850,7 +833,7 @@ vscode-languageclient@^6.1.3: vscode-languageserver-protocol@^3.15.3: version "3.15.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz#3fa9a0702d742cf7883cb6182a6212fcd0a1d8bb" + resolved "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz" integrity sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw== dependencies: vscode-jsonrpc "^5.0.1" @@ -858,31 +841,31 @@ vscode-languageserver-protocol@^3.15.3: vscode-languageserver-types@3.15.1: version "3.15.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz#17be71d78d2f6236d414f0001ce1ef4d23e6b6de" + resolved "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz" integrity sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ== which-module@^2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= which@1.3.1: version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" wide-align@1.1.3: version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz" integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: string-width "^1.0.2 || 2" wrap-ansi@^5.1.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz" integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== dependencies: ansi-styles "^3.2.0" @@ -891,17 +874,17 @@ wrap-ansi@^5.1.0: wrappy@1: version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= y18n@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== + version "4.0.3" + resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== yargs-parser@13.1.2, yargs-parser@^13.1.2: version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz" integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: camelcase "^5.0.0" @@ -909,7 +892,7 @@ yargs-parser@13.1.2, yargs-parser@^13.1.2: yargs-unparser@1.6.0: version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" + resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz" integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== dependencies: flat "^4.1.0" @@ -918,7 +901,7 @@ yargs-unparser@1.6.0: yargs@13.3.2, yargs@^13.3.0: version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz" integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: cliui "^5.0.0" @@ -934,5 +917,5 @@ yargs@13.3.2, yargs@^13.3.0: yarn@^1.22.4: version "1.22.4" - resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.4.tgz#01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e" + resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.4.tgz" integrity sha512-oYM7hi/lIWm9bCoDMEWgffW8aiNZXCWeZ1/tGy0DWrN6vmzjCXIKu2Y21o8DYVBUtiktwKcNoxyGl/2iKLUNGA== diff --git a/server/.run/sqfvm_language_server.run.xml b/server/.run/sqfvm_language_server.run.xml new file mode 100644 index 0000000..b6f8f59 --- /dev/null +++ b/server/.run/sqfvm_language_server.run.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 1b27732..fbc1248 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -1,67 +1,12 @@ -cmake_minimum_required(VERSION 2.8.12) +# CMakeList.txt : Top-level CMake project file, do global configuration +# and include sub-projects here. +cmake_minimum_required(VERSION 3.8) project(sqfvm_language_server) +set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_MACOSX_RPATH 1) +# Add SQF-VM +set(SQFVM_ENABLE_SQC_SUPPORT TRUE) +add_subdirectory("${PROJECT_SOURCE_DIR}/extern/runtime") -# Enable multithreaded compilation in MSVC -if (MSVC) - add_definitions(/MP) - add_definitions(/wd4100) -endif() - -# Add the filesystem library if we are building on Clang or GCC -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(ST_CXXFS_LIBS stdc++fs) -endif() - -set(ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output) -set(LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output) -set(RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output) - -include_directories("${PROJECT_SOURCE_DIR}/extern/json/include") - - -option(SQFVM_BUILD_EXECUTABLE "BUILD EXECUTABLE" OFF) -option(SQFVM_BUILD_EXECUTABLE_ARMA2_LOCALKEYWORD "BUILD ARMA2 EXECUTABLE" OFF) -option(SQFVM_BUILD_LIBRARY "BUILD LIBRARY" OFF) -option(SQFVM_BUILD_LIBRARY_SQC_SUPPORT "BUILD LIBRARY WITH SQC SUPPORT" OFF) -option(SQFVM_BUILD_STATIC_LIBRARY "BUILD STATIC LIBRARY" OFF) -option(SQFVM_BUILD_STATIC_LIBRARY_SQC_SUPPORT "BUILD STATIC LIBRARY WITH SQC SUPPORT" ON) -option(SQFVM_BUILD_EXECUTABLE_SQC_SUPPORT "BUILD EXECUTABLE WITH SQC SUPPORT" OFF) -option(SQFVM_BUILD_EXECUTABLE_FULL_DIAGNOSE "BUILD DIAGNOSE EXECUTABLE" OFF) -add_subdirectory("${PROJECT_SOURCE_DIR}/extern/vm") - -file(GLOB sqfvm_language_server_src - "${PROJECT_SOURCE_DIR}/src/*.h" "${PROJECT_SOURCE_DIR}/src/*.cpp" "${PROJECT_SOURCE_DIR}/src/*.c" - "${CMAKE_CURRENT_BINARY_DIR}/git_sha1.cpp" -) - -# Get the local git revision hash and put it into a header we can use -list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") -include(GetGitRevisionDescription) -get_git_head_revision(GIT_REFSPEC GIT_SHA1) - -configure_file("${PROJECT_SOURCE_DIR}/cmake/git_sha1.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/git_sha1.cpp" @ONLY) -list(APPEND sqfvm_language_server_src "${CMAKE_CURRENT_BINARY_DIR}/git_sha1.cpp") - - - -find_package(Threads) -option(SQFVM_LANGUAGE_SERVER_BUILD_EXECUTABLE "BUILD EXECUTABLE" ON) - - -if (SQFVM_LANGUAGE_SERVER_BUILD_EXECUTABLE) - add_executable(sqfvm_language_server ${sqfvm_language_server_src}) - target_link_libraries(sqfvm_language_server PRIVATE slibsqfvm_sqc) - target_link_libraries(sqfvm_language_server ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${ST_CXXFS_LIBS}) - target_include_directories(sqfvm_language_server PUBLIC ${PROJECT_SOURCE_DIR}/src) - - target_compile_options(sqfvm_language_server PRIVATE - $<$,$,$>: - -Wall -Wno-unknown-pragmas> - $<$: - /W4>) -endif () \ No newline at end of file +# Include sub-projects. +add_subdirectory(sqfvm_language_server) \ No newline at end of file diff --git a/server/ReadMe.md b/server/ReadMe.md new file mode 100644 index 0000000..8f9d27b --- /dev/null +++ b/server/ReadMe.md @@ -0,0 +1,8 @@ +# Build Setup (Setp-By-Step) +1. Run `git clone https://github.com/SQFvm/language-server.git --recursive` +2. Only if your IDE of choice is not supporting CMake projects. +2.1 Download and install (`cmake`)[https://cmake.org/] +2.2 Open a shell in here +2.3 Execute `cmake ./CMakeLists.txt` (optionally provide your IDE informations) +3. Take a step back and glance at your archivements for a moment +4. Start developing \ No newline at end of file diff --git a/server/extern/json b/server/extern/json deleted file mode 160000 index 3be6ee3..0000000 --- a/server/extern/json +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3be6ee35258fbf5eba61a6194fea78b0e7afacd9 diff --git a/server/extern/runtime b/server/extern/runtime new file mode 160000 index 0000000..bfbfcc4 --- /dev/null +++ b/server/extern/runtime @@ -0,0 +1 @@ +Subproject commit bfbfcc4371d386b135ccee7efc31e74dc623213b diff --git a/server/extern/vm b/server/extern/vm deleted file mode 160000 index 233ea16..0000000 --- a/server/extern/vm +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 233ea1676e2414011fc62b217abb58a381a59105 diff --git a/server/identifier.sqlite b/server/identifier.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..25f51dd --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "server", + "lockfileVersion": 2, + "requires": true, + "packages": {} +} diff --git a/server/sample.sqlite b/server/sample.sqlite new file mode 100644 index 0000000..a9c2643 Binary files /dev/null and b/server/sample.sqlite differ diff --git a/server/sqfvm_language_server/CMakeLists.txt b/server/sqfvm_language_server/CMakeLists.txt new file mode 100644 index 0000000..00c6981 --- /dev/null +++ b/server/sqfvm_language_server/CMakeLists.txt @@ -0,0 +1,77 @@ +# CMakeList.txt : CMake project for sqf-vm-language-server, include source and define +# project specific logic here. +# +cmake_minimum_required(VERSION 3.8) + +# Add source to this project's executable. +add_executable(sqfvm_language_server + lsp/jsonrpc.hpp + lsp/lspserver.hpp + language_server.hpp + language_server.cpp + main.cpp + main.hpp + uri.hpp + git_sha1.h + analysis/sqf_ast/ast_visitor.hpp + analysis/sqf_ast/sqf_ast_analyzer.cpp + analysis/sqf_ast/sqf_ast_analyzer.hpp + analysis/sqf_ast/visitors/general_visitor.hpp + analysis/analyzer.hpp + database/tables/t_diagnostic.h + database/tables/t_file.h + database/tables/t_file_history.h + database/tables/t_reference.h + database/tables/t_variable.h + database/context.hpp + database/orm_mappings.hpp + database/orm_mappings.hpp + util.hpp + analysis/sqf_ast/visitors/general_visitor.cpp + sqfvm_factory.cpp + sqfvm_factory.hpp + runtime_logger.hpp + file_system_watcher.cpp + file_system_watcher.hpp + file_system_watcher.hpp + analysis/slspp_context.cpp + analysis/slspp_context.hpp + analysis/config_ast/cfg_ast_analyzer.cpp + analysis/config_ast/cfg_ast_analyzer.hpp + database/context.cpp + database/context.cpp + analysis/sqf_ast/visitors/scripted_visitor.cpp + analysis/sqf_ast/visitors/scripted_visitor.hpp + lsp/server.cpp + lsp/server.hpp + lsp/data/enums.hpp + analysis/sqfvm_analyzer.hpp + analysis/sqfvm_analyzer.cpp + analysis/file_analyzer.hpp + analysis/db_analyzer.hpp +) + +# Set C++ Version +target_compile_features(sqfvm_language_server PUBLIC cxx_std_17) + +# Add local git revision header +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/") +include(GetGitRevisionDescription) +get_git_head_revision(GIT_REFSPEC GIT_SHA1) +configure_file("${PROJECT_SOURCE_DIR}/cmake/git_sha1.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/git_sha1.cpp" @ONLY) +target_sources(sqfvm_language_server PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/git_sha1.cpp") + +# Find packages (use vcpkg to setup packages and pass -DCMAKE_TOOLCHAIN_FILE=[...]\vcpkg\scripts\buildsystems\vcpkg.cmake into cmake) +find_package(nlohmann_json CONFIG REQUIRED) +find_package(unofficial-sqlite3 CONFIG REQUIRED) +find_package(SqliteOrm CONFIG REQUIRED) +find_package(Poco REQUIRED COMPONENTS Foundation) + +target_link_libraries(sqfvm_language_server PRIVATE slibsqfvm) +target_link_libraries(sqfvm_language_server PRIVATE nlohmann_json::nlohmann_json) +target_link_libraries(sqfvm_language_server PRIVATE unofficial::sqlite3::sqlite3) +target_link_libraries(sqfvm_language_server PRIVATE sqlite_orm::sqlite_orm) +target_link_libraries(sqfvm_language_server PRIVATE Poco::Foundation) + + +# TODO: Add tests and install targets if needed. diff --git a/server/sqfvm_language_server/analysis/analyzer.hpp b/server/sqfvm_language_server/analysis/analyzer.hpp new file mode 100644 index 0000000..a7481d1 --- /dev/null +++ b/server/sqfvm_language_server/analysis/analyzer.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "../database/context.hpp" +#include "../sqfvm_factory.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace sqfvm::language_server::analysis { + // An analyzer is responsible for analyzing a document and committing the analysis to the database. + // The analysis is split into two steps: + // 1. Analyze the document and gather references of variables, functions, etc. + // 2. Commit the analysis to the database. + class analyzer { + public: + virtual ~analyzer() = default; + + // Perform an abstract analysis of the document, gathering references of variables, functions, etc. + // to be committed to the database in the next step. + virtual void analyze() = 0; + + // Commit the analysis to the database. + virtual void commit() = 0; + }; + + class analyzer_factory { + public: + using generator_func = std::unique_ptr(*)( + std::filesystem::path ls_path, + std::filesystem::path db_path, + sqfvm_factory &, + database::tables::t_file, + std::string &); + private: + std::unordered_map m_generators; + public: + [[nodiscard]] bool has(const std::string &ext) const { + auto res = m_generators.find(ext); + return res != m_generators.end(); + } + + [[nodiscard]] std::optional> get( + const std::string &ext, + std::filesystem::path ls_path, + std::filesystem::path db_path, + sqfvm_factory &factory, + const database::tables::t_file &file, + std::string &text) const { + auto res = m_generators.find(ext); + return res == m_generators.end() + ? std::optional>{} + : res->second( + std::move(ls_path), + std::move(db_path), + factory, + file, + text); + } + + void set(const std::string &ext, generator_func generator) { + m_generators[ext] = generator; + } + }; +} \ No newline at end of file diff --git a/server/sqfvm_language_server/analysis/config_ast/cfg_ast_analyzer.cpp b/server/sqfvm_language_server/analysis/config_ast/cfg_ast_analyzer.cpp new file mode 100644 index 0000000..ca2adf0 --- /dev/null +++ b/server/sqfvm_language_server/analysis/config_ast/cfg_ast_analyzer.cpp @@ -0,0 +1,5 @@ +// +// Created by marco.silipo on 17.08.2023. +// + +#include "cfg_ast_analyzer.hpp" diff --git a/server/sqfvm_language_server/analysis/config_ast/cfg_ast_analyzer.hpp b/server/sqfvm_language_server/analysis/config_ast/cfg_ast_analyzer.hpp new file mode 100644 index 0000000..965660b --- /dev/null +++ b/server/sqfvm_language_server/analysis/config_ast/cfg_ast_analyzer.hpp @@ -0,0 +1,14 @@ +// +// Created by marco.silipo on 17.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_CFG_AST_ANALYZER_HPP +#define SQFVM_LANGUAGE_SERVER_CFG_AST_ANALYZER_HPP + + +class cfg_ast_analyzer { + +}; + + +#endif //SQFVM_LANGUAGE_SERVER_CFG_AST_ANALYZER_HPP diff --git a/server/sqfvm_language_server/analysis/db_analyzer.hpp b/server/sqfvm_language_server/analysis/db_analyzer.hpp new file mode 100644 index 0000000..b3aac28 --- /dev/null +++ b/server/sqfvm_language_server/analysis/db_analyzer.hpp @@ -0,0 +1,21 @@ +// +// Created by marco.silipo on 28.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_DB_ANALYZER_HPP +#define SQFVM_LANGUAGE_SERVER_DB_ANALYZER_HPP + +#include "analyzer.hpp" + +namespace sqfvm::language_server::analysis { + class db_analyzer : public analyzer { + protected: + database::context m_context; + public: + db_analyzer( + const std::filesystem::path &db_path) + : m_context(db_path) {}; + }; +} + +#endif //SQFVM_LANGUAGE_SERVER_DB_ANALYZER_HPP diff --git a/server/sqfvm_language_server/analysis/file_analyzer.hpp b/server/sqfvm_language_server/analysis/file_analyzer.hpp new file mode 100644 index 0000000..6b41de5 --- /dev/null +++ b/server/sqfvm_language_server/analysis/file_analyzer.hpp @@ -0,0 +1,25 @@ +// +// Created by marco.silipo on 28.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_FILE_ANALYZER_HPP +#define SQFVM_LANGUAGE_SERVER_FILE_ANALYZER_HPP + +#include "db_analyzer.hpp" + +namespace sqfvm::language_server::analysis { + class file_analyzer : public db_analyzer { + protected: + database::tables::t_file m_file; + + public: + file_analyzer( + const std::filesystem::path &db_path, + database::tables::t_file file) + : db_analyzer(db_path), + m_file(std::move(file)) {}; + }; + +} + +#endif //SQFVM_LANGUAGE_SERVER_FILE_ANALYZER_HPP diff --git a/server/sqfvm_language_server/analysis/slspp_context.cpp b/server/sqfvm_language_server/analysis/slspp_context.cpp new file mode 100644 index 0000000..a7ea488 --- /dev/null +++ b/server/sqfvm_language_server/analysis/slspp_context.cpp @@ -0,0 +1,5 @@ +// +// Created by marco.silipo on 17.08.2023. +// + +#include "slspp_context.hpp" diff --git a/server/sqfvm_language_server/analysis/slspp_context.hpp b/server/sqfvm_language_server/analysis/slspp_context.hpp new file mode 100644 index 0000000..b71375a --- /dev/null +++ b/server/sqfvm_language_server/analysis/slspp_context.hpp @@ -0,0 +1,86 @@ +// +// Created by marco.silipo on 17.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_SLSPP_CONTEXT_HPP +#define SQFVM_LANGUAGE_SERVER_SLSPP_CONTEXT_HPP + +#include +#include +#include + +namespace sqfvm::language_server::analysis { + // Context for the SQFVM-Language-Server preprocessor step, storing user-defined preprocessor instructions. + class slspp_context { + public: + enum disable_mode { + enable, + disable, + disable_line, + }; + + struct disable_context { + disable_mode kind; + size_t line; + std::string error_code; + std::string file_name; + + disable_context(disable_mode kind, std::string file_name, size_t line, std::string error_code) + : kind(kind), file_name(std::move(file_name)), line(line), error_code(std::move(error_code)) {} + }; + + private: + std::vector m_disable_contexts; + public: + void push_disable_line(const std::string& file_name, size_t line, std::string_view error_code) { + m_disable_contexts.emplace_back( + disable_mode::disable_line, + file_name, + line, + std::string(error_code.begin(), error_code.end())); + } + + void push_disable(const std::string& file_name, size_t line, std::string_view error_code) { + m_disable_contexts.emplace_back( + disable_mode::disable, + file_name, + line, + std::string(error_code.begin(), error_code.end())); + } + + void push_enable(const std::string& file_name, size_t line, std::string_view error_code) { + m_disable_contexts.emplace_back( + disable_mode::enable, + file_name, + line, + std::string(error_code.begin(), error_code.end())); + } + + bool can_report(std::string_view error_code, size_t line) { + bool can_report = true; + for (const auto &disable_context: m_disable_contexts) { + if (disable_context.line >= line) { + break; + } + if (disable_context.error_code == error_code) { + switch (disable_context.kind) { + case disable_mode::enable: + can_report = false; + break; + case disable_mode::disable: + can_report = true; + break; + case disable_mode::disable_line: + if (disable_context.line + 1 == line) + return false; + break; + } + } + } + return can_report; + } + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_SLSPP_CONTEXT_HPP diff --git a/server/sqfvm_language_server/analysis/sqf_ast/ast_visitor.hpp b/server/sqfvm_language_server/analysis/sqf_ast/ast_visitor.hpp new file mode 100644 index 0000000..f385730 --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/ast_visitor.hpp @@ -0,0 +1,102 @@ +#ifndef SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_AST_VISITOR_HPP +#define SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_AST_VISITOR_HPP + +#include "sqf_ast_analyzer.hpp" +#include "../analyzer.hpp" +#include +#include +#include +#include "parser/sqf/tokenizer.hpp" +#include "parser/sqf/astnode.hpp" + +namespace sqfvm::language_server::analysis::sqf_ast { + + class ast_visitor { + protected: + struct code_action_tuple { + database::tables::t_code_action code_action; + std::vector changes; + }; + std::vector m_references; + std::vector m_variables; + std::vector m_diagnostics; + std::vector m_hovers; + std::vector m_code_actions; + + friend class sqf_ast_analyzer; + + std::filesystem::path ls_folder_of(sqf_ast_analyzer &a) const { + return a.m_ls_path; + } + + auto is_offset_in_macro(sqf_ast_analyzer &a, size_t offset) const { + return a.is_offset_in_macro(offset); + } + auto decode_preprocessed_offset(sqf_ast_analyzer &a, size_t offset) const { + return a.decode_preprocessed_offset(offset); + } + + std::shared_ptr runtime_of(sqf_ast_analyzer &a) { + return a.m_runtime; + } + + const database::tables::t_file &file_of(sqf_ast_analyzer &a) const { + return a.m_file; + } + + const database::context &context_of(sqf_ast_analyzer &a) const { + return a.m_context; + } + + database::context &context_of(sqf_ast_analyzer &a) { + return a.m_context; + } + + const std::string_view text_of(sqf_ast_analyzer &a) const { + return a.m_preprocessed_text; + } + + std::string scope_name_of(sqf_ast_analyzer &a) const { + return a.scope_name(); + } + + [[nodiscard]] bool is_private_variable(std::string_view name) const { + return name.empty() ? false : name[0] == '_'; + } + + [[nodiscard]] bool is_global_variable(std::string_view name) const { + return name.empty() ? true : name[0] != '_'; + } + + [[nodiscard]] bool is_private_variable(const database::tables::t_variable &variable) const { + return is_private_variable(variable.variable_name); + } + + [[nodiscard]] bool is_global_variable(const database::tables::t_variable &variable) const { + return is_global_variable(variable.variable_name); + } + + + public: + virtual ~ast_visitor() = default; + + virtual void start(sqf_ast_analyzer &a) = 0; + + virtual void enter( + sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) = 0; + + virtual void exit( + sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) = 0; + + virtual void end(sqf_ast_analyzer &a) = 0; + + virtual void analyze( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &sqf_ast_analyzer, + const database::context &context) {} + }; +} +#endif // SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_AST_VISITOR_HPP \ No newline at end of file diff --git a/server/sqfvm_language_server/analysis/sqf_ast/sqf_ast_analyzer.cpp b/server/sqfvm_language_server/analysis/sqf_ast/sqf_ast_analyzer.cpp new file mode 100644 index 0000000..c8a8769 --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/sqf_ast_analyzer.cpp @@ -0,0 +1,349 @@ +#include "sqf_ast_analyzer.hpp" +#include "ast_visitor.hpp" +#include "../../util.hpp" +#include "visitors/general_visitor.hpp" +#include "visitors/scripted_visitor.hpp" +#include "../../runtime_logger.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace { + + struct visitor_id_pair { + size_t visitor_index; + uint64_t id; + + bool operator==(const visitor_id_pair &other) const { + return visitor_index == other.visitor_index + && id == other.id; + } + }; + + // from boost (functional/hash): + // see http://www.boost.org/doc/libs/1_35_0/doc/html/hash/combine.html template + template + inline void hash_combine(std::size_t &seed, T const &v) { + seed ^= std::hash()(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } +} +namespace std { + template<> + struct hash { + size_t operator()(visitor_id_pair const &v) const { + size_t seed = 0; + hash_combine(seed, v.visitor_index); + hash_combine(seed, v.id); + return seed; + } + }; +} + +void sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer::commit() { + using namespace sqlite_orm; + std::unordered_map variable_map{}; + + auto &storage = m_context.storage(); + storage.begin_transaction(); + + try { + + // Call analyze on all visitors + for (auto &visitor: m_visitors) { + visitor->analyze(*this, m_context); + } + +#pragma region Variables + // Get all variables related to this file + auto file_scope_name = scope_name(); + auto file_scope_name_like_expression = file_scope_name + "%"; + std::vector db_file_variables = storage.get_all( + where(like(&database::tables::t_variable::scope, file_scope_name_like_expression))); + std::vector file_variables_mapped{}; + + + // Map all variables to their visitor + for (auto visitor_it = m_visitors.begin(); visitor_it != m_visitors.end(); ++visitor_it) { + auto &visitor = *visitor_it; + auto visitor_diff = visitor_it - m_visitors.begin(); + auto visitor_index = static_cast(visitor_diff); + for (auto &visitor_variable: visitor->m_variables) { + if (visitor_variable.scope.length() > file_scope_name.length() + && std::string_view(visitor_variable.scope.begin(), + visitor_variable.scope.begin() + file_scope_name.length()) == + file_scope_name) { + // This variable is in this file + auto db_variable = std::find_if(db_file_variables.begin(), db_file_variables.end(), + [&](auto &db_variable) { + return db_variable.scope == visitor_variable.scope; + }); + if (db_variable != db_file_variables.end()) { + variable_map[visitor_id_pair{ + .visitor_index = visitor_index, + .id = visitor_variable.id_pk + }] = db_variable->id_pk; + file_variables_mapped.push_back(*db_variable); + } else { + auto copy = visitor_variable; + copy.id_pk = 0; + // Variable does not exist + auto insert_res = storage.insert(copy); + variable_map[visitor_id_pair{ + .visitor_index = visitor_index, + .id = visitor_variable.id_pk + }] = insert_res; + } + } else { + // This variable is not in this file + std::optional db_variable = {}; + auto result = storage.get_all( + where((c(&database::tables::t_variable::scope) == visitor_variable.scope) + and + (c(&database::tables::t_variable::variable_name) == visitor_variable.variable_name))); + db_variable = result.empty() ? std::nullopt : std::optional(result[0]); + if (db_variable.has_value()) { + // Variable already exists + variable_map[visitor_id_pair{ + .visitor_index = visitor_index, + .id = visitor_variable.id_pk + }] = db_variable->id_pk; + file_variables_mapped.push_back(*db_variable); + } else { + // Variable does not exist + auto copy = visitor_variable; + copy.id_pk = 0; + auto insert_res = storage.insert(copy); + variable_map[visitor_id_pair{ + .visitor_index = visitor_index, + .id = visitor_variable.id_pk + }] = insert_res; + } + } + } + } + + // Remove all variables that are not in the file anymore + for (auto &db_variable: db_file_variables) { + if (std::find_if(file_variables_mapped.begin(), file_variables_mapped.end(), + [&](auto &variable) { + return variable.scope == db_variable.scope; + }) == file_variables_mapped.end()) { + storage.remove_all( + where(c(&database::tables::t_reference::variable_fk) == db_variable.id_pk)); + storage.remove(db_variable.id_pk); + } + } +#pragma endregion +#pragma region References + // Remove all references related to this file + storage.remove_all( + where(c(&database::tables::t_reference::source_file_fk) == m_file.id_pk)); + + // Add all references + for (auto visitor_it = m_visitors.begin(); visitor_it != m_visitors.end(); ++visitor_it) { + auto &visitor = *visitor_it; + auto visitor_diff = visitor_it - m_visitors.begin(); + for (auto &visitor_reference: visitor->m_references) { + auto copy = visitor_reference; + copy.id_pk = 0; + copy.variable_fk = variable_map[visitor_id_pair{ + .visitor_index = static_cast(visitor_diff), + .id = visitor_reference.variable_fk + }]; + insert(storage, copy); + } + } +#pragma endregion +#pragma region Code Actions + // Remove old code actions + for (auto &it: storage.get_all( + where(c(&database::tables::t_code_action::file_fk) == m_file.id_pk))) { + storage.remove_all( + where(c(&database::tables::t_code_action_change::code_action_fk) == it.id_pk)); + } + storage.remove_all( + where(c(&database::tables::t_code_action::file_fk) == m_file.id_pk)); + + // Add new code actions + for (auto &visitor: m_visitors) { + for (auto &it: visitor->m_code_actions) { + if (it.code_action.file_fk == 0) + it.code_action.file_fk = m_file.id_pk; + auto code_action_id = storage.insert(it.code_action); + for (auto &change: it.changes) { + change.code_action_fk = code_action_id; + } + storage.insert_range(it.changes.begin(), it.changes.end()); + } + } +#pragma endregion +#pragma region Hovers + // Remove old hovers + storage.remove_all( + where(c(&database::tables::t_hover::file_fk) == m_file.id_pk)); + + // Add new hovers + std::vector hovers; + std::stringstream hover_text; + for (auto &it: m_hover_tuples) { + hover_text << "`"; + hover_text << m_text.substr(it.raw_start, it.raw_end - it.raw_start); + hover_text << "`\n"; + hover_text << "```sqf\n"; + hover_text << m_preprocessed_text.substr(it.pp_start, it.pp_end - it.pp_start); + hover_text << "\n```\n"; + + hovers.emplace_back( + 0, + m_file.id_pk, + it.start_line, + it.start_column, + it.end_line, + it.end_column, + hover_text.str()); + hover_text.str(""); + } + storage.insert_range(hovers.begin(), hovers.end()); + for (auto &visitor: m_visitors) { + for (auto &it: visitor->m_hovers) { + if (it.file_fk == 0) + it.file_fk = m_file.id_pk; + } + storage.insert_range(visitor->m_hovers.begin(), visitor->m_hovers.end()); + } +#pragma endregion +#pragma region Diagnostics + // Remove old diagnostics + storage.remove_all( + where(c(&database::tables::t_diagnostic::source_file_fk) == m_file.id_pk)); + + // Add new diagnostics + for (auto &it: m_diagnostics) { + if (it.file_fk == 0) + it.file_fk = m_file.id_pk; + if (it.source_file_fk == 0) + it.source_file_fk = m_file.id_pk; + it.is_suppressed = !m_slspp_context->can_report(it.code, it.line); + } + storage.insert_range(m_diagnostics.begin(), m_diagnostics.end()); + for (auto &visitor: m_visitors) { + for (auto &it: visitor->m_diagnostics) { + if (it.file_fk == 0) + it.file_fk = m_file.id_pk; + if (it.source_file_fk == 0) + it.source_file_fk = m_file.id_pk; + it.is_suppressed = !m_slspp_context->can_report(it.code, it.line); + } + storage.insert_range(visitor->m_diagnostics.begin(), visitor->m_diagnostics.end()); + } +#pragma endregion + + // Remove outdated flag + m_file.is_outdated = false; + storage.update(m_file); + + storage.commit(); + } + catch (std::exception &e) { + storage.rollback(); + throw e; + } + + +} + +void sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer::insert( + database::context::storage_t &storage, + const database::tables::t_reference ©) const { + try { + storage.insert(copy); + } catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to insert into " << database::tables::t_reference::table_name << "." << "\n"; + sstream << "What: " << "\n"; + sstream << " " << e.what() << "\n"; + sstream << "Data: " << "\n"; + sstream << " file_fk: " << copy.file_fk << "\n"; + sstream << " variable_fk: " << copy.variable_fk << "\n"; + sstream << " access: " << static_cast(copy.access) << "\n"; + sstream << " line: " << copy.line << "\n"; + sstream << " column: " << copy.column << "\n"; + sstream << " offset: " << copy.offset << "\n"; + sstream << " length: " << copy.length << "\n"; + sstream << " types: " << static_cast(copy.types); + + throw std::runtime_error(sstream.str()); + } +} + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "misc-no-recursion" + +void sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer::recurse( + const sqf::parser::sqf::bison::astnode &parent) { + for (auto &visitor: m_visitors) { + visitor->enter(*this, parent, m_descend_ast_nodes); + } + m_descend_ast_nodes.push_back(&parent); + for (auto &child: parent.children) { + recurse(child); + } + m_descend_ast_nodes.pop_back(); + for (auto &visitor: m_visitors) { + visitor->exit(*this, parent, m_descend_ast_nodes); + } +} + +#pragma clang diagnostic pop + +void sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer::analyze(sqf::runtime::runtime &runtime) { + for (auto &visitor: m_visitors) { + visitor->start(*this); + } + auto parser = sqf::parser::sqf::parser(runtime.get_logger()); + auto tokenizer = sqf::parser::sqf::tokenizer(m_preprocessed_text.begin(), m_preprocessed_text.end(), m_file.path); + sqf::parser::sqf::bison::astnode root; + auto success = parser.get_tree(runtime, tokenizer, &root); + if (!success) { + return; + } + recurse(root); + for (auto &visitor: m_visitors) { + visitor->end(*this); + } +} + +sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer::sqf_ast_analyzer( + const std::filesystem::path &db_path, + database::tables::t_file file, + sqfvm_factory &factory, + std::string text, + std::filesystem::path ls_path) + : sqfvm_analyzer(db_path, std::move(file), factory, std::move(text)), + m_ls_path(std::move(ls_path)) { + m_visitors.push_back(new visitors::general_visitor()); + m_visitors.push_back(new visitors::scripted_visitor()); +} + +sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer::~sqf_ast_analyzer() { + for (auto v: m_visitors) { + delete v; + } +} + +void sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer::macro_resolved( + ::sqf::parser::preprocessor::impl_default::macro_resolved_data orig_start, + ::sqf::parser::preprocessor::impl_default::macro_resolved_data orig_end, + size_t pp_start, + size_t pp_end, + sqf::runtime::runtime &runtime, sqf::runtime::parser::preprocessor::context &local_fileinfo, + sqf::runtime::parser::preprocessor::context &original_fileinfo, const sqf::runtime::parser::macro &m, + const std::unordered_map ¶m_map) { + m_hover_tuples.emplace_back(orig_start.offset, orig_end.offset, pp_start, pp_end, orig_start.line, orig_start.column, orig_end.line, orig_end.column); +} diff --git a/server/sqfvm_language_server/analysis/sqf_ast/sqf_ast_analyzer.hpp b/server/sqfvm_language_server/analysis/sqf_ast/sqf_ast_analyzer.hpp new file mode 100644 index 0000000..6b58b64 --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/sqf_ast_analyzer.hpp @@ -0,0 +1,83 @@ +#ifndef SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_SQF_AST_ANALYZER_HPP +#define SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_SQF_AST_ANALYZER_HPP + +#include "../slspp_context.hpp" +#include "../sqfvm_analyzer.hpp" +#include "../../sqfvm_factory.hpp" +#include +#include +#include + +namespace sqfvm::language_server::analysis::sqf_ast { + class ast_visitor; + + class sqf_ast_analyzer : public sqfvm_analyzer { + friend class ast_visitor; + + struct hover_tuple { + size_t raw_start; + size_t raw_end; + size_t pp_start; + size_t pp_end; + uint64_t start_line; + uint64_t start_column; + uint64_t end_line; + uint64_t end_column; + }; + std::vector m_diagnostics; + std::vector m_hover_tuples; + std::vector m_visitors; + std::vector m_descend_ast_nodes; + std::filesystem::path m_ls_path; + + void recurse(const sqf::parser::sqf::bison::astnode &parent); + + [[nodiscard]] std::string scope_name() const { + std::string scope{}; + auto id = std::to_string(m_file.id_pk); + scope.reserve("scope@"sv.length() + id.length() + "://"sv.length()); + scope.append("scope@"sv); + scope.append(id); + scope.append("://"sv); + return scope; + } + + void insert( + database::context::storage_t &storage, + const database::tables::t_reference ©) const; + + protected: + void report_diagnostic(const database::tables::t_diagnostic &diagnostic) override { + m_diagnostics.push_back(diagnostic); + } + + void macro_resolved( + ::sqf::parser::preprocessor::impl_default::macro_resolved_data orig_start, + ::sqf::parser::preprocessor::impl_default::macro_resolved_data orig_end, size_t pp_start, + size_t pp_end, ::sqf::runtime::runtime &runtime, + ::sqf::runtime::parser::preprocessor::context &local_fileinfo, + ::sqf::runtime::parser::preprocessor::context &original_fileinfo, + const ::sqf::runtime::parser::macro &m, + const std::unordered_map ¶m_map) override; + + public: + sqf_ast_analyzer( + const std::filesystem::path &db_path, + database::tables::t_file file, + sqfvm_factory &factory, + std::string text, + std::filesystem::path ls_path); + + ~sqf_ast_analyzer() override; + + // Perform an abstract analysis of the document, gathering references of variables, functions, etc. + // to be committed to the database in the next step. + void analyze(sqf::runtime::runtime &runtime) override; + + // Commit the analysis to the database. + void commit() override; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_SQF_AST_ANALYZER_HPP diff --git a/server/sqfvm_language_server/analysis/sqf_ast/visitors/general_visitor.cpp b/server/sqfvm_language_server/analysis/sqf_ast/visitors/general_visitor.cpp new file mode 100644 index 0000000..824db8e --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/visitors/general_visitor.cpp @@ -0,0 +1,1461 @@ +#include "general_visitor.hpp" +#include "../sqf_ast_analyzer.hpp" + +#include + +#define LINE_OFFSET -1 + +using namespace sqfvm::language_server::database::tables; +using namespace std::string_view_literals; + +namespace { + + t_diagnostic diag_private_variable_value_is_never_used_001( + uint64_t self_file_id, + const t_variable &variable, + const t_reference &reference) { + return { + .id_pk = {}, + .file_fk = reference.file_fk, + .source_file_fk = self_file_id, + .line = reference.line + LINE_OFFSET, + .column = reference.column, + .offset = reference.offset, + .length = reference.length, + .severity = t_diagnostic::info, + .message = "Private variable '" + variable.variable_name + "' is never used", + .content = variable.variable_name, + .code = "VV-001", + }; + } + + t_diagnostic diag_global_variable_value_is_never_used_in_file_002( + uint64_t self_file_id, + const t_variable &variable, + const t_reference &reference) { + return { + .id_pk = {}, + .file_fk = reference.file_fk, + .source_file_fk = self_file_id, + .line = reference.line + LINE_OFFSET, + .column = reference.column, + .offset = reference.offset, + .length = reference.length, + .severity = t_diagnostic::verbose, + .message = "Global variable '" + variable.variable_name + "' is never used in this file", + .content = variable.variable_name, + .code = "VV-002", + }; + } + + t_diagnostic diag_private_variable_is_never_assigned_003( + uint64_t self_file_id, + const t_variable &variable, + const t_reference &reference) { + return { + .id_pk = {}, + .file_fk = reference.file_fk, + .source_file_fk = self_file_id, + .line = reference.line + LINE_OFFSET, + .column = reference.column, + .offset = reference.offset, + .length = reference.length, + .severity = t_diagnostic::warning, + .message = "Private variable '" + variable.variable_name + "' is never assigned", + .content = variable.variable_name, + .code = "VV-003", + }; + } + + + t_diagnostic diag_global_variable_never_assigned_in_file_in_file_004( + uint64_t self_file_id, + const t_variable &variable, + const t_reference &reference) { + return { + .id_pk = {}, + .file_fk = reference.file_fk, + .source_file_fk = self_file_id, + .line = reference.line + LINE_OFFSET, + .column = reference.column, + .offset = reference.offset, + .length = reference.length, + .severity = t_diagnostic::verbose, + .message = "Global variable '" + variable.variable_name + "' is never assigned in this file", + .content = variable.variable_name, + .code = "VV-004", + }; + } + + t_diagnostic diag_variable_name_not_similar_005( + uint64_t self_file_id, + const t_variable &variable, + const t_reference &reference, + std::string_view reference_content) { + std::string content{}; + content.reserve( + "Expected: "sv.length() + + variable.variable_name.length() + + ", got: "sv.length() + + reference_content.length() + ); + content.append("Expected: "sv); + content.append(variable.variable_name); + content.append(", got: "sv); + content.append(reference_content); + return { + .id_pk = {}, + .file_fk = reference.file_fk, + .source_file_fk = self_file_id, + .line = reference.line + LINE_OFFSET, + .column = reference.column, + .offset = reference.offset, + .length = reference.length, + .severity = t_diagnostic::info, + .message = "Variable name differs from declared name", + .content = content, + .code = "VV-005", + }; + } + + t_diagnostic diag_type_missmatch_006( + uint64_t self_file_id, + const uint64_t file_fk, + const sqf::parser::sqf::bison::astnode &node, + sqf::parser::sqf::bison::astkind expected_type) { + std::string content{}; + auto got_type_str = to_string_view(node.kind); + auto expected_type_str = to_string_view(expected_type); + content.reserve( + "Expected "sv.length() + + expected_type_str.length() + + ", got "sv.length() + + got_type_str.length() + ); + content.append("Expected: "sv); + content.append(expected_type_str); + content.append(", got: "sv); + content.append(got_type_str); + return { + .id_pk = {}, + .file_fk = file_fk, + .source_file_fk = self_file_id, + .line = node.token.line + LINE_OFFSET, + .column = node.token.column, + .offset = node.token.offset, + .length = node.token.contents.length(), + .severity = t_diagnostic::error, + .message = "Node type mismatch", + .content = content, + .code = "VV-006", + }; + } + + t_diagnostic diag_type_missmatch_006( + uint64_t self_file_id, + const uint64_t file_fk, + const sqf::parser::sqf::bison::astnode &node, + sqf::parser::sqf::bison::astkind expected_type_1, + sqf::parser::sqf::bison::astkind expected_type_2) { + std::string content{}; + auto got_type_str = to_string_view(node.kind); + auto expected_type_1_str = to_string_view(expected_type_1); + auto expected_type_2_str = to_string_view(expected_type_2); + content.reserve( + "Expected "sv.length() + + expected_type_1_str.length() + + " or "sv.length() + + expected_type_2_str.length() + + ", got "sv.length() + + got_type_str.length() + ); + content.append("Expected: "sv); + content.append(expected_type_1_str); + content.append(" or "sv); + content.append(expected_type_2_str); + content.append(", got: "sv); + content.append(got_type_str); + return { + .id_pk = {}, + .file_fk = file_fk, + .source_file_fk = self_file_id, + .line = node.token.line + LINE_OFFSET, + .column = node.token.column, + .offset = node.token.offset, + .length = node.token.contents.length(), + .severity = t_diagnostic::error, + .message = "Node type mismatch", + .content = content, + .code = "VV-006", + }; + } + + t_diagnostic diag_cannot_determine_variable_from_expression_007( + uint64_t self_file_id, + const uint64_t file_fk, + const sqf::parser::sqf::bison::astnode &node, + sqf::parser::sqf::bison::astkind expected_type) { + std::string content{}; + auto got_type_str = to_string_view(node.kind); + auto expected_type_str = to_string_view(expected_type); + content.reserve( + "Expected "sv.length() + + expected_type_str.length() + + ", got "sv.length() + + got_type_str.length() + ); + content.append("Expected: "sv); + content.append(expected_type_str); + content.append(", got: "sv); + content.append(got_type_str); + return { + .id_pk = {}, + .file_fk = file_fk, + .source_file_fk = self_file_id, + .line = node.token.line + LINE_OFFSET, + .column = node.token.column, + .offset = node.token.offset, + .length = node.token.contents.length(), + .severity = t_diagnostic::verbose, + .message = "The provided type cannot be used to determine the variable name for referral", + .content = content, + .code = "VV-007", + }; + } + + t_diagnostic diag_cannot_determine_variable_from_expression_007( + uint64_t self_file_id, + const uint64_t file_fk, + const sqf::parser::sqf::bison::astnode &node, + sqf::parser::sqf::bison::astkind expected_type_1, + sqf::parser::sqf::bison::astkind expected_type_2) { + std::string content{}; + auto got_type_str = to_string_view(node.kind); + auto expected_type_1_str = to_string_view(expected_type_1); + auto expected_type_2_str = to_string_view(expected_type_2); + content.reserve( + "Expected "sv.length() + + expected_type_1_str.length() + + " or "sv.length() + + expected_type_2_str.length() + + ", got "sv.length() + + got_type_str.length() + ); + content.append("Expected: "sv); + content.append(expected_type_1_str); + content.append(" or "sv); + content.append(expected_type_2_str); + content.append(", got: "sv); + content.append(got_type_str); + return { + .id_pk = {}, + .file_fk = file_fk, + .source_file_fk = self_file_id, + .line = node.token.line + LINE_OFFSET, + .column = node.token.column, + .offset = node.token.offset, + .length = node.token.contents.length(), + .severity = t_diagnostic::verbose, + .message = "The provided type cannot be used to determine the variable name for referral", + .content = content, + .code = "VV-007", + }; + } + + t_diagnostic diag_needless_brackets_008( + uint64_t self_file_id, + uint64_t file_fk, + const sqf::parser::sqf::bison::astnode &left_parentheses, + bool is_left) { + return { + .id_pk = {}, + .file_fk = file_fk, + .source_file_fk = self_file_id, + .line = left_parentheses.token.line + LINE_OFFSET, + .column = left_parentheses.token.column, + .offset = left_parentheses.token.offset, + .length = left_parentheses.token.contents.length(), + .severity = is_left ? t_diagnostic::info : t_diagnostic::verbose, + .message = "The round brackets can safely be removed", + .content = "The round brackets can safely be removed", + .code = "VV-008", + }; + } + + t_diagnostic diag_private_variable_is_shadowing_other_private_variable_009( + uint64_t self_file_id, + const t_variable &shadowed_variable, + const t_reference &shadowing_reference) { + std::string message{}; + message.reserve( + "Private variable '"sv.length() + + shadowed_variable.variable_name.length() + + "' is shadowing a previously declared private variable"sv.length() + ); + message.append("Private variable '"sv); + message.append(shadowed_variable.variable_name); + message.append("' is shadowing a previously declared private variable"sv); + + return { + .id_pk = {}, + .file_fk = shadowing_reference.file_fk, + .source_file_fk = self_file_id, + .line = shadowing_reference.line + LINE_OFFSET, + .column = shadowing_reference.column, + .offset = shadowing_reference.offset, + .length = shadowing_reference.length, + .severity = t_diagnostic::warning, + .message = message, + .content = shadowed_variable.variable_name, + .code = "VV-009", + }; + } + + t_diagnostic diag_private_variable_is_shadowed_by_another_private_variable_009( + uint64_t self_file_id, + const t_variable &shadowed_variable, + const t_reference &shadowing_reference) { + std::string message{}; + message.reserve( + "Private variable '"sv.length() + + shadowed_variable.variable_name.length() + + "' is shadowed"sv.length() + ); + message.append("Private variable '"sv); + message.append(shadowed_variable.variable_name); + message.append("' is shadowed"sv); + + return { + .id_pk = {}, + .file_fk = shadowing_reference.file_fk, + .source_file_fk = self_file_id, + .line = shadowing_reference.line + LINE_OFFSET, + .column = shadowing_reference.column, + .offset = shadowing_reference.offset, + .length = shadowing_reference.length, + .severity = t_diagnostic::verbose, + .message = message, + .content = shadowed_variable.variable_name, + .code = "VV-009", + }; + } +} + + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::start(sqf_ast_analyzer &a) { + // Push initial scope + m_scope_stack.push_back({0, scope_name_of(a)}); + + // Push initial namespace + push_namespace("missionNamespace"); + + t_reference reference{}; + reference.line = 0; + reference.column = 0; + reference.offset = 0; + reference.length = 0; + reference.file_fk = file_of(a).id_pk; + auto variable = get_or_create_variable("_this"); + reference.variable_fk = variable.id_pk; + reference.access = t_reference::access_flags::set; + reference.is_magic_variable = true; + m_references.push_back(reference); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::enter( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes +) { + switch (node.kind) { + case ::sqf::parser::sqf::bison::astkind::CODE: { + expression_handle_needless_parentheses(a, node, parent_nodes); + if (is_right_side_of_assignment(parent_nodes, node)) { + m_assignment_candidate->types = t_reference::type_flags::code; + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(*m_assignment_candidate); + m_assignment_candidate = {}; + } + + // Push scope info + push_scope(a, node, parent_nodes); + add_magic_variables_to_current_scope(a, node, parent_nodes); + break; + } + case ::sqf::parser::sqf::bison::astkind::EXP0: + case ::sqf::parser::sqf::bison::astkind::EXP1: + case ::sqf::parser::sqf::bison::astkind::EXP2: + case ::sqf::parser::sqf::bison::astkind::EXP3: + case ::sqf::parser::sqf::bison::astkind::EXP4: + case ::sqf::parser::sqf::bison::astkind::EXP5: + case ::sqf::parser::sqf::bison::astkind::EXP6: + case ::sqf::parser::sqf::bison::astkind::EXP7: + case ::sqf::parser::sqf::bison::astkind::EXP8: + case ::sqf::parser::sqf::bison::astkind::EXP9: + case ::sqf::parser::sqf::bison::astkind::EXPU: { + expression_handle_needless_parentheses(a, node, parent_nodes); + auto contents = node.token.contents; + if (iequal(contents, "private")) { + expression_handling_of_private(a, node); + } else if (iequal(contents, "params")) { + expression_handling_of_params(a, node); + } else if (iequal(contents, "getVariable")) { + expression_handling_of_getvariable(a, node); + } else if (iequal(contents, "setVariable")) { + expression_handling_of_setvariable(a, node); + } else if (iequal(contents, "isNil")) { + expression_handling_of_isnil(a, node); + } else if (iequal(contents, "for")) { + // for being a unary operator, only the first child is relevant and always present. + // First child also always must be a string, otherwise pushing diagnostic 006 is required. + auto first_child = node.children.front(); + if (first_child.kind != ::sqf::parser::sqf::bison::astkind::STRING) { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + node, + ::sqf::parser::sqf::bison::astkind::STRING)); + } else { + auto variable_name = first_child.token.contents; + auto variable = get_or_create_variable(sqf_destringify(variable_name)); + auto reference = make_reference(a, first_child, variable, t_reference::access_flags::set); + reference.is_declaration = true; + m_references.push_back(reference); + } + } + break; + } + case ::sqf::parser::sqf::bison::astkind::EXPN: { + expression_handle_needless_parentheses(a, node, parent_nodes); + if (m_assignment_candidate.has_value() + && iequal(node.token.contents, "nil") + && is_right_side_of_assignment(parent_nodes, node)) { + m_assignment_candidate->types = t_reference::type_flags::nil; + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(*m_assignment_candidate); + m_assignment_candidate = {}; + } + break; + } + case ::sqf::parser::sqf::bison::astkind::BOOLEAN_FALSE: + case ::sqf::parser::sqf::bison::astkind::BOOLEAN_TRUE: { + expression_handle_needless_parentheses(a, node, parent_nodes); + if (m_assignment_candidate.has_value() && is_right_side_of_assignment(parent_nodes, node)) { + m_assignment_candidate->types = t_reference::type_flags::boolean; + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(*m_assignment_candidate); + m_assignment_candidate = {}; + } + break; + } + case ::sqf::parser::sqf::bison::astkind::ARRAY: { + expression_handle_needless_parentheses(a, node, parent_nodes); + if (m_assignment_candidate.has_value() && is_right_side_of_assignment(parent_nodes, node)) { + m_assignment_candidate->types = t_reference::type_flags::array; + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(*m_assignment_candidate); + m_assignment_candidate = {}; + } + break; + } + case ::sqf::parser::sqf::bison::astkind::HEXNUMBER: + case ::sqf::parser::sqf::bison::astkind::NUMBER: { + expression_handle_needless_parentheses(a, node, parent_nodes); + if (m_assignment_candidate.has_value() && is_right_side_of_assignment(parent_nodes, node)) { + m_assignment_candidate->types = t_reference::type_flags::scalar; + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(*m_assignment_candidate); + m_assignment_candidate = {}; + } + break; + } + case ::sqf::parser::sqf::bison::astkind::STRING: { + expression_handle_needless_parentheses(a, node, parent_nodes); + if (m_assignment_candidate.has_value() && is_right_side_of_assignment(parent_nodes, node)) { + m_assignment_candidate->types = t_reference::type_flags::string; + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(*m_assignment_candidate); + m_assignment_candidate = {}; + } + break; + } + case ::sqf::parser::sqf::bison::astkind::IDENT: { + expression_handle_needless_parentheses(a, node, parent_nodes); + if (m_assignment_candidate.has_value()) { + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(*m_assignment_candidate); + m_assignment_candidate = {}; + } + } // Fallthrough + case ::sqf::parser::sqf::bison::astkind::ASSIGNMENT: + case ::sqf::parser::sqf::bison::astkind::ASSIGNMENT_LOCAL: { + auto is_declaration = node.kind == ::sqf::parser::sqf::bison::astkind::ASSIGNMENT_LOCAL + || (parent_nodes.size() > 1 + && parent_nodes.back()->kind == + ::sqf::parser::sqf::bison::astkind::ASSIGNMENT_LOCAL); + auto reference = make_reference(a, node); + auto variable = get_or_create_variable(node.token.contents, is_declaration); + reference.variable_fk = variable.id_pk; + + // Check if we are on the left side of an assignment + if (is_left_side_of_assignment(parent_nodes, node)) { + // Yes - We set this IDENT so put it into temp for later evaluation in parent or the code following + reference.is_declaration = is_declaration; + reference.access = t_reference::access_flags::set; + m_assignment_candidate = reference; + } else { + // No - We get this IDENT + reference.access = t_reference::access_flags::get; + reference.id_pk = m_references.size() + 1; + m_references.push_back(reference); + } + break; + } + default: + break; + } +} + +sqfvm::language_server::database::tables::t_reference +sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::make_reference( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const t_variable &variable, + const t_reference::access_flags &access) { + auto reference = make_reference(a, node); + reference.variable_fk = variable.id_pk; + reference.access = access; + return reference; +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::expression_handling_of_isnil( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node) { + // Call only has a right side argument + auto right_side = node.children.back(); + // Right side always must be a string + if (right_side.kind == sqf::parser::sqf::bison::astkind::STRING) { + auto reference = make_reference(a, right_side); + auto variable = get_or_create_variable(sqf_destringify(right_side.token.contents)); + reference.variable_fk = variable.id_pk; + reference.access = t_reference::access_flags::get; + m_references.push_back(reference); + } else if (right_side.kind == sqf::parser::sqf::bison::astkind::CODE) { + // Handled by normal code block handling, left in for completeness + } else if (right_side.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + right_side, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::CODE)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + right_side, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::CODE)); + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::expression_handling_of_getvariable( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node) { + // Call may have a left side argument but always has a right side argument + auto right_side = node.children.back(); + // Right side may be an identifier or a code block, or another expression + // (namely: namespace getVariable string). We do not care for code blocks + // but do care for identifiers and expressions + std::optional variable_node; + if (right_side.kind == sqf::parser::sqf::bison::astkind::STRING) { + variable_node = {right_side}; + } else if (right_side.kind == sqf::parser::sqf::bison::astkind::ARRAY) { + auto get_variable_right_side_array = right_side.children.front(); + if (get_variable_right_side_array.kind == sqf::parser::sqf::bison::astkind::STRING) { + variable_node = {get_variable_right_side_array}; + } else if (get_variable_right_side_array.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + get_variable_right_side_array, + ::sqf::parser::sqf::bison::astkind::STRING)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + get_variable_right_side_array, + ::sqf::parser::sqf::bison::astkind::STRING)); + } + } else if (right_side.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + right_side, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::ARRAY)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + right_side, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::ARRAY)); + } + if (variable_node.has_value()) { + auto reference = make_reference(a, variable_node.value()); + auto variable = get_or_create_variable(sqf_destringify(variable_node->token.contents)); + reference.variable_fk = variable.id_pk; + reference.access = t_reference::access_flags::get; + m_references.push_back(reference); + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::expression_handling_of_setvariable( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node) { + // Call may have a left side argument but always has a right side argument + auto right_side = node.children.back(); + // Right side may be an identifier or a code block, or another expression + // (namely: namespace getVariable string). We do not care for code blocks + // but do care for identifiers and expressions + std::optional variable_node; + if (right_side.kind == sqf::parser::sqf::bison::astkind::ARRAY) { + auto set_variable_right_side_array = right_side.children.front(); + if (set_variable_right_side_array.kind == sqf::parser::sqf::bison::astkind::STRING) { + variable_node = {set_variable_right_side_array}; + } else if (set_variable_right_side_array.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + set_variable_right_side_array, + ::sqf::parser::sqf::bison::astkind::STRING)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + set_variable_right_side_array, + ::sqf::parser::sqf::bison::astkind::STRING)); + } + } else if (right_side.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + right_side, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::ARRAY)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + right_side, + ::sqf::parser::sqf::bison::astkind::STRING)); + } + if (variable_node.has_value()) { + auto reference = make_reference(a, variable_node.value()); + auto variable = get_or_create_variable(sqf_destringify(variable_node->token.contents)); + reference.variable_fk = variable.id_pk; + reference.access = t_reference::access_flags::set; + m_references.push_back(reference); + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::expression_handle_needless_parentheses( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) { + if (parent_nodes.size() < 2 || + parent_nodes[parent_nodes.size() - 1]->kind != sqf::parser::sqf::bison::astkind::EXP_GROUP) + return; + auto &group_parent = parent_nodes[parent_nodes.size() - 1]; + auto &actual_parent = parent_nodes[parent_nodes.size() - 2]; + if (actual_parent->kind == sqf::parser::sqf::bison::astkind::EXP_GROUP) { + auto &left_bracket = group_parent; + auto &right_bracket = group_parent->children.back(); + // ToDo: Extract in separate function (CLion refuses to do so right now .... again) + m_diagnostics.push_back(diag_needless_brackets_008( + file_id_of(a, node), + file_of(a).id_pk, + *left_bracket, + true)); + m_diagnostics.push_back(diag_needless_brackets_008( + file_id_of(a, node), + file_of(a).id_pk, + right_bracket, + false)); + + if (!is_offset_in_macro(a, left_bracket->token.offset) && !is_offset_in_macro(a, right_bracket.token.offset)) { + code_action_tuple action{}; + action.code_action.kind = database::tables::t_code_action::generic; + action.code_action.identifier = "VV-008"; + action.code_action.text = "Remove needless brackets"; + auto left_bracked_decoded = decode_preprocessed_offset(a, left_bracket->token.offset); + auto left_start_line = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.line + : left_bracket->token.line) + + LINE_OFFSET; + auto left_start_column = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.column + : left_bracket->token.column); + auto left_end_line = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.line + : left_bracket->token.line) + + LINE_OFFSET; + auto left_end_column = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.column + : left_bracket->token.column) + 1; + + action.changes.push_back({ + .operation = database::tables::t_code_action_change::file_change, + .path = file_of(a).path, + .start_line = left_start_line, + .start_column = left_start_column, + .end_line = left_end_line, + .end_column = left_end_column, + .content = "", + }); + auto right_bracked_decoded = decode_preprocessed_offset(a, right_bracket.token.offset); + auto right_start_line = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.line + : right_bracket.token.line) + + LINE_OFFSET; + auto right_start_column = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.column + : right_bracket.token.column); + auto right_end_line = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.line + : right_bracket.token.line) + + LINE_OFFSET; + auto right_end_column = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.column + : right_bracket.token.column) + 1; + + action.changes.push_back({ + .operation = database::tables::t_code_action_change::file_change, + .path = file_of(a).path, + .start_line = right_start_line, + .start_column = right_start_column, + .end_line = right_end_line, + .end_column = right_end_column, + .content = "", + }); + m_code_actions.push_back(action); + } + // End of ToDo + return; + } + uint8_t current_precedence = 0; + switch (node.kind) { + case sqf::parser::sqf::bison::astkind::EXP0: + current_precedence = 0; + break; + case sqf::parser::sqf::bison::astkind::EXP1: + current_precedence = 1; + break; + case sqf::parser::sqf::bison::astkind::EXP2: + current_precedence = 2; + break; + case sqf::parser::sqf::bison::astkind::EXP3: + current_precedence = 3; + break; + case sqf::parser::sqf::bison::astkind::EXP4: + current_precedence = 4; + break; + case sqf::parser::sqf::bison::astkind::EXP5: + current_precedence = 5; + break; + case sqf::parser::sqf::bison::astkind::EXP6: + current_precedence = 6; + break; + case sqf::parser::sqf::bison::astkind::EXP7: + current_precedence = 7; + break; + case sqf::parser::sqf::bison::astkind::EXP8: + current_precedence = 8; + break; + case sqf::parser::sqf::bison::astkind::EXP9: + current_precedence = 9; + break; + case sqf::parser::sqf::bison::astkind::EXPU: + current_precedence = 10; + break; + case sqf::parser::sqf::bison::astkind::EXPN: + current_precedence = 11; + break; + default: + current_precedence = 12; + break; + } + uint8_t actual_parent_precedence = 0; + switch (actual_parent->kind) { + case sqf::parser::sqf::bison::astkind::EXP0: + actual_parent_precedence = 0; + break; + case sqf::parser::sqf::bison::astkind::EXP1: + actual_parent_precedence = 1; + break; + case sqf::parser::sqf::bison::astkind::EXP2: + actual_parent_precedence = 2; + break; + case sqf::parser::sqf::bison::astkind::EXP3: + actual_parent_precedence = 3; + break; + case sqf::parser::sqf::bison::astkind::EXP4: + actual_parent_precedence = 4; + break; + case sqf::parser::sqf::bison::astkind::EXP5: + actual_parent_precedence = 5; + break; + case sqf::parser::sqf::bison::astkind::EXP6: + actual_parent_precedence = 6; + break; + case sqf::parser::sqf::bison::astkind::EXP7: + actual_parent_precedence = 7; + break; + case sqf::parser::sqf::bison::astkind::EXP8: + actual_parent_precedence = 8; + break; + case sqf::parser::sqf::bison::astkind::EXP9: + actual_parent_precedence = 9; + break; + case sqf::parser::sqf::bison::astkind::EXPU: + actual_parent_precedence = 10; + break; + case sqf::parser::sqf::bison::astkind::EXPN: + actual_parent_precedence = 11; + break; + default: + actual_parent_precedence = 12; + break; + } + if (current_precedence > actual_parent_precedence) { + auto &left_bracket = group_parent; + auto &right_bracket = group_parent->children.back(); + // ToDo: Extract in separate function (CLion refuses to do so right now .... again) + m_diagnostics.push_back(diag_needless_brackets_008( + file_id_of(a, node), + file_of(a).id_pk, + *left_bracket, + true)); + m_diagnostics.push_back(diag_needless_brackets_008( + file_id_of(a, node), + file_of(a).id_pk, + right_bracket, + false)); + + if (!is_offset_in_macro(a, left_bracket->token.offset) && !is_offset_in_macro(a, right_bracket.token.offset)) { + code_action_tuple action{}; + action.code_action.kind = database::tables::t_code_action::generic; + action.code_action.identifier = "VV-008"; + action.code_action.text = "Remove needless brackets"; + auto left_bracked_decoded = decode_preprocessed_offset(a, left_bracket->token.offset); + auto left_start_line = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.line + : left_bracket->token.line) + + LINE_OFFSET; + auto left_start_column = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.column + : left_bracket->token.column); + auto left_end_line = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.line + : left_bracket->token.line) + + LINE_OFFSET; + auto left_end_column = (left_bracked_decoded.has_value() + ? left_bracked_decoded.value().resolved.column + : left_bracket->token.column) + 1; + + action.changes.push_back({ + .operation = database::tables::t_code_action_change::file_change, + .path = file_of(a).path, + .start_line = left_start_line, + .start_column = left_start_column, + .end_line = left_end_line, + .end_column = left_end_column, + .content = "", + }); + auto right_bracked_decoded = decode_preprocessed_offset(a, right_bracket.token.offset); + auto right_start_line = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.line + : right_bracket.token.line) + + LINE_OFFSET; + auto right_start_column = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.column + : right_bracket.token.column); + auto right_end_line = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.line + : right_bracket.token.line) + + LINE_OFFSET; + auto right_end_column = (right_bracked_decoded.has_value() + ? right_bracked_decoded.value().resolved.column + : right_bracket.token.column) + 1; + + action.changes.push_back({ + .operation = database::tables::t_code_action_change::file_change, + .path = file_of(a).path, + .start_line = right_start_line, + .start_column = right_start_column, + .end_line = right_end_line, + .end_column = right_end_column, + .content = "", + }); + m_code_actions.push_back(action); + } + // End of ToDo + return; + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::expression_handling_of_params( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node) { + // params may have a left value and always has a right value + auto right_value = node.children.back(); + // Params right value is always an array but the array can contain a mix of strings and arrays, with + // the nested arrays containing the string we are looking for at the start of the array. + std::vector variable_nodes{}; + if (right_value.kind == sqf::parser::sqf::bison::astkind::ARRAY) { + for (auto &child: right_value.children) { + if (child.kind == sqf::parser::sqf::bison::astkind::STRING) { + variable_nodes.push_back(child); + } else if (child.kind == sqf::parser::sqf::bison::astkind::ARRAY) { + if (child.children.front().kind == sqf::parser::sqf::bison::astkind::STRING) { + variable_nodes.push_back(child.children.front()); + } else if (child.children.front().kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + child.children.front(), + ::sqf::parser::sqf::bison::astkind::STRING)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + child.children.front(), + ::sqf::parser::sqf::bison::astkind::STRING)); + } + } else if (child.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + child, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::ARRAY)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + child, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::ARRAY)); + } + } + } + for (auto &variable_node: variable_nodes) { + auto reference = make_reference(a, variable_node); + auto variable = get_or_create_variable(sqf_destringify(variable_node.token.contents)); + reference.variable_fk = variable.id_pk; + reference.access = t_reference::access_flags::set; + m_references.push_back(reference); + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::expression_handling_of_private( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node) {// private always has a right value and never a left value, so we can just take the first child + auto right_value = node.children.front(); + std::vector variable_nodes{}; + if (right_value.kind == sqf::parser::sqf::bison::astkind::STRING) { + variable_nodes.push_back(right_value); + } else if (right_value.kind == sqf::parser::sqf::bison::astkind::ARRAY) { + for (auto &child: right_value.children) { + if (child.kind == sqf::parser::sqf::bison::astkind::STRING) { + variable_nodes.push_back(child); + } else if (child.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + child, + ::sqf::parser::sqf::bison::astkind::STRING)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + child, + ::sqf::parser::sqf::bison::astkind::STRING)); + } + } + } else if (right_value.kind == sqf::parser::sqf::bison::astkind::IDENT) { + m_diagnostics.push_back(diag_cannot_determine_variable_from_expression_007( + file_id_of(a, node), + file_of(a).id_pk, + right_value, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::ARRAY)); + } else { + m_diagnostics.push_back(diag_type_missmatch_006( + file_id_of(a, node), + file_of(a).id_pk, + right_value, + ::sqf::parser::sqf::bison::astkind::STRING, + ::sqf::parser::sqf::bison::astkind::ARRAY)); + } + for (auto &variable_node: variable_nodes) { + auto reference = make_reference(a, variable_node); + reference.is_declaration = true; + auto variable = get_or_create_variable(sqf_destringify(variable_node.token.contents)); + reference.variable_fk = variable.id_pk; + reference.types = database::tables::t_reference::type_flags::nil; + reference.access = t_reference::access_flags::set; + m_references.push_back(reference); + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::exit( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) { + switch (node.kind) { + case ::sqf::parser::sqf::bison::astkind::CODE: { + pop_scope(); + break; + } + case ::sqf::parser::sqf::bison::astkind::ASSIGNMENT_LOCAL: + case ::sqf::parser::sqf::bison::astkind::ASSIGNMENT: { + if (m_assignment_candidate.has_value()) { + m_assignment_candidate->types = t_reference::type_flags::any; + m_assignment_candidate->id_pk = m_references.size() + 1; + m_references.push_back(m_assignment_candidate.value()); + m_assignment_candidate = {}; + } + break; + } + default: + break; + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::end(sqf_ast_analyzer &a) { + pop_scope(); + pop_namespace(); +} + +bool sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::is_left_side_of_assignment( + const std::vector &parent_nodes, + const ::sqf::parser::sqf::bison::astnode &node) const { + // !WARNING! while this method looks similar to is_right_side_of_assignment, it is not the same! + // The return values are not straight inverted, as we are checking explicitly whether this is the + // left side of an assignment, not just the opposite of the right side. + using namespace ::sqf::parser::sqf::bison; + if (node.kind == astkind::ASSIGNMENT_LOCAL) + return true; + if (parent_nodes.empty()) + return false; + auto &parent = *parent_nodes.back(); + if (parent.kind == astkind::ASSIGNMENT_LOCAL) + return false; + return parent.kind == astkind::ASSIGNMENT + && !parent.children.empty() + && &parent.children.front() == &node; +} + +bool sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::is_right_side_of_assignment( + const std::vector &parent_nodes, + const ::sqf::parser::sqf::bison::astnode &node) const { + // !WARNING! while this method looks similar to is_left_side_of_assignment, it is not the same! + // The return values are not straight inverted, as we are checking explicitly whether this is the + // right side of an assignment, not just the opposite of the left side. + using namespace ::sqf::parser::sqf::bison; + if (node.kind == astkind::ASSIGNMENT_LOCAL) + return false; + if (parent_nodes.empty()) + return false; + auto &parent = *parent_nodes.back(); + if (parent.kind == astkind::ASSIGNMENT_LOCAL) + return true; + return parent.kind == astkind::ASSIGNMENT + && !parent.children.empty() + && &parent.children.front() != &node; +} + +t_reference sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::make_reference( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node) { + + t_reference reference{}; + reference.source_file_fk = file_of(a).id_pk; + reference.file_fk = file_id_of(a, node); + reference.line = node.token.line; + reference.column = node.token.column; + reference.offset = node.token.offset; + reference.length = node.token.contents.length(); + + return reference; +} + +uint64_t sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::file_id_of( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node) { + auto node_path = std::filesystem::path(*node.token.path).lexically_normal(); + auto &file = file_of(a); + if (!node.token.path->empty() && file.path != node_path.string()) { + auto node_file = context_of(a).db_get_file_from_path(node_path, true); + if (node_file.has_value()) + return node_file->id_pk; + } + return file.id_pk; +} + +std::string sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::push_scope( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes +) { + bool is_detached = is_detached_scope(parent_nodes); + std::string scope{}; + if (m_scope_stack.empty()) { + scope = "scope://"; + } else { + auto &back = m_scope_stack.back(); + auto scope_string_size = back.full_name.length() + 1; + if (back.child_count < 10) + scope_string_size += 1; + else if (back.child_count < 100) + scope_string_size += 2; + else if (back.child_count < 1000) + scope_string_size += 3; + else if (back.child_count < 10000) + scope_string_size += 4; + else if (back.child_count < 100000) + scope_string_size += 5; + else if (back.child_count < 1000000) + scope_string_size += 6; + else if (back.child_count < 10000000) + scope_string_size += 7; + else if (back.child_count < 100000000) + scope_string_size += 8; + else if (back.child_count < 1000000000) + scope_string_size += 9; + else + scope_string_size += 10; + + scope.reserve(scope_string_size); + scope.append(back.full_name); + scope.append("/"); + scope.append(std::to_string(back.child_count)); + m_scope_stack.back().child_count++; + } + general_visitor::scope s{0, scope, is_detached}; + m_scope_stack.push_back(s); + if (is_detached) { + // ToDo: implement properly (not every {} scope has _this) + auto reference = make_reference(a, node); + reference.is_declaration = true; + auto variable = get_or_create_variable("_this"); + reference.variable_fk = variable.id_pk; + reference.access = t_reference::access_flags::set; + reference.is_magic_variable = true; + m_references.push_back(reference); + } + return scope; +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::add_magic_variables_to_current_scope( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) { + if (!parent_nodes.empty()) { + auto parent = parent_nodes.back(); + if (node_is_expression(*parent)) { + std::vector magic_variables{}; + if (iequal(parent->token.contents, "apply")) { + magic_variables.emplace_back("_x"); + } else if (iequal(parent->token.contents, "select")) { + magic_variables.emplace_back("_x"); + } else if (iequal(parent->token.contents, "count")) { + magic_variables.emplace_back("_x"); + } else if (iequal(parent->token.contents, "findIf")) { + magic_variables.emplace_back("_x"); + } else if (iequal(parent->token.contents, "catch")) { + magic_variables.emplace_back("_exception"); + } else if (iequal(parent->token.contents, "forEach")) { + magic_variables.emplace_back("_x"); + magic_variables.emplace_back("_y"); + magic_variables.emplace_back("_forEachIndex"); + } + for (auto &magic_variable: magic_variables) { + auto reference = make_reference(a, node); + reference.is_declaration = true; + auto variable = get_or_create_variable(magic_variable); + reference.variable_fk = variable.id_pk; + reference.access = t_reference::access_flags::set; + reference.is_magic_variable = true; + m_references.push_back(reference); + } + } + } +} + +bool sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::is_detached_scope( + const std::vector &parent_nodes) const { + bool is_detached = false; + if (!parent_nodes.empty()) { + auto parent = parent_nodes.back(); + if (!node_is_expression(*parent) + || !( + iequal(parent->token.contents, "then") + || iequal(parent->token.contents, "else") + || iequal(parent->token.contents, "exitWith") + || iequal(parent->token.contents, "call") + || iequal(parent->token.contents, "while") + || iequal(parent->token.contents, "do") + || iequal(parent->token.contents, "switch") + || iequal(parent->token.contents, ":") + || iequal(parent->token.contents, "default") + || iequal(parent->token.contents, "isNil") + || iequal(parent->token.contents, "waitUntil") + || iequal(parent->token.contents, "try") + || iequal(parent->token.contents, "catch") + || iequal(parent->token.contents, "count") + || iequal(parent->token.contents, "forEach") + || iequal(parent->token.contents, "apply") + || iequal(parent->token.contents, "select") + || iequal(parent->token.contents, "findIf") + || iequal(parent->token.contents, "&&") + || iequal(parent->token.contents, "and") + || iequal(parent->token.contents, "||") + || iequal(parent->token.contents, "or") + )) { + is_detached = true; + } + } + return is_detached; +} + +bool sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::node_is_expression( + const sqf::parser::sqf::bison::astnode &parent) { + return parent.kind == sqf::parser::sqf::bison::astkind::EXP0 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP1 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP2 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP3 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP4 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP5 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP6 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP7 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP8 + || parent.kind == sqf::parser::sqf::bison::astkind::EXP9 + || parent.kind == sqf::parser::sqf::bison::astkind::EXPU; +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::pop_scope() { + m_scope_stack.pop_back(); +} + +t_variable sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::get_or_create_variable( + std::string_view name, + bool is_declaration) { + if (is_private_variable(name)) { + if (!is_declaration) { + for (auto scope_reverse_it = m_scope_stack.rbegin(); + scope_reverse_it != m_scope_stack.rend(); ++scope_reverse_it) { + auto &scope = *scope_reverse_it; + auto find_res = std::find_if( + m_variables.begin(), + m_variables.end(), + [name, &scope](auto val) { + return iequal(val.variable_name, name) && val.scope == scope.full_name; + }); + if (find_res != m_variables.end()) { + return *find_res; + } + if (scope.is_detached) + break; + } + } + t_variable variable{}; + variable.variable_name = name; + variable.id_pk = m_variables.size() + 1; + variable.scope = m_scope_stack.back().full_name; + m_variables.push_back(variable); + return variable; + } else { + auto find_res = std::find_if( + m_variables.begin(), + m_variables.end(), + [name](auto val) { + return iequal(val.variable_name, name); + }); + if (find_res != m_variables.end()) { + return *find_res; + } else { + t_variable variable{}; + variable.variable_name = name; + variable.id_pk = m_variables.size() + 1; + variable.scope = get_namespace(); + m_variables.push_back(variable); + return variable; + } + } +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::general_visitor::analyze( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &sqf_ast_analyzer, + const sqfvm::language_server::database::context &context) { + using namespace sqlite_orm; + auto file_id = file_of(sqf_ast_analyzer).id_pk; + +#pragma region Find all variables which are only set once and never read + for (auto &variable: m_variables) { + for (auto it = m_references.begin(); it != m_references.end(); it++) { + if (it->variable_fk != variable.id_pk || it->access != t_reference::access_flags::set || + it->is_magic_variable) + continue; // ToDo: Optimize the lookup as we are O(N^M) here + const auto &initial_reference = it; + + if (it->types == database::tables::t_reference::type_flags::nil) + continue; // we treat nil assignment as intentional + + // We just need to check the following references due to read order + auto next_reference = std::find_if(initial_reference + 1, m_references.end(), + [&variable](const t_reference &reference) { + return reference.variable_fk == variable.id_pk; + }); + if (next_reference == m_references.end() || next_reference->access != t_reference::access_flags::get) { + if (is_private_variable(variable)) { + m_diagnostics.push_back(diag_private_variable_value_is_never_used_001( + file_id, + variable, + *initial_reference)); + } else { + m_diagnostics.push_back(diag_global_variable_value_is_never_used_in_file_002( + file_id, + variable, + *initial_reference)); + } + } + } + } +#pragma endregion +#pragma region Find all variables which are never set + for (auto &variable: m_variables) { + for (auto it = m_references.begin(); it != m_references.end(); it++) { + if (it->variable_fk != variable.id_pk || it->access != t_reference::access_flags::get || + it->is_magic_variable) + continue; // ToDo: Optimize the lookup as we are O(N^M) here + const auto &initial_reference = it; + // We just need to check the previous references due to read order + auto previous_set = std::find_if( + m_references.begin(), + initial_reference, + [&variable](const t_reference &reference) { + return reference.variable_fk == variable.id_pk + && reference.access == t_reference::access_flags::set; + }); + if (previous_set == initial_reference) { + if (is_private_variable(variable)) { + m_diagnostics.push_back(diag_private_variable_is_never_assigned_003( + file_id, + variable, + *initial_reference)); + } else { + m_diagnostics.push_back(diag_global_variable_never_assigned_in_file_in_file_004( + file_id, + variable, + *initial_reference)); + } + } + } + } +#pragma endregion +#pragma region Find all variables which are differently named (cased) in different references + for (auto &reference: m_references) { + if (reference.is_magic_variable) + continue; + auto find_res = std::find_if( + m_variables.begin(), + m_variables.end(), + [&reference](const t_variable &var) { + return var.id_pk == reference.variable_fk; + }); + if (find_res == m_variables.end()) + continue; + auto reference_content = text_of(sqf_ast_analyzer).substr(reference.offset, reference.length); + if (!reference_content.empty() && (reference_content[0] == '"' || reference_content[0] == '\'')) { + // Compare variable name with variable name in reference + auto destringified = sqf_destringify(reference_content); + if (find_res->variable_name != destringified) { + m_diagnostics.push_back(diag_variable_name_not_similar_005( + file_id, + *find_res, + reference, + destringified)); + } + } else { + // Compare variable name with variable name in reference + if (find_res->variable_name != reference_content) { + m_diagnostics.push_back(diag_variable_name_not_similar_005( + file_id, + *find_res, + reference, + reference_content)); + } + } + } +#pragma endregion +#pragma region Find all private variable which are shadowing other private variables + for (auto it = m_references.begin(); it != m_references.end(); it++) { + auto test_reference = *it; + if (test_reference.is_magic_variable) + continue; + if (!test_reference.is_declaration) + continue; + auto find_res = std::find_if( + m_variables.begin(), + m_variables.end(), + [&test_reference](const t_variable &var) { + return var.id_pk == test_reference.variable_fk; + }); + if (find_res == m_variables.end()) + continue; + if (!is_private_variable(*find_res)) + continue; + auto test_variable = *find_res; + auto shadowing_reference = std::find_if( + m_references.begin(), + it, + [&](const t_reference &ref) { + if (ref.access != t_reference::access_flags::set + || !ref.is_declaration) { + return false; + } + auto find_res = std::find_if( + m_variables.begin(), + m_variables.end(), + [&ref](const t_variable &var) { + return var.id_pk == ref.variable_fk; + }); + if (find_res == m_variables.end()) + return false; + if (!is_private_variable(*find_res)) + return false; + if (!iequal(find_res->variable_name, test_variable.variable_name)) + return false; + if (find_res->scope.length() > test_variable.scope.length()) + return false; + return std::equal( + find_res->scope.begin(), + find_res->scope.end(), + test_variable.scope.begin()); + }); + if (shadowing_reference != it) { + m_diagnostics.push_back(diag_private_variable_is_shadowing_other_private_variable_009( + file_id, + test_variable, + test_reference)); + m_diagnostics.push_back(diag_private_variable_is_shadowed_by_another_private_variable_009( + file_id, + test_variable, + *shadowing_reference)); + } + } +#pragma endregion +} diff --git a/server/sqfvm_language_server/analysis/sqf_ast/visitors/general_visitor.hpp b/server/sqfvm_language_server/analysis/sqf_ast/visitors/general_visitor.hpp new file mode 100644 index 0000000..c1615b8 --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/visitors/general_visitor.hpp @@ -0,0 +1,138 @@ +#ifndef SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_VISITORS_VARIABLES_VISITOR_HPP +#define SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_VISITORS_VARIABLES_VISITOR_HPP + +#include "../ast_visitor.hpp" +#include "../../../database/tables/t_reference.h" +#include "../../../database/tables/t_variable.h" +#include "../../../database/tables/t_variable.h" +#include +#include +#include +#include +#include +#include + +namespace sqfvm::language_server::analysis::sqf_ast::visitors { + class general_visitor : public ast_visitor { + struct scope { + // The amount of children this scope has + size_t child_count; + + // The scope's name used to identify a variable during visitation + std::string full_name; + + // Whether this scope is detached from the parent scope, aborting further lookup for variables + // once this scope is reached + bool is_detached; + }; + + std::stack m_namespace_stack; + std::vector m_scope_stack; + std::stack m_method_temporary_stack; + std::optional m_assignment_candidate; + + [[nodiscard]] bool is_left_side_of_assignment( + const std::vector &parent_nodes, + const ::sqf::parser::sqf::bison::astnode &node) const; + + [[nodiscard]] bool is_right_side_of_assignment( + const std::vector &parent_nodes, + const ::sqf::parser::sqf::bison::astnode &node) const; + + [[nodiscard]] database::tables::t_reference make_reference( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node); + + [[nodiscard]] database::tables::t_reference make_reference( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const database::tables::t_variable &variable, + const database::tables::t_reference::access_flags &access); + + [[nodiscard]] database::tables::t_variable get_or_create_variable(std::string_view name, bool is_declaration = false); + + std::string push_scope( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes); + + void pop_scope(); + + void push_namespace(std::string name) { + m_namespace_stack.push(std::move(name)); + } + + void pop_namespace() { + m_namespace_stack.pop(); + } + + [[nodiscard]] std::string get_namespace() const { + if (m_namespace_stack.empty()) { + return "missionNamespace"; + } + return m_namespace_stack.top(); + } + + void expression_handle_needless_parentheses( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes); + + void expression_handling_of_private( + sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node); + + void expression_handling_of_params( + sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node); + + void expression_handling_of_getvariable( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node); + + void expression_handling_of_setvariable( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node); + + static bool node_is_expression(const sqf::parser::sqf::bison::astnode &parent); + + [[nodiscard]] bool + is_detached_scope(const std::vector &parent_nodes) const; + + void expression_handling_of_isnil(sqf_ast_analyzer &a, const sqf::parser::sqf::bison::astnode &node); + + void add_magic_variables_to_current_scope( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes); + + uint64_t file_id_of( + sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node); + + public: + ~general_visitor() override = default; + + void start(sqf_ast_analyzer &a) override; + + + void enter( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes + ) override; + + void exit( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) override; + + void end( + sqf_ast_analyzer &a) override; + + void analyze( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &sqf_ast_analyzer, + const database::context &context) override; + }; +} +#endif // SQFVM_LANGUAGE_SERVER_ANALYSIS_SQF_AST_VISITORS_VARIABLES_VISITOR_HPP \ No newline at end of file diff --git a/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.cpp b/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.cpp new file mode 100644 index 0000000..a3c6e60 --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.cpp @@ -0,0 +1,1078 @@ +// +// Created by marco.silipo on 17.08.2023. +// + +#include "scripted_visitor.hpp" +#include +#include "runtime/d_array.h" +#include "runtime/d_string.h" +#include "runtime/d_scalar.h" + +namespace err = logmessage::runtime; +using namespace ::sqf::runtime; +using namespace ::sqf::types; +using namespace ::sqfvm::language_server::database::tables; + +struct storage : public runtime::datastorage { + sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor *m_visitor; + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer *m_ast_analyzer; +}; + +namespace sqf { + namespace runtime { + struct t_astnode : public type::extend { + t_astnode() : extend() {} + + static const std::string name() { return "ASTNODE"; } + }; + } + namespace types { + class d_astnode : public sqf::runtime::data { + public: + using data_type = sqf::runtime::t_scalar; + private: + const sqf::parser::sqf::bison::astnode *m_node; + protected: + bool do_equals(std::shared_ptr other, bool invariant) const override { + return m_node == std::static_pointer_cast(other)->m_node; + } + + public: + d_astnode() = default; + + std::string to_string_sqf() const override { + return "\"ASTNODE\""; + } + + std::string to_string() const override { + return "ASTNODE"; + } + + const sqf::parser::sqf::bison::astnode &node() const { return *m_node; } + + void node(const sqf::parser::sqf::bison::astnode &node) { m_node = &node; } + + sqf::runtime::type type() const override { return data_type(); } + + virtual std::size_t hash() const override { + return std::hash()(m_node); + } + }; + } + +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::start( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a) { + if (m_is_disabled) + return; + auto ls_path = ls_folder_of(a); + if (!exists(ls_path / "use_scripted_analyzers")) { // magic file to disable scripted analyzers + m_is_disabled = true; + return; + } + auto base_path = ls_path / "scripted" / "analyzers" / "sqf"; + if (!exists(base_path)) + create_directories(base_path); + + auto runtime = runtime_of(a); + initialize_functions_and_documentation(a, runtime, base_path / "ReadMe.md"); + initialize_start_script(runtime, base_path / "start.sqf"); + initialize_enter_script(runtime, base_path / "enter.sqf"); + initialize_exit_script(runtime, base_path / "exit.sqf"); + initialize_end_script(runtime, base_path / "end.sqf"); + initialize_analyze_script(runtime, base_path / "analyze.sqf"); + + call(runtime, m_start_script, {}); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::initialize_functions_and_documentation( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + std::shared_ptr runtime, + std::filesystem::path file) { + if (!exists(file)) { + auto f = std::ofstream(file); + f << "\n" + "* [Welcome to scripted analyzers](#welcome-to-scripted-analyzers)\n" + "* [What are scripted analyzers?](#what-are-scripted-analyzers)\n" + "* [Data structures, types and enums](#data-structures-types-and-enums)\n" + " * [Enum: `SEVERITY`](#enum-severity)\n" + " * [Enum: `CODEACTIONKIND`](#enum-codeactionkind)\n" + " * [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind)\n" + " * [Array: `CODEACTION`](#array-codeaction)\n" + " * [Array: `CODEACTIONCHANGE` (`CHANGE`)](#array-codeactionchange-change)\n" + " * [Array: `CODEACTIONCHANGE` (`CREATE`)](#array-codeactionchange-create)\n" + " * [Array: `CODEACTIONCHANGE` (`DELETE`)](#array-codeactionchange-delete)\n" + " * [Array: `CODEACTIONCHANGE` (`RENAME`)](#array-codeactionchange-rename)\n" + " * [Array: `DIAGNOSTIC`](#array-diagnostic)\n" + " * [Array: `file`](#array-file)\n" + " * [Enum: `ASTNODETYPE`](#enum-astnodetype)\n" + " * [Type: `ASTNODE`](#type-astnode)\n" + "* [New operators](#new-operators)\n" + " * [Operator: `lineOf`](#operator-lineof)\n" + " * [Operator: `columnOf`](#operator-columnof)\n" + " * [Operator: `offsetOf`](#operator-offsetof)\n" + " * [Operator: `contentOf`](#operator-contentof)\n" + " * [Operator: `pathOf`](#operator-pathof)\n" + " * [Operator: `typeOf`](#operator-typeof)\n" + " * [Operator: `childrenOf`](#operator-childrenof)\n" + " * [Operator: `fileOf`](#operator-fileof)\n" + " * [Operator: `reportDiagnostic`](#operator-reportdiagnostic)\n" + " * [Operator: `reportCodeAction`](#operator-reportcodeaction)\n" + "\n" + "\n" + "# Welcome to scripted analyzers\n" + "\n" + "This is a short introduction to scripted analyzers.\n" + "It will cover the basics of how to write your own analyzer and how to use it.\n" + "\n" + "# What are scripted analyzers?\n" + "\n" + "Scripted analyzers are a way to extend the functionality of the language server\n" + "by writing your own analyzers in SQF. This allows you to write e.g. your own\n" + "diagnostics. The analyzers are run on the server and the results are sent to\n" + "the client, which will display them in the editor.\n" + "\n" + "# Data structures, types and enums\n" + "\n" + "The following are the data structures, types and enums that are available to the analyzers.\n" + "They will be referred in the documentation by writing `` where type is\n" + "the name of the data structure.\n" + "\n" + "## Enum: `SEVERITY`\n" + "\n" + "```sqf\n" + "// One of the following:\n" + "\"FATAL\";\n" + "\"ERROR\";\n" + "\"WARNING\";\n" + "\"INFO\";\n" + "\"VERBOSE\";\n" + "\"TRACE\";\n" + "```\n" + "\n" + "The severity of a diagnostic. The higher the severity, the more important the\n" + "diagnostic is.\n" + "FATAL is the highest severity and TRACE is the lowest.\n" + "\n" + "| Severity | Description | Problems | Editor |\n" + "|----------|-----------------------------------------------------------------------------------------------------------------------------------|----------|--------|\n" + "| FATAL | The highest severity, should be used sparingly. | ERROR | RED |\n" + "| ERROR | The second-highest severity, should be used for problems that prevent the code from running. | ERROR | RED |\n" + "| WARNING | The third-highest severity, should be used for problems that might cause unexpected behavior. | WARNING | YELLOW |\n" + "| INFO | The fourth-highest severity, should be used for problems that are not necessarily problems, but might be interesting to the user. | INFO | WHITE |\n" + "| VERBOSE | The second-lowest severity, should be used for when INFO is too noisy. | | GRAY |\n" + "| TRACE | The lowest severity. | | GRAY |\n" + "\n" + "## Enum: `CODEACTIONKIND`\n" + "\n" + "```sqf\n" + "// One of the following:\n" + "\"GENERIC\"\n" + "\"QUICK_FIX\"\n" + "\"REFACTOR\"\n" + "\"EXTRACT_REFACTOR\"\n" + "\"INLINE_REFACTOR\"\n" + "\"REWRITE_REFACTOR\"\n" + "\"WHOLE_FILE\"\n" + "```\n" + "\n" + "The kind of [Array: `CODEACTION`](#array-codeaction). The kind groups the different code actions\n" + "into categories.\n" + "\n" + "| Kind | Description |\n" + "|------------------|-------------------------------------------------------------------------------------------------------------------------------|\n" + "| GENERIC | A generic code action. Use only when the code action does not fit into any of the other categories. |\n" + "| QUICK_FIX | A quick fix code action. |\n" + "| REFACTOR | A refactoring code action. Use only when the code action is a refactoring, but does not fit into any of the other categories. |\n" + "| EXTRACT_REFACTOR | Extracting modification of code to eg. extract a method out of a block of code. |\n" + "| INLINE_REFACTOR | Inline eg. a function call. |\n" + "| REWRITE_REFACTOR | Modifications which change how something is written, eg. adding/removing parameters, rewriting array access to variables, ... |\n" + "| WHOLE_FILE | Modifications span entire files. |\n" + "\n" + "## Enum: `CODEACTIONCHANGEKIND`\n" + "\n" + "```sqf\n" + "// One of the following:\n" + "\"CHANGE\"\n" + "\"CREATE\"\n" + "\"DELETE\"\n" + "\"RENAME\"\n" + "```\n" + "\n" + "The kind of `CODEACTIONCHANGE`. The kind describes what kind of change\n" + "the code action will perform. Each kind has a different set of fields.\n" + "\n" + "## Array: `CODEACTION`\n" + "\n" + "```sqf\n" + "[\n" + " identifier, // string\n" + " kind, // CODEACTIONKIND\n" + "]\n" + "```\n" + "\n" + "A code action is a modification that can be performed on the code. It is represented by an array\n" + "of the above structure. The fields are as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|------------|---------------------------------------------------------------------------------------------------------------|----------------------------------------|\n" + "| identifier | An identifier for the code action with the purpose of grouping same code actions together for bulk execution. | string |\n" + "| kind | The kind of the code action. | [CODEACTIONKIND](#enum-codeactionkind) |\n" + "\n" + "## Array: `CODEACTIONCHANGE` (`CHANGE`)\n" + "\n" + "```sqf\n" + "[\n" + " \"CHANGE\",\n" + " path, // string\n" + " start_line, // scalar\n" + " start_column, // scalar\n" + " end_line, // scalar\n" + " end_column, // scalar\n" + " content, // string\n" + "]\n" + "```\n" + "\n" + "A change code action is a modification that changes the code. It is represented by an array\n" + "of the above structure. Note that the first element of the array is\n" + "always [Enum: `\"CHANGE\"`](#enum-codeactionchangekind).\n" + "The fields are as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|--------------|-----------------------------------------|------------------------------------------------------------|\n" + "| \"CHANGE\" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) |\n" + "| path | The path of the file to change. | string |\n" + "| start_line | The line of the start of the change. | scalar |\n" + "| start_column | The column of the start of the change. | scalar |\n" + "| end_line | The line of the end of the change. | scalar |\n" + "| end_column | The column of the end of the change. | scalar |\n" + "| content | The content to replace the change with. | string |\n" + "\n" + "## Array: `CODEACTIONCHANGE` (`CREATE`)\n" + "\n" + "```sqf\n" + "[\n" + " \"CREATE\",\n" + " path, // string\n" + " content, // string\n" + "]\n" + "```\n" + "\n" + "A create code action is a modification that creates a new file. It is represented by an array\n" + "of the above structure. Note that the first element of the array is\n" + "always [Enum: `\"CREATE\"`](#enum-codeactionchangekind).\n" + "The fields are as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|----------|------------------------------------|------------------------------------------------------------|\n" + "| \"CREATE\" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) |\n" + "| path | The path of the file to create. | string |\n" + "| content | The content of the file to create. | string |\n" + "\n" + "## Array: `CODEACTIONCHANGE` (`DELETE`)\n" + "\n" + "```sqf\n" + "[\n" + " \"DELETE\",\n" + " path, // string\n" + "]\n" + "```\n" + "\n" + "A delete code action is a modification that deletes a file. It is represented by an array\n" + "of the above structure. Note that the first element of the array is\n" + "always [Enum: `\"DELETE\"`](#enum-codeactionchangekind).\n" + "The fields are as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|----------|---------------------------------|------------------------------------------------------------|\n" + "| \"DELETE\" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) |\n" + "| path | The path of the file to delete. | string |\n" + "\n" + "## Array: `CODEACTIONCHANGE` (`RENAME`)\n" + "\n" + "```sqf\n" + "[\n" + " \"RENAME\",\n" + " path, // string\n" + " new_path, // string\n" + "]\n" + "```\n" + "\n" + "A rename code action is a modification that renames a file. It is represented by an array\n" + "of the above structure. Note that the first element of the array is\n" + "always [Enum: `\"RENAME\"`](#enum-codeactionchangekind).\n" + "The fields are as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|----------|---------------------------------|------------------------------------------------------------|\n" + "| \"RENAME\" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) |\n" + "| path | The path of the file to rename. | string |\n" + "| new_path | The new path of the file. | string |\n" + "\n" + "## Array: `DIAGNOSTIC`\n" + "\n" + "```sqf\n" + "[\n" + " severity, // SEVERITY\n" + " error_code, // string\n" + " content, // string\n" + " message, // string\n" + " line, // scalar\n" + " column, // scalar\n" + " offset, // scalar\n" + " length, // scalar\n" + " file_id, // scalar\n" + "]\n" + "```\n" + "\n" + "A diagnostic is a problem that is found in the code. It is represented by an\n" + "array of the above structure. The fields are as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|------------|-----------------------------------------|----------------------------|\n" + "| severity | The severity of the diagnostic. | [SEVERITY](#enum-severity) |\n" + "| error_code | A unique identifier for the diagnostic. | string |\n" + "| content | The content of the diagnostic. | string |\n" + "| message | The message of the diagnostic. | string |\n" + "| line | The line of the diagnostic. | scalar |\n" + "| column | The column of the diagnostic. | scalar |\n" + "| offset | The offset of the diagnostic. | scalar |\n" + "| length | The length of the diagnostic. | scalar |\n" + "| file_id | The file id of the diagnostic. | scalar |\n" + "\n" + "## Array: `file`\n" + "\n" + "```sqf\n" + "[\n" + " file_id, // scalar\n" + " file_name // string\n" + "]\n" + "```\n" + "\n" + "A file is a file that is being analyzed. It is represented by an array of the\n" + "above structure. The fields are as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|-----------|----------------------------|--------|\n" + "| file_id | The file id of the file. | scalar |\n" + "| file_name | The file name of the file. | string |\n" + "\n" + "## Enum: `ASTNODETYPE`\n" + "\n" + "```sqf\n" + "// One of the following:\n" + "\"ENDOFFILE\"\n" + "\"INVALID\"\n" + "\"__TOKEN\"\n" + "\"NA\"\n" + "\"STATEMENTS\"\n" + "\"STATEMENT\"\n" + "\"IDENT\"\n" + "\"NUMBER\"\n" + "\"HEXNUMBER\"\n" + "\"STRING\"\n" + "\"BOOLEAN_TRUE\"\n" + "\"BOOLEAN_FALSE\"\n" + "\"EXPRESSION_LIST\"\n" + "\"CODE\"\n" + "\"ARRAY\"\n" + "\"ASSIGNMENT\"\n" + "\"ASSIGNMENT_LOCAL\"\n" + "\"EXPN\"\n" + "\"EXP0\"\n" + "\"EXP1\"\n" + "\"EXP2\"\n" + "\"EXP3\"\n" + "\"EXP4\"\n" + "\"EXP5\"\n" + "\"EXP6\"\n" + "\"EXP7\"\n" + "\"EXP8\"\n" + "\"EXP9\"\n" + "\"EXPU\"\n" + "\"EXP_GROUP\"\n" + "```\n" + "\n" + "The type of AST node. The AST is a tree representation of the code. It is\n" + "used to analyze the code. The type of node is represented by one of the above\n" + "strings.\n" + "\n" + "## Type: `ASTNODE`\n" + "\n" + "A new type introduced to allow introspection of the AST for SQF.\n" + "\n" + "## Array: `HOVER`\n" + "\n" + "```sqf\n" + "[\n" + " start_line, // scalar\n" + " start_column, // scalar\n" + " end_line, // scalar\n" + " end_column, // scalar\n" + " markdown // string\n" + "]\n" + "```\n" + "\n" + "A hover is a piece of text that is displayed when the user hovers over a piece\n" + "of code. It is represented by an array of the above structure. The fields are\n" + "as follows:\n" + "\n" + "| Field | Description | Type |\n" + "|--------------|-----------------------------------------|--------|\n" + "| start_line | The line of the start of the hover. | scalar |\n" + "| start_column | The column of the start of the hover. | scalar |\n" + "| end_line | The line of the end of the hover. | scalar |\n" + "| end_column | The column of the end of the hover. | scalar |\n" + "| markdown | The markdown of the hover. | string |\n" + "\n" + "\n" + "# New operators\n" + "\n" + "The following are the operators that are available to the analyzers.\n" + "\n" + "## Operator: `lineOf`\n" + "\n" + "```sqf\n" + "lineOf ASTNODE\n" + "```\n" + "\n" + "Returns the line of the given AST node ([Type: `ASTNODE`](#type-astnode)).\n" + "\n" + "## Operator: `columnOf`\n" + "\n" + "```sqf\n" + "columnOf ASTNODE\n" + "```\n" + "\n" + "Returns the column of the given AST node ([Type: `ASTNODE`](#type-astnode)).\n" + "\n" + "## Operator: `offsetOf`\n" + "\n" + "```sqf\n" + "offsetOf ASTNODE\n" + "```\n" + "\n" + "Returns the offset of the given AST node ([Type: `ASTNODE`](#type-astnode)).\n" + "\n" + "## Operator: `contentOf`\n" + "\n" + "```sqf\n" + "contentOf ASTNODE\n" + "```\n" + "\n" + "Returns the content of the given AST node ([Type: `ASTNODE`](#type-astnode)).\n" + "\n" + "## Operator: `pathOf`\n" + "\n" + "```sqf\n" + "pathOf ASTNODE\n" + "```\n" + "\n" + "Returns the path of the given AST node ([Type: `ASTNODE`](#type-astnode)).\n" + "\n" + "## Operator: `typeOf`\n" + "\n" + "```sqf\n" + "typeOf ASTNODE\n" + "```\n" + "\n" + "Returns the [Enum: `ASTNODETYPE`](#enum-astnodetype) of the given AST node ([Type: `ASTNODE`](#type-astnode)).\n" + "\n" + "## Operator: `childrenOf`\n" + "\n" + "```sqf\n" + "childrenOf ASTNODE\n" + "```\n" + "\n" + "Returns the children (`[ASTNODE]`) of the given AST node.\n" + "\n" + "## Operator: `fileOf`\n" + "\n" + "```sqf\n" + "fileOf ASTNODE\n" + "```\n" + "\n" + "Returns the [Array: `file`](#array-file) of the given AST node ([Type: `ASTNODE`](#type-astnode)).\n" + "\n" + "## Operator: `reportDiagnostic`\n" + "\n" + "```sqf\n" + "reportDiagnostic DIAGNOSTIC\n" + "```\n" + "\n" + "Reports the given [Array: `DIAGNOSTIC`](#array-diagnostic) to the client.\n" + "\n" + "## Operator: `reportCodeAction`\n" + "\n" + "```sqf\n" + "CODEACTION reportCodeAction [CODEACTIONCHANGE]\n" + "```\n" + "\n" + "Reports the given [Array: `CODEACTION`](#array-codeaction) to the client,\n" + "containing the given `CODEACTIONCHANGE`s.\n" + "The `CODEACTIONCHANGE`s are one of their corresponding subtypes:\n" + "\n" + "- [Array: `CODEACTIONCHANGE` (`CHANGE`)](#array-codeactionchange-change)\n" + "- [Array: `CODEACTIONCHANGE` (`RENAME`)](#array-codeactionchange-rename)\n" + "- [Array: `CODEACTIONCHANGE` (`CREATE`)](#array-codeactionchange-create)\n" + "- [Array: `CODEACTIONCHANGE` (`DELETE`)](#array-codeactionchange-delete)\n" + "\n" + "## Operator: `reportHover`\n" + "\n" + "```sqf\n" + "reportHover HOVER\n" + "```\n" + "\n" + "Reports the given [Array: `HOVER`](#array-hover) to the client.\n"; + f.flush(); + } + + // this is "dangerous" but we never leak the storage type into the runtime, so it should be fine + auto &s = runtime->storage(); + s.m_visitor = this; + s.m_ast_analyzer = &a; + runtime->register_sqfop( + sqfop::unary( + "lineOf", + t_astnode(), + "Returns the line of the given AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto d_node = right.data(); + return {d_node->node().token.line}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "columnOf", + t_astnode(), + "Returns the column of the given AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto d_node = right.data(); + return {d_node->node().token.column}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "offsetOf", + t_astnode(), + "Returns the offset of the given AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto d_node = right.data(); + return {d_node->node().token.offset}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "contentOf", + t_astnode(), + "Returns the content of the given AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto d_node = right.data(); + return {std::string(d_node->node().token.contents.begin(), + d_node->node().token.contents.end())}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "pathOf", + t_astnode(), + "Returns the path of the given AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto d_node = right.data(); + return {*d_node->node().token.path}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "typeOf", + t_astnode(), + "Returns the type of the given AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto d_node = right.data(); + auto kind = to_string_view(d_node->node().kind); + return {std::string(kind.begin(), kind.end())}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "childrenOf", + t_astnode(), + "Returns the children of the given AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto d_node = right.data(); + auto children = d_node->node().children; + std::vector result; + result.reserve(children.size()); + for (auto &child: children) { + auto ast_node = std::make_shared(); + ast_node->node(child); + result.emplace_back(ast_node); + } + return {std::move(result)}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "fileOf", + t_astnode(), + "Returns the file for the AST node.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + // file is an array [id, path] + auto d_node = right.data(); + auto &s = runtime.storage(); + auto file = s.m_visitor->file_of(*s.m_ast_analyzer); + return std::vector{file.id_pk, file.path}; + } + )); + runtime->register_sqfop( + sqfop::unary( + "reportDiagnostic", + t_array(), + "Reports the given diagnostic to the client.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + auto array = right.template data(); + + if (!array->check_type( + runtime, + std::array{ + t_string(), + t_string(), + t_string(), + t_string(), + t_scalar(), + t_scalar(), + t_scalar(), + t_scalar(), + t_scalar(), + })) + return {}; + + + auto severity_string = array->template data<0, d_string, std::string>(); + auto error_code = array->template data<1, d_string, std::string>(); + auto content = array->template data<2, d_string, std::string>(); + auto message = array->template data<3, d_string, std::string>(); + auto line = array->template data<4, d_scalar, uint64_t>(); + auto column = array->template data<5, d_scalar, uint64_t>(); + auto offset = array->template data<6, d_scalar, uint64_t>(); + auto length = array->template data<7, d_scalar, uint64_t>(); + auto file_id = array->template data<8, d_scalar, uint64_t>(); + + + auto severity = iequal(severity_string, "error") + ? t_diagnostic::error + : iequal(severity_string, "fatal") + ? t_diagnostic::fatal + : iequal(severity_string, "error") + ? t_diagnostic::error + : iequal(severity_string, "warning") + ? t_diagnostic::warning + : iequal(severity_string, "info") + ? t_diagnostic::info + : iequal(severity_string, "verbose") + ? t_diagnostic::verbose + : iequal(severity_string, "trace") + ? t_diagnostic::trace + : t_diagnostic::error; + auto &s = runtime.template storage(); + s.m_visitor->m_diagnostics.push_back( + { + .id_pk = s.m_visitor->m_diagnostics.size(), + .file_fk = file_id, + .source_file_fk = {}, + .line = line, + .column = column, + .offset = offset, + .length = length, + .severity = severity, + .message = message, + .code = error_code + } + ); + return {}; + })); + runtime->register_sqfop( + sqfop::binary( + 4, + "reportCodeAction", + t_array(), + t_array(), + "Reports the given code action to the client.", + [](auto &runtime, auto &left, auto &right) -> sqf::runtime::value { + auto code_action_array = left.template data(); + if (!code_action_array->check_type( + runtime, + std::array{t_string(), t_string()})) + return {}; + std::vector changes; + auto code_action_identifier = code_action_array->template data<0, d_string, std::string>(); + auto code_action_kind_string = code_action_array->template data<1, d_string, std::string>(); + auto right_array = right.template data>(); + bool flag = false; + for (size_t i = 0; i < right_array.size(); i++) { + auto &it = right_array.at(i); + if (!it.template is()) { + runtime.__logmsg( + err::ExpectedSubArrayTypeMissmatch( + runtime.context_active().current_frame().diag_info_from_position(), + std::array{i}, + t_array(), + it.type())); + flag = true; + continue; + } + auto it_array = it.template data(); + + if (it_array->size() < 1) + continue; + auto code_action_change_operation = it_array->template data<0, d_string, std::string>(); + if (iequal(code_action_change_operation, "change")) { + if (!it_array->check_type( + runtime, + std::array{ + t_string(), + t_string(), + t_scalar(), + t_scalar(), + t_scalar(), + t_scalar(), + t_string() + })) { + flag = true; + continue; + } + auto change = t_code_action_change{ + .operation = t_code_action_change::file_change, + .path = it_array->template data<1, d_string, std::string>(), + .start_line = it_array->template data<2, d_scalar, uint64_t>(), + .start_column = it_array->template data<3, d_scalar, uint64_t>(), + .end_line = it_array->template data<4, d_scalar, uint64_t>(), + .end_column = it_array->template data<5, d_scalar, uint64_t>(), + .content = it_array->template data<6, d_string, std::string>() + }; + changes.push_back(change); + } else if (iequal(code_action_change_operation, "create")) { + + if (!it_array->check_type( + runtime, + std::array{ + t_string(), + t_string(), + t_string()})) { + flag = true; + continue; + } + auto change = t_code_action_change{ + .operation = t_code_action_change::file_create, + .path = it_array->template data<1, d_string, std::string>(), + .content = it_array->template data<2, d_string, std::string>() + }; + changes.push_back(change); + } else if (iequal(code_action_change_operation, "delete")) { + if (!it_array->check_type( + runtime, + std::array{ + t_string(), + t_string()})) { + flag = true; + continue; + } + auto change = t_code_action_change{ + .operation = t_code_action_change::file_delete, + .path = it_array->template data<1, d_string, std::string>() + }; + changes.push_back(change); + } else if (iequal(code_action_change_operation, "rename")) { + if (!it_array->check_type( + runtime, + std::array{ + t_string(), + t_string(), + t_string()})) { + flag = true; + continue; + } + auto change = t_code_action_change{ + .operation = t_code_action_change::file_change, + .old_path = it_array->template data<1, d_string, std::string>(), + .path = it_array->template data<2, d_string, std::string>(), + }; + changes.push_back(change); + } else { + runtime.__logmsg( + err::ExpectedSubArrayTypeMissmatch( + runtime.context_active().current_frame().diag_info_from_position(), + std::array{i}, + t_array(), + it.type())); + flag = true; + continue; + } + } + if (flag) + return {}; + + auto code_action_kind = iequal(code_action_kind_string, "generic") + ? t_code_action::generic + : iequal(code_action_kind_string, "quick_fix") + ? t_code_action::quick_fix + : iequal(code_action_kind_string, "refactor") + ? t_code_action::refactor + : iequal(code_action_kind_string, "extract_refactor") + ? t_code_action::extract_refactor + : iequal(code_action_kind_string, "inline_refactor") + ? t_code_action::inline_refactor + : iequal(code_action_kind_string, "rewrite_refactor") + ? t_code_action::rewrite_refactor + : iequal(code_action_kind_string, "whole_file") + ? t_code_action::whole_file + : t_code_action::generic; + + auto &s = runtime.template storage(); + s.m_visitor->m_code_actions.push_back( + { + .code_action = t_code_action{ + .identifier = code_action_identifier, + .kind = code_action_kind, + }, + .changes = changes + } + ); + + return {}; + })); + runtime->register_sqfop( + sqfop::unary( + "reportHover", + t_array(), + "Reports a hover information to the client.", + [](auto &runtime, auto &right) -> sqf::runtime::value { + // SQF input: [start_line: scalar, start_column: scalar, end_line: scalar, end_column: scalar, markdown: string] + + auto array = right.template data(); + if (!array->check_type( + runtime, + std::array{t_scalar(), t_scalar(), t_scalar(), t_scalar(), + t_string()})) + return {}; + + auto start_line = array->template data<0, d_scalar, uint64_t>(); + auto start_column = array->template data<1, d_scalar, uint64_t>(); + auto end_line = array->template data<2, d_scalar, uint64_t>(); + auto end_column = array->template data<3, d_scalar, uint64_t>(); + auto markdown = array->template data<4, d_string, std::string>(); + + auto &s = runtime.template storage(); + s.m_visitor->m_hovers.push_back( + { + .start_line = start_line, + .start_column = start_column, + .end_line = end_line, + .end_column = end_column, + .markdown = markdown + } + ); + })); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::initialize_start_script( + std::shared_ptr runtime, + std::filesystem::path file) { + if (!exists(file)) { + auto f = std::ofstream(file); + f << std::endl; + } + auto contents = runtime->fileio().read_file_from_disk(file.string()); + if (!contents) { + return; + } + auto pp_result = runtime->parser_preprocessor().preprocess( + *runtime, + contents.value(), + {file.string(), {}}); + if (!pp_result) { + return; + } + auto parse_result = runtime->parser_sqf().parse( + *runtime, + pp_result.value(), + {file.string(), {}}); + if (!parse_result) { + return; + } + m_start_script = std::move(parse_result); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::initialize_enter_script( + std::shared_ptr runtime, + std::filesystem::path file) { + if (!exists(file)) { + auto f = std::ofstream(file); + f << "params [\"_node\", \"_parents\"];" << std::endl; + } + auto contents = runtime->fileio().read_file_from_disk(file.string()); + if (!contents) { + return; + } + auto pp_result = runtime->parser_preprocessor().preprocess( + *runtime, + contents.value(), + {file.string(), {}}); + if (!pp_result) { + return; + } + auto parse_result = runtime->parser_sqf().parse( + *runtime, + pp_result.value(), + {file.string(), {}}); + if (!parse_result) { + return; + } + m_start_script = std::move(parse_result); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::initialize_exit_script( + std::shared_ptr runtime, + std::filesystem::path file) { + if (!exists(file)) { + auto f = std::ofstream(file); + f << "params [\"_node\", \"_parents\"];" << std::endl; + } + auto contents = runtime->fileio().read_file_from_disk(file.string()); + if (!contents) { + return; + } + auto pp_result = runtime->parser_preprocessor().preprocess( + *runtime, + contents.value(), + {file.string(), {}}); + if (!pp_result) { + return; + } + auto parse_result = runtime->parser_sqf().parse( + *runtime, + pp_result.value(), + {file.string(), {}}); + if (!parse_result) { + return; + } + m_start_script = std::move(parse_result); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::initialize_end_script( + std::shared_ptr runtime, + std::filesystem::path file) { + if (!exists(file)) { + auto f = std::ofstream(file); + f << std::endl; + } + auto contents = runtime->fileio().read_file_from_disk(file.string()); + if (!contents) { + return; + } + auto pp_result = runtime->parser_preprocessor().preprocess( + *runtime, + contents.value(), + {file.string(), {}}); + if (!pp_result) { + return; + } + auto parse_result = runtime->parser_sqf().parse( + *runtime, + pp_result.value(), + {file.string(), {}}); + if (!parse_result) { + return; + } + m_start_script = std::move(parse_result); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::initialize_analyze_script( + std::shared_ptr runtime, + std::filesystem::path file) { + if (!exists(file)) { + auto f = std::ofstream(file); + f << std::endl; + } + auto contents = runtime->fileio().read_file_from_disk(file.string()); + if (!contents) { + return; + } + auto pp_result = runtime->parser_preprocessor().preprocess( + *runtime, + contents.value(), + {file.string(), {}}); + if (!pp_result) { + return; + } + auto parse_result = runtime->parser_sqf().parse( + *runtime, + pp_result.value(), + {file.string(), {}}); + if (!parse_result) { + return; + } + m_start_script = std::move(parse_result); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::enter( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) { + auto d_node = std::make_shared(); + d_node->node(node); + auto d_parent_nodes = std::make_shared(); + for (auto &p: parent_nodes) { + auto d_p = std::make_shared(); + d_p->node(*p); + d_parent_nodes->push_back(d_p); + } + call(runtime_of(a), m_enter_script, {d_node, d_parent_nodes}); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::exit( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + const sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes) { + auto d_node = std::make_shared(); + d_node->node(node); + auto d_parent_nodes = std::make_shared(); + for (auto &p: parent_nodes) { + auto d_p = std::make_shared(); + d_p->node(*p); + d_parent_nodes->push_back(d_p); + } + call(runtime_of(a), m_exit_script, {d_node, d_parent_nodes}); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::end( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a) { + call(runtime_of(a), m_exit_script, {}); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::analyze( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &sqf_ast_analyzer, + const sqfvm::language_server::database::context &context) { + ast_visitor::analyze(sqf_ast_analyzer, context); + call(runtime_of(sqf_ast_analyzer), m_analyze_script, {}); +} + +void sqfvm::language_server::analysis::sqf_ast::visitors::scripted_visitor::call( + std::shared_ptr runtime, + std::optional<::sqf::runtime::instruction_set> &instruction_set, + std::vector this_values) { + if (!instruction_set.has_value()) + return; + auto &is = instruction_set.value(); + auto context_weak = runtime->context_create(); + auto context = context_weak.lock(); + if (!context) + return; + sqf::runtime::frame f(runtime->default_value_scope(), is); + f["_this"] = {this_values}; + context->push_frame(f); + auto result = runtime->execute(sqf::runtime::runtime::action::start); + if (result != sqf::runtime::runtime::result::ok) + runtime->context_remove(context); +} diff --git a/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.documentation.md b/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.documentation.md new file mode 100644 index 0000000..8990ae4 --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.documentation.md @@ -0,0 +1,435 @@ + +* [Welcome to scripted analyzers](#welcome-to-scripted-analyzers) +* [What are scripted analyzers?](#what-are-scripted-analyzers) +* [Data structures, types and enums](#data-structures-types-and-enums) + * [Enum: `SEVERITY`](#enum-severity) + * [Enum: `CODEACTIONKIND`](#enum-codeactionkind) + * [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) + * [Array: `CODEACTION`](#array-codeaction) + * [Array: `CODEACTIONCHANGE` (`CHANGE`)](#array-codeactionchange-change) + * [Array: `CODEACTIONCHANGE` (`CREATE`)](#array-codeactionchange-create) + * [Array: `CODEACTIONCHANGE` (`DELETE`)](#array-codeactionchange-delete) + * [Array: `CODEACTIONCHANGE` (`RENAME`)](#array-codeactionchange-rename) + * [Array: `DIAGNOSTIC`](#array-diagnostic) + * [Array: `file`](#array-file) + * [Enum: `ASTNODETYPE`](#enum-astnodetype) + * [Type: `ASTNODE`](#type-astnode) + * [Array: `HOVER`](#array-hover) +* [New operators](#new-operators) + * [Operator: `lineOf ASTNODE`](#operator-lineof-astnode) + * [Operator: `columnOf ASTNODE`](#operator-columnof-astnode) + * [Operator: `offsetOf ASTNODE`](#operator-offsetof-astnode) + * [Operator: `contentOf ASTNODE`](#operator-contentof-astnode) + * [Operator: `pathOf ASTNODE`](#operator-pathof-astnode) + * [Operator: `typeOf ASTNODE`](#operator-typeof-astnode) + * [Operator: `childrenOf ASTNODE`](#operator-childrenof-astnode) + * [Operator: `fileOf ASTNODE`](#operator-fileof-astnode) + * [Operator: `reportDiagnostic ARRAY`](#operator-reportdiagnostic-array) + * [Operator: `CODEACTION reportCodeAction ARRAY`](#operator-codeaction-reportcodeaction-array) + * [Operator: `reportHover ARRAY`](#operator-reporthover-array) + + +# Welcome to scripted analyzers + +This is a short introduction to scripted analyzers. +It will cover the basics of how to write your own analyzer and how to use it. + +# What are scripted analyzers? + +Scripted analyzers are a way to extend the functionality of the language server +by writing your own analyzers in SQF. This allows you to write e.g. your own +diagnostics. The analyzers are run on the server and the results are sent to +the client, which will display them in the editor. + +# Data structures, types and enums + +The following are the data structures, types and enums that are available to the analyzers. +They will be referred in the documentation by writing `` where type is +the name of the data structure. + +## Enum: `SEVERITY` + +```sqf +// One of the following: +"FATAL"; +"ERROR"; +"WARNING"; +"INFO"; +"VERBOSE"; +"TRACE"; +``` + +The severity of a diagnostic. The higher the severity, the more important the +diagnostic is. +FATAL is the highest severity and TRACE is the lowest. + +| Severity | Description | Problems | Editor | +|----------|-----------------------------------------------------------------------------------------------------------------------------------|----------|--------| +| FATAL | The highest severity, should be used sparingly. | ERROR | RED | +| ERROR | The second-highest severity, should be used for problems that prevent the code from running. | ERROR | RED | +| WARNING | The third-highest severity, should be used for problems that might cause unexpected behavior. | WARNING | YELLOW | +| INFO | The fourth-highest severity, should be used for problems that are not necessarily problems, but might be interesting to the user. | INFO | WHITE | +| VERBOSE | The second-lowest severity, should be used for when INFO is too noisy. | | GRAY | +| TRACE | The lowest severity. | | GRAY | + +## Enum: `CODEACTIONKIND` + +```sqf +// One of the following: +"GENERIC" +"QUICK_FIX" +"REFACTOR" +"EXTRACT_REFACTOR" +"INLINE_REFACTOR" +"REWRITE_REFACTOR" +"WHOLE_FILE" +``` + +The kind of [Array: `CODEACTION`](#array-codeaction). The kind groups the different code actions +into categories. + +| Kind | Description | +|------------------|-------------------------------------------------------------------------------------------------------------------------------| +| GENERIC | A generic code action. Use only when the code action does not fit into any of the other categories. | +| QUICK_FIX | A quick fix code action. | +| REFACTOR | A refactoring code action. Use only when the code action is a refactoring, but does not fit into any of the other categories. | +| EXTRACT_REFACTOR | Extracting modification of code to eg. extract a method out of a block of code. | +| INLINE_REFACTOR | Inline eg. a function call. | +| REWRITE_REFACTOR | Modifications which change how something is written, eg. adding/removing parameters, rewriting array access to variables, ... | +| WHOLE_FILE | Modifications span entire files. | + +## Enum: `CODEACTIONCHANGEKIND` + +```sqf +// One of the following: +"CHANGE" +"CREATE" +"DELETE" +"RENAME" +``` + +The kind of `CODEACTIONCHANGE`. The kind describes what kind of change +the code action will perform. Each kind has a different set of fields. + +## Array: `CODEACTION` + +```sqf +[ + identifier, // string + kind, // CODEACTIONKIND +] +``` + +A code action is a modification that can be performed on the code. It is represented by an array +of the above structure. The fields are as follows: + +| Field | Description | Type | +|------------|---------------------------------------------------------------------------------------------------------------|----------------------------------------| +| identifier | An identifier for the code action with the purpose of grouping same code actions together for bulk execution. | string | +| kind | The kind of the code action. | [CODEACTIONKIND](#enum-codeactionkind) | + +## Array: `CODEACTIONCHANGE` (`CHANGE`) + +```sqf +[ + "CHANGE", + path, // string + start_line, // scalar + start_column, // scalar + end_line, // scalar + end_column, // scalar + content, // string +] +``` + +A change code action is a modification that changes the code. It is represented by an array +of the above structure. Note that the first element of the array is +always [Enum: `"CHANGE"`](#enum-codeactionchangekind). +The fields are as follows: + +| Field | Description | Type | +|--------------|-----------------------------------------|------------------------------------------------------------| +| "CHANGE" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) | +| path | The path of the file to change. | string | +| start_line | The line of the start of the change. | scalar | +| start_column | The column of the start of the change. | scalar | +| end_line | The line of the end of the change. | scalar | +| end_column | The column of the end of the change. | scalar | +| content | The content to replace the change with. | string | + +## Array: `CODEACTIONCHANGE` (`CREATE`) + +```sqf +[ + "CREATE", + path, // string + content, // string +] +``` + +A create code action is a modification that creates a new file. It is represented by an array +of the above structure. Note that the first element of the array is +always [Enum: `"CREATE"`](#enum-codeactionchangekind). +The fields are as follows: + +| Field | Description | Type | +|----------|------------------------------------|------------------------------------------------------------| +| "CREATE" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) | +| path | The path of the file to create. | string | +| content | The content of the file to create. | string | + +## Array: `CODEACTIONCHANGE` (`DELETE`) + +```sqf +[ + "DELETE", + path, // string +] +``` + +A delete code action is a modification that deletes a file. It is represented by an array +of the above structure. Note that the first element of the array is +always [Enum: `"DELETE"`](#enum-codeactionchangekind). +The fields are as follows: + +| Field | Description | Type | +|----------|---------------------------------|------------------------------------------------------------| +| "DELETE" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) | +| path | The path of the file to delete. | string | + +## Array: `CODEACTIONCHANGE` (`RENAME`) + +```sqf +[ + "RENAME", + path, // string + new_path, // string +] +``` + +A rename code action is a modification that renames a file. It is represented by an array +of the above structure. Note that the first element of the array is +always [Enum: `"RENAME"`](#enum-codeactionchangekind). +The fields are as follows: + +| Field | Description | Type | +|----------|---------------------------------|------------------------------------------------------------| +| "RENAME" | The kind of the code action. | [Enum: `CODEACTIONCHANGEKIND`](#enum-codeactionchangekind) | +| path | The path of the file to rename. | string | +| new_path | The new path of the file. | string | + +## Array: `DIAGNOSTIC` + +```sqf +[ + severity, // SEVERITY + error_code, // string + content, // string + message, // string + line, // scalar + column, // scalar + offset, // scalar + length, // scalar + file_id, // scalar +] +``` + +A diagnostic is a problem that is found in the code. It is represented by an +array of the above structure. The fields are as follows: + +| Field | Description | Type | +|------------|-----------------------------------------|----------------------------| +| severity | The severity of the diagnostic. | [SEVERITY](#enum-severity) | +| error_code | A unique identifier for the diagnostic. | string | +| content | The content of the diagnostic. | string | +| message | The message of the diagnostic. | string | +| line | The line of the diagnostic. | scalar | +| column | The column of the diagnostic. | scalar | +| offset | The offset of the diagnostic. | scalar | +| length | The length of the diagnostic. | scalar | +| file_id | The file id of the diagnostic. | scalar | + +## Array: `file` + +```sqf +[ + file_id, // scalar + file_name // string +] +``` + +A file is a file that is being analyzed. It is represented by an array of the +above structure. The fields are as follows: + +| Field | Description | Type | +|-----------|----------------------------|--------| +| file_id | The file id of the file. | scalar | +| file_name | The file name of the file. | string | + +## Enum: `ASTNODETYPE` + +```sqf +// One of the following: +"ENDOFFILE" +"INVALID" +"__TOKEN" +"NA" +"STATEMENTS" +"STATEMENT" +"IDENT" +"NUMBER" +"HEXNUMBER" +"STRING" +"BOOLEAN_TRUE" +"BOOLEAN_FALSE" +"EXPRESSION_LIST" +"CODE" +"ARRAY" +"ASSIGNMENT" +"ASSIGNMENT_LOCAL" +"EXPN" +"EXP0" +"EXP1" +"EXP2" +"EXP3" +"EXP4" +"EXP5" +"EXP6" +"EXP7" +"EXP8" +"EXP9" +"EXPU" +"EXP_GROUP" +``` + +The type of AST node. The AST is a tree representation of the code. It is +used to analyze the code. The type of node is represented by one of the above +strings. + +## Type: `ASTNODE` + +A new type introduced to allow introspection of the AST for SQF. + +## Array: `HOVER` + +```sqf +[ + start_line, // scalar + start_column, // scalar + end_line, // scalar + end_column, // scalar + markdown // string +] +``` + +A hover is a piece of text that is displayed when the user hovers over a piece +of code. It is represented by an array of the above structure. The fields are +as follows: + +| Field | Description | Type | +|--------------|-----------------------------------------|--------| +| start_line | The line of the start of the hover. | scalar | +| start_column | The column of the start of the hover. | scalar | +| end_line | The line of the end of the hover. | scalar | +| end_column | The column of the end of the hover. | scalar | +| markdown | The markdown of the hover. | string | + + +# New operators + +The following are the operators that are available to the analyzers. + +## Operator: `lineOf ASTNODE` + +```sqf +lineOf ASTNODE +``` + +Returns the line of the given AST node ([Type: `ASTNODE`](#type-astnode)). + +## Operator: `columnOf ASTNODE` + +```sqf +columnOf ASTNODE +``` + +Returns the column of the given AST node ([Type: `ASTNODE`](#type-astnode)). + +## Operator: `offsetOf ASTNODE` + +```sqf +offsetOf ASTNODE +``` + +Returns the offset of the given AST node ([Type: `ASTNODE`](#type-astnode)). + +## Operator: `contentOf ASTNODE` + +```sqf +contentOf ASTNODE +``` + +Returns the content of the given AST node ([Type: `ASTNODE`](#type-astnode)). + +## Operator: `pathOf ASTNODE` + +```sqf +pathOf ASTNODE +``` + +Returns the path of the given AST node ([Type: `ASTNODE`](#type-astnode)). + +## Operator: `typeOf ASTNODE` + +```sqf +typeOf ASTNODE +``` + +Returns the [Enum: `ASTNODETYPE`](#enum-astnodetype) of the given AST node ([Type: `ASTNODE`](#type-astnode)). + +## Operator: `childrenOf ASTNODE` + +```sqf +childrenOf ASTNODE +``` + +Returns the children (`[ASTNODE]`) of the given AST node. + +## Operator: `fileOf ASTNODE` + +```sqf +fileOf ASTNODE +``` + +Returns the [Array: `file`](#array-file) of the given AST node ([Type: `ASTNODE`](#type-astnode)). + +## Operator: `reportDiagnostic ARRAY` + +```sqf +reportDiagnostic DIAGNOSTIC +``` + +Reports the given [Array: `DIAGNOSTIC`](#array-diagnostic) to the client. + +## Operator: `ARRAY reportCodeAction ARRAY` + +```sqf +CODEACTION reportCodeAction [CODEACTIONCHANGE] +``` + +Reports the given [Array: `CODEACTION`](#array-codeaction) to the client, +containing the given `CODEACTIONCHANGE`s. +The `CODEACTIONCHANGE`s are one of their corresponding subtypes: + +- [Array: `CODEACTIONCHANGE` (`CHANGE`)](#array-codeactionchange-change) +- [Array: `CODEACTIONCHANGE` (`RENAME`)](#array-codeactionchange-rename) +- [Array: `CODEACTIONCHANGE` (`CREATE`)](#array-codeactionchange-create) +- [Array: `CODEACTIONCHANGE` (`DELETE`)](#array-codeactionchange-delete) + +## Operator: `reportHover ARRAY` + +```sqf +reportHover HOVER +``` + +Reports the given [Array: `HOVER`](#array-hover) to the client. \ No newline at end of file diff --git a/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.hpp b/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.hpp new file mode 100644 index 0000000..0d8541c --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqf_ast/visitors/scripted_visitor.hpp @@ -0,0 +1,73 @@ +// +// Created by marco.silipo on 17.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_SCRIPTED_VISITOR_HPP +#define SQFVM_LANGUAGE_SERVER_SCRIPTED_VISITOR_HPP + + +#include "../ast_visitor.hpp" + +namespace sqfvm::language_server::analysis::sqf_ast::visitors { + class scripted_visitor : public ast_visitor { + bool m_is_disabled; + std::optional<::sqf::runtime::instruction_set> m_start_script; + std::optional<::sqf::runtime::instruction_set> m_enter_script; + std::optional<::sqf::runtime::instruction_set> m_exit_script; + std::optional<::sqf::runtime::instruction_set> m_end_script; + std::optional<::sqf::runtime::instruction_set> m_analyze_script; + + + void initialize_functions_and_documentation( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &a, + std::shared_ptr right, + std::filesystem::path file); + + void initialize_start_script(std::shared_ptr runtime, std::filesystem::path file); + + void call( + std::shared_ptr runtime, + std::optional<::sqf::runtime::instruction_set> &instruction_set, + std::vector this_values); + + void initialize_enter_script(std::shared_ptr runtime, std::filesystem::path file); + + void initialize_exit_script(std::shared_ptr runtime, std::filesystem::path file); + + void initialize_end_script(std::shared_ptr runtime, std::filesystem::path file); + + void initialize_analyze_script(std::shared_ptr runtime, std::filesystem::path file); + + public: + ~scripted_visitor() override = default; + + void start( + sqf_ast_analyzer &a + ) override; + + + void enter( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes + ) override; + + void exit( + sqf_ast_analyzer &a, + const ::sqf::parser::sqf::bison::astnode &node, + const std::vector &parent_nodes + ) override; + + void end( + sqf_ast_analyzer &a + ) override; + + void analyze( + sqfvm::language_server::analysis::sqf_ast::sqf_ast_analyzer &sqf_ast_analyzer, + const database::context &context + ) override; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_SCRIPTED_VISITOR_HPP diff --git a/server/sqfvm_language_server/analysis/sqfvm_analyzer.cpp b/server/sqfvm_language_server/analysis/sqfvm_analyzer.cpp new file mode 100644 index 0000000..122f9ba --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqfvm_analyzer.cpp @@ -0,0 +1,59 @@ +// +// Created by marco.silipo on 28.08.2023. +// +#include "sqfvm_analyzer.hpp" + +void sqfvm::language_server::analysis::sqfvm_analyzer::analyze() { + auto path_info = m_runtime->fileio().get_info(m_file.path, {m_file.path, {}, {}}); + if (!path_info.has_value()) { + report_diagnostic({ + .file_fk = m_file.id_pk, + .severity = database::tables::t_diagnostic::severity_level::error, + .message = "Failed to get path info for file: " + m_file.path, + }); + return; + } + auto &preprocessor = m_runtime->parser_preprocessor(); + auto casted = dynamic_cast(&preprocessor); + if (casted == nullptr) { + report_diagnostic({ + .file_fk = m_file.id_pk, + .severity = database::tables::t_diagnostic::severity_level::error, + .message = "Failed to cast preprocessor to default implementation. " + "Please report this issue and rollback to a previous version of the language server.", + }); + return; + } + casted->macro_resolved([&]( + auto orig_start, + auto orig_end, + auto pp_start, + auto pp_end, + auto &runtime, + auto &local_fileinfo, + auto &original_fileinfo, + auto &m, + auto ¶m_map) { + m_offset_pairs.push_back({ + .raw = orig_start, + .preprocessed_offset = pp_start, + .kind = offset_pair::kind::start, + }); + m_offset_pairs.push_back({ + .raw = orig_end, + .preprocessed_offset = pp_end, + .kind = offset_pair::kind::end, + }); + macro_resolved(orig_start, orig_end, pp_start, pp_end, runtime, local_fileinfo, original_fileinfo, m, + param_map); + }); + + auto preprocessed_opt = preprocessor.preprocess(*m_runtime, m_text, {m_file.path, {}, {}}); + if (!preprocessed_opt.has_value()) { + // Preprocessor already reported the error + return; + } + m_preprocessed_text = preprocessed_opt.value(); + + analyze(*m_runtime); +} diff --git a/server/sqfvm_language_server/analysis/sqfvm_analyzer.hpp b/server/sqfvm_language_server/analysis/sqfvm_analyzer.hpp new file mode 100644 index 0000000..5778b63 --- /dev/null +++ b/server/sqfvm_language_server/analysis/sqfvm_analyzer.hpp @@ -0,0 +1,131 @@ +// +// Created by marco.silipo on 28.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_SQFVM_ANALYZER_HPP +#define SQFVM_LANGUAGE_SERVER_SQFVM_ANALYZER_HPP + +#include "slspp_context.hpp" +#include "file_analyzer.hpp" +#include "../sqfvm_factory.hpp" +#include + + +namespace sqfvm::language_server::analysis { + class sqfvm_analyzer : public file_analyzer { + struct offset_pair { + enum kind { + start, + end, + }; + ::sqf::parser::preprocessor::impl_default::macro_resolved_data raw; + size_t preprocessed_offset; + enum kind kind; + }; + std::vector m_offset_pairs; + protected: + std::string m_text; + std::string m_preprocessed_text; + std::shared_ptr m_runtime; + std::shared_ptr m_slspp_context; + + virtual void report_diagnostic(const database::tables::t_diagnostic &diagnostic) = 0; + + virtual void macro_resolved( + ::sqf::parser::preprocessor::impl_default::macro_resolved_data orig_start, + ::sqf::parser::preprocessor::impl_default::macro_resolved_data orig_end, + size_t pp_start, + size_t pp_end, + ::sqf::runtime::runtime &runtime, + ::sqf::runtime::parser::preprocessor::context &local_fileinfo, + ::sqf::runtime::parser::preprocessor::context &original_fileinfo, + const ::sqf::runtime::parser::macro &m, + const std::unordered_map ¶m_map) {}; + + struct decoded_offset { + ::sqf::parser::preprocessor::impl_default::macro_resolved_data resolved; + size_t length; + }; + + [[nodiscard]] bool is_offset_in_macro(size_t offset) const { + if (m_offset_pairs.empty()) { + return false; + } + auto it = std::lower_bound( + m_offset_pairs.begin(), m_offset_pairs.end(), offset, + [](auto &pair, auto offset) { + return pair.preprocessed_offset < offset; + }); + if (it == m_offset_pairs.end()) { + return false; + } + if (it->kind == offset_pair::kind::start) { + auto next = std::next(it); + if (next == m_offset_pairs.end()) { + return false; + } + if (next->preprocessed_offset > offset) { + return true; + } + } + return false; + } + + [[nodiscard]] std::optional decode_preprocessed_offset(size_t offset) const { + if (m_offset_pairs.empty()) { + return {}; + } + auto it = std::lower_bound( + m_offset_pairs.begin(), m_offset_pairs.end(), offset, + [](auto &pair, auto offset) { + return pair.preprocessed_offset < offset; + }); + if (it == m_offset_pairs.end()) { + return {}; + } + if (it->kind == offset_pair::kind::start) { + auto next = std::next(it); + if (next == m_offset_pairs.end()) { + // This is an error, technically, as a start pair must always be followed by an end pair + return {}; + } + if (next->preprocessed_offset > offset) { + // we are in the middle of a macro, return full macro as result + return decoded_offset{it->raw, next->raw.offset - it->raw.offset}; + } + } + // we are after a macro, return the delta to the end of that macro + return decoded_offset{it->raw, 0}; + } + + public: + sqfvm_analyzer( + const std::filesystem::path &db_path, + database::tables::t_file file, + sqfvm_factory &factory, + std::string text) + : file_analyzer(db_path, std::move(file)), + m_slspp_context(std::make_shared()), + m_text(std::move(text)), + m_preprocessed_text({}) { + m_runtime = factory.create([&](auto &msg) { + auto copy = msg; + copy.source_file_fk = m_file.id_pk; + auto decoded = decode_preprocessed_offset(msg.offset); + if (decoded.has_value()) { + copy.line = decoded->resolved.line - /* lsp garbage random offset */ 1; + copy.column = decoded->resolved.column; + copy.offset = decoded->resolved.offset; + copy.length = decoded->length; + } + report_diagnostic(copy); + }, m_context, m_slspp_context); + }; + + void analyze() final; + + virtual void analyze(sqf::runtime::runtime &runtime) = 0; + }; +} + +#endif //SQFVM_LANGUAGE_SERVER_SQFVM_ANALYZER_HPP diff --git a/server/sqfvm_language_server/database/context.cpp b/server/sqfvm_language_server/database/context.cpp new file mode 100644 index 0000000..fda4d13 --- /dev/null +++ b/server/sqfvm_language_server/database/context.cpp @@ -0,0 +1,46 @@ +// +// Created by marco.silipo on 17.08.2023. +// +#include "context.hpp" +#include "../util.hpp" + +using namespace sqlite_orm; +using namespace ::sqfvm::language_server::database::tables; + +void sqfvm::language_server::database::context::db_clear() { + m_storage.remove_all(); + m_storage.remove_all(); + m_storage.remove_all(); + m_storage.remove_all(); + m_storage.remove_all(); + m_storage.remove_all(); +} + + +std::optional sqfvm::language_server::database::context::db_get_file_from_path( + std::filesystem::path path, + bool create_if_not_exists) { + auto orm = storage(); + path = path.lexically_normal(); + auto files = orm.get_all(where(c(&t_file::path) == path.string())); + t_file file; + if (files.empty()) { + if (!create_if_not_exists) + return {}; + file = t_file{ + .is_outdated = true, + .is_deleted = false, + .last_changed = unix_timestamp(), + .path = path.string(), + }; + auto result = orm.insert(file); + file.id_pk = result; + } else { + file = files.front(); + if (file.is_deleted && exists(path)) { + file.is_deleted = false; + orm.update(file); + } + } + return {file}; +} \ No newline at end of file diff --git a/server/sqfvm_language_server/database/context.hpp b/server/sqfvm_language_server/database/context.hpp new file mode 100644 index 0000000..b5dc213 --- /dev/null +++ b/server/sqfvm_language_server/database/context.hpp @@ -0,0 +1,201 @@ + +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_CONTEXT_HPP +#define SQFVM_LANGUAGE_SERVER_DATABASE_CONTEXT_HPP + +#include +#include +#include +#include "tables/t_code_action.h" +#include "tables/t_code_action_change.h" +#include "tables/t_diagnostic.h" +#include "tables/t_hover.h" +#include "tables/t_file.h" +#include "tables/t_file_history.h" +#include "tables/t_reference.h" +#include "tables/t_variable.h" +#include "orm_mappings.hpp" + +namespace sqfvm::language_server::database { + + namespace internal { + struct t_db_generation { + static constexpr const char *table_name = "tDbGeneration"; + static const int expected_generation = 4; + int id_pk; + int generation; + }; + + // Maps the database connection out + inline auto create_storage(const std::string &path) { + using namespace sqfvm::language_server::database::tables; + using namespace sqlite_orm; + auto storage = make_storage( + path, + make_table(t_db_generation::table_name, + make_column("id_pk", &t_db_generation::id_pk, primary_key().autoincrement()), + make_column("generation", &t_db_generation::generation)), + make_table(t_file::table_name, + make_column("id_pk", &t_file::id_pk, primary_key().autoincrement()), + make_column("last_changed", &t_file::last_changed), + make_column("path", &t_file::path), + make_column("is_outdated", &t_file::is_outdated), + make_column("is_deleted", &t_file::is_deleted)), + make_table(t_hover::table_name, + make_column("id_pk", &t_hover::id_pk, primary_key().autoincrement()), + make_column("file_fk", &t_hover::file_fk), + make_column("start_line", &t_hover::start_line), + make_column("start_column", &t_hover::start_column), + make_column("end_line", &t_hover::end_line), + make_column("end_column", &t_hover::end_column), + make_column("markdown", &t_hover::markdown), + foreign_key(&t_hover::file_fk).references(&t_file::id_pk)), + make_table(t_file_history::table_name, + make_column("id_pk", &t_file_history::id_pk, primary_key().autoincrement()), + make_column("file_fk", &t_file_history::file_fk), + make_column("content", &t_file_history::content), + make_column("is_external", &t_file_history::is_external), + make_column("time_stamp_created", &t_file_history::time_stamp_created), + foreign_key(&t_file_history::file_fk).references(&t_file::id_pk)), + make_table(t_code_action::table_name, + make_column("id_pk", &t_code_action::id_pk, primary_key().autoincrement()), + make_column("file_fk", &t_code_action::file_fk), + make_column("kind", &t_code_action::kind), + make_column("text", &t_code_action::text), + make_column("identifier", &t_code_action::identifier), + foreign_key(&t_code_action::file_fk).references(&t_file::id_pk)), + make_table(t_code_action_change::table_name, + make_column("id_pk", &t_code_action_change::id_pk, primary_key().autoincrement()), + make_column("code_action_fk", &t_code_action_change::code_action_fk), + make_column("operation", &t_code_action_change::operation), + make_column("old_path", &t_code_action_change::old_path), + make_column("path", &t_code_action_change::path), + make_column("start_line", &t_code_action_change::start_line), + make_column("start_column", &t_code_action_change::start_column), + make_column("end_line", &t_code_action_change::end_line), + make_column("end_column", &t_code_action_change::end_column), + make_column("content", &t_code_action_change::content), + foreign_key(&t_code_action_change::code_action_fk).references(&t_code_action::id_pk)), + make_table(t_reference::table_name, + make_column("id_pk", &t_reference::id_pk, primary_key().autoincrement()), + make_column("file_fk", &t_reference::file_fk), + make_column("source_file_fk", &t_reference::source_file_fk), + make_column("variable_fk", &t_reference::variable_fk), + make_column("access", &t_reference::access), + make_column("line", &t_reference::line), + make_column("column", &t_reference::column), + make_column("offset", &t_reference::offset), + make_column("length", &t_reference::length), + make_column("types", &t_reference::types), + make_column("is_declaration", &t_reference::is_declaration), + make_column("is_magic_variable", &t_reference::is_magic_variable), + foreign_key(&t_reference::file_fk).references(&t_file::id_pk), + foreign_key(&t_reference::variable_fk).references(&t_variable::id_pk)), + make_table(t_variable::table_name, + make_column("id_pk", &t_variable::id_pk, primary_key().autoincrement()), + make_column("variable_name", &t_variable::variable_name), + make_column("scope", &t_variable::scope), + make_column("opt_file_fk", &t_variable::opt_file_fk), + foreign_key(&t_variable::opt_file_fk).references(&t_file::id_pk)), + make_table(t_diagnostic::table_name, + make_column("id_pk", &t_diagnostic::id_pk, primary_key().autoincrement()), + make_column("file_fk", &t_diagnostic::file_fk), + make_column("source_file_fk", &t_diagnostic::source_file_fk), + make_column("severity", &t_diagnostic::severity), + make_column("code", &t_diagnostic::code), + make_column("message", &t_diagnostic::message), + make_column("content", &t_diagnostic::content), + make_column("line", &t_diagnostic::line), + make_column("column", &t_diagnostic::column), + make_column("offset", &t_diagnostic::offset), + make_column("length", &t_diagnostic::length), + make_column("is_suppressed", &t_diagnostic::is_suppressed), + foreign_key(&t_diagnostic::source_file_fk).references(&t_file::id_pk), + foreign_key(&t_diagnostic::file_fk).references(&t_file::id_pk))); + return storage; + } + } + class context { + public: + using storage_t = decltype(internal::create_storage(std::string{})); + private: + std::filesystem::path m_db_path; + bool m_bad; + storage_t m_storage; + std::map m_sync_result; + std::string m_error; + + void handle_generation() { + auto generations = m_storage.get_all(); + auto generation = generations.empty() ? std::nullopt : std::make_optional(generations.front()); + if (!generation.has_value()) { + db_clear(); + m_storage.insert(internal::t_db_generation{ + .id_pk = 1, + .generation = internal::t_db_generation::expected_generation + }); + return; + } + if (generation->generation != internal::t_db_generation::expected_generation) { + m_storage.remove_all(); + m_storage.insert(internal::t_db_generation{ + .id_pk = 1, + .generation = internal::t_db_generation::expected_generation + }); + db_clear(); + } + } + + public: + explicit context(const std::filesystem::path &db_path) + : m_db_path(absolute(db_path)), + m_bad(true), + m_storage(internal::create_storage(m_db_path.string())) { + } + + void migrate() { + try { + if (!std::filesystem::exists(m_db_path)) { + auto parent_path = m_db_path.parent_path(); + std::filesystem::create_directories(parent_path); + } + m_sync_result = m_storage.sync_schema(false); + handle_generation(); + m_bad = false; + } catch (const std::exception &e) { + m_error = e.what(); + m_bad = true; + } + } + + + // The sync results as provided by the sqlite_orm sync_schema method + [[nodiscard]] std::map + sync_result() const { return m_sync_result; } + + // Whether the connection to the database could not be established + [[nodiscard]] bool bad() const { return m_bad; } + + // Whether the connection to the database could be established + [[nodiscard]] bool good() const { return !m_bad; } + + // Whether the connection to the database could be established + [[nodiscard]] std::string error() const { return m_error; } + + // Returns the storage object + [[nodiscard]] storage_t &storage() { return m_storage; } + + // Returns the storage object + [[nodiscard]] const storage_t &storage() const { return m_storage; } + + // Returns the path to the database + [[nodiscard]] const std::filesystem::path &db_path() const { return m_db_path; } + + std::optional<::sqfvm::language_server::database::tables::t_file> db_get_file_from_path( + std::filesystem::path path, + bool create_if_not_exists = false); + + void db_clear(); + }; +} + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_CONTEXT_HPP diff --git a/server/sqfvm_language_server/database/orm_mappings.hpp b/server/sqfvm_language_server/database/orm_mappings.hpp new file mode 100644 index 0000000..b832132 --- /dev/null +++ b/server/sqfvm_language_server/database/orm_mappings.hpp @@ -0,0 +1,55 @@ +// +// Created by marco.silipo on 06.06.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_ORM_MAPPINGS_HPP +#define SQFVM_LANGUAGE_SERVER_DATABASE_ORM_MAPPINGS_HPP + +#include "tables/t_diagnostic.h" +#include "tables/t_file.h" +#include "tables/t_reference.h" +#include "tables/t_variable.h" + +#define ORM_ENUM_MAPPING(TYPE, PRIMITIVE) \ +namespace sqlite_orm { \ + template<> \ + struct type_printer : public integer_printer {}; \ + template<> \ + struct statement_binder { \ + int bind(sqlite3_stmt* stmt, int index, const TYPE& value) { \ + return statement_binder().bind(stmt, index, static_cast(value)); \ + } \ + }; \ + template<> \ + struct field_printer { \ + std::string operator()(const TYPE& value) const { \ + return std::to_string(static_cast(value)); \ + } \ + }; \ + template<> \ + struct row_extractor { \ + TYPE extract(sqlite3_stmt* stmt, int column_index) { \ + auto value = sqlite3_column_int64(stmt, column_index); \ + return static_cast(value); \ + } \ + }; \ + inline TYPE operator|(TYPE a, TYPE b) \ + { \ + return static_cast(static_cast(a) | static_cast(b)); \ + } \ + inline TYPE operator&(TYPE a, TYPE b) \ + { \ + return static_cast(static_cast(a) & static_cast(b)); \ + } \ + inline TYPE operator~(TYPE a) \ + { \ + return static_cast(~static_cast(a)); \ + } \ +} + +ORM_ENUM_MAPPING(sqfvm::language_server::database::tables::t_diagnostic::severity_level, int) +ORM_ENUM_MAPPING(sqfvm::language_server::database::tables::t_reference::type_flags, int) +ORM_ENUM_MAPPING(sqfvm::language_server::database::tables::t_reference::access_flags, int) +ORM_ENUM_MAPPING(sqfvm::language_server::database::tables::t_code_action::action_kind, int) +ORM_ENUM_MAPPING(sqfvm::language_server::database::tables::t_code_action_change::file_operation, int) +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_ORM_MAPPINGS_HPP diff --git a/server/sqfvm_language_server/database/tables/t_code_action.h b/server/sqfvm_language_server/database/tables/t_code_action.h new file mode 100644 index 0000000..e838c20 --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_code_action.h @@ -0,0 +1,79 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_CODE_ACTION_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_CODE_ACTION_H + +#include +#include + +namespace sqfvm::language_server::database::tables { + // Represents a file in the workspace in a virtual way. + struct t_code_action { + static constexpr const char *table_name = "tCodeAction"; + enum action_kind { + + /** + * Generic actions that don't fit into any other category. + */ + generic, + + /** + * Fixes for a specific problem. + */ + quick_fix, + + /** + * Generic refactorings. + */ + refactor, + + /** + * - Extract method + * - Extract function + * - Extract variable + * - Extract interface from class + * - ... + */ + extract_refactor, + + /** + * - Inline function + * - Inline variable + * - Inline constant + * - ... + */ + inline_refactor, + + /** + * - Convert JavaScript function to class + * - Add or remove parameter + * - Encapsulate field + * - Make method static + * - Move method to base class + * - ... + */ + rewrite_refactor, + + /** + * Source code actions apply to the entire file. + */ + whole_file, + }; + + // The primary key of this t_file + uint64_t id_pk; + + // Foreign key referring to the t_file this belongs to. + uint64_t file_fk; + + // Foreign key referring to the t_file this belongs to. + action_kind kind; + + // Identifier to group actions by. + std::string identifier; + + // Text to present the user describing the action. + std::string text; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_CODE_ACTION_H diff --git a/server/sqfvm_language_server/database/tables/t_code_action_change.h b/server/sqfvm_language_server/database/tables/t_code_action_change.h new file mode 100644 index 0000000..cf3f407 --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_code_action_change.h @@ -0,0 +1,64 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_CODE_ACTION_CHANGE_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_CODE_ACTION_CHANGE_H + +#include +#include +#include + +namespace sqfvm::language_server::database::tables { + // Represents a file in the workspace in a virtual way. + struct t_code_action_change { + static constexpr const char *table_name = "tCodeActionChange"; + enum file_operation { + file_change, + file_create, + file_delete, + file_rename, + }; + + // The primary key of this t_file + uint64_t id_pk; + + // The code action this change belongs to. + uint64_t code_action_fk; + + // The kind of change this is. + file_operation operation; + + + // If kind is file_rename, this is the current path of the file. + // Otherwise, this is empty. + std::optional old_path; + + // If kind is file_rename, this is the new path of the file. + // If kind is file_create, this is the path of the file to create. + // If kind is file_delete, this is the path of the file to delete. + // If kind is file_change, this is the path of the file to change. + std::string path; + + + // If kind is file_change, this is the line where this change starts in the file. + // Otherwise, this is empty. + std::optional start_line; + + // If kind is file_change, this is the column where this change starts in the file. + // Otherwise, this is empty. + std::optional start_column; + + // If kind is file_change, this is the line where this change ends in the file. + // Otherwise, this is empty. + std::optional end_line; + + // If kind is file_change, this is the column where this change ends in the file. + // Otherwise, this is empty. + std::optional end_column; + + // If kind is file_change, this is the new content to be placed in place of start_line and start_column to end_line and end_column. + // if kind is file_create, this is the content of the file to create. + // Otherwise, this is empty. + std::optional content; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_CODE_ACTION_CHANGE_H diff --git a/server/sqfvm_language_server/database/tables/t_diagnostic.h b/server/sqfvm_language_server/database/tables/t_diagnostic.h new file mode 100644 index 0000000..e9c78be --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_diagnostic.h @@ -0,0 +1,60 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_DIAGNOSTIC_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_DIAGNOSTIC_H + +#include +#include + +namespace sqfvm::language_server::database::tables { + // Represents a file in the workspace in a virtual way. + struct t_diagnostic { + static constexpr const char *table_name = "tDiagnostic"; + enum severity_level { + fatal, + error, + warning, + info, + verbose, + trace + }; + + // The primary key of this t_file + uint64_t id_pk; + + // Foreign key referring to the t_file this belongs to. + uint64_t file_fk; + + // Foreign key referring to the t_file this was discovered in. + uint64_t source_file_fk; + + // The line of this diagnostic in the t_file referred to via file_fk. + uint64_t line; + + // The column of this diagnostic in the t_file referred to via file_fk. + uint64_t column; + + // The column of this diagnostic in the t_file referred to via file_fk. + uint64_t offset; + + // The length of this diagnostic in the t_file referred to via file_fk. + uint64_t length; + + // The severity of this diagnostic + severity_level severity; + + // The diagnostic message + std::string message; + + // The content at the position of the diagnostic, if available + std::string content; + + // The code of the diagnostic message, if applicable + std::string code; + + // Whether this diagnostic is held back from being reported to the client. + // A diagnostic should only be suppressed by the user code analysed, never by the language server! + bool is_suppressed; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_DIAGNOSTIC_H diff --git a/server/sqfvm_language_server/database/tables/t_file.h b/server/sqfvm_language_server/database/tables/t_file.h new file mode 100644 index 0000000..23e2bde --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_file.h @@ -0,0 +1,30 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_FILE_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_FILE_H + +#include +#include + +namespace sqfvm::language_server::database::tables { + // Represents a file in the workspace in a virtual way. + struct t_file { + static constexpr const char *table_name = "tFile"; + + // The file is outdated and should be reanalyzed. + bool is_outdated; + + // The file is deleted and should be removed from the database. + bool is_deleted; + + // The primary key of this t_file + uint64_t id_pk; + + // Unix-Timestamp this file was last changed + uint64_t last_changed; + + // The physical path, relative to the workspace, of this file. + std::string path; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_FILE_H diff --git a/server/sqfvm_language_server/database/tables/t_file_history.h b/server/sqfvm_language_server/database/tables/t_file_history.h new file mode 100644 index 0000000..8d13b07 --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_file_history.h @@ -0,0 +1,31 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_FILE_HISTORY_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_FILE_HISTORY_H + +#include +#include + +namespace sqfvm::language_server::database::tables { +// Represents the content of a file at a specific point in time. + struct t_file_history { + static constexpr const char *table_name = "tFileHistory"; + // The primary key of this t_reference. + uint64_t id_pk; + + // Foreign key referring to the t_file this belongs to. + uint64_t file_fk; + + // The type of access done. + std::string content; + + // Unix-Timestamp this change was recorded. + uint64_t time_stamp_created; + + // Whether this change was provided by the file system or language server. + // External changes are those which are detected by the file system watcher and done by version control for example. + // Internal changes are those which are detected by the language server and done by the user. + bool is_external; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_FILE_HISTORY_H diff --git a/server/sqfvm_language_server/database/tables/t_hover.h b/server/sqfvm_language_server/database/tables/t_hover.h new file mode 100644 index 0000000..b5b4c80 --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_hover.h @@ -0,0 +1,36 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_HOVER_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_HOVER_H + +#include +#include + +namespace sqfvm::language_server::database::tables { + // Represents hover information for a file. + struct t_hover { + static constexpr const char *table_name = "tHover"; + + // The primary key of this t_hover + uint64_t id_pk; + + // Foreign key referring to the t_file this belongs to. + uint64_t file_fk; + + // The line where this hover starts in the t_file referred to via file_fk. + uint64_t start_line; + + // The column where this hover starts in the t_file referred to via file_fk. + uint64_t start_column; + + // The line where this hover ends in the t_file referred to via file_fk. + uint64_t end_line; + + // The column where this hover ends in the t_file referred to via file_fk. + uint64_t end_column; + + // The markdown content of this hover. + std::string markdown; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_HOVER_H diff --git a/server/sqfvm_language_server/database/tables/t_reference.h b/server/sqfvm_language_server/database/tables/t_reference.h new file mode 100644 index 0000000..13642a9 --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_reference.h @@ -0,0 +1,77 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_REFERENCE_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_REFERENCE_H + +#include +#include + +namespace sqfvm::language_server::database::tables { +// Represents a variable in code + struct t_reference { + static constexpr const char *table_name = "tReference"; + enum class type_flags { + none = 0x0000, + + code = 0x0001, + scalar = 0x0002, + boolean = 0x0004, + object = 0x0008, + hashmap = 0x0010, + array = 0x0020, + string = 0x0040, + nil = 0x0080, + + any = 0xFFFF, + }; + enum class access_flags { + none = 0x00, + + // The reference is received + get = 0x01, + + // The reference is updated + set = 0x02, + + all = get | set + }; + + // The primary key of this t_reference. + uint64_t id_pk; + + // Foreign key referring to the t_file this belongs to. + uint64_t file_fk; + + // Foreign key referring to the t_file this was discovered in. + uint64_t source_file_fk; + + // Foreign key referring to the t_variable this refers to. + uint64_t variable_fk; + + // The type of access done. + access_flags access; + + // The line of this reference in the t_file referred to via file_fk. + uint64_t line; + + // The column of this reference in the t_file referred to via file_fk. + uint64_t column; + + // The column of this reference in the t_file referred to via file_fk. + uint64_t offset; + + // The length of this reference in the t_file referred to via file_fk. + uint64_t length; + + // The type this t_reference refers to. + type_flags types; + + // Whether this reference is a declaration. + bool is_declaration; + + // Whether this reference is a magic variable. Magic variables are variables that are not explicitly declared + // but rather implicitly created by the engine. Examples are: _this, _x, _forEachIndex + bool is_magic_variable; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_REFERENCE_H diff --git a/server/sqfvm_language_server/database/tables/t_variable.h b/server/sqfvm_language_server/database/tables/t_variable.h new file mode 100644 index 0000000..99fbca2 --- /dev/null +++ b/server/sqfvm_language_server/database/tables/t_variable.h @@ -0,0 +1,28 @@ +#ifndef SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_VARIABLE_H +#define SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_VARIABLE_H + +#include +#include +#include + +namespace sqfvm::language_server::database::tables { + // Represents an entry of a variable, referable by multiple t_file's using t_reference + struct t_variable { + static constexpr const char *table_name = "tVariable"; + + // The primary key of this t_variable. + uint64_t id_pk; + + // The name of this t_variable. + std::string variable_name; + + // Either the scope-qualified name of this t_variable, or the namespace this t_variable belongs to. + std::string scope; + + // The file this t_variable belongs to. nullopt if this t_variable is a global. + std::optional opt_file_fk; + }; +} + + +#endif //SQFVM_LANGUAGE_SERVER_DATABASE_TABLES_T_VARIABLE_H diff --git a/server/sqfvm_language_server/file_system_watcher.cpp b/server/sqfvm_language_server/file_system_watcher.cpp new file mode 100644 index 0000000..5424b82 --- /dev/null +++ b/server/sqfvm_language_server/file_system_watcher.cpp @@ -0,0 +1,126 @@ +// +// Created by marco.silipo on 14.08.2023. +// + +#include "file_system_watcher.hpp" +#include "Poco/Delegate.h" + +#include +#include + +void sqfvm::language_server::file_system_watcher::watch_directory( + const std::filesystem::path &path) { + if (!std::filesystem::is_directory(path)) { + throw std::runtime_error(path.string() + " is not a directory"); + } + auto lock = std::lock_guard(m_watchers_mutex); + if (m_watchers.find(path) != m_watchers.end()) { + throw std::runtime_error("Already watching " + path.string()); + } + auto watcher = std::make_shared( + path.string(), + Poco::DirectoryWatcher::DW_FILTER_ENABLE_ALL, + 2048); + watcher->itemAdded += Poco::delegate( + this, + &sqfvm::language_server::file_system_watcher::handle_item_added); + watcher->itemRemoved += Poco::delegate( + this, + &sqfvm::language_server::file_system_watcher::handle_item_removed); + watcher->itemModified += Poco::delegate( + this, + &sqfvm::language_server::file_system_watcher::handle_item_modified); + m_watchers[path] = watcher; +} + +void sqfvm::language_server::file_system_watcher::unwatch_directory( + const std::filesystem::path &path) { + auto lock = std::lock_guard(m_watchers_mutex); + auto find_res = m_watchers.find(path); + if (find_res == m_watchers.end()) { + throw std::runtime_error("Not watching " + path.string()); + } + auto watcher = find_res->second; + watcher->itemAdded -= Poco::delegate( + this, + &sqfvm::language_server::file_system_watcher::handle_item_added); + watcher->itemRemoved -= Poco::delegate( + this, + &sqfvm::language_server::file_system_watcher::handle_item_removed); + watcher->itemModified -= Poco::delegate( + this, + &sqfvm::language_server::file_system_watcher::handle_item_modified); + m_watchers.erase(find_res); + assert(m_watchers.find(path) == m_watchers.end()); +} + +void sqfvm::language_server::file_system_watcher::callback_add( + std::function callback) { + m_callback_add = std::move(callback); +} + +void sqfvm::language_server::file_system_watcher::callback_remove( + std::function callback) { + m_callback_remove = std::move(callback); +} + +void sqfvm::language_server::file_system_watcher::callback_modify( + std::function callback) { + m_callback_modify = std::move(callback); +} + +void sqfvm::language_server::file_system_watcher::handle_item_added( + const Poco::DirectoryWatcher::DirectoryEvent &event) { + std::filesystem::path path = event.item.path(); + + auto is_directory = std::filesystem::is_directory(path); + if (is_directory) { + watch(path); + } + + if (m_callback_add) + m_callback_add(path, is_directory); +} + +void sqfvm::language_server::file_system_watcher::handle_item_removed( + const Poco::DirectoryWatcher::DirectoryEvent &event) { + std::filesystem::path path = event.item.path(); + + auto is_directory = unwatch(path); + + if (m_callback_remove) + m_callback_remove(path, is_directory); +} + +void sqfvm::language_server::file_system_watcher::handle_item_modified( + const Poco::DirectoryWatcher::DirectoryEvent &event) { + std::filesystem::path path = event.item.path(); + if (m_callback_modify) + m_callback_modify(path, is_directory(path)); +} + +void sqfvm::language_server::file_system_watcher::watch( + const std::filesystem::path &path) { + if (!std::filesystem::is_directory(path)) + return; + watch_directory(path); + for (auto &p: std::filesystem::recursive_directory_iterator(path)) { + if (std::filesystem::is_directory(p)) + watch_directory(p); + } +} + +bool sqfvm::language_server::file_system_watcher::unwatch( + const std::filesystem::path &path) { + std::vector to_unwatch; + { + auto lock = std::lock_guard(m_watchers_mutex); + for (auto &p: m_watchers) { + if (p.first.string().starts_with(path.string())) + to_unwatch.push_back(p.first); + } + } + for (auto &p: to_unwatch) + unwatch_directory(p); + return !to_unwatch.empty(); +} \ No newline at end of file diff --git a/server/sqfvm_language_server/file_system_watcher.hpp b/server/sqfvm_language_server/file_system_watcher.hpp new file mode 100644 index 0000000..47462ca --- /dev/null +++ b/server/sqfvm_language_server/file_system_watcher.hpp @@ -0,0 +1,45 @@ +// +// Created by marco.silipo on 14.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_FILE_SYSTEM_WATCHER_HPP +#define SQFVM_LANGUAGE_SERVER_FILE_SYSTEM_WATCHER_HPP + +#include +#include +#include +#include +#include + +namespace sqfvm::language_server { + class file_system_watcher { + std::unordered_map> m_watchers; + std::mutex m_watchers_mutex; + std::function m_callback_add; + std::function m_callback_remove; + std::function m_callback_modify; + + void watch_directory(const std::filesystem::path &path); + + void unwatch_directory(const std::filesystem::path &path); + + public: + void watch(const std::filesystem::path &path); + + bool unwatch(const std::filesystem::path &path); + + void callback_add(std::function callback); + + void callback_remove(std::function callback); + + void callback_modify(std::function callback); + + void handle_item_added(const Poco::DirectoryWatcher::DirectoryEvent &); + + void handle_item_removed(const Poco::DirectoryWatcher::DirectoryEvent &); + + void handle_item_modified(const Poco::DirectoryWatcher::DirectoryEvent &); + }; +} + +#endif //SQFVM_LANGUAGE_SERVER_FILE_SYSTEM_WATCHER_HPP diff --git a/server/sqfvm_language_server/git_sha1.h b/server/sqfvm_language_server/git_sha1.h new file mode 100644 index 0000000..2b3e0b7 --- /dev/null +++ b/server/sqfvm_language_server/git_sha1.h @@ -0,0 +1,5 @@ +#ifndef SQFVM_LANGUAGE_SERVER_GIT_SHA1_H +#define SQFVM_LANGUAGE_SERVER_GIT_SHA1_H +extern const char g_GIT_SHA1[]; + +#endif // SQFVM_LANGUAGE_SERVER_GIT_SHA1_H \ No newline at end of file diff --git a/server/sqfvm_language_server/language_server.cpp b/server/sqfvm_language_server/language_server.cpp new file mode 100644 index 0000000..332bed9 --- /dev/null +++ b/server/sqfvm_language_server/language_server.cpp @@ -0,0 +1,887 @@ +#include "language_server.hpp" + +#include "analysis/sqf_ast/sqf_ast_analyzer.hpp" + + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +using namespace sqlite_orm; + +void sqfvm::language_server::language_server::after_initialize(const ::lsp::data::initialize_params ¶ms) { + auto root_uri_string = params.rootUri.has_value() ? params.rootUri.value().path() : "./"sv; + std::filesystem::path uri(root_uri_string); + uri = std::filesystem::absolute(uri).lexically_normal(); + m_lsp_folder = uri / ".vscode"sv / "sqfvm-lsp"; + m_db_path = m_lsp_folder / "sqlite3.db"; + ensure_git_ignore_file_exists(); + m_context = std::make_shared(m_db_path); + m_context->migrate(); + m_sqfvm_factory.add_mapping(uri.string(), ""); + m_file_system_watcher.watch(uri); + m_file_system_watcher.callback_add( + [&](auto &path, bool is_directory) { file_system_item_added(path, is_directory); }); + m_file_system_watcher.callback_remove( + [&](auto &path, bool is_directory) { file_system_item_removed(path, is_directory); }); + m_file_system_watcher.callback_modify( + [&](auto &path, bool is_directory) { file_system_item_modified(path, is_directory); }); + + // Handle SQLite3 database + if (!m_context->good()) { + std::stringstream sstream; + sstream << "Failed to open SQLite3 database '" << m_db_path << "': " << m_context->error() + << ". Language server will not work."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return; + } else { + std::stringstream sstream; + sstream << "Opened SQLite3 database at '" << m_db_path << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + + log_sqlite_migration_report(); + + // Mark all files as deleted, so we can remove them later if they are not in the workspace anymore + try { + m_context->storage().update_all(set(c(&database::tables::t_file::is_deleted) = true)); + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to perform pre-check (mark all files as deleted) with '" << e.what() + << "'. Language server will not work."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return; + } + + auto runtime = m_sqfvm_factory.create([](auto &_) {}, *m_context, std::make_shared()); + + // Mark all files according to their state (deleted, outdated) + for (auto &workspace_folder: params.workspace_folders.value()) { + std::filesystem::path workspace_path(workspace_folder.uri.path()); + + // Iterate over all files recursive + std::filesystem::recursive_directory_iterator + iter(workspace_path, std::filesystem::directory_options::skip_permission_denied); + std::filesystem::recursive_directory_iterator iter_end; + for (; iter != iter_end; iter++) { + auto file_path = iter->path().lexically_normal(); + if (m_analyzer_factory.has(file_path.extension().string())) { + auto last_write_time = iter->last_write_time(); + uint64_t timestamp = (uint64_t) std::chrono::duration_cast( + std::chrono::clock_cast(last_write_time).time_since_epoch()) + .count(); + std::optional file; + try { + auto file_results = m_context->storage().get_all( + where(c(&database::tables::t_file::path) == file_path.string())); + file = file_results.empty() ? std::nullopt : std::optional(file_results[0]); + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to find file '" << file_path.string() << "' with '" << e.what() + << "'. Language server will not work."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return; + } + if (file.has_value()) { + file->is_deleted = false; + file->is_outdated = file->is_outdated || file->last_changed < timestamp; + file->last_changed = timestamp; + try { + m_context->storage().update(file.value()); + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to update file '" << file_path.string() << "' with '" << e.what() + << "'. Language server will not work."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return; + } + if (file->is_outdated) { + auto file_contents = sqf::fileio::passthrough::read_file_from_disk(file_path.string()); + if (file_contents.has_value()) + push_file_history(*file, *file_contents, true); + } + } else { + database::tables::t_file f; + f.path = file_path.string(); + f.last_changed = timestamp; + f.is_deleted = false; + f.is_outdated = true; + try { + m_context->storage().insert(f); + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to insert file '" << f.path << "' with '" << e.what() + << "'. Language server will not work."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return; + } + } + } else if (file_path.filename() == "$PBOPREFIX$") { + add_or_update_pboprefix_mapping_logging(file_path); + } + } + } + + try { + for (const auto &file: m_context->storage().get_all( + where(c(&database::tables::t_file::is_deleted) == true))) { + delete_file(file); + } + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to delete deleted files with '" << e.what() + << "'. Language server will not work."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return; + } + + try { + for (const auto &file: m_context->storage().get_all( + where(c(&database::tables::t_file::is_outdated) == false))) { + publish_diagnostics(file); + } + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to republish diagnostics with '" << e.what() + << "'. Language server will not work."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return; + } + + analyze_outdated_files(); +} + +void sqfvm::language_server::language_server::log_sqlite_migration_report() { + std::stringstream sstream; + sstream << "SQLITE migration report:\n"; + for (auto &it: m_context->sync_result()) { + sstream << it.first << ": "; + switch (it.second) { + case sync_schema_result::new_table_created: + sstream << "new_table_created\n"; + break; + case sync_schema_result::already_in_sync: + sstream << "already_in_sync\n"; + break; + case sync_schema_result::old_columns_removed: + sstream << "old_columns_removed\n"; + break; + case sync_schema_result::new_columns_added: + sstream << "new_columns_added\n"; + break; + case sync_schema_result::new_columns_added_and_old_columns_removed: + sstream << "new_columns_added_and_old_columns_removed\n"; + break; + case sync_schema_result::dropped_and_recreated: + sstream << "dropped_and_recreated\n"; + break; + } + } + window_logMessage(lsp::data::message_type::Log, sstream.str()); +} + +void sqfvm::language_server::language_server::analyze_outdated_files() { + try { + for (const auto &file: m_context->storage().get_all( + where(c(&database::tables::t_file::is_outdated) == true && + c(&database::tables::t_file::is_deleted) == false))) { + analyse_file(file); + } + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to analyze outdated files with '" << e.what() + << "'."; + window_logMessage(lsp::data::message_type::Error, sstream.str()); + return; + } +} + +void sqfvm::language_server::language_server::on_workspace_didChangeConfiguration( + const ::lsp::data::did_change_configuration_params ¶ms) { + m_sqfvm_factory.clear_workspace_mappings(); + if (params.settings.has_value() && params.settings->contains("sqfVmLanguageServer")) { + auto settings = params.settings.value()["sqfVmLanguageServer"]; + if (settings.is_object() && settings.contains("Executable")) { + auto executable = settings["Executable"]; + if (executable.is_object() && executable.contains("PathMappings")) { + auto path_mappings = executable["PathMappings"]; + if (path_mappings.is_array()) { + for (auto &mapping: path_mappings) { + if (mapping.is_object() && mapping.contains("physical") && mapping.contains("virtual")) { + auto physical = mapping["physical"]; + auto virtual_ = mapping["virtual"]; + if (physical.is_string() && virtual_.is_string()) { + m_sqfvm_factory.add_mapping(physical, virtual_, true); + } + } + } + } + } + } + } +} + +std::optional> sqfvm::language_server::language_server::on_textDocument_references( + const lsp::data::references_params ¶ms) { + auto path = std::filesystem::path( + std::string(params.textDocument.uri.path().begin(), + params.textDocument.uri.path().end())) + .lexically_normal(); + auto files = m_context->storage().get_all( + where(c(&database::tables::t_file::path) == path.string())); + if (files.empty()) + return std::nullopt; + auto file = files.front(); + auto references = m_context->storage().get_all( + where(c(&database::tables::t_reference::file_fk) == file.id_pk + && c(&database::tables::t_reference::line) == params.position.line + 1 + && c(&database::tables::t_reference::is_magic_variable) == false)); + if (references.empty()) + return std::nullopt; + std::optional variable_id_opt; + for (auto &reference: references) { + if (reference.column >= params.position.character + 1 || + reference.column + reference.length <= params.position.character + 1) + continue; + variable_id_opt = reference.variable_fk; + break; + } + if (!variable_id_opt.has_value()) + return std::nullopt; + auto variable_id = variable_id_opt.value(); + auto variable_references = m_context->storage().get_all( + where(c(&database::tables::t_reference::variable_fk) == variable_id)); + std::vector locations; + for (const auto &reference: variable_references) { + auto file_opt = m_context->storage().get_optional(reference.file_fk); + auto file_path = file_opt->path; + lsp::data::uri file_uri("file:///" + file_path); + locations.emplace_back(lsp::data::location{ + .uri = file_uri, + .range = lsp::data::range{ + .start = lsp::data::position{ + .line = reference.line - 1, + .character = reference.column + }, + .end = lsp::data::position{ + .line = reference.line - 1, + .character = reference.column + reference.length + } + }, + }); + } + return {locations}; +} + +std::optional<::sqfvm::language_server::database::tables::t_file> +sqfvm::language_server::language_server::get_file_from_path( + const std::filesystem::path &path, + bool create_if_not_exists) { + try { + return m_context->db_get_file_from_path(path, create_if_not_exists); + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to insert file '" << path << "' with '" << e.what() + << "'. Language server will not work for this file."; + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + return {}; + } +} + +void sqfvm::language_server::language_server::remove_pboprefix_mapping( + const std::filesystem::path &pboprefix) { + m_sqfvm_factory.remove_mapping(pboprefix.parent_path().string()); +} + +std::string sqfvm::language_server::language_server::add_or_update_pboprefix_mapping_safe( + const std::filesystem::path &pboprefix) { + if (!exists(pboprefix)) + return ("File '" + pboprefix.string() + "' does not exist."); + auto content_opt = sqf::fileio::passthrough::read_file_from_disk(pboprefix.string()); + if (!content_opt.has_value()) + return ("Failed to read file '" + pboprefix.string() + "'."); + auto content = content_opt.value(); + m_sqfvm_factory.update_mapping(pboprefix.parent_path().string(), content); + return {}; +} + +void sqfvm::language_server::language_server::add_or_update_pboprefix_mapping_logging( + const std::filesystem::path &pboprefix) { + auto result = add_or_update_pboprefix_mapping_safe(pboprefix); + if (!result.empty()) { + window_log(::lsp::data::message_type::Error, [&](auto &sstream) { + sstream << "Failed to read '" << pboprefix << "': " << result; + }); + } +} + +void sqfvm::language_server::language_server::on_textDocument_didOpen( + const lsp::data::did_open_text_document_params ¶ms) { + m_versions[static_cast<::lsp::data::document_uri>(params.text_document.uri.full())] = params.text_document.version; +} + +void sqfvm::language_server::language_server::on_textDocument_didChange( + const ::lsp::data::did_change_text_document_params ¶ms) { + m_versions[static_cast<::lsp::data::document_uri>(params.text_document.uri.full())] = params.text_document.version; + auto path = std::filesystem::path( + std::string(params.text_document.uri.path().begin(), + params.text_document.uri.path().end())) + .lexically_normal(); + + if (iequal(path.filename().string(), "$PBOPREFIX$")) { + // We intentionally ignore the results here to delay the update of the mapping until the user saves the file + window_log(::lsp::data::message_type::Info, [&](auto &sstream) { + sstream << "Detected change in '" + << path + << "'. Language server will update the mapping when the file is saved."; + }); + } else { + auto file_opt = get_file_from_path(path, true); + if (!file_opt.has_value()) + return; + auto file = file_opt.value(); + file.is_outdated = true; + m_context->storage().update(file); + + push_file_history(file, params.content_changes[0].text); + mark_related_files_as_outdated(file); + analyze_outdated_files(); + } +} + +void sqfvm::language_server::language_server::push_file_history( + const ::sqfvm::language_server::database::tables::t_file &file, + std::string contents, + bool is_external) { + auto time_stamp = (uint64_t) std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + m_context->storage().insert(database::tables::t_file_history{ + .file_fk = file.id_pk, + .content = std::move(contents), + .time_stamp_created = time_stamp, + .is_external = is_external, + }); +} + +std::optional> +sqfvm::language_server::language_server::on_textDocument_foldingRange( + const ::lsp::data::folding_range_params ¶ms) { + return {}; +} + +std::optional<::lsp::data::completion_list> sqfvm::language_server::language_server::on_textDocument_completion( + const ::lsp::data::completion_params ¶ms) { + return {}; +} + +sqfvm::language_server::language_server::language_server() : m_sqfvm_factory(this) { + // Setup SQF-VM + // Setup analyzers + m_analyzer_factory.set( + ".sqf", []( + auto ls_path, + auto db_path, + auto &factory, + auto file, + auto text) -> std::unique_ptr { + return std::make_unique( + db_path, + file, + factory, + std::move(text), + ls_path); + }); +} + +bool sqfvm::language_server::language_server::delete_file(const std::filesystem::path &file) { + auto file_opt = get_file_from_path(file.string(), false); + if (!file_opt.has_value()) { + return false; + } + delete_file(file_opt.value()); + return true; +} + +void sqfvm::language_server::language_server::delete_file(sqfvm::language_server::database::tables::t_file file) { + mark_related_files_as_outdated(file); + file.is_deleted = true; + m_context->storage().update(file); + m_context->storage().remove_all( + where(c(&database::tables::t_diagnostic::file_fk) == file.id_pk)); + m_context->storage().remove_all( + where(c(&database::tables::t_reference::file_fk) == file.id_pk)); + m_context->storage().remove_all( + where(c(&database::tables::t_variable::opt_file_fk) == file.id_pk)); + publish_diagnostics(file); +} + +void sqfvm::language_server::language_server::mark_related_files_as_outdated( + const sqfvm::language_server::database::tables::t_file &file) { + std::set outdated_file_ids{}; + auto variables = m_context->storage().get_all( + where(c(&database::tables::t_variable::opt_file_fk) == file.id_pk + and c(&database::tables::t_variable::scope) == "missionNamespace")); + for (const auto &variable: variables) { + auto non_local_references = m_context->storage().get_all( + where(c(&database::tables::t_reference::variable_fk) == variable.id_pk + and c(&database::tables::t_reference::file_fk) != file.id_pk)); + for (const auto &reference: non_local_references) { + outdated_file_ids.insert(reference.file_fk); + } + } + + // Mark all files as outdated + for (const auto &outdated_file_id: outdated_file_ids) { + auto outdated_file = m_context->storage().get(outdated_file_id); + outdated_file.is_outdated = true; + m_context->storage().update(outdated_file); + } + +} + +void sqfvm::language_server::language_server::analyse_file( + const sqfvm::language_server::database::tables::t_file &file) { + uint64_t timestamp = (uint64_t) std::chrono::duration_cast( + std::chrono::clock_cast(last_write_time(std::filesystem::path(file.path))) + .time_since_epoch()) + .count(); + // Create analyzer + auto extension = std::filesystem::path(file.path).extension().string(); + auto contents = m_context->storage().get_all( + where(c(&database::tables::t_file_history::file_fk) == file.id_pk), + order_by(&database::tables::t_file_history::time_stamp_created).desc(), + limit(1)); + std::string content; + if (contents.empty() || contents[0].time_stamp_created < timestamp) { + auto file_contents = sqf::fileio::passthrough::read_file_from_disk(file.path); + if (file_contents.has_value()) + push_file_history(file, *file_contents, true); + else + return; + content = file_contents.value(); + } else { + content = contents[0].content; + } + auto analyzer_opt = m_analyzer_factory.get( + extension, + m_lsp_folder, + m_context->db_path(), + m_sqfvm_factory, + file, + content); + if (!analyzer_opt.has_value()) { + return; + } + window_log(::lsp::data::message_type::Log, [&](auto &sstream) { + sstream << "Analyzing '" << file.path << "'"; + }); + try { + analyzer_opt.value()->analyze(); + analyzer_opt.value()->commit(); + } + catch (std::exception &e) { + std::stringstream sstream; + sstream << "Failed to analyze '" << file.path << "': " << e.what(); + window_logMessage(::lsp::data::message_type::Error, sstream.str()); + m_context->storage().remove_all( + where(c(&database::tables::t_reference::file_fk) == file.id_pk)); + m_context->storage().insert(database::tables::t_diagnostic{ + .file_fk = file.id_pk, + .source_file_fk = file.id_pk, + .severity = database::tables::t_diagnostic::error, + .message = sstream.str(), + .code = "VV-ERR", + }); + } + try { + publish_diagnostics(file); + } + catch (std::exception &e) { + window_log(::lsp::data::message_type::Error, [&](auto &sstream) { + sstream << "Failed to publish diagnostics for '" << file.path << "': " << e.what(); + }); + } +} + +void sqfvm::language_server::language_server::publish_diagnostics( + const sqfvm::language_server::database::tables::t_file &file, + bool publish_sub_files) { + auto file_diagnostics = m_context->storage().get_all( + where((c(&database::tables::t_diagnostic::source_file_fk) == file.id_pk + or c(&database::tables::t_diagnostic::file_fk) == file.id_pk) + and c(&database::tables::t_diagnostic::is_suppressed) == false)); + lsp::data::publish_diagnostics_params params = {}; + params.uri = sanitize_to_uri(file.path); + std::set additional_files{}; + for (const auto &diagnostic: file_diagnostics) { + if (diagnostic.file_fk != file.id_pk) { + additional_files.insert(diagnostic.file_fk); + continue; + } + lsp::data::diagnostics diag = {}; + diag.code = diagnostic.code; + diag.message = diagnostic.message; + diag.range.start.line = diagnostic.line; + diag.range.start.character = diagnostic.column; + diag.range.end.line = diagnostic.line; + diag.range.end.character = diagnostic.column + diagnostic.length; + diag.severity = diagnostic.severity == database::tables::t_diagnostic::fatal + ? lsp::data::diagnostic_severity::Error + : diagnostic.severity == database::tables::t_diagnostic::error + ? lsp::data::diagnostic_severity::Error + : diagnostic.severity == database::tables::t_diagnostic::warning + ? lsp::data::diagnostic_severity::Warning + : diagnostic.severity == database::tables::t_diagnostic::info + ? lsp::data::diagnostic_severity::Information + : lsp::data::diagnostic_severity::Hint; + params.diagnostics.push_back(diag); + } + textDocument_publishDiagnostics(params); + if (!publish_sub_files) + return; + for (auto &additional_file_id: additional_files) { + auto additional_file = m_context->storage().get_optional(additional_file_id); + if (!additional_file.has_value()) + continue; + publish_diagnostics(additional_file.value(), false); + } +} + +void sqfvm::language_server::language_server::mark_file_as_outdated(const std::filesystem::path &path) { + auto file_opt = get_file_from_path(path.string(), true); + if (!file_opt.has_value()) { + return; + } + auto file = file_opt.value(); + bool changed = false; + if (!file.is_outdated) { + file.is_outdated = true; + changed = true; + } + if (changed) + m_context->storage().update(file); +} + +void sqfvm::language_server::language_server::mark_all_files_as_outdated() { + m_context->storage().update_all(set(c(&database::tables::t_file::is_outdated) = true)); +} + +void sqfvm::language_server::language_server::file_system_item_removed( + const std::filesystem::path &path, + bool is_directory) { + if (is_subpath(path, m_lsp_folder.parent_path())) + return; + if (is_directory) { + auto files = m_context->storage().get_all( + where(like(&database::tables::t_file::path, path.string() + "%"))); + for (const auto &file: files) { + delete_file(file); + } + } else if (iequal(path.filename().string(), "$PBOPREFIX$")) { + remove_pboprefix_mapping(path); + mark_all_files_as_outdated(); + } else { + if (!delete_file(path)) + return; + } + analyze_outdated_files(); +} + +void sqfvm::language_server::language_server::file_system_item_added( + const std::filesystem::path &path, + bool is_directory) { + if (is_subpath(path, m_lsp_folder.parent_path())) + return; + if (is_directory) { + for (auto &p: std::filesystem::recursive_directory_iterator(path)) { + if (std::filesystem::is_directory(p)) + continue; + mark_file_as_outdated(p); + } + } else if (iequal(path.filename().string(), "$PBOPREFIX$")) { + add_or_update_pboprefix_mapping_logging(path); + mark_all_files_as_outdated(); + } else { + mark_file_as_outdated(path); + } + analyze_outdated_files(); +} + +void sqfvm::language_server::language_server::file_system_item_modified( + const std::filesystem::path &path, + bool is_directory) { + if (is_directory) + return; + if (is_subpath(path, m_lsp_folder.parent_path())) + return; + if (iequal(path.filename().string(), "$PBOPREFIX$")) { + add_or_update_pboprefix_mapping_logging(path); + mark_all_files_as_outdated(); + } else { + auto file_opt = get_file_from_path(path.string(), true); + if (!file_opt.has_value()) { + return; + } + auto file = file_opt.value(); + if (!file.is_outdated) { + file.is_outdated = true; + m_context->storage().update(file); + } + mark_related_files_as_outdated(file); + } + analyze_outdated_files(); +} + +void sqfvm::language_server::language_server::ensure_git_ignore_file_exists() { + std::filesystem::path git_ignore_path = m_lsp_folder / ".gitignore"; + if (!std::filesystem::exists(git_ignore_path)) { + std::ofstream file(git_ignore_path); + file << ".gitignore" << std::endl; + file << "sqlite3.db" << std::endl; + file << "sqlite3.db-journal" << std::endl; + file << "sqlite3.db-wal" << std::endl; + file << "sqlite3.db-shm" << std::endl; + file.close(); + } +} + +::lsp::data::initialize_result sqfvm::language_server::language_server::on_initialize( + const lsp::data::initialize_params ¶ms) { + m_client_params = params; + ::lsp::data::initialize_result res; + res.serverInfo = ::lsp::data::initialize_result::server_info{}; + res.serverInfo->name = "SQF-VM Language Server"; + res.serverInfo->version = std::string(g_GIT_SHA1); + res.capabilities.textDocumentSync = ::lsp::data::initialize_result::server_capabilities::text_document_sync_options{}; + res.capabilities.textDocumentSync->change = ::lsp::data::text_document_sync_kind::Full; + res.capabilities.textDocumentSync->openClose = true; + res.capabilities.textDocumentSync->save = ::lsp::data::initialize_result::server_capabilities::text_document_sync_options::SaveOptions{}; + res.capabilities.textDocumentSync->save->includeText = true; + res.capabilities.textDocumentSync->willSave = false; + // res.capabilities.foldingRangeProvider = ::lsp::data::initialize_result::server_capabilities::folding_range_registration_options{}; + // res.capabilities.foldingRangeProvider->documentSelector = ::lsp::data::document_filter{}; + // res.capabilities.foldingRangeProvider->documentSelector->language = "sqf"; + res.capabilities.completionProvider = lsp::data::initialize_result::server_capabilities::completion_options{.resolveProvider = true}; + res.capabilities.referencesProvider = lsp::data::initialize_result::server_capabilities::reference_options{.workDoneProgress = false}; + res.capabilities.codeActionProvider = lsp::data::initialize_result::server_capabilities::code_action_options{ + .codeActionKinds = {std::vector{ + lsp::data::code_action_kind::QuickFix, + lsp::data::code_action_kind::Refactor, + lsp::data::code_action_kind::RefactorExtract, + lsp::data::code_action_kind::RefactorInline, + lsp::data::code_action_kind::Source, + lsp::data::code_action_kind::RefactorRewrite, + }} + }; + res.capabilities.hoverProvider = lsp::data::initialize_result::server_capabilities::hover_options{.workDoneProgress = false}; + + return res; +} + +std::optional>> +sqfvm::language_server::language_server::on_textDocument_codeAction(const lsp::data::code_action_params ¶ms) { + using namespace lsp::data; + using namespace database::tables; + using namespace std::string_literals; + const size_t lsp_trash_offset = 1; + auto path = std::filesystem::path( + std::string(params.textDocument.uri.path().begin(), + params.textDocument.uri.path().end())) + .lexically_normal(); + auto file_opt = get_file_from_path(path.string(), false); + if (!file_opt.has_value()) + return std::nullopt; + auto file = file_opt.value(); + + std::vector> out_data{}; + + { + // Get code actions and their corresponding changes from database + auto code_actions = m_context->storage().get_all( + where(c(&t_code_action::file_fk) == file.id_pk)); + for (const auto &code_action: code_actions) { + std::vector> out_changes{}; + auto changes = m_context->storage().get_all( + where(c(&t_code_action_change::code_action_fk) == code_action.id_pk)); + bool in_range = false; + for (const auto &change: changes) { + in_range = in_range || change.start_line <= params.range.start.line + && change.start_column <= params.range.start.character + && change.end_line >= params.range.end.line + && change.end_column >= params.range.end.character; + auto change_path = sanitize_to_uri(change.path); + auto document_uri = static_cast<::lsp::data::document_uri>(change_path.full()); + auto lsp_file_version = m_versions.contains(document_uri) + ? std::optional<::lsp::data::integer>{m_versions.at(document_uri)} + : std::nullopt; + switch (change.operation) { + case t_code_action_change::file_change: + out_changes.emplace_back(text_document_edit{ + .textDocument = optional_versioned_text_document_identifier{ + .version = lsp_file_version, + .uri = change_path, + }, + .edits = {text_edit{ + .range = range{ + .start = position{ + .line = change.start_line.value_or(0), + .character = change.start_column.value_or(0), + }, + .end = position{ + .line = change.end_line.value_or(0), + .character = change.end_column.value_or(0), + }, + }, + .new_text = change.content.value_or(""s), + }}, + }); + break; + case t_code_action_change::file_create: + out_changes.emplace_back(create_file{ + .uri = change_path, + .options = create_file::create_file_options{ + .overwrite = true, + .ignore_if_exists = true, + }, + }); + out_changes.emplace_back(text_document_edit{ + .textDocument = optional_versioned_text_document_identifier{ + .version = lsp_file_version, + .uri = change_path, + }, + .edits = {text_edit{ + .range = range{ + .start = position{0, 0}, + .end = position{0, 0}, + }, + .new_text = change.content.value_or(""s), + }}, + }); + break; + case t_code_action_change::file_delete: + out_changes.emplace_back(lsp::data::delete_file{ + .uri = change_path, + .options = lsp::data::delete_file::delete_file_options{ + .recursive = true, + .ignore_if_not_exists = true, + }, + }); + break; + case t_code_action_change::file_rename: + out_changes.emplace_back(rename_file{ + .oldUri = sanitize_to_uri(change.old_path.value_or(""s)), + .newUri = change_path, + .options = rename_file::rename_file_options{ + .overwrite = true, + .ignore_if_exists = true, + }, + }); + break; + } + } + if (out_changes.empty() || !in_range) + continue; + out_data.push_back(lsp::data::code_action{ + .title = code_action.text, + .kind = code_action.kind == database::tables::t_code_action::quick_fix + ? code_action_kind::QuickFix + : code_action.kind == database::tables::t_code_action::refactor + ? code_action_kind::Refactor + : code_action.kind == database::tables::t_code_action::extract_refactor + ? code_action_kind::RefactorExtract + : code_action.kind == database::tables::t_code_action::inline_refactor + ? code_action_kind::RefactorInline + : code_action.kind == database::tables::t_code_action::whole_file + ? code_action_kind::Source + : code_action.kind == database::tables::t_code_action::rewrite_refactor + ? code_action_kind::RefactorRewrite + : code_action_kind::Empty, + .isPreferred = true, + .edit = workspace_edit{ + .changes = {}, + .document_changes = out_changes, + .change_annotations = {}, + }, + }); + } + } + return {out_data}; +} + +std::optional sqfvm::language_server::language_server::on_textDocument_hover( + const lsp::data::hover_params ¶ms) { + using namespace ::lsp::data; + using namespace std::string_literals; + + auto path = std::filesystem::path( + std::string(params.text_document.uri.path().begin(), + params.text_document.uri.path().end())) + .lexically_normal(); + auto file_opt = get_file_from_path(path.string(), false); + if (!file_opt.has_value()) + return std::nullopt; + auto file = file_opt.value(); + auto hovers = m_context->storage().get_all( + where(c(&database::tables::t_hover::file_fk) == file.id_pk + && c(&database::tables::t_hover::start_line) <= params.position.line + 1 + && c(&database::tables::t_hover::start_column) <= params.position.character + 1 + && c(&database::tables::t_hover::end_line) >= params.position.line + 1 + && c(&database::tables::t_hover::end_column) >= params.position.character + 1)); + if (hovers.empty()) + return std::nullopt; + + std::vector out_hovers; + for (const auto &hover: hovers) { + out_hovers.emplace_back( + markup_content{markup_kind::Markdown, hover.markdown}, + range{ + .start = position{ + .line = hover.start_line, + .character = hover.start_column, + }, + .end = position{ + .line = hover.end_line, + .character = hover.end_column, + }, + }); + } + + if (out_hovers.size() == 1) + return out_hovers.front(); + + std::stringstream sstream; + position smallest_start = out_hovers.front().range->start; + position largest_end = out_hovers.front().range->end; + + for (const auto &hover: out_hovers) { + if (hover.range->start.line < smallest_start.line || (hover.range->start.line == smallest_start.line && hover.range->start.character < smallest_start.character)) + smallest_start = hover.range->start; + if (hover.range->end.line > largest_end.line || (hover.range->end.line == largest_end.line && hover.range->end.character > largest_end.character)) + largest_end = hover.range->end; + if (sstream.tellp() > 0) + sstream << "\n\n"; + sstream << hover.contents.value; + } + + return hover{ + .contents = markup_content{markup_kind::Markdown, sstream.str()}, + .range = range{ + .start = smallest_start, + .end = largest_end, + }, + }; +} diff --git a/server/sqfvm_language_server/language_server.hpp b/server/sqfvm_language_server/language_server.hpp new file mode 100644 index 0000000..1acb774 --- /dev/null +++ b/server/sqfvm_language_server/language_server.hpp @@ -0,0 +1,112 @@ +#ifndef SQFVM_LANGUAGE_SERVER_LANGUAGE_SERVER_HPP +#define SQFVM_LANGUAGE_SERVER_LANGUAGE_SERVER_HPP + +#include "sqfvm_factory.hpp" +#include "lsp/server.hpp" +#include "git_sha1.h" +#include "analysis/analyzer.hpp" +#include "runtime/runtime.h" +#include "database/context.hpp" +#include "file_system_watcher.hpp" + +#include +#include +#include +#include +#include +#include + +namespace sqfvm::language_server { + class language_server : public ::lsp::server { + ::lsp::data::initialize_params m_client_params; + std::filesystem::path m_lsp_folder; + std::filesystem::path m_db_path; + analysis::analyzer_factory m_analyzer_factory; + std::shared_ptr m_context; + std::unordered_map<::lsp::data::document_uri, ::lsp::data::integer> m_versions; + sqfvm_factory m_sqfvm_factory; + file_system_watcher m_file_system_watcher; + + bool delete_file(const std::filesystem::path &file); + + void delete_file(database::tables::t_file file); + + void mark_related_files_as_outdated(const sqfvm::language_server::database::tables::t_file &file); + + void analyse_file(const database::tables::t_file &file); + + void publish_diagnostics(const database::tables::t_file &file, bool publish_sub_files = true); + + void analyze_outdated_files(); + + void push_file_history( + const ::sqfvm::language_server::database::tables::t_file &file, + std::string contents, + bool is_external = false); + + void file_system_item_added(const std::filesystem::path &path, + bool is_directory); + + void file_system_item_removed(const std::filesystem::path &path, + bool is_directory); + + void file_system_item_modified(const std::filesystem::path &path, + bool is_directory); + + [[nodiscard]] std::string add_or_update_pboprefix_mapping_safe(const std::filesystem::path &); + + void mark_all_files_as_outdated(); + void add_or_update_pboprefix_mapping_logging(const std::filesystem::path &); + void remove_pboprefix_mapping(const std::filesystem::path &); + + std::optional get_file_from_path( + const std::filesystem::path &path, + bool create_if_not_exists = false); + + protected: + ::lsp::data::initialize_result on_initialize(const ::lsp::data::initialize_params ¶ms) override; + + void on_shutdown() override {} + + void after_initialize(const ::lsp::data::initialize_params ¶ms) override; + + void on_workspace_didChangeConfiguration(const ::lsp::data::did_change_configuration_params ¶ms) override; + + void on_textDocument_didOpen(const ::lsp::data::did_open_text_document_params ¶ms) override; + + void on_textDocument_didChange(const ::lsp::data::did_change_text_document_params ¶ms) override; + + std::optional> + on_textDocument_references(const lsp::data::references_params ¶ms) override; + + + std::optional> + on_textDocument_foldingRange(const ::lsp::data::folding_range_params ¶ms) override; + + std::optional<::lsp::data::completion_list> + on_textDocument_completion(const ::lsp::data::completion_params ¶ms) override; + + std::optional>> + on_textDocument_codeAction(const lsp::data::code_action_params ¶ms) override; + + std::optional on_textDocument_hover(const lsp::data::hover_params ¶ms) override; + + public: + language_server(); + + void ensure_git_ignore_file_exists(); + + void log_sqlite_migration_report(); + + void mark_file_as_outdated(const std::filesystem::path &path); + + void window_log( + ::lsp::data::message_type type, + const std::function &func) { + std::stringstream sstream; + func(sstream); + window_logMessage(type, sstream.str()); + } + }; +} +#endif // SQFVM_LANGUAGE_SERVER_LANGUAGE_SERVER_HPP \ No newline at end of file diff --git a/server/sqfvm_language_server/lsp/data/enums.hpp b/server/sqfvm_language_server/lsp/data/enums.hpp new file mode 100644 index 0000000..7953453 --- /dev/null +++ b/server/sqfvm_language_server/lsp/data/enums.hpp @@ -0,0 +1,388 @@ +// +// Created by marco.silipo on 20.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_ENUMS_HPP +#define SQFVM_LANGUAGE_SERVER_ENUMS_HPP + +#include +#include +#include +#include +#include + +namespace lsp { + namespace data { + enum class resource_operations { + Empty = 0b000, + /* + Supports creating new files and folders. + */ + Create = 0b001, + /* + Supports renaming existing files and folders. + */ + Rename = 0b010, + /* + Supports deleting existing files and folders. + */ + Delete = 0b100 + }; + + inline resource_operations operator|(resource_operations lhs, resource_operations rhs) { + return static_cast ( + static_cast::type>(lhs) + | static_cast::type>(rhs) + ); + } + + inline resource_operations operator&(resource_operations lhs, resource_operations rhs) { + return static_cast ( + static_cast::type>(lhs) + & static_cast::type>(rhs) + ); + } + + enum class failure_handling { + empty, + /* + Applying the workspace change is simply aborted if one of the changes provided + fails. All operations executed before the failing operation stay executed. + */ + abort, + /* + All operations are executed transactional. That means they either all + succeed or no changes at all are applied to the workspace. + */ + transactional, + /* + If the workspace edit contains only textual file changes they are executed transactional. + If resource changes (create, rename or delete file) are part of the change the failure + handling strategy is abort. + */ + textOnlyTransactional, + /* + The client tries to undo the operations already executed. But there is no + guarantee that this is succeeding. + */ + undo + }; + enum class folding_range_kind { + /** + * Folding range for a comment + * 'comment' + */ + Comment, + /** + * Folding range for a imports or includes + * 'imports' + */ + Imports, + /** + * Folding range for a region (e.g. `#region`) + * 'region' + */ + Region + }; + enum class symbol_kind { + 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 message_type { + /** + * An error message. + */ + Error = 1, + /** + * A warning message. + */ + Warning = 2, + /** + * An information message. + */ + Info = 3, + /** + * A log message. + */ + Log = 4 + }; + + enum class trace_mode { + off, message, verbose + }; + + enum class completion_item_tag { + Deprecated = 1 + }; + + /** + * Describes the content type that a client supports in various + * result literals like `Hover`, `ParameterInfo` or `CompletionItem`. + * + * Please note that `MarkupKinds` must not start with a `$`. This kinds + * are reserved for internal usage. + */ + enum class markup_kind { + /** + * Plain text is supported as a content format + * Value String: "plaintext" + */ + PlainText, /** + * Markdown is supported as a content format + * Value String: "markdown + */ + Markdown + }; + + enum class completion_item_kind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25 + }; + + enum class diagnostic_tag { + /** + * 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 + }; + + + enum class code_action_kind { + /** + * Empty kind. + */ + Empty, + + /** + * Base kind for quickfix actions: 'quickfix'. + */ + QuickFix, + + /** + * Base kind for refactoring actions: 'refactor'. + */ + Refactor, + + /** + * Base kind for refactoring extraction actions: 'refactor.extract'. + * + * Example extract actions: + * + * - Extract method + * - Extract function + * - Extract variable + * - Extract interface from class + * - ... + */ + RefactorExtract, + + /** + * Base kind for refactoring inline actions: 'refactor.inline'. + * + * Example inline actions: + * + * - Inline function + * - Inline variable + * - Inline constant + * - ... + */ + RefactorInline, + + /** + * Base kind for refactoring rewrite actions: 'refactor.rewrite'. + * + * Example rewrite actions: + * + * - Convert JavaScript function to class + * - Add or remove parameter + * - Encapsulate field + * - Make method static + * - Move method to base class + * - ... + */ + RefactorRewrite, + + /** + * Base kind for source actions: `source`. + * + * Source code actions apply to the entire file. + */ + Source, + + /** + * Base kind for an organize imports source action: `source.organizeImports`. + */ + SourceOrganizeImports + }; + + enum class text_document_sync_kind { + /** + * 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 + * send. + */ + Incremental = 2 + }; + + /** + * How a completion was triggered + */ + enum class completion_trigger_kind { + /** + * Completion was triggered by typing an identifier (24x7 code + * complete), manual invocation (e.g Ctrl+Space) or via API. + */ + Invoked = 1, + + /** + * Completion was triggered by a trigger character specified by + * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. + */ + TriggerCharacter = 2, + + /** + * Completion was re-triggered as the current completion list is incomplete. + */ + TriggerForIncompleteCompletions = 3 + }; + + /** + * Defines whether the insert text in a completion item should be interpreted as + * plain text or a snippet. + */ + enum class insert_text_format { + /** + * The primary text to be inserted is treated as a plain string. + */ + PlainText = 1, + + /** + * The primary text to be inserted is treated as a snippet. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ + Snippet = 2 + }; + + enum class diagnostic_severity { + /** + * Reports an error. + */ + Error = 1, /** + * Reports a warning. + */ + Warning = 2, /** + * Reports an information. + */ + Information = 3, /** + * Reports a hint. + */ + Hint = 4 + }; + + enum class text_document_save_reason { + /** + * Manually triggered, e.g. by the user pressing save, by starting debugging, + * or by an API call. + */ + Manual = 1, /** + * Automatic after a delay. + */ + AfterDelay = 2, /** + * When the editor lost focus. + */ + FocusOut = 3, + }; + + /** + * The reason why code actions were requested. + * + * @since 3.17.0 + */ + enum class code_action_trigger_kind { + /** + * Code actions were explicitly requested by the user or by an extension. + */ + Invoked = 1, + /** + * Code actions were requested automatically. + * + * This typically happens when current selection in a file changes, but can + * also be triggered when file content changes. + */ + Automatic = 2, + }; + } +} + +#endif //SQFVM_LANGUAGE_SERVER_ENUMS_HPP diff --git a/server/src/jsonrpc.h b/server/sqfvm_language_server/lsp/jsonrpc.hpp similarity index 55% rename from server/src/jsonrpc.h rename to server/sqfvm_language_server/lsp/jsonrpc.hpp index 495dcff..ab328f0 100644 --- a/server/src/jsonrpc.h +++ b/server/sqfvm_language_server/lsp/jsonrpc.hpp @@ -1,10 +1,14 @@ -#pragma once +#ifndef SQFVM_LANGUAGE_SERVER_LSP_JSONRPC_HPP +#define SQFVM_LANGUAGE_SERVER_LSP_JSONRPC_HPP + + #include #include #include #include #include #include +#include #include #include #include @@ -14,7 +18,7 @@ #include #include -#include +#include "nlohmann/json.hpp" #ifdef _DEBUG #define JSONRPC_DUMP_CHAT_TO_FILE @@ -38,12 +42,12 @@ class jsonrpc std::optional result; std::optional params; - rpcmessage() : protocol_version("2.0"), id({}), method({}), result(), params() {} - rpcmessage(std::string id, std::string method) : protocol_version("2.0"), id(id), method(method), result(), params() {} - rpcmessage(std::string id, std::string method, nlohmann::json params) : protocol_version("2.0"), id(id), method(method), result(), params(params) {} - rpcmessage(std::string id, nlohmann::json result) : protocol_version("2.0"), id(id), method({}), result(result), params() {} + [[maybe_unused]] rpcmessage() : protocol_version("2.0"), id({}), method({}), result(), params() { } + [[maybe_unused]] rpcmessage(std::string id, std::string method) : protocol_version("2.0"), id(std::move(id)), method(std::move(method)), result(), params() { } + [[maybe_unused]] rpcmessage(std::string id, std::string method, nlohmann::json params) : protocol_version("2.0"), id(std::move(id)), method(std::move(method)), result(), params(params) { } + [[maybe_unused]] rpcmessage(std::string id, nlohmann::json result) : protocol_version("2.0"), id(std::move(id)), method({}), result(result), params() { } - nlohmann::json serialize() const + [[nodiscard]] nlohmann::json serialize() const { nlohmann::json res = { { "jsonrpc", protocol_version } }; if (result.has_value()) @@ -86,7 +90,7 @@ class jsonrpc { std::string key; std::string value; - header_kind kind; + header_kind kind = header_kind::other; }; std::vector headers; rpcmessage message; @@ -136,20 +140,20 @@ class jsonrpc *m_write_terminate = true; switch (m_destruct_strategy) { - case detach: + case detach: m_read_thread.detach(); m_write_thread.detach(); break; - case join: + case join: m_read_thread.join(); m_write_thread.join(); break; } } - void register_method(std::string name, mthd callback) + void register_method(const std::string& name, mthd callback) { - m_methods[name] = callback; + m_methods[name] = std::move(callback); } @@ -159,7 +163,7 @@ class jsonrpc { rpcframe frame; { - std::lock_guard ____lock(m_read_mutex); + std::lock_guard lock(m_read_mutex); if (m_qin.empty()) { return false; @@ -167,7 +171,7 @@ class jsonrpc frame = m_qin.front(); m_qin.pop(); } - + auto iter = m_methods.find(frame.message.method); if (iter == m_methods.end()) { return true; } auto mthd = iter->second; @@ -180,7 +184,7 @@ class jsonrpc frame.message = msg; frame.headers.push_back({ "Content-Type", "application/json-rpc;charset=utf-8", rpcframe::header_kind::other }); { - std::lock_guard ____lock(m_write_mutex); + std::lock_guard lock(m_write_mutex); m_qout.push(frame); } } @@ -196,8 +200,8 @@ class jsonrpc #ifdef JSONRPC_DUMP_CHAT_TO_FILE std::filesystem::path p("jsonrpc-read-dump.txt"); p = std::filesystem::absolute(p); - std::fstream __dbg_dump(p, std::fstream::out); - if (!__dbg_dump.good()) + std::fstream dbg_dump(p, std::fstream::out); + if (!dbg_dump.good()) { throw std::runtime_error("Failed to open dump file"); } @@ -211,107 +215,110 @@ class jsonrpc { switch (state) { - case read_header: { - m_in.read(buffer, 1); - auto c = buffer[0]; + case read_header: + { + m_in.read(buffer, 1); + auto c = buffer[0]; #ifdef JSONRPC_DUMP_CHAT_TO_FILE - __dbg_dump << c; - __dbg_dump.flush(); + dbg_dump << c; + dbg_dump.flush(); #endif - if (c == ':') - { - state = read_header_content; - header.key = std::string(message_buffer.begin(), message_buffer.end()); - message_buffer.clear(); - if (header.key == "Content-Length") - { - header.kind = rpcframe::header_kind::content_length; - } - else - { - header.kind = rpcframe::header_kind::other; - } - } - else if (c == '\n') - { - auto findres = std::find_if(frame.headers.begin(), frame.headers.end(), [](rpcframe::header_value_pair& header) -> bool { return header.kind == rpcframe::header_kind::content_length; }); - if (findres == frame.headers.end()) + if (c == ':') { - switch (m_parse_error_strategy) + state = read_header_content; + header.key = std::string(message_buffer.begin(), message_buffer.end()); + message_buffer.clear(); + if (header.key == "Content-Length") { - case jsonrpc::exception: - throw std::runtime_error("No Content-Length header passed"); - case jsonrpc::skip: - frame = {}; - continue; + header.kind = rpcframe::header_kind::content_length; + } + else + { + header.kind = rpcframe::header_kind::other; } } - auto content_length_res = std::from_chars(findres->value.data(), findres->value.data() + findres->value.size(), content_length); - if (content_length_res.ec == std::errc::invalid_argument) + else if (c == '\n') { - switch (m_parse_error_strategy) + auto findres = std::find_if(frame.headers.begin(), frame.headers.end(), [](rpcframe::header_value_pair& header) -> bool { return header.kind == rpcframe::header_kind::content_length; }); + if (findres == frame.headers.end()) + { + switch (m_parse_error_strategy) + { + case jsonrpc::exception: + throw std::runtime_error("No Content-Length header passed"); + case jsonrpc::skip: + frame = {}; + continue; + } + } + auto content_length_res = std::from_chars(findres->value.data(), findres->value.data() + findres->value.size(), content_length); + if (content_length_res.ec == std::errc::invalid_argument) { - case jsonrpc::exception: - throw std::runtime_error("Failed to parse Content-Length"); - case jsonrpc::skip: - frame = {}; - continue; + switch (m_parse_error_strategy) + { + case jsonrpc::exception: + throw std::runtime_error("Failed to parse Content-Length"); + case jsonrpc::skip: + frame = {}; + continue; + } } + state = read_content; + message_buffer.clear(); } - state = read_content; - message_buffer.clear(); - } - else + else + { + message_buffer.push_back(c); + } + } break; + case read_header_content: { - message_buffer.push_back(c); - } - } break; - case read_header_content: { - m_in.read(buffer, 1); - auto c = buffer[0]; + m_in.read(buffer, 1); + auto c = buffer[0]; #ifdef JSONRPC_DUMP_CHAT_TO_FILE - __dbg_dump << c; - __dbg_dump.flush(); + dbg_dump << c; + dbg_dump.flush(); #endif - if (c == '\n') - { - state = read_header; - size_t off = 0; - for (; off < message_buffer.size() && std::isspace(message_buffer[off]); off++); - header.value = std::string(message_buffer.begin() + off, message_buffer.end()); - frame.headers.push_back(header); - message_buffer.clear(); - } - else - { - message_buffer.push_back(c); - } + if (c == '\n') + { + state = read_header; + std::vector::difference_type off = 0; + for (; off < message_buffer.size() && std::isspace(message_buffer[off]) && off >= 0; off++); + header.value = std::string(message_buffer.begin() + off, message_buffer.end()); + frame.headers.push_back(header); + message_buffer.clear(); + } + else + { + message_buffer.push_back(c); + } - } break; - case read_content: { - if (content_length == 0) + } break; + case read_content: { - auto data = std::string(message_buffer.begin(), message_buffer.end()); - message_buffer.clear(); - state = read_header; - frame.message = rpcmessage::deserialize(nlohmann::json::parse(data, nullptr, true, false)); + if (content_length == 0) { - std::lock_guard ____lock(m_read_mutex); - m_qin.push(frame); + auto data = std::string(message_buffer.begin(), message_buffer.end()); + message_buffer.clear(); + state = read_header; + frame.message = rpcmessage::deserialize(nlohmann::json::parse(data, nullptr, true, false)); + { + std::lock_guard lock(m_read_mutex); + m_qin.push(frame); + } + frame = {}; } - frame = {}; - } - else - { - auto read = m_in.read(buffer, std::min(content_length, buffersize)).gcount(); - message_buffer.insert(message_buffer.end(), buffer, buffer + read); - content_length -= read; + else + { + auto read = m_in.read(buffer, std::min(content_length, buffersize)).gcount(); + message_buffer.insert(message_buffer.end(), buffer, buffer + read); + content_length -= read; #ifdef JSONRPC_DUMP_CHAT_TO_FILE - __dbg_dump << std::string_view(buffer, read); - __dbg_dump.flush(); + dbg_dump << std::string_view(buffer, read); + dbg_dump.flush(); #endif - } - } break; + } + } break; } } @@ -324,8 +331,8 @@ class jsonrpc #ifdef JSONRPC_DUMP_CHAT_TO_FILE std::filesystem::path p("jsonrpc-write-dump.txt"); p = std::filesystem::absolute(p); - std::fstream __dbg_dump(p, std::fstream::out); - if (!__dbg_dump.good()) + std::fstream dbg_dump(p, std::fstream::out); + if (!dbg_dump.good()) { throw std::runtime_error("Failed to open dump file"); } @@ -336,7 +343,7 @@ class jsonrpc bool queue_empty; { - std::lock_guard ____lock(m_write_mutex); + std::lock_guard lock(m_write_mutex); queue_empty = m_qout.empty(); } if (queue_empty) @@ -349,7 +356,7 @@ class jsonrpc // Dequeue single frame { - std::lock_guard ____lock(m_write_mutex); + std::lock_guard lock(m_write_mutex); frame = m_qout.front(); m_qout.pop(); } @@ -360,10 +367,10 @@ class jsonrpc // Send frame over m_out m_out << "Content-Length: " << dumped.size() << newline; #ifdef JSONRPC_DUMP_CHAT_TO_FILE - __dbg_dump << "Content-Length: " << dumped.size() << newline; - __dbg_dump.flush(); + dbg_dump << "Content-Length: " << dumped.size() << newline; + dbg_dump.flush(); #endif - for (auto header : frame.headers) + for (const auto& header : frame.headers) { if (header.kind == rpcframe::header_kind::content_length) { @@ -371,14 +378,14 @@ class jsonrpc } m_out << header.key << ": " << header.value << newline; #ifdef JSONRPC_DUMP_CHAT_TO_FILE - __dbg_dump << header.key << ": " << header.value << newline; - __dbg_dump.flush(); + dbg_dump << header.key << ": " << header.value << newline; + dbg_dump.flush(); #endif } m_out << newline << dumped; #ifdef JSONRPC_DUMP_CHAT_TO_FILE - __dbg_dump << newline << dumped; - __dbg_dump.flush(); + dbg_dump << newline << dumped; + dbg_dump.flush(); #endif m_out.flush(); } @@ -386,4 +393,6 @@ class jsonrpc delete terminate; } -}; \ No newline at end of file +}; + +#endif // SQFVM_LANGUAGE_SERVER_LSP_JSONRPC_HPP \ No newline at end of file diff --git a/server/sqfvm_language_server/lsp/lspserver.hpp b/server/sqfvm_language_server/lsp/lspserver.hpp new file mode 100644 index 0000000..3f86a51 --- /dev/null +++ b/server/sqfvm_language_server/lsp/lspserver.hpp @@ -0,0 +1,4930 @@ +#ifndef SQFVM_LANGUAGE_SERVER_LSP_LSPSERVER_HPP +#define SQFVM_LANGUAGE_SERVER_LSP_LSPSERVER_HPP + +#include "data/enums.hpp" +#include "jsonrpc.hpp" +#include "../uri.hpp" + +#include +#include +#include +#include +#include + +namespace lsp::data { + +#pragma region Usings + /** + * An identifier referring to a change annotation managed by a workspace + * edit. + * + * @since 3.16.0. + */ + using change_annotation_identifier = std::string; + /** + * An identifier referring to a change annotation managed by a workspace + * edit. + * + * @since 3.16.0. + */ + using integer = int32_t; + + using change_annotation_identifier = std::string; + using document_uri = std::string; +#pragma endregion +#pragma region from_json + + // IMPORTANT: Always have primitives first, then unordered_map, then vector, then optional! + template + inline void from_json(const nlohmann::json &node, T &t) { + t = T::from_json(node); + } + + template<> + inline void from_json(const nlohmann::json &node, nlohmann::json &t) { + t = node; + } + + template<> + inline void from_json(const nlohmann::json &node, bool &t) { + t = node; + } + + template<> + inline void from_json(const nlohmann::json &node, int &t) { + t = node; + } + + template<> + inline void from_json(const nlohmann::json &node, unsigned int &t) { + t = node; + } + + template<> + inline void from_json(const nlohmann::json &node, float &t) { + t = node; + } + + template<> + inline void from_json(const nlohmann::json &node, uint64_t &t) { + t = node; + } + + template<> + inline void from_json(const nlohmann::json &node, std::string &t) { + t = node; + } + + template<> + inline void from_json(const nlohmann::json &node, resource_operations &t) { + t = resource_operations::Empty; + for (const auto &resource_operation_json: node) { + std::string resource_operation_string = resource_operation_json; + if (resource_operation_string == "create") { + t = t | resource_operations::Create; + } else if (resource_operation_string == "rename") { + t = t | resource_operations::Rename; + } else if (resource_operation_string == "delete") { + t = t | resource_operations::Delete; + } + } + } + + + template<> + inline void from_json(const nlohmann::json &node, failure_handling &t) { + + t = failure_handling::empty; + std::string actual = node; + if (actual == "abort") { + t = failure_handling::abort; + return; + } + if (actual == "transactional") { + t = failure_handling::transactional; + return; + } + if (actual == "textOnlyTransactional") { + t = failure_handling::textOnlyTransactional; + return; + } + if (actual == "undo") { + t = failure_handling::undo; + return; + } + } + + + template<> + inline void from_json(const nlohmann::json &node, folding_range_kind &t) { + t = folding_range_kind::Comment; + std::string actual = node; + if (actual == "comment") { + t = folding_range_kind::Comment; + return; + } + if (actual == "imports") { + t = folding_range_kind::Imports; + return; + } + if (actual == "region") { + t = folding_range_kind::Region; + return; + } + } + + template<> + inline void from_json(const nlohmann::json &node, symbol_kind &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, message_type &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, text_document_save_reason &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, completion_trigger_kind &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, insert_text_format &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, diagnostic_severity &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, code_action_trigger_kind &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, diagnostic_tag &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, code_action_kind &t) { + + t = code_action_kind::Empty; + std::string actual = node; + if (actual == "quickfix") { + t = code_action_kind::QuickFix; + return; + } + if (actual == "refactor") { + t = code_action_kind::Refactor; + return; + } + if (actual == "refactor.extract") { + t = code_action_kind::RefactorExtract; + return; + } + if (actual == "refactor.inline") { + t = code_action_kind::RefactorInline; + return; + } + if (actual == "refactor.rewrite") { + t = code_action_kind::RefactorRewrite; + return; + } + if (actual == "source") { + t = code_action_kind::Source; + return; + } + if (actual == "source.organizeImports") { + t = code_action_kind::SourceOrganizeImports; + return; + } + } + + template<> + inline void from_json(const nlohmann::json &node, text_document_sync_kind &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, completion_item_kind &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, completion_item_tag &t) { + t = static_cast(node.get()); + } + + template<> + inline void from_json(const nlohmann::json &node, markup_kind &t) { + + t = markup_kind::PlainText; + std::string actual = node; + if (actual == "plaintext") { + t = markup_kind::PlainText; + return; + } + if (actual == "markdown") { + t = markup_kind::Markdown; + return; + } + } + + template<> + inline void from_json(const nlohmann::json &node, trace_mode &t) { + + t = trace_mode::off; + std::string actual = node; + if (actual == "message") { + t = trace_mode::message; + return; + } + if (actual == "verbose") { + t = trace_mode::verbose; + return; + } + } + + + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + + + template + inline void from_json(const nlohmann::json &node, std::unordered_map &ts) { + ts = std::unordered_map(); + for (auto it = node.begin(); it != node.end(); ++it) { + TKey key; + TValue value; + from_json(it.key(), key); + from_json(it.value(), value); + ts[key] = value; + } + } + + template + inline void from_json(const nlohmann::json &node, std::vector &ts) { + ts = std::vector(); + for (const auto &subnode: node) { + T t; + from_json(subnode, t); + ts.push_back(t); + } + } + + template + inline void from_json(const nlohmann::json &node, std::optional &opt) { + if (!node.empty() && !node.is_null()) { + T t{}; + from_json(node, t); + opt = t; + } else { + opt = {}; + } + } + +#pragma endregion +#pragma region to_json + // IMPORTANT: Always have primitives first, then unordered_map, then vector, then optional! + + template + inline nlohmann::json to_json(const T &t) { + return t.to_json(); + } + + template<> + inline nlohmann::json to_json(const nlohmann::json &t) { + return t; + } + + template<> + inline nlohmann::json to_json(const bool &t) { + return t; + } + + template<> + inline nlohmann::json to_json(const int &t) { + return t; + } + + template<> + inline nlohmann::json to_json(const unsigned int &t) { + return t; + } + + template<> + inline nlohmann::json to_json(const float &t) { + return t; + } + + template<> + inline nlohmann::json to_json(const uint64_t &t) { + return t; + } + + template<> + inline nlohmann::json to_json(const std::string &t) { + return t; + } + + template<> + inline nlohmann::json to_json(const resource_operations &t) { + nlohmann::json arr; + if ((t & resource_operations::Create) == resource_operations::Create) { + arr.push_back("create"); + } + if ((t & resource_operations::Delete) == resource_operations::Delete) { + arr.push_back("delete"); + } + if ((t & resource_operations::Rename) == resource_operations::Rename) { + arr.push_back("rename"); + } + return arr; + } + + template<> + inline nlohmann::json to_json(const failure_handling &t) { + switch (t) { + case failure_handling::abort: + return "abort"; + case failure_handling::transactional: + return "transactional"; + case failure_handling::textOnlyTransactional: + return "textOnlyTransactional"; + case failure_handling::undo: + return "undo"; + default: + return {}; + } + } + + template<> + inline nlohmann::json to_json(const folding_range_kind &t) { + switch (t) { + case folding_range_kind::Comment: + return "comment"; + case folding_range_kind::Imports: + return "imports"; + case folding_range_kind::Region: + return "region"; + default: + return {}; + } + } + + template<> + inline nlohmann::json to_json(const symbol_kind &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const message_type &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const trace_mode &t) { + switch (t) { + case trace_mode::off: + return "off"; + case trace_mode::message: + return "message"; + case trace_mode::verbose: + return "verbose"; + default: + return {}; + } + } + + template<> + inline nlohmann::json to_json(const completion_item_tag &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const markup_kind &t) { + switch (t) { + case markup_kind::PlainText: + return "plaintext"; + case markup_kind::Markdown: + return "markdown"; + default: + return {}; + } + } + + template<> + inline nlohmann::json to_json(const completion_item_kind &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const code_action_trigger_kind &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const diagnostic_tag &t) { + return static_cast(t); + } + + + template<> + inline nlohmann::json to_json(const code_action_kind &t) { + switch (t) { + case code_action_kind::QuickFix: + return "quickfix"; + case code_action_kind::Refactor: + return "refactor"; + case code_action_kind::RefactorExtract: + return "refactor.extract"; + case code_action_kind::RefactorInline: + return "refactor.inline"; + case code_action_kind::RefactorRewrite: + return "refactor.rewrite"; + case code_action_kind::Source: + return "source"; + case code_action_kind::SourceOrganizeImports: + return "source.organizeImports"; + default: + return {}; + } + } + + template<> + inline nlohmann::json to_json(const text_document_sync_kind &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const text_document_save_reason &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const completion_trigger_kind &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const insert_text_format &t) { + return static_cast(t); + } + + template<> + inline nlohmann::json to_json(const diagnostic_severity &t) { + return static_cast(t); + } + + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + // LEAVE THE FOLLOWING HERE! ADD NEW ABOVE! + + template + inline nlohmann::json to_json(const std::variant &t) { + return std::visit([](auto &&arg) { + return to_json(arg); + }, t); + } + + template + inline nlohmann::json to_json(const std::unordered_map &ts) { + nlohmann::json json = nlohmann::json::object(); + for (const auto &t: ts) { + json[t.first] = to_json(t.second); + } + return json; + } + + template + inline nlohmann::json to_json(const std::vector &ts) { + nlohmann::json json = nlohmann::json::array(); + for (const auto &t: ts) { + json.push_back(to_json(t)); + } + return json; + } + + template + inline nlohmann::json to_json(const std::optional &t) { + if (t.has_value()) { + return to_json(t.value()); + } else { + return nullptr; + } + } + +#pragma endregion + + + // ALWAYS LEAVE THIS AT TOP! + template + inline void from_json(const nlohmann::json &node, const char *key, T &t) { + from_json(node[key], t); + } + + template + inline void from_json(const nlohmann::json &node, const char *key, std::optional &opt) { + if (node.contains(key) && !node[key].is_null()) { + T t{}; + from_json(node, key, t); + opt = t; + } else { + opt = {}; + } + } + + template + inline void set_json(nlohmann::json &json, const char *key, const std::optional &t) { + if (t.has_value()) { + json[key] = to_json(t.value()); + } + } + + template + inline void set_json(nlohmann::json &json, const char *key, const T &t) { + json[key] = to_json(t); + } + +} +namespace lsp::data { + struct uri : public ::x39::uri { + uri() : ::x39::uri() {} + + explicit uri(const std::string &input) : ::x39::uri(input) { + } + + explicit uri(std::string_view input) : ::x39::uri(input) { + } + + uri(std::string_view schema, std::string_view user, std::string_view password, std::string_view host, + std::string_view port, std::string_view path, std::string_view query, std::string_view fragment) + : ::x39::uri(schema, user, password, host, port, path, query, fragment) { + } + + static uri from_json(const nlohmann::json &node) { + return uri(node.get()); + } + + [[nodiscard]] nlohmann::json to_json() const { + return encoded(); + } + }; + +} +namespace std { + template<> + struct std::hash + { + std::size_t operator()(lsp::data::uri const& s) const noexcept + { + return std::hash{}(s.full()); + } + }; +} +namespace lsp::data { + template + inline nlohmann::json to_json(const std::unordered_map &ts) { + nlohmann::json json = nlohmann::json::object(); + for (const auto &t: ts) { + json[t.first.encoded()] = to_json(t.second); + } + return json; + } + + /* + * A document filter denotes a document through properties like language, scheme or pattern. + * An example is a filter that applies to TypeScript files on disk. + * Another example is a filter the applies to JSON files with name package.json: + */ + struct document_filter { + /** + * A language id, like `typescript`. + */ + std::string language; + + /** + * A Uri [scheme](#Uri.scheme), like `file` or `untitled`. + */ + std::string scheme; + + /** + * A glob pattern, like `*.{ts,js}`. + * + * Glob patterns can have the following syntax: + * - `*` to match one or more characters in a path segment + * - `?` to match on one character in a path segment + * - `**` to match any number of path segments, including none + * - `{}` to group conditions (e.g. `**?/*.{ts,js}` matches all TypeScript and JavaScript files) + * - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) + * - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) + */ + std::string pattern; + + static document_filter from_json(const nlohmann::json &node) { + document_filter res; + data::from_json(node, "language", res.language); + data::from_json(node, "scheme", res.scheme); + data::from_json(node, "pattern", res.pattern); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "language", language); + data::set_json(json, "scheme", scheme); + data::set_json(json, "pattern", pattern); + return json; + } + }; + + struct position { + size_t line; + size_t character; + + static position from_json(const nlohmann::json &node) { + position res{}; + data::from_json(node, "line", res.line); + data::from_json(node, "character", res.character); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "line", line); + data::set_json(json, "character", character); + return json; + } + }; + + struct range { + position start; + position end; + + static range from_json(const nlohmann::json &node) { + range res{}; + data::from_json(node, "start", res.start); + data::from_json(node, "end", res.end); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "start", start); + data::set_json(json, "end", end); + return json; + } + }; + + struct text_edit { + range range{}; + std::string new_text; + + static text_edit from_json(const nlohmann::json &node) { + text_edit res; + data::from_json(node, "range", res.range); + data::from_json(node, "newText", res.new_text); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "range", range); + data::set_json(json, "newText", new_text); + return json; + } + }; + + /** + * A special text edit with an additional change annotation. + * + * @since 3.16.0. + */ + struct annotated_text_edit { + /** + * The actual annotation identifier. + */ + change_annotation_identifier annotationId; + range range{}; + std::string new_text; + + static annotated_text_edit from_json(const nlohmann::json &node) { + annotated_text_edit res; + data::from_json(node, "annotationId", res.annotationId); + data::from_json(node, "range", res.range); + data::from_json(node, "newText", res.new_text); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "annotationId", annotationId); + data::set_json(json, "range", range); + data::set_json(json, "newText", new_text); + return json; + } + }; + + struct text_document_identifier { + uri uri; + + static text_document_identifier from_json(const nlohmann::json &node) { + text_document_identifier res; + data::from_json(node, "uri", res.uri); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "uri", uri); + return json; + } + }; + + struct text_document_item { + /** + * The text document's URI. + */ + uri uri; + /** + * The text document's language identifier. + */ + std::string language_id; + /** + * The version number of this document (it will increase after each + * change, including undo/redo). + */ + integer version{}; + /** + * The content of the opened text document. + */ + std::string text; + + static text_document_item from_json(const nlohmann::json &node) { + text_document_item res; + data::from_json(node, "uri", res.uri); + data::from_json(node, "languageId", res.language_id); + data::from_json(node, "version", res.version); + data::from_json(node, "text", res.text); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "uri", uri); + data::set_json(json, "languageId", language_id); + data::set_json(json, "version", version); + data::set_json(json, "text", text); + return json; + } + }; + + struct versioned_text_document_identifier { + uri uri{}; + /** + * The version number of this document. + * + * The version number of a document will increase after each change, + * including undo/redo. The number doesn't need to be consecutive. + */ + integer version{}; + + static versioned_text_document_identifier from_json(const nlohmann::json &node) { + versioned_text_document_identifier res; + data::from_json(node, "uri", res.uri); + data::from_json(node, "version", res.version); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "uri", uri); + data::set_json(json, "version", version); + return json; + } + }; + + /** + * A `MarkupContent` literal represents a string value which content is interpreted base on its + * kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. + * + * If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. + * See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + * + * Here is an example how such a string can be constructed using JavaScript / TypeScript: + * ```typescript + * let markdown: MarkdownContent = { + * kind: MarkupKind.Markdown, + * value: [ + * '# Header', + * 'Some text', + * '```typescript', + * 'someCode();', + * '```' + * ].join('\n') + * }; + * ``` + * + * *Please Note* that clients might sanitize the return markdown. A client could decide to + * remove HTML from the markdown to avoid script execution. + */ + struct markup_content { + /** + * The type of the Markup + */ + markup_kind kind = markup_kind::PlainText; + + /** + * The content itself + */ + std::string value; + + static markup_content from_json(const nlohmann::json &node) { + markup_content res; + data::from_json(node, "kind", res.kind); + data::from_json(node, "value", res.value); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "kind", kind); + data::set_json(json, "value", value); + return json; + } + }; + + /** + * Represents a reference to a command. + * Provides a title which will be used to represent a command in the UI. + * Commands are identified by a string identifier. + * The recommended way to handle commands is to implement their execution on the server side + * if the client and server provides the corresponding capabilities. + * Alternatively the tool extension code could handle the command. + * The protocol currently does not specify a set of well-known commands. + */ + + struct command { + /** + * Title of the command, like `save`. + */ + std::string title; + /** + * The identifier of the actual command handler. + */ + std::string command_identifier; + /** + * Arguments that the command handler should be + * invoked with. + */ + std::optional> arguments; + + static command from_json(const nlohmann::json &node) { + command res; + data::from_json(node, "title", res.title); + data::from_json(node, "command", res.command_identifier); + auto arguments_find_res = node.find("arguments"); + if (arguments_find_res != node.end()) { + res.arguments = std::vector{}; + for (const auto &it: (*arguments_find_res)) { + res.arguments->push_back(it); + } + } + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "title", title); + data::set_json(json, "command", command_identifier); + if (arguments.has_value()) { + json["arguments"] = arguments.value(); + } + return json; + } + }; + + /** + * An identifier which optionally denotes a specific version of a text document. + * This information usually flows from the server to the client. + */ + struct optional_versioned_text_document_identifier { + /** + * The version number of this document. If an optional versioned text document + * identifier is sent from the server to the client and the file is not + * open in the editor (the server has not received an open notification + * before) the server can send `null` to indicate that the version is + * known and the content on disk is the master (as specified with document + * content ownership). + * + * The version number of a document will increase after each change, + * including undo/redo. The number doesn't need to be consecutive. + */ + std::optional version; + + uri uri; + + static optional_versioned_text_document_identifier from_json(const nlohmann::json &node) { + optional_versioned_text_document_identifier res; + data::from_json(node, "version", res.version); + data::from_json(node, "uri", res.uri); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "version", version); + data::set_json(json, "uri", uri); + return json; + } + }; + + /** + * New in version 3.16: + * support for AnnotatedTextEdit. + * The support is guarded by the client capability workspace.workspaceEdit.changeAnnotationSupport. + * If a client doesn’t signal the capability, + * servers shouldn’t send AnnotatedTextEdit literals back to the client. + * + * Describes textual changes on a single text document. + * The text document is referred to as a OptionalVersionedTextDocumentIdentifier + * to allow clients to check the text document version before an edit is applied. + * A TextDocumentEdit describes all changes on a version Si and after they are applied move the document + * to version Si+1. + * So the creator of a TextDocumentEdit doesn’t need to sort the array of edits or do any kind of ordering. + * However the edits must be non overlapping. + */ + struct text_document_edit { + /** + * The text document to change. + */ + optional_versioned_text_document_identifier textDocument; + + /** + * The edits to be applied. + * + * @since 3.16.0 - support for AnnotatedTextEdit. This is guarded by the + * client capability `workspace.workspaceEdit.changeAnnotationSupport` + */ + std::vector> edits; + + // implementing generic std::variant support is hard and implementing type-specific from_json is not worth + // it here as it is server-only anyway. + // static text_document_edit from_json(const nlohmann::json &node) { + // text_document_edit res; + // data::from_json(node, "textDocument", res.textDocument); + // data::from_json(node, "edits", res.edits); + // return res; + // } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "textDocument", textDocument); + data::set_json(json, "edits", edits); + return json; + } + }; + + /** + * Create file operation + */ + struct create_file { + /** + * Options to create a file. + */ + struct create_file_options { + /** + * Overwrite existing file. Overwrite wins over `ignoreIfExists` + */ + std::optional overwrite; + + /** + * Ignore if exists. + */ + std::optional ignore_if_exists; + + static create_file_options from_json(const nlohmann::json &node) { + create_file_options res; + data::from_json(node, "overwrite", res.overwrite); + data::from_json(node, "ignoreIfExists", res.ignore_if_exists); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "overwrite", overwrite); + data::set_json(json, "ignoreIfExists", ignore_if_exists); + return json; + } + }; + + /** + * The resource to create. + */ + data::uri uri; + + /** + * Additional options + */ + std::optional options; + + /** + * An optional annotation identifier describing the operation. + * + * @since 3.16.0 + */ + std::optional annotationId; + + static create_file from_json(const nlohmann::json &node) { + create_file res; + data::from_json(node, "uri", res.uri); + data::from_json(node, "options", res.options); + data::from_json(node, "annotationId", res.annotationId); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + json["kind"] = data::to_json(std::string("create")); + data::set_json(json, "uri", uri); + data::set_json(json, "options", options); + data::set_json(json, "annotationId", annotationId); + return json; + } + }; + + /** + * Rename file operation + */ + struct rename_file { + /** + * Rename file options + */ + struct rename_file_options { + /** + * Overwrite target if existing. Overwrite wins over `ignoreIfExists` + */ + std::optional overwrite; + + /** + * Ignores if target exists. + */ + std::optional ignore_if_exists; + + static rename_file_options from_json(const nlohmann::json &node) { + rename_file_options res; + data::from_json(node, "overwrite", res.overwrite); + data::from_json(node, "ignoreIfExists", res.ignore_if_exists); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "overwrite", overwrite); + data::set_json(json, "ignoreIfExists", ignore_if_exists); + return json; + } + }; + + /** + * The old (existing) location. + */ + uri oldUri; + + /** + * The new location. + */ + uri newUri; + + /** + * Rename options. + */ + std::optional options; + + /** + * An optional annotation identifier describing the operation. + * + * @since 3.16.0 + */ + std::optional annotationId; + + static rename_file from_json(const nlohmann::json &node) { + rename_file res; + data::from_json(node, "oldUri", res.oldUri); + data::from_json(node, "newUri", res.newUri); + data::from_json(node, "options", res.options); + data::from_json(node, "annotationId", res.annotationId); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + json["rename"] = data::to_json(std::string("rename")); + data::set_json(json, "oldUri", oldUri); + data::set_json(json, "newUri", newUri); + data::set_json(json, "options", options); + data::set_json(json, "annotationId", annotationId); + return json; + } + }; + + /** + * Delete file operation + */ + struct delete_file { + /** + * Delete file options + */ + struct delete_file_options { + /** + * Delete the content recursively if a folder is denoted. + */ + std::optional recursive; + + /** + * Ignore the operation if the file doesn't exist. + */ + std::optional ignore_if_not_exists; + + static delete_file_options from_json(const nlohmann::json &node) { + delete_file_options res; + data::from_json(node, "recursive", res.recursive); + data::from_json(node, "ignoreIfNotExists", res.ignore_if_not_exists); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "recursive", recursive); + data::set_json(json, "ignoreIfNotExists", ignore_if_not_exists); + return json; + } + }; + + /** + * The file to delete. + */ + data::uri uri; + + /** + * Delete options. + */ + std::optional options; + + /** + * An optional annotation identifier describing the operation. + * + * @since 3.16.0 + */ + std::optional annotationId; + + static create_file from_json(const nlohmann::json &node) { + create_file res; + data::from_json(node, "uri", res.uri); + data::from_json(node, "options", res.options); + data::from_json(node, "annotationId", res.annotationId); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + json["delete"] = data::to_json(std::string("delete")); + data::set_json(json, "uri", uri); + data::set_json(json, "options", options); + data::set_json(json, "annotationId", annotationId); + return json; + } + }; + + /** + * Additional information that describes document changes. + * + * @since 3.16.0 + */ + struct change_annotation { + /** + * A human-readable string describing the actual change. The string + * is rendered prominent in the user interface. + */ + std::string label; + + /** + * A flag which indicates that user confirmation is needed + * before applying the change. + */ + std::optional needsConfirmation; + + /** + * A human-readable string which is rendered less prominent in + * the user interface. + */ + std::optional description; + + static change_annotation from_json(const nlohmann::json &node) { + change_annotation res; + data::from_json(node, "label", res.label); + data::from_json(node, "needsConfirmation", res.needsConfirmation); + data::from_json(node, "description", res.description); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "label", label); + data::set_json(json, "needsConfirmation", needsConfirmation); + data::set_json(json, "description", description); + return json; + } + }; + + /** + * A workspace edit represents changes to many resources managed in the workspace. + * The edit should either provide changes or documentChanges. + * If the client can handle versioned document edits and if documentChanges are present, + * the latter are preferred over changes. + * + * Since version 3.13.0 a workspace edit can contain resource operations + * (create, delete or rename files and folders) as well. + * If resource operations are present clients need to execute the operations + * in the order in which they are provided. + * So a workspace edit for example can consist of the following two changes: + * (1) create file a.txt and + * (2) a text document edit which insert text into file a.txt. + * An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) + * will cause failure of the operation. + * How the client recovers from the failure is described by the client capability: + * workspace.workspaceEdit.failureHandling + */ + struct workspace_edit { + /** + * Holds changes to existing resources. + */ + std::optional>> changes; + /** + * Depending on the client capability + * `workspace.workspaceEdit.resourceOperations` document changes are either + * an array of `TextDocumentEdit`s to express changes to n different text documents + * where each text document edit addresses a specific version of a text document. + * Or it can contain above `TextDocumentEdit`s mixed with create, rename and delete + * file / folder operations. + * + * Whether a client supports versioned document edits is expressed via + * `workspace.workspaceEdit.documentChanges` client capability. + * + * If a client neither supports `documentChanges` nor + * `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s using the + * `changes` property are supported. + */ + std::optional>> document_changes; + /** + * A map of change annotations that can be referenced in + * `AnnotatedTextEdit`s or create, rename and delete file / folder operations. + * + * Whether clients honor this property depends on the client capability + * `workspace.workspaceEdit.changeAnnotationSupport`. + */ + std::optional> change_annotations; + + // implementing generic std::variant support is hard and implementing type-specific from_json is not worth + // it here as it is server-only anyway. + // static workspace_edit from_json(const nlohmann::json &node) { + // workspace_edit res; + // data::from_json(node, "changes", res.changes); + // data::from_json(node, "documentChanges", res.document_changes); + // data::from_json(node, "changeAnnotations", res.change_annotations); + // return res; + // } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "changes", changes); + data::set_json(json, "documentChanges", document_changes); + data::set_json(json, "changeAnnotations", change_annotations); + return json; + } + }; + + + struct location { + uri uri; + range range{}; + + static location from_json(const nlohmann::json &node) { + location res; + data::from_json(node, "uri", res.uri); + data::from_json(node, "range", res.range); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "uri", uri); + data::set_json(json, "range", range); + return json; + } + }; + + struct diagnostics { + struct diagnostic_related_information { + /** + * The location of this related diagnostic information. + */ + location location; + + /** + * The message of this related diagnostic information. + */ + std::string message; + + static diagnostic_related_information from_json(const nlohmann::json &node) { + diagnostic_related_information res; + data::from_json(node, "location", res.location); + data::from_json(node, "message", res.message); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "location", location); + data::set_json(json, "message", message); + return json; + } + }; + + /** + * The range at which the message applies. + */ + range range{}; + + /** + * The diagnostic's severity. Can be omitted. If omitted it is up to the + * client to interpret diagnostics as error, warning, info or hint. + */ + std::optional severity; + + /** + * The diagnostic's code, which might appear in the user interface. + */ + std::optional code; + + /** + * A human-readable string describing the source of this + * diagnostic, e.g. 'typescript' or 'super lint'. + */ + std::optional source; + + /** + * The diagnostic's message. + */ + std::string message; + + /** + * Additional metadata about the diagnostic. + * + * @since 3.15.0 + */ + std::optional> tags; + + /** + * An array of related diagnostic information, e.g. when symbol-names within + * a scope collide all definitions can be marked via this property. + */ + std::optional> relatedInformation; + + static diagnostics from_json(const nlohmann::json &node) { + diagnostics res; + data::from_json(node, "range", res.range); + data::from_json(node, "severity", res.severity); + data::from_json(node, "code", res.code); + data::from_json(node, "source", res.source); + data::from_json(node, "message", res.message); + data::from_json(node, "tags", res.tags); + data::from_json(node, "relatedInformation", res.relatedInformation); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "range", range); + data::set_json(json, "severity", severity); + data::set_json(json, "code", code); + data::set_json(json, "source", source); + data::set_json(json, "message", message); + data::set_json(json, "tags", tags); + data::set_json(json, "relatedInformation", relatedInformation); + return json; + } + }; + + struct folding_range { + /** + * The zero-based line number from where the folded range starts. + */ + size_t startLine{}; + + /** + * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. + */ + std::optional startCharacter; + + /** + * The zero-based line number where the folded range ends. + */ + size_t endLine{}; + + /** + * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. + */ + std::optional 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. + */ + std::optional kind; + + static struct folding_range from_json(const nlohmann::json &node) { + struct folding_range res; + data::from_json(node, "startLine", res.startLine); + data::from_json(node, "startCharacter", res.startCharacter); + data::from_json(node, "endLine", res.endLine); + data::from_json(node, "endCharacter", res.endCharacter); + data::from_json(node, "kind", res.kind); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "startLine", startLine); + data::set_json(json, "startCharacter", startCharacter); + data::set_json(json, "endLine", endLine); + data::set_json(json, "endCharacter", endCharacter); + data::set_json(json, "kind", kind); + return json; + } + }; + + /** + * A code action represents a change that can be performed in code, e.g. to fix + * a problem or to refactor code. + * + * A CodeAction must set either `edit` and/or a `command`. If both are supplied, + * the `edit` is applied first, then the `command` is executed. + */ + struct code_action { + struct disabled_info { + /** + * Human readable description of why the code action is currently + * disabled. + * + * This is displayed in the code actions UI. + */ + std::string reason; + + static disabled_info from_json(const nlohmann::json &node) { + disabled_info res; + data::from_json(node, "reason", res.reason); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "reason", reason); + return json; + } + }; + + /** + * A short, human-readable, title for this code action. + */ + std::string title; + /** + * The kind of the code action. + * + * Used to filter code actions. + */ + std::optional kind; + /** + * The diagnostics that this code action resolves. + */ + std::optional> diagnostics; + /** + * Marks this as a preferred action. Preferred actions are used by the + * `auto fix` command and can be targeted by keybindings. + * + * A quick fix should be marked preferred if it properly addresses the + * underlying error. A refactoring should be marked preferred if it is the + * most reasonable choice of actions to take. + * + * @since 3.15.0 + */ + std::optional isPreferred; + /** + * Marks that the code action cannot currently be applied. + * + * Clients should follow the following guidelines regarding disabled code + * actions: + * + * - Disabled code actions are not shown in automatic lightbulbs code + * action menus. + * + * - Disabled actions are shown as faded out in the code action menu when + * the user request a more specific type of code action, such as + * refactorings. + * + * - If the user has a keybinding that auto applies a code action and only + * a disabled code actions are returned, the client should show the user + * an error message with `reason` in the editor. + * + * @since 3.16.0 + */ + std::optional disabled; + /** + * The workspace edit this code action performs. + */ + std::optional edit; + /** + * A command this code action executes. If a code action + * provides an edit and a command, first the edit is + * executed and then the command. + */ + std::optional command; + /** + * A data entry field that is preserved on a code action between + * a `textDocument/codeAction` and a `codeAction/resolve` request. + * + * @since 3.16.0 + */ + std::optional data; + + // implementing generic std::variant support is hard and implementing type-specific from_json is not worth + // it here as it is server-only anyway. + // static code_action from_json(const nlohmann::json &node) { + // code_action res; + // data::from_json(node, "title", res.title); + // data::from_json(node, "kind", res.kind); + // data::from_json(node, "diagnostics", res.diagnostics); + // data::from_json(node, "isPreferred", res.isPreferred); + // data::from_json(node, "disabled", res.disabled); + // data::from_json(node, "edit", res.edit); + // data::from_json(node, "command", res.command); + // data::from_json(node, "data", res.data); + // return res; + // } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "title", title); + data::set_json(json, "kind", kind); + data::set_json(json, "diagnostics", diagnostics); + data::set_json(json, "isPreferred", isPreferred); + data::set_json(json, "disabled", disabled); + data::set_json(json, "edit", edit); + data::set_json(json, "command", command); + data::set_json(json, "data", data); + return json; + } + }; + + struct completion_item { + /** + * The label of this completion item. By default + * also the text that is inserted when selecting + * this completion. + */ + std::string label; + + /** + * The kind of this completion item. Based of the kind + * an icon is chosen by the editor. The standardized set + * of available values is defined in `CompletionItemKind`. + */ + std::optional kind; + + /** + * Tags for this completion item. + * + * @since 3.15.0 + */ + std::optional> tags; + + /** + * A human-readable string with additional information + * about this item, like type or symbol information. + */ + std::optional detail; + + /** + * A human-readable string that represents a doc-comment. + */ + std::optional documentation; + + /** + * Indicates if this item is deprecated. + * + * @deprecated Use `tags` instead if supported. + */ + std::optional deprecated; + + /** + * Select this item when showing. + * + * *Note* that only one completion item can be selected and that the + * tool / client decides which item that is. The rule is that the *first* + * item of those that match best is selected. + */ + std::optional preselect; + + /** + * A string that should be used when comparing this item + * with other items. When `falsy` the label is used. + */ + std::optional sort_text; + + /** + * A string that should be used when filtering a set of + * completion items. When `falsy` the label is used. + */ + std::optional filter_text; + + /** + * A string that should be inserted into a document when selecting + * this completion. When `falsy` the label is used. + * + * The `insertText` is subject to interpretation by the client side. + * Some tools might not take the string literally. For example + * VS Code when code complete is requested in this example `con` + * and a completion item with an `insertText` of `console` is provided it + * will only insert `sole`. Therefore it is recommended to use `textEdit` instead + * since it avoids additional client side interpretation. + */ + std::optional insert_text; + + /** + * The format of the insert text. The format applies to both the `insertText` property + * and the `newText` property of a provided `textEdit`. If omitted defaults to + * `InsertTextFormat.PlainText`. + */ + std::optional insert_text_format; + + /** + * An edit which is applied to a document when selecting this completion. When an edit is provided the value of + * `insertText` is ignored. + * + * *Note:* The range of the edit must be a single line range and it must contain the position at which completion + * has been requested. + */ + std::optional textEdit; + + /** + * An optional array of additional text edits that are applied when + * selecting this completion. Edits must not overlap (including the same insert position) + * with the main edit nor with themselves. + * + * Additional text edits should be used to change text unrelated to the current cursor position + * (for example adding an import statement at the top of the file if the completion item will + * insert an unqualified type). + */ + std::optional> additionalTextEdits; + + /** + * An optional set of characters that when pressed while this completion is active will accept it first and + * then type that character. *Note* that all commit characters should have `length=1` and that superfluous + * characters will be ignored. + */ + std::optional> commitCharacters; + + /** + * An optional command that is executed *after* inserting this completion. *Note* that + * additional modifications to the current document should be described with the + * additionalTextEdits-property. + */ + std::optional command; + + /** + * A data entry field that is preserved on a completion item between + * a completion and a completion resolve request. + */ + std::optional data; + + static completion_item from_json(const nlohmann::json &node) { + completion_item res; + data::from_json(node, "label", res.label); + data::from_json(node, "kind", res.kind); + data::from_json(node, "tags", res.tags); + data::from_json(node, "detail", res.detail); + data::from_json(node, "documentation", res.documentation); + data::from_json(node, "deprecated", res.deprecated); + data::from_json(node, "preselect", res.preselect); + data::from_json(node, "sortText", res.sort_text); + data::from_json(node, "filterText", res.filter_text); + data::from_json(node, "insertText", res.insert_text); + data::from_json(node, "insertTextFormat", res.insert_text_format); + data::from_json(node, "textEdit", res.textEdit); + data::from_json(node, "additionalTextEdits", res.additionalTextEdits); + data::from_json(node, "commitCharacters", res.commitCharacters); + data::from_json(node, "command", res.command); + res.data = node.contains("data") ? node["data"] : nlohmann::json(nullptr); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "label", label); + data::set_json(json, "kind", kind); + data::set_json(json, "tags", tags); + data::set_json(json, "detail", detail); + data::set_json(json, "documentation", documentation); + data::set_json(json, "deprecated", deprecated); + data::set_json(json, "preselect", preselect); + data::set_json(json, "sortText", sort_text); + data::set_json(json, "filterText", filter_text); + data::set_json(json, "insertText", insert_text); + data::set_json(json, "insertTextFormat", insert_text_format); + data::set_json(json, "textEdit", textEdit); + data::set_json(json, "additionalTextEdits", additionalTextEdits); + data::set_json(json, "commitCharacters", commitCharacters); + data::set_json(json, "command", command); + if (data.has_value()) { + json["data"] = *data; + } + return json; + } + }; + + /** + * Represents a collection of [completion items](#CompletionItem) to be presented + * in the editor. + */ + struct completion_list { + /** + * This list it not complete. Further typing should result in recomputing + * this list. + */ + bool isIncomplete; + + /** + * The completion items. + */ + std::vector items; + + static completion_list from_json(const nlohmann::json &node) { + completion_list res; + data::from_json(node, "isIncomplete", res.isIncomplete); + data::from_json(node, "items", res.items); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "isIncomplete", isIncomplete); + data::set_json(json, "items", items); + return json; + } + }; + + struct initialize_result { + struct server_capabilities { + struct text_document_sync_options { + struct SaveOptions { + /** + * The client is supposed to include the content on save. + */ + std::optional includeText; + + static SaveOptions from_json(const nlohmann::json &node) { + SaveOptions res; + data::from_json(node, "includeText", res.includeText); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "includeText", includeText); + return json; + } + }; + + /** + * Open and close notifications are sent to the server. If omitted open close notification should not + * be sent. + */ + std::optional openClose; + /** + * If present will save notifications are sent to the server. If omitted the notification should not be + * sent. + */ + std::optional willSave; + /** + * If present will save wait until requests are sent to the server. If omitted the request should not be + * sent. + */ + std::optional willSaveWaitUntil; + /** + * If present save notifications are sent to the server. If omitted the notification should not be + * sent. + */ + std::optional save; + + /** + * Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full + * and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None. + */ + std::optional change; + + static text_document_sync_options from_json(const nlohmann::json &node) { + text_document_sync_options res; + data::from_json(node, "openClose", res.openClose); + data::from_json(node, "willSave", res.willSave); + data::from_json(node, "willSaveWaitUntil", res.willSaveWaitUntil); + data::from_json(node, "save", res.save); + data::from_json(node, "change", res.change); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "openClose", openClose); + data::set_json(json, "willSave", willSave); + data::set_json(json, "willSaveWaitUntil", willSaveWaitUntil); + data::set_json(json, "change", change); + return json; + } + }; + + struct completion_options { + /** + * Most tools trigger 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. + * + * If code complete should automatically be trigger on characters not being valid inside + * an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. + */ + std::optional> triggerCharacters; + + /** + * The list of all possible characters that commit a completion. This field can be used + * if clients don't support individual commit characters per completion item. See + * `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport`. + * + * If a server provides both `allCommitCharacters` and commit characters on an individual + * completion item the ones on the completion item win. + * + * @since 3.2.0 + */ + std::optional> allCommitCharacters; + + /** + * The server provides support to resolve additional + * information for a completion item. + */ + std::optional resolveProvider; + + static completion_options from_json(const nlohmann::json &node) { + completion_options res; + data::from_json(node, "triggerCharacters", res.triggerCharacters); + data::from_json(node, "allCommitCharacters", res.allCommitCharacters); + data::from_json(node, "resolveProvider", res.resolveProvider); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "triggerCharacters", triggerCharacters); + data::set_json(json, "allCommitCharacters", allCommitCharacters); + data::set_json(json, "resolveProvider", resolveProvider); + return json; + } + }; + + struct hover_options { + std::optional workDoneProgress; + + static hover_options from_json(const nlohmann::json &node) { + hover_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct signature_help_options { + std::optional workDoneProgress; + /** + * The characters that trigger signature help + * automatically. + */ + std::optional> 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. + * + * @since 3.15.0 + */ + std::optional> retriggerCharacters; + + static signature_help_options from_json(const nlohmann::json &node) { + signature_help_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "triggerCharacters", res.triggerCharacters); + data::from_json(node, "retriggerCharacters", res.retriggerCharacters); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "triggerCharacters", triggerCharacters); + data::set_json(json, "retriggerCharacters", retriggerCharacters); + return json; + } + }; + + struct declaration_registration_options { + std::optional workDoneProgress; + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + std::optional id; + /** + * A document selector to identify the scope of the registration. If set to null + * the document selector provided on the client side will be used. + */ + std::optional documentSelector; + + static declaration_registration_options from_json(const nlohmann::json &node) { + declaration_registration_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "id", res.id); + data::from_json(node, "documentSelector", res.documentSelector); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "id", id); + data::set_json(json, "documentSelector", documentSelector); + return json; + } + }; + + struct definition_options { + std::optional workDoneProgress; + + static definition_options from_json(const nlohmann::json &node) { + definition_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct type_definition_registration_options { + std::optional workDoneProgress; + /** + * A document selector to identify the scope of the registration. If set to null + * the document selector provided on the client side will be used. + */ + std::optional documentSelector; + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + std::optional id; + + static type_definition_registration_options from_json(const nlohmann::json &node) { + type_definition_registration_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "documentSelector", res.documentSelector); + data::from_json(node, "id", res.id); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "documentSelector", documentSelector); + data::set_json(json, "id", id); + return json; + } + }; + + struct implementation_registration_options { + std::optional workDoneProgress; + /** + * A document selector to identify the scope of the registration. If set to null + * the document selector provided on the client side will be used. + */ + std::optional documentSelector; + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + std::optional id; + + static implementation_registration_options from_json(const nlohmann::json &node) { + implementation_registration_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "documentSelector", res.documentSelector); + data::from_json(node, "id", res.id); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "documentSelector", documentSelector); + data::set_json(json, "id", id); + return json; + } + }; + + struct reference_options { + std::optional workDoneProgress; + + static reference_options from_json(const nlohmann::json &node) { + reference_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct document_highlight_options { + std::optional workDoneProgress; + + static document_highlight_options from_json(const nlohmann::json &node) { + document_highlight_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct document_symbol_options { + std::optional workDoneProgress; + + static document_symbol_options from_json(const nlohmann::json &node) { + document_symbol_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct code_action_options { + std::optional workDoneProgress; + /** + * CodeActionKinds that this server may return. + * + * The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server + * may list out every specific kind they provide. + */ + std::optional> codeActionKinds; + + static code_action_options from_json(const nlohmann::json &node) { + code_action_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "codeActionKinds", res.codeActionKinds); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "codeActionKinds", codeActionKinds); + return json; + } + }; + + struct code_lens_options { + std::optional workDoneProgress; + /** + * Code lens has a resolve provider as well. + */ + std::optional resolveProvider; + + static code_lens_options from_json(const nlohmann::json &node) { + code_lens_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "resolveProvider", res.resolveProvider); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "resolveProvider", resolveProvider); + return json; + } + }; + + struct document_link_options { + std::optional workDoneProgress; + /** + * Code lens has a resolve provider as well. + */ + std::optional resolveProvider; + + static document_link_options from_json(const nlohmann::json &node) { + document_link_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "resolveProvider", res.resolveProvider); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "resolveProvider", resolveProvider); + return json; + } + }; + + struct document_color_registration_options { + std::optional workDoneProgress; + /** + * A document selector to identify the scope of the registration. If set to null + * the document selector provided on the client side will be used. + */ + std::optional documentSelector; + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + std::optional id; + + static document_color_registration_options from_json(const nlohmann::json &node) { + document_color_registration_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "documentSelector", res.documentSelector); + data::from_json(node, "id", res.id); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "documentSelector", documentSelector); + data::set_json(json, "id", id); + return json; + } + }; + + struct document_formatting_options { + std::optional workDoneProgress; + + static document_formatting_options from_json(const nlohmann::json &node) { + document_formatting_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct document_range_formatting_options { + std::optional workDoneProgress; + + static document_range_formatting_options from_json(const nlohmann::json &node) { + document_range_formatting_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct document_on_type_formatting_options { + /** + * A character on which formatting should be triggered, like `}`. + */ + std::string firstTriggerCharacter; + + /** + * More trigger characters. + */ + std::optional> moreTriggerCharacter; + + static document_on_type_formatting_options from_json(const nlohmann::json &node) { + document_on_type_formatting_options res; + data::from_json(node, "firstTriggerCharacter", res.firstTriggerCharacter); + data::from_json(node, "moreTriggerCharacter", res.moreTriggerCharacter); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "firstTriggerCharacter", firstTriggerCharacter); + data::set_json(json, "moreTriggerCharacter", moreTriggerCharacter); + return json; + } + }; + + struct rename_options { + std::optional workDoneProgress; + /** + * Renames should be checked and tested before being executed. + */ + std::optional prepareProvider; + + static rename_options from_json(const nlohmann::json &node) { + rename_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "prepareProvider", res.prepareProvider); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "prepareProvider", prepareProvider); + return json; + } + }; + + struct folding_range_registration_options { + std::optional workDoneProgress; + /** + * A document selector to identify the scope of the registration. If set to null + * the document selector provided on the client side will be used. + */ + std::optional documentSelector; + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + std::optional id; + + static folding_range_registration_options from_json(const nlohmann::json &node) { + folding_range_registration_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "documentSelector", res.documentSelector); + data::from_json(node, "id", res.id); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "documentSelector", documentSelector); + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + struct execute_command_options { + std::optional workDoneProgress; + /** + * The commands to be executed on the server. + */ + std::optional> commands; + + static execute_command_options from_json(const nlohmann::json &node) { + execute_command_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "commands", res.commands); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "commands", commands); + return json; + } + }; + + struct selection_range_registration_options { + std::optional workDoneProgress; + /** + * A document selector to identify the scope of the registration. If set to null + * the document selector provided on the client side will be used. + */ + std::optional documentSelector; + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + std::optional id; + + static selection_range_registration_options from_json(const nlohmann::json &node) { + selection_range_registration_options res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + data::from_json(node, "documentSelector", res.documentSelector); + data::from_json(node, "id", res.id); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + data::set_json(json, "documentSelector", documentSelector); + data::set_json(json, "id", id); + return json; + } + }; + + struct workspace_folders_server_capabilities { + /** + * The server has support for workspace folders + */ + std::optional supported; + + /** + * Whether the server wants to receive workspace folder + * change notifications. + * + * If a string is provided, the string is treated as an ID + * under which the notification is registered on the client + * side. The ID can be used to unregister for these events + * using the `client/unregisterCapability` request. + * + * Implementors note: For simplicity (on my end ...), the option to use a string here was removed. + */ + std::optional changeNotifications; + + static workspace_folders_server_capabilities from_json(const nlohmann::json &node) { + workspace_folders_server_capabilities res; + data::from_json(node, "supported", res.supported); + data::from_json(node, "changeNotifications", res.changeNotifications); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "supported", supported); + data::set_json(json, "changeNotifications", changeNotifications); + return json; + } + }; + + struct Workspace { + /** + * The server supports workspace folder. + * + * @since 3.6.0 + */ + std::optional workspaceFolders; + + static Workspace from_json(const nlohmann::json &node) { + Workspace res; + data::from_json(node, "workspaceFolders", res.workspaceFolders); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workspaceFolders", workspaceFolders); + return json; + } + }; + + /** + * Defines how text documents are synced. Is either a detailed structure defining each notification or + * for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`. + * + * Implementors note: Technically, this should support `TextDocumentSyncOptions | number` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional textDocumentSync; + + /** + * The server provides completion support. + */ + std::optional completionProvider; + + /** + * The server provides hover support. + * + * Implementors note: Technically, this should support `boolean | HoverOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional hoverProvider; + + /** + * The server provides signature help support. + */ + std::optional signatureHelpProvider; + + /** + * The server provides go to declaration support. + * + * @since 3.14.0 + * + * Implementors note: Technically, this should support `boolean | DeclarationOptions | DeclarationRegistrationOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional declarationProvider; + + /** + * The server provides goto definition support. + * + * Implementors note: Technically, this should support `boolean | DefinitionOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional definitionProvider; + + /** + * The server provides goto type definition support. + * + * @since 3.6.0 + * + * Implementors note: Technically, this should support `boolean | TypeDefinitionOptions | TypeDefinitionRegistrationOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional typeDefinitionProvider;; + + /** + * The server provides goto implementation support. + * + * @since 3.6.0 + * + * Implementors note: Technically, this should support `boolean | ImplementationOptions | ImplementationRegistrationOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional implementationProvider; + + /** + * The server provides find references support. + * + * Implementors note: Technically, this should support `boolean | ReferenceOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional referencesProvider; + + /** + * The server provides document highlight support. + * + * Implementors note: Technically, this should support `boolean | DocumentHighlightOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional documentHighlightProvider; + + /** + * The server provides document symbol support. + * + * Implementors note: Technically, this should support `boolean | DocumentSymbolOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional 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`. + * + * Implementors note: Technically, this should support `boolean | CodeActionOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional codeActionProvider; + + /** + * The server provides code lens. + */ + std::optional codeLensProvider; + + /** + * The server provides document link support. + */ + std::optional documentLinkProvider; + + /** + * The server provides color provider support. + * + * @since 3.6.0 + * + * Implementors note: Technically, this should support `boolean | DocumentColorOptions | DocumentColorRegistrationOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional colorProvider; + + /** + * The server provides document formatting. + * + * Implementors note: Technically, this should support `boolean | DocumentFormattingOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional documentFormattingProvider; + + /** + * The server provides document range formatting. + * + * Implementors note: Technically, this should support `boolean | DocumentRangeFormattingOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional documentRangeFormattingProvider; + + /** + * The server provides document formatting on typing. + */ + std::optional documentOnTypeFormattingProvider; + + /** + * The server provides rename support. RenameOptions may only be + * specified if the client states that it supports + * `prepareSupport` in its initial `initialize` request. + * + * Implementors note: Technically, this should support `boolean | RenameOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional renameProvider; + + /** + * The server provides folding provider support. + * + * @since 3.10.0 + * + * Implementors note: Technically, this should support `boolean | FoldingRangeOptions | FoldingRangeRegistrationOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional foldingRangeProvider; + + /** + * The server provides execute command support. + */ + std::optional executeCommandProvider; + + /** + * The server provides selection range support. + * + * @since 3.15.0 + * + * Implementors note: Technically, this should support `boolean | SelectionRangeOptions | SelectionRangeRegistrationOptions` for backwards compatibility ... but we ignore that simply because + * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement + * a server. + */ + std::optional selectionRangeProvider; + + /** + * The server provides workspace symbol support. + */ + std::optional workspaceSymbolProvider; + + /** + * Workspace specific server capabilities + */ + std::optional workspace; + + /** + * Experimental server capabilities. + */ + std::optional experimental; + + static server_capabilities from_json(const nlohmann::json &node) { + server_capabilities res; + data::from_json(node, "textDocumentSync", res.textDocumentSync); + data::from_json(node, "completionProvider", res.completionProvider); + data::from_json(node, "hoverProvider", res.hoverProvider); + data::from_json(node, "signatureHelpProvider", res.signatureHelpProvider); + data::from_json(node, "declarationProvider", res.declarationProvider); + data::from_json(node, "definitionProvider", res.definitionProvider); + data::from_json(node, "typeDefinitionProvider", res.typeDefinitionProvider); + data::from_json(node, "implementationProvider", res.implementationProvider); + data::from_json(node, "referencesProvider", res.referencesProvider); + data::from_json(node, "documentHighlightProvider", res.documentHighlightProvider); + data::from_json(node, "documentSymbolProvider", res.documentSymbolProvider); + data::from_json(node, "codeActionProvider", res.codeActionProvider); + data::from_json(node, "codeLensProvider", res.codeLensProvider); + data::from_json(node, "documentLinkProvider", res.documentLinkProvider); + data::from_json(node, "colorProvider", res.colorProvider); + data::from_json(node, "documentFormattingProvider", res.documentFormattingProvider); + data::from_json(node, "documentRangeFormattingProvider", res.documentRangeFormattingProvider); + data::from_json(node, "documentOnTypeFormattingProvider", res.documentOnTypeFormattingProvider); + data::from_json(node, "renameProvider", res.renameProvider); + data::from_json(node, "foldingRangeProvider", res.foldingRangeProvider); + data::from_json(node, "executeCommandProvider", res.executeCommandProvider); + data::from_json(node, "selectionRangeProvider", res.selectionRangeProvider); + data::from_json(node, "workspaceSymbolProvider", res.workspaceSymbolProvider); + data::from_json(node, "workspace", res.workspace); + res.experimental = node.contains("experimental") ? node["experimental"] : nlohmann::json(nullptr); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "textDocumentSync", textDocumentSync); + data::set_json(json, "completionProvider", completionProvider); + data::set_json(json, "hoverProvider", hoverProvider); + data::set_json(json, "signatureHelpProvider", signatureHelpProvider); + data::set_json(json, "declarationProvider", declarationProvider); + data::set_json(json, "definitionProvider", definitionProvider); + data::set_json(json, "typeDefinitionProvider", typeDefinitionProvider); + data::set_json(json, "implementationProvider", implementationProvider); + data::set_json(json, "referencesProvider", referencesProvider); + data::set_json(json, "documentHighlightProvider", documentHighlightProvider); + data::set_json(json, "documentSymbolProvider", documentSymbolProvider); + data::set_json(json, "codeActionProvider", codeActionProvider); + data::set_json(json, "codeLensProvider", codeLensProvider); + data::set_json(json, "documentLinkProvider", documentLinkProvider); + data::set_json(json, "colorProvider", colorProvider); + data::set_json(json, "documentFormattingProvider", documentFormattingProvider); + data::set_json(json, "documentRangeFormattingProvider", documentRangeFormattingProvider); + data::set_json(json, "documentOnTypeFormattingProvider", documentOnTypeFormattingProvider); + data::set_json(json, "renameProvider", renameProvider); + data::set_json(json, "foldingRangeProvider", foldingRangeProvider); + data::set_json(json, "executeCommandProvider", executeCommandProvider); + data::set_json(json, "selectionRangeProvider", selectionRangeProvider); + data::set_json(json, "workspaceSymbolProvider", workspaceSymbolProvider); + data::set_json(json, "workspace", workspace); + if (experimental.has_value()) { + json["experimental"] = *experimental; + } + return json; + } + }; + + struct server_info { + std::string name; + std::optional version; + + static server_info from_json(const nlohmann::json &node) { + server_info res; + data::from_json(node, "name", res.name); + data::from_json(node, "version", res.version); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "name", name); + data::set_json(json, "version", version); + return json; + } + }; + + /** + * The capabilities the language server provides. + */ + server_capabilities capabilities; + + /** + * Information about the server. + * + * @since 3.15.0 + */ + std::optional serverInfo; + + static initialize_result from_json(const nlohmann::json &node) { + initialize_result res; + data::from_json(node, "capabilities", res.capabilities); + data::from_json(node, "serverInfo", res.serverInfo); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "capabilities", capabilities); + data::set_json(json, "serverInfo", serverInfo); + return json; + } + }; + + struct initialize_params { + struct client_info { + std::string name; + std::string version; + + static client_info from_json(const nlohmann::json &node) { + return { + node["name"], node["version"]}; + } + + nlohmann::json to_json() const { + return { + {"name", name}, + {"version", version}}; + } + }; + + struct workspace_edit_client_capabilities { + /* + The client supports versioned document changes in `WorkspaceEdit`s + */ + std::optional documentChanges; + /* + The resource operations the client supports. Clients should at least + support 'create', 'rename' and 'delete' files and folders. + + @since 3.13.0 + */ + resource_operations resourceOperations; + /* + The failure handling strategy of a client if applying the workspace edit + fails. + + @since 3.13.0 + */ + failure_handling failureHandling; + + static workspace_edit_client_capabilities from_json(const nlohmann::json &node) { + workspace_edit_client_capabilities cap; + data::from_json(node, "documentChanges", cap.documentChanges); + data::from_json(node, "resourceOperations", cap.resourceOperations); + data::from_json(node, "failureHandling", cap.failureHandling); + return cap; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "documentChanges", documentChanges); + data::set_json(json, "resourceOperations", resourceOperations); + data::set_json(json, "failureHandling", failureHandling); + return json; + } + }; + + struct did_change_configuration_client_capabilities { + /* + Did change configuration notification supports dynamic registration. + */ + std::optional dynamicRegistration; + + static did_change_configuration_client_capabilities from_json(const nlohmann::json &node) { + did_change_configuration_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct did_change_watched_files_client_capabilities { + /* + Did change watched files notification supports dynamic registration. Please note + that the current protocol doesn't support static configuration for file changes + from the server side. + */ + std::optional dynamicRegistration; + + static did_change_watched_files_client_capabilities from_json(const nlohmann::json &node) { + did_change_watched_files_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct workspace_symbol_client_capabilities { + struct SymbolKind { + std::optional> valueSet; + + static SymbolKind from_json(const nlohmann::json &node) { + SymbolKind res; + data::from_json(node, "valueSet", res.valueSet); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "valueSet", valueSet); + return json; + } + }; + + /* + Symbol request supports dynamic registration. + */ + std::optional dynamicRegistration; + /* + Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. + */ + std::optional symbolKind; + + static workspace_symbol_client_capabilities from_json(const nlohmann::json &node) { + workspace_symbol_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "symbolKind", res.symbolKind); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "symbolKind", symbolKind); + return json; + } + }; + + struct execute_command_client_capabilities { + /* + Execute command supports dynamic registration. + */ + std::optional dynamicRegistration; + + static execute_command_client_capabilities from_json(const nlohmann::json &node) { + execute_command_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct text_document_sync_client_capabilities { + /* + Whether text document synchronization supports dynamic registration. + */ + std::optional dynamicRegistration; + + /* + The client supports sending will save notifications. + */ + std::optional willSave; + + /* + The client supports sending a will save request and + waits for a response providing text edits which will + be applied to the document before it is saved. + */ + std::optional willSaveWaitUntil; + + /* + The client supports did save notifications. + */ + std::optional didSave; + + static text_document_sync_client_capabilities from_json(const nlohmann::json &node) { + text_document_sync_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "willSave", res.willSave); + data::from_json(node, "willSaveWaitUntil", res.willSaveWaitUntil); + data::from_json(node, "didSave", res.didSave); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "willSave", willSave); + data::set_json(json, "willSaveWaitUntil", willSaveWaitUntil); + data::set_json(json, "didSave", didSave); + return json; + } + }; + + struct completion_client_capabilities { + struct CompletionItem { + struct TagSupport { + /** + * The tags supported by the client. + */ + std::optional> valueSet; + + static TagSupport from_json(const nlohmann::json &node) { + TagSupport res; + data::from_json(node, "valueSet", res.valueSet); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "valueSet", valueSet); + return json; + } + }; + + /** + * Client supports snippets as insert text. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are linked, + * that is typing in one will update others too. + */ + std::optional snippetSupport; + + /** + * Client supports commit characters on a completion item. + */ + std::optional commitCharactersSupport; + + /** + * Client supports the follow content formats for the documentation + * property. The order describes the preferred format of the client. + */ + std::optional> documentationFormat; + + /** + * Client supports the deprecated property on a completion item. + */ + std::optional deprecatedSupport; + + /** + * Client supports the preselect property on a completion item. + */ + std::optional preselectSupport; + + /** + * Client supports the tag property on a completion item. Clients supporting + * tags have to handle unknown tags gracefully. Clients especially need to + * preserve unknown tags when sending a completion item back to the server in + * a resolve call. + * + * @since 3.15.0 + */ + std::optional tagSupport; + + static CompletionItem from_json(const nlohmann::json &node) { + CompletionItem res; + data::from_json(node, "snippetSupport", res.snippetSupport); + data::from_json(node, "commitCharactersSupport", res.commitCharactersSupport); + data::from_json(node, "documentationFormat", res.documentationFormat); + data::from_json(node, "deprecatedSupport", res.deprecatedSupport); + data::from_json(node, "preselectSupport", res.preselectSupport); + data::from_json(node, "tagSupport", res.tagSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "snippetSupport", snippetSupport); + data::set_json(json, "commitCharactersSupport", commitCharactersSupport); + data::set_json(json, "documentationFormat", documentationFormat); + data::set_json(json, "deprecatedSupport", deprecatedSupport); + data::set_json(json, "preselectSupport", preselectSupport); + data::set_json(json, "tagSupport", tagSupport); + return json; + } + }; + + struct CompletionItemKind { + /* + The completion item 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 completion items kinds from `Text` to `Reference` as defined in + the initial version of the protocol. + */ + std::optional> valueSet; + + static CompletionItemKind from_json(const nlohmann::json &node) { + CompletionItemKind res; + data::from_json(node, "valueSet", res.valueSet); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "valueSet", valueSet); + return json; + } + }; + + /* + The client supports the following `CompletionItem` specific + capabilities. + */ + std::optional dynamicRegistration; + /* + The client supports the following `CompletionItem` specific + capabilities. + */ + std::optional completionItem; + /* + The client supports the following `CompletionItem` specific + capabilities. + */ + std::optional completionItemKind; + /* + The client supports to send additional context information for a + `textDocument/completion` request. + */ + std::optional contextSupport; + + static completion_client_capabilities from_json(const nlohmann::json &node) { + completion_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "completionItem", res.completionItem); + data::from_json(node, "completionItemKind", res.completionItemKind); + data::from_json(node, "contextSupport", res.contextSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "completionItem", completionItem); + data::set_json(json, "completionItemKind", completionItemKind); + data::set_json(json, "contextSupport", contextSupport); + return json; + } + }; + + struct hover_client_capabilities { + /** + * Whether hover supports dynamic registration. + */ + std::optional dynamicRegistration; + + /** + * Client supports the follow content formats for the content + * property. The order describes the preferred format of the client. + */ + std::optional> contentFormat; + + static hover_client_capabilities from_json(const nlohmann::json &node) { + hover_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "contentFormat", res.contentFormat); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "contentFormat", contentFormat); + return json; + } + }; + + struct signature_help_client_capabilities { + struct SignatureInformation { + struct ParameterInformation { + /** + * The client supports processing label offsets instead of a + * simple label string. + * + * @since 3.14.0 + */ + std::optional labelOffsetSupport; + + static ParameterInformation from_json(const nlohmann::json &node) { + ParameterInformation res; + data::from_json(node, "labelOffsetSupport", res.labelOffsetSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "labelOffsetSupport", labelOffsetSupport); + return json; + } + }; + + /** + * Client supports the follow content formats for the documentation + * property. The order describes the preferred format of the client. + */ + std::optional> documentationFormat; + + /** + * Client capabilities specific to parameter information. + */ + std::optional parameterInformation; + + static SignatureInformation from_json(const nlohmann::json &node) { + SignatureInformation res; + data::from_json(node, "documentationFormat", res.documentationFormat); + data::from_json(node, "parameterInformation", res.parameterInformation); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "documentationFormat", documentationFormat); + data::set_json(json, "parameterInformation", parameterInformation); + return json; + } + }; + + /** + * Whether signature help supports dynamic registration. + */ + std::optional dynamicRegistration; + + /** + * The client supports the following `SignatureInformation` + * specific properties. + */ + std::optional signatureInformation; + + /** + * The client supports to send additional context information for a + * `textDocument/signatureHelp` request. A client that opts into + * contextSupport will also support the `retriggerCharacters` on + * `SignatureHelpOptions`. + * + * @since 3.15.0 + */ + std::optional contextSupport; + + static signature_help_client_capabilities from_json(const nlohmann::json &node) { + signature_help_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "signatureInformation", res.signatureInformation); + data::from_json(node, "contextSupport", res.contextSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "signatureInformation", signatureInformation); + data::set_json(json, "contextSupport", contextSupport); + return json; + } + }; + + struct declaration_client_capabilities { + /** + * Whether declaration supports dynamic registration. If this is set to `true` + * the client supports the new `DeclarationRegistrationOptions` return value + * for the corresponding server capability as well. + */ + std::optional dynamicRegistration; + + /** + * The client supports additional metadata in the form of declaration links. + */ + std::optional linkSupport; + + static declaration_client_capabilities from_json(const nlohmann::json &node) { + declaration_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "linkSupport", res.linkSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "linkSupport", linkSupport); + return json; + } + }; + + struct definition_client_capabilities { + /** + * Whether definition supports dynamic registration. + */ + std::optional dynamicRegistration; + + /** + * The client supports additional metadata in the form of definition links. + * + * @since 3.14.0 + */ + std::optional linkSupport; + + static definition_client_capabilities from_json(const nlohmann::json &node) { + definition_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "linkSupport", res.linkSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "linkSupport", linkSupport); + return json; + } + }; + + struct type_definition_client_capabilities { + /** + * Whether implementation supports dynamic registration. If this is set to `true` + * the client supports the new `TypeDefinitionRegistrationOptions` return value + * for the corresponding server capability as well. + */ + std::optional dynamicRegistration; + + /** + * The client supports additional metadata in the form of definition links. + * + * @since 3.14.0 + */ + std::optional linkSupport; + + static type_definition_client_capabilities from_json(const nlohmann::json &node) { + type_definition_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "linkSupport", res.linkSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "linkSupport", linkSupport); + return json; + } + }; + + struct implementation_client_capabilities { + /** + * Whether implementation supports dynamic registration. If this is set to `true` + * the client supports the new `ImplementationRegistrationOptions` return value + * for the corresponding server capability as well. + */ + std::optional dynamicRegistration; + + /** + * The client supports additional metadata in the form of definition links. + * + * @since 3.14.0 + */ + std::optional linkSupport; + + static implementation_client_capabilities from_json(const nlohmann::json &node) { + implementation_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "linkSupport", res.linkSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "linkSupport", linkSupport); + return json; + } + }; + + struct reference_client_capabilities { + /** + * Whether references supports dynamic registration. + */ + std::optional dynamicRegistration; + + static reference_client_capabilities from_json(const nlohmann::json &node) { + reference_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct document_highlight_client_capabilities { + /** + * Whether document highlight supports dynamic registration. + */ + std::optional dynamicRegistration; + + static document_highlight_client_capabilities from_json(const nlohmann::json &node) { + document_highlight_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct document_symbol_client_capabilities { + struct SymbolKind { + /** + * 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. + */ + std::optional> valueSet; + + static SymbolKind from_json(const nlohmann::json &node) { + SymbolKind res; + data::from_json(node, "valueSet", res.valueSet); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "valueSet", valueSet); + return json; + } + }; + + /** + * Whether document symbol supports dynamic registration. + */ + std::optional dynamicRegistration; + + /** + * Specific capabilities for the `SymbolKind` in the `textDocument/documentSymbol` request. + */ + std::optional symbolKind; + + /** + * The client supports hierarchical document symbols. + */ + std::optional hierarchicalDocumentSymbolSupport; + + static document_symbol_client_capabilities from_json(const nlohmann::json &node) { + document_symbol_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "symbolKind", res.symbolKind); + data::from_json(node, "hierarchicalDocumentSymbolSupport", res.hierarchicalDocumentSymbolSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "symbolKind", symbolKind); + data::set_json(json, "hierarchicalDocumentSymbolSupport", hierarchicalDocumentSymbolSupport); + return json; + } + }; + + struct code_action_client_capabilities { + struct CodeActionLiteralSupport { + /** + * The code action kind is supported with the following value + * set. + */ + struct { + /** + * The code action 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. + */ + std::vector valueSet; + } codeActionKind; + + static CodeActionLiteralSupport from_json(const nlohmann::json &node) { + CodeActionLiteralSupport res; + data::from_json(node["codeActionKind"], "valueSet", res.codeActionKind.valueSet); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + json["codeActionKind"] = {"valueSet", data::to_json(codeActionKind.valueSet)}; + return json; + } + }; + + /** + * Whether code action supports dynamic registration. + */ + std::optional dynamicRegistration; + + /** + * The client supports code action literals as a valid + * response of the `textDocument/codeAction` request. + * + * @since 3.8.0 + */ + std::optional codeActionLiteralSupport; + + /** + * Whether code action supports the `isPreferred` property. + * @since 3.15.0 + */ + std::optional isPreferredSupport; + + static code_action_client_capabilities from_json(const nlohmann::json &node) { + code_action_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "codeActionLiteralSupport", res.codeActionLiteralSupport); + data::from_json(node, "isPreferredSupport", res.isPreferredSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "codeActionLiteralSupport", codeActionLiteralSupport); + data::set_json(json, "isPreferredSupport", isPreferredSupport); + return json; + } + }; + + struct code_lens_client_capabilities { + /** + * Whether code lens supports dynamic registration. + */ + std::optional dynamicRegistration; + + static code_lens_client_capabilities from_json(const nlohmann::json &node) { + code_lens_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct document_link_client_capabilities { + /** + * Whether document link supports dynamic registration. + */ + std::optional dynamicRegistration; + + /** + * Whether the client supports the `tooltip` property on `DocumentLink`. + * + * @since 3.15.0 + */ + std::optional tooltipSupport; + + static document_link_client_capabilities from_json(const nlohmann::json &node) { + document_link_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "tooltipSupport", res.tooltipSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "tooltipSupport", tooltipSupport); + return json; + } + }; + + struct document_color_client_capabilities { + /** + * Whether document color supports dynamic registration. + */ + std::optional dynamicRegistration; + + static document_color_client_capabilities from_json(const nlohmann::json &node) { + document_color_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct document_formatting_client_capabilities { + /** + * Whether formatting supports dynamic registration. + */ + std::optional dynamicRegistration; + + static document_formatting_client_capabilities from_json(const nlohmann::json &node) { + document_formatting_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct document_range_formatting_client_capabilities { + /** + * Whether formatting supports dynamic registration. + */ + std::optional dynamicRegistration; + + static document_range_formatting_client_capabilities from_json(const nlohmann::json &node) { + document_range_formatting_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct document_on_type_formatting_client_capabilities { + /** + * Whether on type formatting supports dynamic registration. + */ + std::optional dynamicRegistration; + + static document_on_type_formatting_client_capabilities from_json(const nlohmann::json &node) { + document_on_type_formatting_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + struct rename_client_capabilities { + /** + * Whether rename supports dynamic registration. + */ + std::optional dynamicRegistration; + + /** + * Client supports testing for validity of rename operations + * before execution. + * + * @since version 3.12.0 + */ + std::optional prepareSupport; + + static rename_client_capabilities from_json(const nlohmann::json &node) { + rename_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "prepareSupport", res.prepareSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "prepareSupport", prepareSupport); + return json; + } + }; + + struct publish_diagnostics_client_capabilities { + struct TagSupport { + /** + * The tags supported by the client. + */ + std::optional> valueSet; + + static TagSupport from_json(const nlohmann::json &node) { + TagSupport res; + data::from_json(node, "valueSet", res.valueSet); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "valueSet", valueSet); + return json; + } + }; + + /** + * Whether the clients accepts diagnostics with related information. + */ + std::optional relatedInformation; + + /** + * Client supports the tag property to provide meta data about a diagnostic. + * Clients supporting tags have to handle unknown tags gracefully. + * + * @since 3.15.0 + */ + std::optional tagSupport; + + /** + * Whether the client interprets the version property of the + * `textDocument/publishDiagnostics` notification's parameter. + * + * @since 3.15.0 + */ + std::optional versionSupport; + + static publish_diagnostics_client_capabilities from_json(const nlohmann::json &node) { + publish_diagnostics_client_capabilities res; + data::from_json(node, "relatedInformation", res.relatedInformation); + data::from_json(node, "tagSupport", res.tagSupport); + data::from_json(node, "versionSupport", res.versionSupport); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "relatedInformation", relatedInformation); + data::set_json(json, "tagSupport", tagSupport); + data::set_json(json, "versionSupport", versionSupport); + return json; + } + }; + + struct folding_range_client_capabilities { + /** + * Whether implementation supports dynamic registration for folding range providers. If this is set to `true` + * the client supports the new `FoldingRangeRegistrationOptions` return value for the corresponding server + * capability as well. + */ + std::optional dynamicRegistration; + /** + * 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. + */ + std::optional 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. + */ + std::optional lineFoldingOnly; + + static folding_range_client_capabilities from_json(const nlohmann::json &node) { + folding_range_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + data::from_json(node, "rangeLimit", res.rangeLimit); + data::from_json(node, "lineFoldingOnly", res.lineFoldingOnly); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + data::set_json(json, "rangeLimit", rangeLimit); + data::set_json(json, "lineFoldingOnly", lineFoldingOnly); + return json; + } + }; + + struct selection_range_client_capabilities { + /** + * Whether implementation supports dynamic registration for selection range providers. If this is set to `true` + * the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server + * capability as well. + */ + std::optional dynamicRegistration; + + static selection_range_client_capabilities from_json(const nlohmann::json &node) { + selection_range_client_capabilities res; + data::from_json(node, "dynamicRegistration", res.dynamicRegistration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "dynamicRegistration", dynamicRegistration); + return json; + } + }; + + /* + Text document specific client capabilities. + */ + struct text_document_client_capabilities { + std::optional synchronization; + + /** + Capabilities specific to the `textDocument/completion` request. + */ + std::optional completion; + + /** + Capabilities specific to the `textDocument/hover` request. + */ + std::optional hover; + + /** + Capabilities specific to the `textDocument/signatureHelp` request. + */ + std::optional signatureHelp; + + /** + Capabilities specific to the `textDocument/declaration` request. + + @since 3.14.0 + */ + std::optional declaration; + + /** + Capabilities specific to the `textDocument/definition` request. + */ + std::optional definition; + + /** + Capabilities specific to the `textDocument/typeDefinition` request. + + @since 3.6.0 + */ + std::optional typeDefinition; + + /** + Capabilities specific to the `textDocument/implementation` request. + + @since 3.6.0 + */ + std::optional implementation; + + /** + Capabilities specific to the `textDocument/references` request. + */ + std::optional references; + + /** + Capabilities specific to the `textDocument/documentHighlight` request. + */ + std::optional documentHighlight; + + /** + Capabilities specific to the `textDocument/documentSymbol` request. + */ + std::optional documentSymbol; + + /** + Capabilities specific to the `textDocument/codeAction` request. + */ + std::optional codeAction; + + /** + Capabilities specific to the `textDocument/codeLens` request. + */ + std::optional codeLens; + + /** + Capabilities specific to the `textDocument/documentLink` request. + */ + std::optional documentLink; + + /** + Capabilities specific to the `textDocument/documentColor` and the + `textDocument/colorPresentation` request. + + @since 3.6.0 + */ + std::optional colorProvider; + + /** + Capabilities specific to the `textDocument/formatting` request. + */ + std::optional formatting; + + /** + Capabilities specific to the `textDocument/rangeFormatting` request. + */ + std::optional rangeFormatting; + + /** request. + Capabilities specific to the `textDocument/onTypeFormatting` request. + */ + std::optional onTypeFormatting; + + /** + Capabilities specific to the `textDocument/rename` request. + */ + std::optional rename; + + /** + Capabilities specific to the `textDocument/publishDiagnostics` notification. + */ + std::optional publishDiagnostics; + + /** + Capabilities specific to the `textDocument/foldingRange` request. + + @since 3.10.0 + */ + std::optional foldingRange; + + /** + Capabilities specific to the `textDocument/selectionRange` request. + + @since 3.15.0 + */ + std::optional selectionRange; + + static text_document_client_capabilities from_json(const nlohmann::json &node) { + text_document_client_capabilities res; + data::from_json(node, "synchronization", res.synchronization); + data::from_json(node, "completion", res.completion); + data::from_json(node, "hover", res.hover); + data::from_json(node, "signatureHelp", res.signatureHelp); + data::from_json(node, "declaration", res.declaration); + data::from_json(node, "definition", res.definition); + data::from_json(node, "typeDefinition", res.typeDefinition); + data::from_json(node, "implementation", res.implementation); + data::from_json(node, "references", res.references); + data::from_json(node, "documentHighlight", res.documentHighlight); + data::from_json(node, "documentSymbol", res.documentSymbol); + data::from_json(node, "codeAction", res.codeAction); + data::from_json(node, "codeLens", res.codeLens); + data::from_json(node, "documentLink", res.documentLink); + data::from_json(node, "colorProvider", res.colorProvider); + data::from_json(node, "formatting", res.formatting); + data::from_json(node, "rangeFormatting", res.rangeFormatting); + data::from_json(node, "onTypeFormatting", res.onTypeFormatting); + data::from_json(node, "rename", res.rename); + data::from_json(node, "publishDiagnostics", res.publishDiagnostics); + data::from_json(node, "foldingRange", res.foldingRange); + data::from_json(node, "selectionRange", res.selectionRange); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "synchronization", synchronization); + data::set_json(json, "completion", completion); + data::set_json(json, "hover", hover); + data::set_json(json, "signatureHelp", signatureHelp); + data::set_json(json, "declaration", declaration); + data::set_json(json, "definition", definition); + data::set_json(json, "typeDefinition", typeDefinition); + data::set_json(json, "implementation", implementation); + data::set_json(json, "references", references); + data::set_json(json, "documentHighlight", documentHighlight); + data::set_json(json, "documentSymbol", documentSymbol); + data::set_json(json, "codeAction", codeAction); + data::set_json(json, "codeLens", codeLens); + data::set_json(json, "documentLink", documentLink); + data::set_json(json, "colorProvider", colorProvider); + data::set_json(json, "formatting", formatting); + data::set_json(json, "rangeFormatting", rangeFormatting); + data::set_json(json, "onTypeFormatting", onTypeFormatting); + data::set_json(json, "rename", rename); + data::set_json(json, "publishDiagnostics", publishDiagnostics); + data::set_json(json, "foldingRange", foldingRange); + data::set_json(json, "selectionRange", selectionRange); + return json; + } + }; + + struct client_capabilities { + struct Workspace { + /* + The client supports applying batch edits + to the workspace by supporting the request + 'workspace/applyEdit' + */ + std::optional applyEdit; + /* + Capabilities specific to `WorkspaceEdit`s + */ + std::optional workspaceEdit; + /* + Capabilities specific to the `workspace/didChangeConfiguration` notification. + */ + std::optional didChangeConfiguration; + /* + Capabilities specific to the `workspace/didChangeWatchedFiles` notification. + */ + std::optional didChangeWatchedFiles; + /* + Capabilities specific to the `workspace/symbol` request. + */ + std::optional symbol; + /* + Capabilities specific to the `workspace/executeCommand` request. + */ + std::optional executeCommand; + /* + The client has support for workspace folders. + + Since 3.6.0 + */ + std::optional workspaceFolders; + /* + The client supports `workspace/configuration` requests. + + Since 3.6.0 + */ + std::optional configuration; + + static Workspace from_json(const nlohmann::json &node) { + Workspace res; + data::from_json(node, "applyEdit", res.applyEdit); + data::from_json(node, "workspaceEdit", res.workspaceEdit); + data::from_json(node, "didChangeConfiguration", res.didChangeConfiguration); + data::from_json(node, "didChangeWatchedFiles", res.didChangeWatchedFiles); + data::from_json(node, "symbol", res.symbol); + data::from_json(node, "executeCommand", res.executeCommand); + data::from_json(node, "workspaceFolders", res.workspaceFolders); + data::from_json(node, "configuration", res.configuration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "applyEdit", applyEdit); + data::set_json(json, "workspaceEdit", workspaceEdit); + data::set_json(json, "didChangeConfiguration", didChangeConfiguration); + data::set_json(json, "didChangeWatchedFiles", didChangeWatchedFiles); + data::set_json(json, "symbol", symbol); + data::set_json(json, "executeCommand", executeCommand); + data::set_json(json, "workspaceFolders", workspaceFolders); + data::set_json(json, "configuration", configuration); + return json; + } + }; + + struct Window { + /* + Whether client supports handling progress notifications. If set servers are allowed to + report in `workDoneProgress` property in the request specific server capabilities. + + Since 3.15.0 + */ + std::optional workDoneProgress; + + static Window from_json(const nlohmann::json &node) { + Window res; + data::from_json(node, "workDoneProgress", res.workDoneProgress); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneProgress", workDoneProgress); + return json; + } + }; + + /* + Workspace specific client capabilities. + */ + std::optional workspace; + /* + Text document specific client capabilities. + */ + std::optional textDocument; + /* + Experimental client capabilities. + */ + std::optional experimental; + + static client_capabilities from_json(const nlohmann::json &node) { + client_capabilities res; + data::from_json(node, "workspace", res.workspace); + data::from_json(node, "textDocument", res.textDocument); + res.experimental = node.contains("experimental") ? node["experimental"] : nlohmann::json(nullptr); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workspace", workspace); + data::set_json(json, "textDocument", textDocument); + if (experimental.has_value()) { + json["experimental"] = *experimental; + } + return json; + } + }; + + struct workspace_folder { + /* + 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. + */ + std::string name; + + static workspace_folder from_json(const nlohmann::json &node) { + workspace_folder res; + data::from_json(node, "uri", res.uri); + data::from_json(node, "name", res.name); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "uri", uri); + data::set_json(json, "name", name); + return json; + } + }; + + /* + The process Id of the parent process that started + the server. Is null if the process has not been started by another process. + If the parent process is not alive then the server should exit (see exit notification) its process. + */ + std::optional processId; + /* + Information about the client + + @since 3.15.0 + */ + client_info clientInfo; + /* + The rootPath of the workspace. Is null + if no folder is open. + + @deprecated in favour of rootUri. + */ + std::optional rootPath; + /* + The rootUri of the workspace. Is null if no + folder is open. If both `rootPath` and `rootUri` are set + `rootUri` wins. + */ + std::optional rootUri; + /* + User provided initialization options. + */ + nlohmann::json initializationOptions; + /* + The capabilities provided by the client (editor or tool) + */ + client_capabilities capabilities; + /* + The initial trace setting. If omitted trace is disabled ('off'). + */ + trace_mode trace; + /** + 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. + + @since 3.6.0 + */ + std::optional> workspace_folders; + + static initialize_params from_json(const nlohmann::json &node) { + initialize_params res; + data::from_json(node, "processId", res.processId); + data::from_json(node, "clientInfo", res.clientInfo); + data::from_json(node, "rootPath", res.rootPath); + data::from_json(node, "rootUri", res.rootUri); + res.initializationOptions = node.contains("initializationOptions") + ? node["initializationOptions"] + : nlohmann::json(nullptr); + data::from_json(node, "capabilities", res.capabilities); + data::from_json(node, "trace", res.trace); + data::from_json(node, "workspaceFolders", res.workspace_folders); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "processId", processId); + data::set_json(json, "clientInfo", clientInfo); + data::set_json(json, "rootPath", rootPath); + data::set_json(json, "rootUri", rootUri); + json["initializationOptions"] = initializationOptions; + data::set_json(json, "capabilities", capabilities); + data::set_json(json, "trace", trace); + data::set_json(json, "workspaceFolders", workspace_folders); + return json; + } + }; + + struct did_open_text_document_params { + /** + * The document that was opened. + */ + text_document_item text_document; + + static did_open_text_document_params from_json(const nlohmann::json &node) { + did_open_text_document_params res; + data::from_json(node, "textDocument", res.text_document); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "textDocument", text_document); + return json; + } + }; + + struct did_change_text_document_params { + struct text_document_content_change_event { + /** + * The range of the document that changed. + */ + std::optional range; + + /** + * The optional length of the range that got replaced. + * + * @deprecated use range instead. + */ + std::optional range_length; + + /** + * The new text for the provided range. + */ + std::string text; + + static text_document_content_change_event from_json(const nlohmann::json &node) { + text_document_content_change_event res; + data::from_json(node, "range", res.range); + data::from_json(node, "rangeLength", res.range_length); + data::from_json(node, "text", res.text); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "range", range); + data::set_json(json, "rangeLength", range_length); + data::set_json(json, "text", text); + return json; + } + }; + + /** + * The document that did change. The version number points + * to the version after all provided content changes have + * been applied. + */ + versioned_text_document_identifier text_document; + + /** + * 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 recevie them. + * - apply the `TextDocumentContentChangeEvent`s in a single notification in the order + * you receive them. + */ + std::vector content_changes; + + static did_change_text_document_params from_json(const nlohmann::json &node) { + did_change_text_document_params res; + data::from_json(node, "textDocument", res.text_document); + data::from_json(node, "contentChanges", res.content_changes); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "textDocument", text_document); + data::set_json(json, "contentChanges", content_changes); + return json; + } + }; + + struct will_save_text_document_params { + /** + * The document that will be saved. + */ + text_document_identifier text_document; + + /** + * The 'TextDocumentSaveReason'. + */ + text_document_save_reason reason; + + static will_save_text_document_params from_json(const nlohmann::json &node) { + will_save_text_document_params res; + data::from_json(node, "textDocument", res.text_document); + data::from_json(node, "reason", res.reason); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "textDocument", text_document); + data::set_json(json, "reason", reason); + return json; + } + }; + + struct did_save_text_document_params { + /** + * The document that was saved. + */ + text_document_identifier text_document; + + /** + * Optional the content when saved. Depends on the includeText value + * when the save notification was requested. + */ + std::optional text; + + static did_save_text_document_params from_json(const nlohmann::json &node) { + did_save_text_document_params res; + data::from_json(node, "textDocument", res.text_document); + data::from_json(node, "text", res.text); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "textDocument", text_document); + data::set_json(json, "text", text); + return json; + } + }; + + struct did_close_text_document_params { + /** + * The document that was closed. + */ + text_document_identifier text_document; + + static did_close_text_document_params from_json(const nlohmann::json &node) { + did_close_text_document_params res; + data::from_json(node, "textDocument", res.text_document); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "textDocument", text_document); + return json; + } + }; + struct hover { + /** + * The hover's content + */ + markup_content 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. + */ + std::optional range; + + // static hover_params from_json(const nlohmann::json &node) { + // hover_params res; + // data::from_json(node, "contents", res.contents); + // data::from_json(node, "range", res.range); + // return res; + // } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "contents", contents); + data::set_json(json, "range", range); + return json; + } + }; + struct hover_params { + + /** + * An optional token that a server can use to report work done progress. + */ + std::optional work_done_token; + /** + * The text document. + */ + text_document_identifier text_document; + + /** + * The position inside the text document. + */ + position position; + + static hover_params from_json(const nlohmann::json &node) { + hover_params res; + data::from_json(node, "workDoneToken", res.work_done_token); + data::from_json(node, "textDocument", res.text_document); + data::from_json(node, "position", res.position); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "workDoneToken", work_done_token); + data::set_json(json, "textDocument", text_document); + data::set_json(json, "position", position); + return json; + } + }; + + struct completion_params { + struct CompletionContext { + /** + * How the completion was triggered. + */ + completion_trigger_kind trigger_kind; + + /** + * The trigger character (a single character) that has trigger code complete. + * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` + */ + std::optional trigger_character; + + static CompletionContext from_json(const nlohmann::json &node) { + CompletionContext res; + data::from_json(node, "triggerKind", res.trigger_kind); + data::from_json(node, "triggerCharacter", res.trigger_character); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "triggerKind", trigger_kind); + data::set_json(json, "triggerCharacter", trigger_character); + return json; + } + }; + + /** + * An optional token that a server can use to report partial results (e.g. streaming) to + * the client. + */ + std::optional partialResultToken; + /** + * An optional token that a server can use to report work done progress. + */ + std::optional workDoneToken; + /** + * The text document. + */ + text_document_identifier textDocument; + + /** + * The position inside the text document. + */ + position position; + /** + * The completion context. This is only available if the client specifies + * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true` + */ + std::optional context; + + static completion_params from_json(const nlohmann::json &node) { + completion_params res; + data::from_json(node, "partialResultToken", res.partialResultToken); + data::from_json(node, "workDoneToken", res.workDoneToken); + data::from_json(node, "textDocument", res.textDocument); + data::from_json(node, "position", res.position); + data::from_json(node, "context", res.context); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "partialResultToken", partialResultToken); + data::set_json(json, "workDoneToken", workDoneToken); + data::set_json(json, "textDocument", textDocument); + data::set_json(json, "position", position); + data::set_json(json, "context", context); + return json; + } + }; + + struct code_action_params { + struct code_action_context { + /** + * An array of diagnostics known on the client side overlapping the range + * provided to the `textDocument/codeAction` request. They are provided so + * that the server knows which errors are currently presented to the user + * for the given range. There is no guarantee that these accurately reflect + * the error state of the resource. The primary parameter + * to compute code actions is the provided range. + */ + std::vector diagnostics; + /** + * Requested kind of actions to return. + * + * Actions not of this kind are filtered out by the client before being + * shown. So servers can omit computing them. + */ + std::optional> only; + /** + * The reason why code actions were requested. + * + * @since 3.17.0 + */ + std::optional triggerKind; + + + static code_action_context from_json(const nlohmann::json &node) { + code_action_context res; + data::from_json(node, "diagnostics", res.diagnostics); + data::from_json(node, "only", res.only); + data::from_json(node, "triggerKind", res.triggerKind); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "diagnostics", diagnostics); + data::set_json(json, "only", only); + data::set_json(json, "triggerKind", triggerKind); + return json; + } + }; + + /** + * An optional token that a server can use to report partial results (e.g. streaming) to + * the client. + */ + std::optional partialResultToken; + + /** + * An optional token that a server can use to report work done progress. + */ + std::optional workDoneToken; + + /** + * The text document. + */ + text_document_identifier textDocument; + + /** + * The range for which the command was invoked. + */ + range range; + + /** + * Context carrying additional information. + */ + code_action_context context; + + static code_action_params from_json(const nlohmann::json &node) { + code_action_params res; + data::from_json(node, "partialResultToken", res.partialResultToken); + data::from_json(node, "workDoneToken", res.workDoneToken); + data::from_json(node, "textDocument", res.textDocument); + data::from_json(node, "range", res.range); + data::from_json(node, "context", res.context); + return res; + } + + [[nodiscard]] nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "partialResultToken", partialResultToken); + data::set_json(json, "workDoneToken", workDoneToken); + data::set_json(json, "textDocument", textDocument); + data::set_json(json, "range", range); + data::set_json(json, "context", context); + return json; + } + }; + + struct references_params { + struct ReferenceContext { + /** + * Include the declaration of the current symbol. + */ + bool includeDeclaration; + + + static ReferenceContext from_json(const nlohmann::json &node) { + ReferenceContext res; + data::from_json(node, "includeDeclaration", res.includeDeclaration); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "includeDeclaration", includeDeclaration); + return json; + } + }; + + /** + * An optional token that a server can use to report partial results (e.g. streaming) to + * the client. + */ + std::optional partialResultToken; + /** + * An optional token that a server can use to report work done progress. + */ + std::optional workDoneToken; + /** + * The text document. + */ + text_document_identifier textDocument; + + /** + * The position inside the text document. + */ + position position; + + ReferenceContext context; + + static references_params from_json(const nlohmann::json &node) { + references_params res; + data::from_json(node, "partialResultToken", res.partialResultToken); + data::from_json(node, "workDoneToken", res.workDoneToken); + data::from_json(node, "textDocument", res.textDocument); + data::from_json(node, "position", res.position); + data::from_json(node, "context", res.context); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "partialResultToken", partialResultToken); + data::set_json(json, "workDoneToken", workDoneToken); + data::set_json(json, "textDocument", textDocument); + data::set_json(json, "position", position); + data::set_json(json, "context", context); + return json; + } + }; + + struct publish_diagnostics_params { + /** + * The URI for which diagnostic information is reported. + */ + uri uri; + + /** + * Optional the version number of the document the diagnostics are published for. + * + * @since 3.15.0 + */ + std::optional version; + + /** + * An array of diagnostic information items. + */ + std::vector diagnostics; + + static publish_diagnostics_params from_json(const nlohmann::json &node) { + publish_diagnostics_params res; + data::from_json(node, "uri", res.uri); + data::from_json(node, "version", res.version); + data::from_json(node, "diagnostics", res.diagnostics); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "uri", uri); + data::set_json(json, "version", version); + data::set_json(json, "diagnostics", diagnostics); + return json; + } + }; + + struct folding_range_params { + /** + * An optional token that a server can use to report partial results (e.g. streaming) to + * the client. + */ + std::optional partialResultToken; + /** + * An optional token that a server can use to report work done progress. + */ + std::optional workDoneToken; + /** + * The text document. + */ + text_document_identifier textDocument; + + static folding_range_params from_json(const nlohmann::json &node) { + folding_range_params res; + data::from_json(node, "partialResultToken", res.partialResultToken); + data::from_json(node, "workDoneToken", res.workDoneToken); + data::from_json(node, "textDocument", res.textDocument); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "partialResultToken", partialResultToken); + data::set_json(json, "workDoneToken", workDoneToken); + data::set_json(json, "textDocument", textDocument); + return json; + } + }; + + /** + * Represents a color in RGBA space. + */ + struct color { + + /** + * The red component of this color in the range [0-1]. + */ + float red; + + /** + * The green component of this color in the range [0-1]. + */ + float green; + + /** + * The blue component of this color in the range [0-1]. + */ + float blue; + + /** + * The alpha component of this color in the range [0-1]. + */ + float alpha; + + static color from_json(const nlohmann::json &node) { + color res; + data::from_json(node, "red", res.red); + data::from_json(node, "green", res.green); + data::from_json(node, "blue", res.blue); + data::from_json(node, "alpha", res.alpha); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "red", red); + data::set_json(json, "green", green); + data::set_json(json, "blue", blue); + data::set_json(json, "alpha", alpha); + return json; + } + }; + + struct color_information { + /** + * The range in the document where this color appears. + */ + range range; + + /** + * The actual color value for this color range. + */ + color color; + + static color_information from_json(const nlohmann::json &node) { + color_information res; + data::from_json(node, "range", res.range); + data::from_json(node, "color", res.color); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "range", range); + data::set_json(json, "color", color); + return json; + } + }; + + struct document_color_params { + /** + * An optional token that a server can use to report partial results (e.g. streaming) to + * the client. + */ + std::optional partialResultToken; + /** + * An optional token that a server can use to report work done progress. + */ + std::optional workDoneToken; + /** + * The text document. + */ + text_document_identifier textDocument; + + static document_color_params from_json(const nlohmann::json &node) { + document_color_params res; + data::from_json(node, "partialResultToken", res.partialResultToken); + data::from_json(node, "workDoneToken", res.workDoneToken); + data::from_json(node, "textDocument", res.textDocument); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "partialResultToken", partialResultToken); + data::set_json(json, "workDoneToken", workDoneToken); + data::set_json(json, "textDocument", textDocument); + return json; + } + }; + + struct did_change_configuration_params { + /** + * The actual changed settings + */ + std::optional settings; + + static did_change_configuration_params from_json(const nlohmann::json &node) { + did_change_configuration_params res; + res.settings = node.contains("settings") ? node["settings"] : nlohmann::json(nullptr); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + if (settings.has_value()) { + json["settings"] = *settings; + } + return json; + } + }; + + struct color_presentation { + /** + * The label of this color presentation. It will be shown on the color + * picker header. By default this is also the text that is inserted when selecting + * this color presentation. + */ + std::string label; + /** + * An [edit](#TextEdit) which is applied to a document when selecting + * this presentation for the color. When `falsy` the [label](#ColorPresentation.label) + * is used. + */ + std::optional textEdit; + /** + * An optional array of additional [text edits](#TextEdit) that are applied when + * selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves. + */ + std::optional> additionalTextEdits; + + static color_presentation from_json(const nlohmann::json &node) { + color_presentation res; + data::from_json(node, "label", res.label); + data::from_json(node, "textEdit", res.textEdit); + data::from_json(node, "additionalTextEdits", res.additionalTextEdits); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "label", label); + data::set_json(json, "textEdit", textEdit); + data::set_json(json, "additionalTextEdits", additionalTextEdits); + return json; + } + }; + + struct color_presentation_params { + /** + * An optional token that a server can use to report partial results (e.g. streaming) to + * the client. + */ + std::optional partialResultToken; + /** + * An optional token that a server can use to report work done progress. + */ + std::optional workDoneToken; + /** + * The text document. + */ + text_document_identifier textDocument; + + /** + * The color information to request presentations for. + */ + color color; + + /** + * The range where the color would be inserted. Serves as a context. + */ + range range; + + static color_presentation_params from_json(const nlohmann::json &node) { + color_presentation_params res; + data::from_json(node, "partialResultToken", res.partialResultToken); + data::from_json(node, "workDoneToken", res.workDoneToken); + data::from_json(node, "textDocument", res.textDocument); + data::from_json(node, "color", res.color); + data::from_json(node, "range", res.range); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "partialResultToken", partialResultToken); + data::set_json(json, "workDoneToken", workDoneToken); + data::set_json(json, "textDocument", textDocument); + data::set_json(json, "color", color); + data::set_json(json, "range", range); + return json; + } + }; + + struct log_message_params { + /** + * The message type. See {@link MessageType} + */ + message_type type; + + /** + * The actual message + */ + std::string message; + + static log_message_params from_json(const nlohmann::json &node) { + log_message_params res; + data::from_json(node, "type", res.type); + data::from_json(node, "message", res.message); + return res; + } + + nlohmann::json to_json() const { + nlohmann::json json; + data::set_json(json, "type", type); + data::set_json(json, "message", message); + return json; + } + }; +} + + +#endif // SQFVM_LANGUAGE_SERVER_LSP_LSPSERVER_HPP \ No newline at end of file diff --git a/server/sqfvm_language_server/lsp/server.cpp b/server/sqfvm_language_server/lsp/server.cpp new file mode 100644 index 0000000..500e80c --- /dev/null +++ b/server/sqfvm_language_server/lsp/server.cpp @@ -0,0 +1,217 @@ +// +// Created by marco.silipo on 20.08.2023. +// + +#include "server.hpp" + + +lsp::server::server() : m_rpc(std::cin, std::cout, jsonrpc::detach, jsonrpc::skip), m_die(false) { + m_rpc.register_method( + "initialize", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::initialize_params::from_json(msg.params.value()); + auto res = on_initialize(params); + rpc.send({msg.id, res.to_json()}); + after_initialize(params); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'initialize' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "shutdown", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + kill(); + on_shutdown(); + }); + m_rpc.register_method( + "textDocument/didOpen", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::did_open_text_document_params::from_json(msg.params.value()); + on_textDocument_didOpen(params); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/didOpen' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/didChange", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::did_change_text_document_params::from_json(msg.params.value()); + on_textDocument_didChange(params); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/didChange' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/willSave", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::will_save_text_document_params::from_json(msg.params.value()); + on_textDocument_willSave(params); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/willSave' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/willSaveWaitUntil", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::will_save_text_document_params::from_json(msg.params.value()); + auto res = on_textDocument_willSaveWaitUntil(params); + rpc.send({msg.id, res.has_value() ? res->to_json() : nlohmann::json(nullptr)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/willSaveWaitUntil' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/didSave", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::did_save_text_document_params::from_json(msg.params.value()); + on_textDocument_didSave(params); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/didSave' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/didClose", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::did_close_text_document_params::from_json(msg.params.value()); + on_textDocument_didClose(params); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/didClose' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/completion", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::completion_params::from_json(msg.params.value()); + auto res = on_textDocument_completion(params); + rpc.send({msg.id, res.has_value() ? res->to_json() : nlohmann::json(nullptr)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/completion' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/foldingRange", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::folding_range_params::from_json(msg.params.value()); + auto res = on_textDocument_foldingRange(params); + rpc.send({msg.id, res.has_value() ? to_json(*res) : nlohmann::json(nullptr)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/foldingRange' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/documentColor", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::document_color_params::from_json(msg.params.value()); + auto res = on_textDocument_documentColor(params); + rpc.send({msg.id, to_json(res)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/documentColor' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/references", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::references_params::from_json(msg.params.value()); + auto res = on_textDocument_references(params); + rpc.send({msg.id, to_json(res)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/references' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/colorPresentation", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::color_presentation_params::from_json(msg.params.value()); + auto res = on_textDocument_colorPresentation(params); + rpc.send({msg.id, to_json(res)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/colorPresentation' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/codeAction", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::code_action_params::from_json(msg.params.value()); + auto res = on_textDocument_codeAction(params); + rpc.send({msg.id, to_json(res)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/codeAction' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "textDocument/hover", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::hover_params::from_json(msg.params.value()); + auto res = on_textDocument_hover(params); + rpc.send({msg.id, to_json(res)}); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'textDocument/codeAction' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); + m_rpc.register_method( + "workspace/didChangeConfiguration", [&](jsonrpc &rpc, const jsonrpc::rpcmessage &msg) { + try { + auto params = data::did_change_configuration_params::from_json(msg.params.value()); + on_workspace_didChangeConfiguration(params); + } + catch (const std::exception &e) { + std::stringstream sstream; + sstream << "rpc call 'workspace/didChangeConfiguration' failed with: '" << e.what() << "'."; + window_logMessage(::lsp::data::message_type::Log, sstream.str()); + } + }); +} + +void lsp::server::listen() { + while (!m_die) { + if (!m_rpc.handle_single_message()) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + } +} + +void lsp::server::kill() { + m_die = true; +} \ No newline at end of file diff --git a/server/sqfvm_language_server/lsp/server.hpp b/server/sqfvm_language_server/lsp/server.hpp new file mode 100644 index 0000000..24f036c --- /dev/null +++ b/server/sqfvm_language_server/lsp/server.hpp @@ -0,0 +1,120 @@ +// +// Created by marco.silipo on 20.08.2023. +// + +#ifndef SQFVM_LANGUAGE_SERVER_SERVER_HPP +#define SQFVM_LANGUAGE_SERVER_SERVER_HPP + +// +#include "lspserver.hpp" +// + + +#include "jsonrpc.hpp" +#include "../uri.hpp" + +#include +#include +#include +#include +#include + +namespace lsp { + class server { + + private: + bool m_die; + public: + jsonrpc m_rpc; + + server(); + + void listen(); + + void kill(); + + // Methods that must be overriden by clients + protected: + virtual lsp::data::initialize_result on_initialize(const lsp::data::initialize_params ¶ms) = 0; + + virtual void after_initialize(const lsp::data::initialize_params ¶ms) { /* empty */ } + + virtual void on_shutdown() = 0; + + + // Methods that can be overriden by implementing clients + protected: + virtual void on_textDocument_didOpen(const lsp::data::did_open_text_document_params ¶ms) { /* empty */ } + + virtual void on_textDocument_didChange(const lsp::data::did_change_text_document_params ¶ms) { /* empty */ } + + virtual void on_textDocument_willSave(const lsp::data::will_save_text_document_params ¶ms) { /* empty */ } + + virtual std::optional on_textDocument_willSaveWaitUntil( + const lsp::data::will_save_text_document_params ¶ms) { + return {}; + } + + virtual void on_textDocument_didSave( + const lsp::data::did_save_text_document_params ¶ms) { /* empty */ } + + virtual void on_textDocument_didClose( + const lsp::data::did_close_text_document_params ¶ms) { /* empty */ } + + virtual std::optional on_textDocument_completion( + const lsp::data::completion_params ¶ms) { + return {}; + } + + virtual std::optional on_textDocument_hover( + const lsp::data::hover_params ¶ms) { + return {}; + } + + virtual std::optional> on_textDocument_foldingRange( + const lsp::data::folding_range_params ¶ms) { + return {}; + } + + virtual std::vector on_textDocument_documentColor( + const lsp::data::document_color_params ¶ms) { + return {}; + } + + virtual std::vector on_textDocument_colorPresentation( + const lsp::data::color_presentation_params ¶ms) { + return {}; + } + + virtual std::optional>> + + on_textDocument_codeAction( + const lsp::data::code_action_params ¶ms) { + return {}; + } + + virtual std::optional> on_textDocument_references( + const lsp::data::references_params ¶ms) { + return {}; + } + + virtual void on_workspace_didChangeConfiguration( + const lsp::data::did_change_configuration_params ¶ms) { + } + + public: + void textDocument_publishDiagnostics(const lsp::data::publish_diagnostics_params ¶ms) { + m_rpc.send({{}, "textDocument/publishDiagnostics", params.to_json()}); + } + + void window_logMessage(lsp::data::message_type type, std::string message) { + m_rpc.send({{}, "window/logMessage", lsp::data::log_message_params{type, message}.to_json()}); + } + + void window_logMessage(const lsp::data::log_message_params ¶ms) { + m_rpc.send({{}, "window/logMessage", params.to_json()}); + } + }; +} + +#endif //SQFVM_LANGUAGE_SERVER_SERVER_HPP diff --git a/server/sqfvm_language_server/main.cpp b/server/sqfvm_language_server/main.cpp new file mode 100644 index 0000000..313d7bc --- /dev/null +++ b/server/sqfvm_language_server/main.cpp @@ -0,0 +1,16 @@ +#include "main.hpp" +#include "language_server.hpp" +#include + +using namespace std; + + +int main(int argc, char **argv) +{ +#ifdef _DEBUG + _CrtDbgReport(_CRT_ASSERT, "", 0, "", "Waiting for debugger."); +#endif // _DEBUG + sqfvm::language_server::language_server lssqf; + lssqf.listen(); + return 0; +} diff --git a/server/sqfvm_language_server/main.hpp b/server/sqfvm_language_server/main.hpp new file mode 100644 index 0000000..a75d77a --- /dev/null +++ b/server/sqfvm_language_server/main.hpp @@ -0,0 +1,7 @@ +#ifndef SQFVM_LANGUAGE_SERVER_MAIN_HPP +#define SQFVM_LANGUAGE_SERVER_MAIN_HPP + +int main(int argc, char **argv); + + +#endif //SQFVM_LANGUAGE_SERVER_MAIN_HPP \ No newline at end of file diff --git a/server/sqfvm_language_server/runtime_logger.hpp b/server/sqfvm_language_server/runtime_logger.hpp new file mode 100644 index 0000000..7a49c3e --- /dev/null +++ b/server/sqfvm_language_server/runtime_logger.hpp @@ -0,0 +1,70 @@ +#ifndef SQFVM_LANGUAGE_SERVER_RUNTIME_LOGGER_HPP +#define SQFVM_LANGUAGE_SERVER_RUNTIME_LOGGER_HPP + +#include "database/context.hpp" +#include "util.hpp" +#include + +namespace sqfvm::language_server { + class runtime_logger : public Logger { + sqfvm::language_server::database::context &m_context; + std::function m_vscode_log_func; + std::function m_func; + public: + explicit runtime_logger( + sqfvm::language_server::database::context &context, + std::function vscode_log_func, + std::function func) + : Logger(), + m_context(context), + m_vscode_log_func(std::move(vscode_log_func)), + m_func(std::move(func)) {} + + void log(const LogMessageBase &base) override { + m_vscode_log_func(base); + // Skip virtual file lookup errors + if (base.getErrorCode() >= 70000 && base.getErrorCode() < 80000 && base.getErrorCode() != 70014) { + return; + } + using namespace sqlite_orm; + auto &message = dynamic_cast(base); + if (message == nullptr) { + return; + } + + auto location = message.location(); + auto uri = sanitize_to_uri(location.path); + auto path_str = sanitize_to_string(uri); + auto path = std::filesystem::path(path_str).lexically_normal(); + + auto file = m_context.db_get_file_from_path(path, true); + if (!file.has_value()) { + m_func({ + .severity = sqfvm::language_server::database::tables::t_diagnostic::severity_level::error, + .message = "Failed to find file '" + path.string() + "' in database", + }); + return; + } + + sqfvm::language_server::database::tables::t_diagnostic msg; + msg.file_fk = file->id_pk; + msg.source_file_fk = file->id_pk; + msg.line = location.line - 1; + msg.column = location.col; + msg.offset = location.offset; + msg.length = location.length; + msg.content = std::to_string(base.getErrorCode()); + msg.message = message.formatMessage(); + { + std::stringstream sstream; + sstream << "SQFVM-" << std::setfill('0') << std::setw(5) << base.getErrorCode(); + msg.code = sstream.str(); + } + msg.severity = static_cast(base.getLevel()); + + m_func(msg); + } + }; +} + +#endif //SQFVM_LANGUAGE_SERVER_RUNTIME_LOGGER_HPP diff --git a/server/sqfvm_language_server/sqfvm_factory.cpp b/server/sqfvm_language_server/sqfvm_factory.cpp new file mode 100644 index 0000000..4d97bc8 --- /dev/null +++ b/server/sqfvm_language_server/sqfvm_factory.cpp @@ -0,0 +1,117 @@ +#include "sqfvm_factory.hpp" +#include +#include +#include +#include +#include +#include +#include "language_server.hpp" + + +void sqfvm::language_server::sqfvm_factory::log_to_window(const LogMessageBase &msg) const { + auto level = msg.getLevel(); + ::lsp::data::message_type message_type; + switch (level) { + case loglevel::fatal: + case loglevel::error: + message_type = ::lsp::data::message_type::Error; + break; + case loglevel::warning: + message_type = ::lsp::data::message_type::Warning; + break; + case loglevel::info: + message_type = ::lsp::data::message_type::Info; + break; + case loglevel::verbose: + case loglevel::trace: + message_type = ::lsp::data::message_type::Log; + break; + } + m_language_server->window_log(message_type, [&](auto &sstream) { + auto error_code = msg.getErrorCode(); + auto message = msg.formatMessage(); + switch (level) { + case loglevel::fatal: + sstream << "[" << std::setfill('0') << std::setw(5) << error_code << "] FTL: " << message; + break; + case loglevel::error: + sstream << "[" << std::setfill('0') << std::setw(5) << error_code << "] ERR: " << message; + break; + case loglevel::warning: + sstream << "[" << std::setfill('0') << std::setw(5) << error_code << "] WRN: " << message; + break; + case loglevel::info: + sstream << "[" << std::setfill('0') << std::setw(5) << error_code << "] INF: " << message; + break; + case loglevel::verbose: + sstream << "[" << std::setfill('0') << std::setw(5) << error_code << "] VRB: " << message; + break; + case loglevel::trace: + sstream << "[" << std::setfill('0') << std::setw(5) << error_code << "] TRC: " << message; + break; + } + + + }); +} + +std::shared_ptr sqfvm::language_server::sqfvm_factory::create( + const std::function &log, + sqfvm::language_server::database::context &context, + const std::shared_ptr &slspp) const { + using namespace std::string_literals; + auto logger = std::make_shared( + context, + [&](const LogMessageBase &msg) { log_to_window(msg); }, + log); + auto runtime = std::make_shared<::sqf::runtime::runtime>(*logger, ::sqf::runtime::runtime::runtime_conf{}); + runtime->add_finalizer([logger]() { /* holds reference to logger */ }); + runtime->fileio(std::make_unique<::sqf::fileio::impl_default>(*logger)); + runtime->parser_config(std::make_unique<::sqf::parser::config::parser>(*logger)); + runtime->parser_sqf(std::make_unique<::sqf::parser::sqf::parser>(*logger)); + auto preprocessor = std::make_unique<::sqf::parser::preprocessor::impl_default>(*logger); + + preprocessor->push_back(::sqf::runtime::parser::pragma{"sls"s, [slspp]( + const ::sqf::runtime::parser::pragma &self, + ::sqf::runtime::runtime &runtime, + ::sqf::runtime::parser::preprocessor::context &file_context, + const std::string &data) -> std::optional { + // The offset required to convert from the human-readable line index (1-based) to the 0-based line index + // including the end-of-line offset to correct for pragma read position + const size_t line_offset = -2; + // split data by spaces + std::vector args{}; + std::string_view data_view{data}; + while (!data_view.empty()) { + auto space = data_view.find(' '); + if (space == std::string_view::npos) { + args.emplace_back(data_view); + break; + } + args.emplace_back(data_view.substr(0, space)); + data_view.remove_prefix(space + 1); + } + if (args.size() < 2) { + return std::nullopt; + } + auto command = args.front(); + auto error_code = args.back(); + if (command == "enable") { + slspp->push_enable(file_context.path.physical, file_context.line + line_offset, error_code); + } else if (command == "disable") { + if (args.size() < 3) { + slspp->push_disable(file_context.path.physical, file_context.line + line_offset, error_code); + } else if (args[1] == "line") { + slspp->push_disable_line(file_context.path.physical, file_context.line + line_offset, error_code); + } + } + return std::nullopt; + }}); + runtime->parser_preprocessor(std::move(preprocessor)); + sqf::operators::ops(*runtime); + for (const auto &tuple: m_mappings) { + auto& mapping = tuple.mapping; + runtime->fileio().add_mapping(mapping.physical, mapping.virtual_); + } + return runtime; +} diff --git a/server/sqfvm_language_server/sqfvm_factory.hpp b/server/sqfvm_language_server/sqfvm_factory.hpp new file mode 100644 index 0000000..c201d87 --- /dev/null +++ b/server/sqfvm_language_server/sqfvm_factory.hpp @@ -0,0 +1,71 @@ +#ifndef SQFVM_LANGUAGE_SERVER_SQFVM_FACTORY_HPP +#define SQFVM_LANGUAGE_SERVER_SQFVM_FACTORY_HPP + +#include "runtime_logger.hpp" +#include "analysis/slspp_context.hpp" +#include "database/context.hpp" +#include +#include +#include +#include +#include + +namespace sqfvm::language_server { + class language_server; + class sqfvm_factory { + struct mapping_tuple { + sqf::runtime::fileio::pathinfo mapping; + bool is_workspace_mapping; + }; + std::vector m_mappings; + language_server* m_language_server; + void log_to_window(const LogMessageBase &base) const; + public: + explicit sqfvm_factory(language_server* ls) : m_language_server(ls) {} + void add_mapping(std::string physical_path, std::string virtual_path, bool is_workspace_mapping = false) { + m_mappings.emplace_back( + sqf::runtime::fileio::pathinfo{ + std::move(physical_path), + std::move(virtual_path) + }, + false + ); + } + void update_mapping(std::string physical_path, std::string virtual_path, bool is_workspace_mapping = false) { + for (auto& tuple : m_mappings) { + auto& mapping = tuple.mapping; + if (mapping.physical == physical_path) { + mapping.virtual_ = std::move(virtual_path); + return; + } + } + add_mapping(std::move(physical_path), std::move(virtual_path), is_workspace_mapping); + } + + void remove_mapping(std::string physical_path) { + for (auto it = m_mappings.begin(); it != m_mappings.end(); ++it) { + if (it->mapping.physical == physical_path) { + m_mappings.erase(it); + return; + } + } + } + + void clear_workspace_mappings() { + for (auto i = 0; i < m_mappings.size(); ++i) { + if (m_mappings[i].is_workspace_mapping) { + m_mappings.erase(m_mappings.begin() + i); + --i; + } + } + } + + [[nodiscard]] std::shared_ptr create( + const std::function& log, + database::context &context, + const std::shared_ptr& slspp + ) const; + }; +} + +#endif //SQFVM_LANGUAGE_SERVER_SQFVM_FACTORY_HPP diff --git a/server/sqfvm_language_server/uri.hpp b/server/sqfvm_language_server/uri.hpp new file mode 100644 index 0000000..1fa2117 --- /dev/null +++ b/server/sqfvm_language_server/uri.hpp @@ -0,0 +1,698 @@ +#ifndef SQFVM_LANGUAGE_SERVER_URI_HPP +#define SQFVM_LANGUAGE_SERVER_URI_HPP + +#include +#include +#include +#include + +namespace x39 { + class uri { + private: + std::string m_data; + size_t m_schema_start; + size_t m_schema_length; + size_t m_user_start; + size_t m_user_length; + size_t m_password_start; + size_t m_password_length; + size_t m_host_start; + size_t m_host_length; + size_t m_port_start; + size_t m_port_length; + size_t m_path_start; + size_t m_path_length; + size_t m_query_start; + size_t m_query_length; + size_t m_fragment_start; + size_t m_fragment_length; + public: + uri() : + m_data(), + m_schema_start(0), + m_schema_length(0), + m_user_start(0), + m_user_length(0), + m_password_start(0), + m_password_length(0), + m_host_start(0), + m_host_length(0), + m_port_start(0), + m_port_length(0), + m_path_start(0), + m_path_length(0), + m_query_start(0), + m_query_length(0), + m_fragment_start(0), + m_fragment_length(0) { + } + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "google-explicit-constructor" + + uri(const char *input) : uri(std::string_view(input)) {} + + uri(const std::string &input) : uri(std::string_view(input)) {} + + uri(std::string_view input) : uri() +#pragma clang diagnostic pop + { + auto &output = m_data; + output.reserve(input.size()); // parsed string is always at max as long as input + + enum estate { + s_schema_read, + s_schema_wait_length, + s_schema_wait_length_slash, + s_user, + s_password, + s_host, + s_port, + s_path, + s_query, + s_fragment + }; + estate state = s_schema_read; + size_t start = 0; + size_t current = 0; + int specialCharacter = 0; + char specialBuffer[4] = {' ', '\0', '\0', '\0'}; + for (auto c: input) { + if (specialCharacter != 0) { + specialBuffer[specialCharacter++] = c; + if (specialCharacter == 3) { + specialCharacter = 0; + switch (specialBuffer[1]) { + case '0': + c = static_cast(0x00); + break; + case '1': + c = static_cast(0x10); + break; + case '2': + c = static_cast(0x20); + break; + case '3': + c = static_cast(0x30); + break; + case '4': + c = static_cast(0x40); + break; + case '5': + c = static_cast(0x50); + break; + case '6': + c = static_cast(0x60); + break; + case '7': + c = static_cast(0x70); + break; + case '8': + c = static_cast(0x80); + break; + case '9': + c = static_cast(0x90); + break; + case 'a': + case 'A': + c = static_cast(0xA0); + break; + case 'b': + case 'B': + c = static_cast(0xB0); + break; + case 'c': + case 'C': + c = static_cast(0xC0); + break; + case 'd': + case 'D': + c = static_cast(0xD0); + break; + case 'e': + case 'E': + c = static_cast(0xE0); + break; + case 'f': + case 'F': + c = static_cast(0xF0); + break; + } + switch (specialBuffer[2]) { + case '0': + c |= static_cast(0x00); + break; + case '1': + c |= static_cast(0x01); + break; + case '2': + c |= static_cast(0x02); + break; + case '3': + c |= static_cast(0x03); + break; + case '4': + c |= static_cast(0x04); + break; + case '5': + c |= static_cast(0x05); + break; + case '6': + c |= static_cast(0x06); + break; + case '7': + c |= static_cast(0x07); + break; + case '8': + c |= static_cast(0x08); + break; + case '9': + c |= static_cast(0x09); + break; + case 'a': + case 'A': + c |= static_cast(0x0A); + break; + case 'b': + case 'B': + c |= static_cast(0x0B); + break; + case 'c': + case 'C': + c |= static_cast(0x0C); + break; + case 'd': + case 'D': + c |= static_cast(0x0D); + break; + case 'e': + case 'E': + c |= static_cast(0x0E); + break; + case 'f': + case 'F': + c |= static_cast(0x0F); + break; + } + current++; + output.append(&c, &c + 1); + continue; + } else { + continue; + } + } else if (c == '%') { + specialCharacter++; + continue; + } + current++; + output.append(&c, &c + 1); + switch (state) { + case s_schema_read: + if (c == ':') { + state = s_schema_wait_length; + m_schema_start = start; + m_schema_length = current - start - 1; + } + break; + case s_schema_wait_length: + if (c == '/') { + state = s_schema_wait_length_slash; + } else if (c != ':') { // invalid uri. Still try to parse + state = s_user; + start = current - 1; + } + break; + case s_schema_wait_length_slash: + if (c != '/') { // invalid uri. Still try to parse + state = s_user; + start = current - 1; + goto l_user; + } else // char c is '/' here + { + state = s_user; + start = current; // Skip current '/' + } + break; + l_user: + case s_user: + if (c == ':') { + auto atLocation = input.find('@'); + if (atLocation == std::string::npos) { // no user present, pass to host state + state = s_host; + goto case_host; + } + m_user_start = start; + m_user_length = current - start - 1; + state = s_password; + start = current; + } else if (c == '@') { + m_user_start = start; + m_user_length = current - start - 1; + state = s_host; + start = current; + } else if (c == '/') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_path; + start = current; + } else if (c == '?') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_query; + start = current; + } else if (c == '#') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_fragment; + start = current; + } else if (c == ':') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_port; + start = current; + } + break; + case s_password: + if (c == '@') { + m_password_start = start; + m_password_length = current - start - 1; + state = s_host; + start = current; + } else if (c == '/') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_path; + start = current; + } else if (c == '?') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_query; + start = current; + } else if (c == '#') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_fragment; + start = current; + } else if (c == ':') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_port; + start = current; + } + break; + case s_host: + case_host: + if (c == '/') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_path; + start = current; + } else if (c == '?') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_query; + start = current; + } else if (c == '#') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_fragment; + start = current; + } else if (c == ':') { + m_host_start = start; + m_host_length = current - start - 1; + state = s_port; + start = current; + } + break; + case s_port: + if (c == '/') { + m_port_start = start; + m_port_length = current - start - 1; + if (m_port_length == 0) { + m_host_length++; + } + state = s_path; + start = current; + } else if (c == '?') { + m_port_start = start; + m_port_length = current - start - 1; + state = s_query; + start = current; + } else if (c == '#') { + m_port_start = start; + m_port_length = current - start - 1; + state = s_fragment; + start = current; + } + break; + case s_path: + if (c == '?') { + m_path_start = start; + m_path_length = current - start - 1; + state = s_query; + start = current; + } else if (c == '#') { + m_path_start = start; + m_path_length = current - start - 1; + state = s_fragment; + start = current; + } + break; + case s_query: + if (c == '#') { + m_query_start = start; + m_query_length = current - start - 1; + state = s_fragment; + start = current; + } + break; + case s_fragment: + break; + } + } + switch (state) { + case s_user: + case s_password: + case s_host: + m_host_start = start; + m_host_length = current - start; + break; + case s_port: + m_port_start = start; + m_port_length = current - start; + break; + case s_path: + m_path_start = start; + m_path_length = current - start; + break; + case s_query: + m_query_start = start; + m_query_length = current - start; + break; + case s_fragment: + m_fragment_start = start; + m_fragment_length = current - start - 1; + break; + default: + break; + } + } + + uri(std::string_view schema, + std::string_view user, + std::string_view password, + std::string_view host, + std::string_view port, + std::string_view path, + std::string_view query, + std::string_view fragment) : uri() { + using namespace std::string_view_literals; + m_data.reserve( + schema.length() + + "://"sv.length() + + (user.empty() ? 0 : user.length() + "@"sv.length() + + (password.empty() ? 0 : password.length() + ":"sv.length())) + + host.length() + + "/"sv.length() + + (port.empty() ? 0 : port.length() + ":"sv.length()) + + path.length() + + (query.empty() ? 0 : query.length() + "?"sv.length()) + + (fragment.empty() ? 0 : fragment.length() + "#"sv.length()) + ); + size_t cur = 0; + + m_data.append(schema); + m_schema_start = cur; + cur += m_schema_length = schema.length(); + + m_data.append("://"sv); + cur += "://"sv.length(); + + if (!user.empty()) { + m_data.append(user); + m_user_start = cur; + cur += m_user_length = user.length(); + + if (!password.empty()) { + m_data.append(":"sv); + cur += ":"sv.length(); + + m_data.append(password); + m_password_start = cur; + cur += m_password_length = password.length(); + } + + m_data.append("@"sv); + cur += "@"sv.length(); + } + + m_data.append(host); + m_host_start = cur; + cur += m_host_length = host.length(); + + if (!port.empty()) { + m_data.append(":"sv); + cur += ":"sv.length(); + + m_data.append(port); + m_port_start = cur; + cur += m_port_length = port.length(); + } + + m_data.append("/"sv); + cur += "/"sv.length(); + + m_data.append(path); + m_path_start = cur; + cur += m_path_length = path.length(); + + if (!query.empty()) { + m_data.append("?"sv); + cur += "?"sv.length(); + + m_data.append(query); + m_query_start = cur; + cur += m_query_length = query.length(); + } + + if (!fragment.empty()) { + m_data.append("#"sv); + cur += "#"sv.length(); + + m_data.append(fragment); + m_fragment_start = cur; + cur += m_fragment_length = fragment.length(); + } + } + + + [[nodiscard]] std::string_view full() const { return m_data; } + + [[nodiscard]] std::string_view schema() const { + return {m_data.data() + m_schema_start, m_schema_length}; + } + + [[nodiscard]] std::string_view user() const { + return {m_data.data() + m_user_start, m_user_length}; + } + + [[nodiscard]] std::string_view password() const { + return {m_data.data() + m_password_start, m_password_length}; + } + + [[nodiscard]] std::string_view host() const { + return {m_data.data() + m_host_start, m_host_length}; + } + + [[nodiscard]] std::string_view port() const { + return {m_data.data() + m_port_start, m_port_length}; + } + + [[nodiscard]] std::string_view path() const { + return {m_data.data() + m_path_start, m_path_length}; + } + + [[nodiscard]] std::string_view query() const { + return {m_data.data() + m_query_start, m_query_length}; + } + + [[nodiscard]] std::string_view fragment() const { + return {m_data.data() + m_fragment_start, m_fragment_length}; + } + + static std::array escape(char c) { + std::array arr = {'%', '\0', '\0'}; + switch ((c & 0xF0) >> 4) { + case 0: + arr[1] = '0'; + break; + case 1: + arr[1] = '1'; + break; + case 2: + arr[1] = '2'; + break; + case 3: + arr[1] = '3'; + break; + case 4: + arr[1] = '4'; + break; + case 5: + arr[1] = '5'; + break; + case 6: + arr[1] = '6'; + break; + case 7: + arr[1] = '7'; + break; + case 8: + arr[1] = '8'; + break; + case 9: + arr[1] = '9'; + break; + case 10: + arr[1] = 'A'; + break; + case 11: + arr[1] = 'B'; + break; + case 12: + arr[1] = 'C'; + break; + case 13: + arr[1] = 'D'; + break; + case 14: + arr[1] = 'E'; + break; + case 15: + arr[1] = 'F'; + break; + } + switch (c & 0x0F) { + default: + case 0: + arr[2] = '0'; + break; + case 1: + arr[2] = '1'; + break; + case 2: + arr[2] = '2'; + break; + case 3: + arr[2] = '3'; + break; + case 4: + arr[2] = '4'; + break; + case 5: + arr[2] = '5'; + break; + case 6: + arr[2] = '6'; + break; + case 7: + arr[2] = '7'; + break; + case 8: + arr[2] = '8'; + break; + case 9: + arr[2] = '9'; + break; + case 10: + arr[2] = 'A'; + break; + case 11: + arr[2] = 'B'; + break; + case 12: + arr[2] = 'C'; + break; + case 13: + arr[2] = 'D'; + break; + case 14: + arr[2] = 'E'; + break; + case 15: + arr[2] = 'F'; + break; + } + + return arr; + } + + private: + static void encode_helper(std::stringstream &sstream, std::string_view selected_view, const char *allowed) { + for (auto c: selected_view) { + bool flag = false; + for (const char *it = allowed; *it != '\0'; it++) { + if (*it == c) { + flag = true; + break; + } + } + if (flag) { + sstream << c; + } else { + auto res = escape(c); + sstream << std::string_view(res.data(), res.size()); + } + } + } + + public: + [[nodiscard]] std::string encoded() const { + std::stringstream sstream; + + // ToDo: Parse into https://de.wikipedia.org/wiki/URL-Encoding + encode_helper(sstream, schema(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); + sstream << "://"; + if (!user().empty()) { + encode_helper(sstream, user(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); + if (!password().empty()) { + sstream << ":"; + encode_helper(sstream, password(), + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); + } + sstream << "@"; + } + encode_helper(sstream, host(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); + if (!port().empty()) { + sstream << ":"; + encode_helper(sstream, port(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); + } + sstream << "/"; + encode_helper(sstream, path(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~/"); + if (!query().empty()) { + sstream << "?"; + encode_helper(sstream, query(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~&"); + } + if (!fragment().empty()) { + sstream << "#"; + encode_helper(sstream, fragment(), + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); + } + + return sstream.str(); + } + }; + +} +namespace std { + template<> + struct std::hash + { + std::size_t operator()(x39::uri const& s) const noexcept + { + return std::hash{}(s.full()); + } + }; +} + +#endif // SQFVM_LANGUAGE_SERVER_URI_HPP \ No newline at end of file diff --git a/server/sqfvm_language_server/util.hpp b/server/sqfvm_language_server/util.hpp new file mode 100644 index 0000000..b5829f6 --- /dev/null +++ b/server/sqfvm_language_server/util.hpp @@ -0,0 +1,139 @@ +#ifndef SQFVM_LANGUAGE_SERVER_UTIL_HPP +#define SQFVM_LANGUAGE_SERVER_UTIL_HPP + +#include "lsp/lspserver.hpp" +#include "database/context.hpp" +#include "parser/sqf/astnode.hpp" +#include +#include +#include +#include +#include + +inline static std::string_view to_string_view(sqf::parser::sqf::bison::astkind kind) { + using namespace std::string_view_literals; + switch (kind) { + case sqf::parser::sqf::bison::astkind::ENDOFFILE: + return "ENDOFFILE"sv; + case sqf::parser::sqf::bison::astkind::INVALID: + return "INVALID"sv; + case sqf::parser::sqf::bison::astkind::__TOKEN: + return "__TOKEN"sv; + case sqf::parser::sqf::bison::astkind::NA: + return "NA"sv; + case sqf::parser::sqf::bison::astkind::STATEMENTS: + return "STATEMENTS"sv; + case sqf::parser::sqf::bison::astkind::STATEMENT: + return "STATEMENT"sv; + case sqf::parser::sqf::bison::astkind::IDENT: + return "IDENT"sv; + case sqf::parser::sqf::bison::astkind::NUMBER: + return "NUMBER"sv; + case sqf::parser::sqf::bison::astkind::HEXNUMBER: + return "HEXNUMBER"sv; + case sqf::parser::sqf::bison::astkind::STRING: + return "STRING"sv; + case sqf::parser::sqf::bison::astkind::BOOLEAN_TRUE: + return "BOOLEAN_TRUE"sv; + case sqf::parser::sqf::bison::astkind::BOOLEAN_FALSE: + return "BOOLEAN_FALSE"sv; + case sqf::parser::sqf::bison::astkind::EXPRESSION_LIST: + return "EXPRESSION_LIST"sv; + case sqf::parser::sqf::bison::astkind::CODE: + return "CODE"sv; + case sqf::parser::sqf::bison::astkind::ARRAY: + return "ARRAY"sv; + case sqf::parser::sqf::bison::astkind::ASSIGNMENT: + return "ASSIGNMENT"sv; + case sqf::parser::sqf::bison::astkind::ASSIGNMENT_LOCAL: + return "ASSIGNMENT_LOCAL"sv; + case sqf::parser::sqf::bison::astkind::EXPN: + return "EXPN"sv; + case sqf::parser::sqf::bison::astkind::EXP0: + return "EXP0"sv; + case sqf::parser::sqf::bison::astkind::EXP1: + return "EXP1"sv; + case sqf::parser::sqf::bison::astkind::EXP2: + return "EXP2"sv; + case sqf::parser::sqf::bison::astkind::EXP3: + return "EXP3"sv; + case sqf::parser::sqf::bison::astkind::EXP4: + return "EXP4"sv; + case sqf::parser::sqf::bison::astkind::EXP5: + return "EXP5"sv; + case sqf::parser::sqf::bison::astkind::EXP6: + return "EXP6"sv; + case sqf::parser::sqf::bison::astkind::EXP7: + return "EXP7"sv; + case sqf::parser::sqf::bison::astkind::EXP8: + return "EXP8"sv; + case sqf::parser::sqf::bison::astkind::EXP9: + return "EXP9"sv; + case sqf::parser::sqf::bison::astkind::EXPU: + return "EXPU"sv; + case sqf::parser::sqf::bison::astkind::EXP_GROUP: + return "EXP_GROUP"sv; + default: + return "UNKNOWN"sv; + } +} + +inline static std::string sqf_destringify(std::string_view input) { + if (input.length() < 2) + return std::string(input); + std::vector buff{}; + buff.reserve(input.length() - 2); + char quote_char = input[0]; + bool was_quote = false; + for (size_t i = 1; i < input.length() - 1; i++) { + if (input[i] == quote_char && !was_quote) { + was_quote = true; + continue; + } + buff.push_back(input[i]); + was_quote = false; + } + return {buff.begin(), buff.end()}; +} + +inline static bool iequal(std::string_view left, std::string_view right) { + return std::equal(left.begin(), left.end(), right.begin(), right.end(), [](auto a, auto b) { + return std::tolower(a) == std::tolower(b); + }); +} + +inline static uint64_t unix_timestamp() { + return (uint64_t) std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); +} + +inline static bool is_subpath(const std::filesystem::path &path, const std::filesystem::path &base) { + const auto mismatch_pair = std::mismatch(path.begin(), path.end(), base.begin(), base.end()); + return mismatch_pair.second == base.end(); +} + +// Method to get a clear & clean uri string out of the uri provided by vscode. +inline static std::string sanitize_to_string(const lsp::data::uri &uri) { + std::string dpath; + dpath.reserve(uri.path().length()); + dpath.append(uri.path()); + std::filesystem::path data_path(dpath); + data_path = data_path.lexically_normal(); + dpath = data_path.string(); + std::replace(dpath.begin(), dpath.end(), '\\', '/'); + return dpath; +} + +// Method to get a clear & clean uri out of the string provided by sqfvm. +inline static lsp::data::uri sanitize_to_uri(const std::string_view sv) { + auto path = std::filesystem::path(sv).lexically_normal(); + auto str = path.string(); + std::replace(str.begin(), str.end(), '\\', '/'); + return lsp::data::uri("file", {}, {}, {}, {}, str, {}, {}); +} + +// Method to get a clear & clean uri out of the string provided by sqfvm. +inline static lsp::data::uri sanitize_to_uri(const std::string str) { return sanitize_to_uri(std::string_view(str)); } + + +#endif //SQFVM_LANGUAGE_SERVER_UTIL_HPP diff --git a/server/src/git_sha1.h b/server/src/git_sha1.h deleted file mode 100644 index 733d027..0000000 --- a/server/src/git_sha1.h +++ /dev/null @@ -1,2 +0,0 @@ -#pragma once -extern const char g_GIT_SHA1[]; \ No newline at end of file diff --git a/server/src/language_server_logger.cpp b/server/src/language_server_logger.cpp deleted file mode 100644 index 95de23f..0000000 --- a/server/src/language_server_logger.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "language_server_logger.h" -#include "sqf_language_server.h" -#include - -void language_server_logger::log(const LogMessageBase& base) -{ - { - std::stringstream sstream; - sstream << Logger::loglevelstring(base.getLevel()) << ' ' << base.formatMessage(); - language_server.window_logMessage(lsp::data::message_type::Log, sstream.str()); - } - auto& message = dynamic_cast(base); - if (message == nullptr) - { - return; - } - - auto location = message.location(); - auto uri = sanitize_to_uri(location.path); - auto fpath = sanitize_to_string(uri); - auto& doc = language_server.get_or_create(uri); - - lsp::data::publish_diagnostics_params& params = doc.diagnostics; - - lsp::data::diagnostics msg; - msg.range.start.line = location.line - 1; - msg.range.start.character = location.col; - msg.range.end.line = location.line - 1; - msg.range.end.character = location.col; - - - msg.code = std::to_string(base.getErrorCode()); - msg.message = message.formatMessage(); - msg.source = "SQF-VM"; - - switch (message.getLevel()) - { - case loglevel::fatal: - case loglevel::error: - msg.severity = lsp::data::diagnostic_severity::Error; - break; - case loglevel::warning: - msg.severity = lsp::data::diagnostic_severity::Warning; - break; - case loglevel::info: - msg.severity = lsp::data::diagnostic_severity::Information; - break; - case loglevel::verbose: - case loglevel::trace: - default: - msg.severity = lsp::data::diagnostic_severity::Hint; - break; - } - params.diagnostics.push_back(msg); -} \ No newline at end of file diff --git a/server/src/language_server_logger.h b/server/src/language_server_logger.h deleted file mode 100644 index e2a3720..0000000 --- a/server/src/language_server_logger.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#include - -class sqf_language_server; - -class language_server_logger : public Logger { - sqf_language_server& language_server; -public: - language_server_logger(sqf_language_server& ref) : Logger(), language_server(ref) {} - - virtual void log(const LogMessageBase& base) override; -}; \ No newline at end of file diff --git a/server/src/lspserver.h b/server/src/lspserver.h deleted file mode 100644 index 88c6a43..0000000 --- a/server/src/lspserver.h +++ /dev/null @@ -1,4579 +0,0 @@ -#pragma once -#include "jsonrpc.h" -#include "uri.h" - -#include -#include -#include -#include -#include - -namespace lsp -{ - namespace data - { - template - inline void from_json(const nlohmann::json& node, T& t) - { - t = T::from_json(node); - } - template<> - inline void from_json(const nlohmann::json& node, bool& t) - { - t = node; - } - template<> - inline void from_json(const nlohmann::json& node, int& t) - { - t = node; - } - template<> - inline void from_json(const nlohmann::json& node, float& t) - { - t = node; - } - template<> - inline void from_json(const nlohmann::json& node, uint64_t& t) - { - t = node; - } - template<> - inline void from_json(const nlohmann::json& node, std::string& t) - { - t = node; - } - template - inline nlohmann::json to_json(const T& t) - { - return t.to_json(); - } - template<> - inline nlohmann::json to_json(const bool& t) - { - return t; - } - template<> - inline nlohmann::json to_json(const int& t) - { - return t; - } - template<> - inline nlohmann::json to_json(const float& t) - { - return t; - } - template<> - inline nlohmann::json to_json(const uint64_t& t) - { - return t; - } - template<> - inline nlohmann::json to_json(const std::string& t) - { - return t; - } - - - enum class resource_operations - { - Empty = 0b000, - /* - Supports creating new files and folders. - */ - Create = 0b001, - /* - Supports renaming existing files and folders. - */ - Rename = 0b010, - /* - Supports deleting existing files and folders. - */ - Delete = 0b100 - }; - inline resource_operations operator | (resource_operations lhs, resource_operations rhs) - { - return static_cast ( - static_cast::type>(lhs) | - static_cast::type>(rhs) - ); - } - inline resource_operations operator & (resource_operations lhs, resource_operations rhs) - { - return static_cast ( - static_cast::type>(lhs) & - static_cast::type>(rhs) - ); - } - template<> - inline void from_json(const nlohmann::json& node, resource_operations& t) - { - t = resource_operations::Empty; - for (auto resourceOperationJson : node) - { - std::string resourceOperationString = resourceOperationJson; - if (resourceOperationString == "create") { t = t | resource_operations::Create; } - else if (resourceOperationString == "rename") { t = t | resource_operations::Rename; } - else if (resourceOperationString == "delete") { t = t | resource_operations::Delete; } - } - } - template<> - inline nlohmann::json to_json(const resource_operations& t) - { - nlohmann::json arr; - if ((t & resource_operations::Create) == resource_operations::Create) { arr.push_back("create"); } - if ((t & resource_operations::Delete) == resource_operations::Delete) { arr.push_back("delete"); } - if ((t & resource_operations::Rename) == resource_operations::Rename) { arr.push_back("rename"); } - return arr; - } - - enum class failure_handling - { - empty, - /* - Applying the workspace change is simply aborted if one of the changes provided - fails. All operations executed before the failing operation stay executed. - */ - abort, - /* - All operations are executed transactional. That means they either all - succeed or no changes at all are applied to the workspace. - */ - transactional, - /* - If the workspace edit contains only textual file changes they are executed transactional. - If resource changes (create, rename or delete file) are part of the change the failure - handling strategy is abort. - */ - textOnlyTransactional, - /* - The client tries to undo the operations already executed. But there is no - guarantee that this is succeeding. - */ - undo - }; - template<> - inline void from_json(const nlohmann::json& node, failure_handling& t) - { - - t = failure_handling::empty; - std::string actual = node; - if (actual == "abort") { t = failure_handling::abort; return; } - if (actual == "transactional") { t = failure_handling::transactional; return; } - if (actual == "textOnlyTransactional") { t = failure_handling::textOnlyTransactional; return; } - if (actual == "undo") { t = failure_handling::undo; return; } - } - template<> - inline nlohmann::json to_json(const failure_handling& t) - { - switch (t) - { - case failure_handling::abort: return "abort"; - case failure_handling::transactional: return "transactional"; - case failure_handling::textOnlyTransactional: return "textOnlyTransactional"; - case failure_handling::undo: return "undo"; - default: return {}; - } - } - - enum class folding_range_kind - { - /** - * Folding range for a comment - * 'comment' - */ - Comment, - /** - * Folding range for a imports or includes - * 'imports' - */ - Imports, - /** - * Folding range for a region (e.g. `#region`) - * 'region' - */ - Region - }; - template<> - inline void from_json(const nlohmann::json& node, folding_range_kind& t) - { - t = folding_range_kind::Comment; - std::string actual = node; - if (actual == "comment") { t = folding_range_kind::Comment; return; } - if (actual == "imports") { t = folding_range_kind::Imports; return; } - if (actual == "region") { t = folding_range_kind::Region; return; } - } - template<> - inline nlohmann::json to_json(const folding_range_kind& t) - { - switch (t) - { - case folding_range_kind::Comment: return "comment"; - case folding_range_kind::Imports: return "imports"; - case folding_range_kind::Region: return "region"; - default: return {}; - } - } - - enum class symbol_kind { - 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 - }; - template<> - inline void from_json(const nlohmann::json& node, symbol_kind& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const symbol_kind& t) - { - return static_cast(t); - } - - enum class message_type { - /** - * An error message. - */ - Error = 1, - /** - * A warning message. - */ - Warning = 2, - /** - * An information message. - */ - Info = 3, - /** - * A log message. - */ - Log = 4 - }; - template<> - inline void from_json(const nlohmann::json& node, message_type& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const message_type& t) - { - return static_cast(t); - } - - enum class trace_mode - { - off, - message, - verbose - }; - template<> - inline void from_json(const nlohmann::json& node, trace_mode& t) - { - - t = trace_mode::off; - std::string actual = node; - if (actual == "message") { t = trace_mode::message; return; } - if (actual == "verbose") { t = trace_mode::verbose; return; } - } - template<> - inline nlohmann::json to_json(const trace_mode& t) - { - switch (t) - { - case trace_mode::off: return "off"; - case trace_mode::message: return "message"; - case trace_mode::verbose: return "verbose"; - default: return {}; - } - } - - enum class completion_item_tag - { - Deprecated = 1 - }; - template<> - inline void from_json(const nlohmann::json& node, completion_item_tag& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const completion_item_tag& t) - { - return static_cast(t); - } - - /** - * Describes the content type that a client supports in various - * result literals like `Hover`, `ParameterInfo` or `CompletionItem`. - * - * Please note that `MarkupKinds` must not start with a `$`. This kinds - * are reserved for internal usage. - */ - enum class markup_kind - { - /** - * Plain text is supported as a content format - * Value String: "plaintext" - */ - PlainText, - /** - * Markdown is supported as a content format - * Value String: "markdown - */ - Markdown - }; - template<> - inline void from_json(const nlohmann::json& node, markup_kind& t) - { - - t = markup_kind::PlainText; - std::string actual = node; - if (actual == "plaintext") { t = markup_kind::PlainText; return; } - if (actual == "markdown") { t = markup_kind::Markdown; return; } - } - template<> - inline nlohmann::json to_json(const markup_kind& t) - { - switch (t) - { - case markup_kind::PlainText: return "plaintext"; - case markup_kind::Markdown: return "markdown"; - default: return {}; - } - } - - enum class completion_item_kind { - Text = 1, - Method = 2, - Function = 3, - Constructor = 4, - Field = 5, - Variable = 6, - Class = 7, - Interface = 8, - Module = 9, - Property = 10, - Unit = 11, - Value = 12, - Enum = 13, - Keyword = 14, - Snippet = 15, - Color = 16, - File = 17, - Reference = 18, - Folder = 19, - EnumMember = 20, - Constant = 21, - Struct = 22, - Event = 23, - Operator = 24, - TypeParameter = 25 - }; - template<> - inline void from_json(const nlohmann::json& node, completion_item_kind& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const completion_item_kind& t) - { - return static_cast(t); - } - - enum class diagnostic_tag - { - /** - * 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 - }; - template<> - inline void from_json(const nlohmann::json& node, diagnostic_tag& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const diagnostic_tag& t) - { - return static_cast(t); - } - - enum class code_action_kind { - /** - * Empty kind. - */ - Empty, - - /** - * Base kind for quickfix actions: 'quickfix'. - */ - QuickFix, - - /** - * Base kind for refactoring actions: 'refactor'. - */ - Refactor, - - /** - * Base kind for refactoring extraction actions: 'refactor.extract'. - * - * Example extract actions: - * - * - Extract method - * - Extract function - * - Extract variable - * - Extract interface from class - * - ... - */ - RefactorExtract, - - /** - * Base kind for refactoring inline actions: 'refactor.inline'. - * - * Example inline actions: - * - * - Inline function - * - Inline variable - * - Inline constant - * - ... - */ - RefactorInline, - - /** - * Base kind for refactoring rewrite actions: 'refactor.rewrite'. - * - * Example rewrite actions: - * - * - Convert JavaScript function to class - * - Add or remove parameter - * - Encapsulate field - * - Make method static - * - Move method to base class - * - ... - */ - RefactorRewrite, - - /** - * Base kind for source actions: `source`. - * - * Source code actions apply to the entire file. - */ - Source, - - /** - * Base kind for an organize imports source action: `source.organizeImports`. - */ - SourceOrganizeImports - }; - template<> - inline void from_json(const nlohmann::json& node, code_action_kind& t) - { - - t = code_action_kind::Empty; - std::string actual = node; - if (actual == "quickfix") { t = code_action_kind::QuickFix; return; } - if (actual == "refactor") { t = code_action_kind::Refactor; return; } - if (actual == "refactor.extract") { t = code_action_kind::RefactorExtract; return; } - if (actual == "refactor.inline") { t = code_action_kind::RefactorInline; return; } - if (actual == "refactor.rewrite") { t = code_action_kind::RefactorRewrite; return; } - if (actual == "source") { t = code_action_kind::Source; return; } - if (actual == "source.organizeImports") { t = code_action_kind::SourceOrganizeImports; return; } - } - template<> - inline nlohmann::json to_json(const code_action_kind& t) - { - switch (t) - { - case code_action_kind::QuickFix: return "quickfix"; - case code_action_kind::Refactor: return "refactor"; - case code_action_kind::RefactorExtract: return "refactor.extract"; - case code_action_kind::RefactorInline: return "refactor.inline"; - case code_action_kind::RefactorRewrite: return "refactor.rewrite"; - case code_action_kind::Source: return "source"; - case code_action_kind::SourceOrganizeImports: return "source.organizeImports"; - default: return {}; - } - } - - enum class text_document_sync_kind - { - /** - * 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 - * send. - */ - Incremental = 2 - }; - template<> - inline void from_json(const nlohmann::json& node, text_document_sync_kind& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const text_document_sync_kind& t) - { - return static_cast(t); - } - - enum class text_document_save_reason - { - /** - * Manually triggered, e.g. by the user pressing save, by starting debugging, - * or by an API call. - */ - Manual = 1, - /** - * Automatic after a delay. - */ - AfterDelay = 2, - /** - * When the editor lost focus. - */ - FocusOut = 3, - }; - template<> - inline void from_json(const nlohmann::json& node, text_document_save_reason& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const text_document_save_reason& t) - { - return static_cast(t); - } - - /** - * How a completion was triggered - */ - enum class completion_trigger_kind - { - /** - * Completion was triggered by typing an identifier (24x7 code - * complete), manual invocation (e.g Ctrl+Space) or via API. - */ - Invoked = 1, - - /** - * Completion was triggered by a trigger character specified by - * the `triggerCharacters` properties of the `CompletionRegistrationOptions`. - */ - TriggerCharacter = 2, - - /** - * Completion was re-triggered as the current completion list is incomplete. - */ - TriggerForIncompleteCompletions = 3 - }; - template<> - inline void from_json(const nlohmann::json& node, completion_trigger_kind& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const completion_trigger_kind& t) - { - return static_cast(t); - } - - /** - * Defines whether the insert text in a completion item should be interpreted as - * plain text or a snippet. - */ - enum class insert_text_format - { - /** - * The primary text to be inserted is treated as a plain string. - */ - PlainText = 1, - - /** - * The primary text to be inserted is treated as a snippet. - * - * A snippet can define tab stops and placeholders with `$1`, `$2` - * and `${3:foo}`. `$0` defines the final tab stop, it defaults to - * the end of the snippet. Placeholders with equal identifiers are linked, - * that is typing in one will update others too. - */ - Snippet = 2 - }; - template<> - inline void from_json(const nlohmann::json& node, insert_text_format& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const insert_text_format& t) - { - return static_cast(t); - } - - enum class diagnostic_severity - { - /** - * Reports an error. - */ - Error = 1, - /** - * Reports a warning. - */ - Warning = 2, - /** - * Reports an information. - */ - Information = 3, - /** - * Reports a hint. - */ - Hint = 4 - }; - template<> - inline void from_json(const nlohmann::json& node, diagnostic_severity& t) - { - t = static_cast(node.get()); - } - template<> - inline nlohmann::json to_json(const diagnostic_severity& t) - { - return static_cast(t); - } - - template - inline void from_json(const nlohmann::json& node, std::vector& ts) - { - ts = std::vector(); - for (auto subnode : node) - { - T t; - from_json(subnode, t); - ts.push_back(t); - } - } - template - inline void from_json(const nlohmann::json& node, const char* key, T& t) - { - from_json(node[key], t); - } - template - inline void from_json(const nlohmann::json& node, const char* key, std::optional& opt) - { - if (node.contains(key) && !node[key].is_null()) - { - T t; - from_json(node, key, t); - opt = t; - } - else - { - opt = {}; - } - } - - template - inline nlohmann::json to_json(const std::vector& ts) - { - nlohmann::json json = nlohmann::json::array(); - for (auto t : ts) - { - json.push_back(to_json(t)); - } - return json; - } - template - inline nlohmann::json to_json(const std::optional& t) - { - if (t.has_value()) - { - return to_json(t.value()); - } - else - { - return nullptr; - } - } - template - inline void set_json(nlohmann::json& json, const char* key, const std::optional& t) - { - if (t.has_value()) - { - json[key] = to_json(t.value()); - } - } - template - inline void set_json(nlohmann::json& json, const char* key, const T& t) - { - json[key] = to_json(t); - } - struct uri : public ::x39::uri - { - uri() : ::x39::uri() {} - uri(const std::string& input) : ::x39::uri(input) {} - uri(std::string_view input) : ::x39::uri(input) {} - uri(std::string_view schema, - std::string_view user, - std::string_view password, - std::string_view host, - std::string_view port, - std::string_view path, - std::string_view query, - std::string_view fragment) : - ::x39::uri(schema, user, password, host, port, path, query, fragment) {} - static uri from_json(const nlohmann::json& node) - { - return { node.get() }; - } - nlohmann::json to_json() const - { - return encoded(); - } - }; - - /* - * A document filter denotes a document through properties like language, scheme or pattern. - * An example is a filter that applies to TypeScript files on disk. - * Another example is a filter the applies to JSON files with name package.json: - */ - struct document_filter { - /** - * A language id, like `typescript`. - */ - std::string language; - - /** - * A Uri [scheme](#Uri.scheme), like `file` or `untitled`. - */ - std::string scheme; - - /** - * A glob pattern, like `*.{ts,js}`. - * - * Glob patterns can have the following syntax: - * - `*` to match one or more characters in a path segment - * - `?` to match on one character in a path segment - * - `**` to match any number of path segments, including none - * - `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) - * - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - * - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) - */ - std::string pattern; - - static document_filter from_json(const nlohmann::json& node) - { - document_filter res; - data::from_json(node, "language", res.language); - data::from_json(node, "scheme", res.scheme); - data::from_json(node, "pattern", res.pattern); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "language", language); - data::set_json(json, "scheme", scheme); - data::set_json(json, "pattern", pattern); - return json; - } - }; - struct position - { - size_t line; - size_t character; - static position from_json(const nlohmann::json& node) - { - position res; - data::from_json(node, "line", res.line); - data::from_json(node, "character", res.character); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "line", line); - data::set_json(json, "character", character); - return json; - } - }; - struct range - { - position start; - position end; - - static range from_json(const nlohmann::json& node) - { - range res; - data::from_json(node, "start", res.start); - data::from_json(node, "end", res.end); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "start", start); - data::set_json(json, "end", end); - return json; - } - }; - struct text_edit - { - range range; - std::string newText; - static text_edit from_json(const nlohmann::json& node) - { - text_edit res; - data::from_json(node, "range", res.range); - data::from_json(node, "newText", res.newText); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "range", range); - data::set_json(json, "newText", newText); - return json; - } - }; - struct text_document_identifier - { - uri uri; - static text_document_identifier from_json(const nlohmann::json& node) - { - text_document_identifier res; - data::from_json(node, "uri", res.uri); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "uri", uri); - return json; - } - }; - struct text_document_item - { - /** - * The text document's URI. - */ - uri uri; - /** - * The text document's language identifier. - */ - std::string languageId; - /** - * The version number of this document (it will increase after each - * change, including undo/redo). - */ - size_t version; - /** - * The content of the opened text document. - */ - std::string text; - static text_document_item from_json(const nlohmann::json& node) - { - text_document_item res; - data::from_json(node, "uri", res.uri); - data::from_json(node, "languageId", res.languageId); - data::from_json(node, "version", res.version); - data::from_json(node, "text", res.text); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "uri", uri); - data::set_json(json, "languageId", languageId); - data::set_json(json, "version", version); - data::set_json(json, "text", text); - return json; - } - }; - struct versioned_text_document_identifier - { - uri uri; - /** - * The version number of this document. If a versioned text document identifier - * is sent from the server to the client and the file is not open in the editor - * (the server has not received an open notification before) the server can send - * `null` to indicate that the version is known and the content on disk is the - * master (as speced with document content ownership). - * - * The version number of a document will increase after each change, including - * undo/redo. The number doesn't need to be consecutive. - */ - std::optional version; - static versioned_text_document_identifier from_json(const nlohmann::json& node) - { - versioned_text_document_identifier res; - data::from_json(node, "uri", res.uri); - data::from_json(node, "version", res.version); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "uri", uri); - data::set_json(json, "version", version); - return json; - } - }; - /** - * A `MarkupContent` literal represents a string value which content is interpreted base on its - * kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. - * - * If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. - * See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting - * - * Here is an example how such a string can be constructed using JavaScript / TypeScript: - * ```typescript - * let markdown: MarkdownContent = { - * kind: MarkupKind.Markdown, - * value: [ - * '# Header', - * 'Some text', - * '```typescript', - * 'someCode();', - * '```' - * ].join('\n') - * }; - * ``` - * - * *Please Note* that clients might sanitize the return markdown. A client could decide to - * remove HTML from the markdown to avoid script execution. - */ - struct markup_content - { - /** - * The type of the Markup - */ - markup_kind kind; - - /** - * The content itself - */ - std::string value; - - static markup_content from_json(const nlohmann::json& node) - { - markup_content res; - data::from_json(node, "kind", res.kind); - data::from_json(node, "value", res.value); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "kind", kind); - data::set_json(json, "value", value); - return json; - } - }; - struct location - { - uri uri; - range range; - - static location from_json(const nlohmann::json& node) - { - location res; - data::from_json(node, "uri", res.uri); - data::from_json(node, "range", res.range); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "uri", uri); - data::set_json(json, "range", range); - return json; - } - }; - struct diagnostics - { - struct diagnostic_related_information - { - /** - * The location of this related diagnostic information. - */ - location location; - - /** - * The message of this related diagnostic information. - */ - std::string message; - - static diagnostic_related_information from_json(const nlohmann::json& node) - { - diagnostic_related_information res; - data::from_json(node, "location", res.location); - data::from_json(node, "message", res.message); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "location", location); - data::set_json(json, "message", message); - return json; - } - }; - /** - * The range at which the message applies. - */ - range range; - - /** - * The diagnostic's severity. Can be omitted. If omitted it is up to the - * client to interpret diagnostics as error, warning, info or hint. - */ - std::optional severity; - - /** - * The diagnostic's code, which might appear in the user interface. - */ - std::optional code; - - /** - * A human-readable string describing the source of this - * diagnostic, e.g. 'typescript' or 'super lint'. - */ - std::optional source; - - /** - * The diagnostic's message. - */ - std::string message; - - /** - * Additional metadata about the diagnostic. - * - * @since 3.15.0 - */ - std::optional> tags; - - /** - * An array of related diagnostic information, e.g. when symbol-names within - * a scope collide all definitions can be marked via this property. - */ - std::optional> relatedInformation; - - static diagnostics from_json(const nlohmann::json& node) - { - diagnostics res; - data::from_json(node, "range", res.range); - data::from_json(node, "severity", res.severity); - data::from_json(node, "code", res.code); - data::from_json(node, "source", res.source); - data::from_json(node, "message", res.message); - data::from_json(node, "tags", res.tags); - data::from_json(node, "relatedInformation", res.relatedInformation); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "range", range); - data::set_json(json, "severity", severity); - data::set_json(json, "code", code); - data::set_json(json, "source", source); - data::set_json(json, "message", message); - data::set_json(json, "tags", tags); - data::set_json(json, "relatedInformation", relatedInformation); - return json; - } - }; - struct folding_range - { - /** - * The zero-based line number from where the folded range starts. - */ - size_t startLine; - - /** - * The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. - */ - std::optional startCharacter; - - /** - * The zero-based line number where the folded range ends. - */ - size_t endLine; - - /** - * The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. - */ - std::optional 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. - */ - std::optional kind; - - static struct folding_range from_json(const nlohmann::json& node) - { - struct folding_range res; - data::from_json(node, "startLine", res.startLine); - data::from_json(node, "startCharacter", res.startCharacter); - data::from_json(node, "endLine", res.endLine); - data::from_json(node, "endCharacter", res.endCharacter); - data::from_json(node, "kind", res.kind); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "startLine", startLine); - data::set_json(json, "startCharacter", startCharacter); - data::set_json(json, "endLine", endLine); - data::set_json(json, "endCharacter", endCharacter); - data::set_json(json, "kind", kind); - return json; - } - }; - struct command - { - /** - * Title of the command, like `save`. - */ - std::string title; - /** - * The identifier of the actual command handler. - */ - std::string command_; - /** - * Arguments that the command handler should be - * invoked with. - */ - std::optional> arguments; - - static command from_json(const nlohmann::json& node) - { - command res; - data::from_json(node, "title", res.title); - data::from_json(node, "command", res.command_); - auto argumentsFindRes = node.find("arguments"); - if (argumentsFindRes != node.end()) - { - res.arguments = std::vector{}; - for (auto it : (*argumentsFindRes)) - { - res.arguments->push_back(it); - } - } - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "title", title); - data::set_json(json, "command", command_); - if (arguments.has_value()) - { - json["arguments"] = arguments.value(); - } - return json; - } - }; - struct completion_item - { - /** - * The label of this completion item. By default - * also the text that is inserted when selecting - * this completion. - */ - std::string label; - - /** - * The kind of this completion item. Based of the kind - * an icon is chosen by the editor. The standardized set - * of available values is defined in `CompletionItemKind`. - */ - std::optional kind; - - /** - * Tags for this completion item. - * - * @since 3.15.0 - */ - std::optional> tags; - - /** - * A human-readable string with additional information - * about this item, like type or symbol information. - */ - std::optional detail; - - /** - * A human-readable string that represents a doc-comment. - */ - std::optional documentation; - - /** - * Indicates if this item is deprecated. - * - * @deprecated Use `tags` instead if supported. - */ - std::optional deprecated; - - /** - * Select this item when showing. - * - * *Note* that only one completion item can be selected and that the - * tool / client decides which item that is. The rule is that the *first* - * item of those that match best is selected. - */ - std::optional preselect; - - /** - * A string that should be used when comparing this item - * with other items. When `falsy` the label is used. - */ - std::optional sortText; - - /** - * A string that should be used when filtering a set of - * completion items. When `falsy` the label is used. - */ - std::optional filterText; - - /** - * A string that should be inserted into a document when selecting - * this completion. When `falsy` the label is used. - * - * The `insertText` is subject to interpretation by the client side. - * Some tools might not take the string literally. For example - * VS Code when code complete is requested in this example `con` - * and a completion item with an `insertText` of `console` is provided it - * will only insert `sole`. Therefore it is recommended to use `textEdit` instead - * since it avoids additional client side interpretation. - */ - std::optional insertText; - - /** - * The format of the insert text. The format applies to both the `insertText` property - * and the `newText` property of a provided `textEdit`. If omitted defaults to - * `InsertTextFormat.PlainText`. - */ - std::optional insertTextFormat; - - /** - * An edit which is applied to a document when selecting this completion. When an edit is provided the value of - * `insertText` is ignored. - * - * *Note:* The range of the edit must be a single line range and it must contain the position at which completion - * has been requested. - */ - std::optional textEdit; - - /** - * An optional array of additional text edits that are applied when - * selecting this completion. Edits must not overlap (including the same insert position) - * with the main edit nor with themselves. - * - * Additional text edits should be used to change text unrelated to the current cursor position - * (for example adding an import statement at the top of the file if the completion item will - * insert an unqualified type). - */ - std::optional> additionalTextEdits; - - /** - * An optional set of characters that when pressed while this completion is active will accept it first and - * then type that character. *Note* that all commit characters should have `length=1` and that superfluous - * characters will be ignored. - */ - std::optional> commitCharacters; - - /** - * An optional command that is executed *after* inserting this completion. *Note* that - * additional modifications to the current document should be described with the - * additionalTextEdits-property. - */ - std::optional command; - - /** - * A data entry field that is preserved on a completion item between - * a completion and a completion resolve request. - */ - std::optional data; - - static completion_item from_json(const nlohmann::json& node) - { - completion_item res; - data::from_json(node, "label", res.label); - data::from_json(node, "kind", res.kind); - data::from_json(node, "tags", res.tags); - data::from_json(node, "detail", res.detail); - data::from_json(node, "documentation", res.documentation); - data::from_json(node, "deprecated", res.deprecated); - data::from_json(node, "preselect", res.preselect); - data::from_json(node, "sortText", res.sortText); - data::from_json(node, "filterText", res.filterText); - data::from_json(node, "insertText", res.insertText); - data::from_json(node, "insertTextFormat", res.insertTextFormat); - data::from_json(node, "textEdit", res.textEdit); - data::from_json(node, "additionalTextEdits", res.additionalTextEdits); - data::from_json(node, "commitCharacters", res.commitCharacters); - data::from_json(node, "command", res.command); - res.data = node.contains("data") ? node["data"] : nlohmann::json(nullptr); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "label", label); - data::set_json(json, "kind", kind); - data::set_json(json, "tags", tags); - data::set_json(json, "detail", detail); - data::set_json(json, "documentation", documentation); - data::set_json(json, "deprecated", deprecated); - data::set_json(json, "preselect", preselect); - data::set_json(json, "sortText", sortText); - data::set_json(json, "filterText", filterText); - data::set_json(json, "insertText", insertText); - data::set_json(json, "insertTextFormat", insertTextFormat); - data::set_json(json, "textEdit", textEdit); - data::set_json(json, "additionalTextEdits", additionalTextEdits); - data::set_json(json, "commitCharacters", commitCharacters); - data::set_json(json, "command", command); - if (data.has_value()) { json["data"] = *data; } - return json; - } - }; - /** - * Represents a collection of [completion items](#CompletionItem) to be presented - * in the editor. - */ - struct completion_list - { - /** - * This list it not complete. Further typing should result in recomputing - * this list. - */ - bool isIncomplete; - - /** - * The completion items. - */ - std::vector items; - - static completion_list from_json(const nlohmann::json& node) - { - completion_list res; - data::from_json(node, "isIncomplete", res.isIncomplete); - data::from_json(node, "items", res.items); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "isIncomplete", isIncomplete); - data::set_json(json, "items", items); - return json; - } - }; - struct initialize_result - { - struct server_capabilities - { - struct text_document_sync_options - { - struct SaveOptions - { - /** - * The client is supposed to include the content on save. - */ - std::optional includeText; - - static SaveOptions from_json(const nlohmann::json& node) - { - SaveOptions res; - data::from_json(node, "includeText", res.includeText); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "includeText", includeText); - return json; - } - }; - /** - * Open and close notifications are sent to the server. If omitted open close notification should not - * be sent. - */ - std::optional openClose; - /** - * If present will save notifications are sent to the server. If omitted the notification should not be - * sent. - */ - std::optional willSave; - /** - * If present will save wait until requests are sent to the server. If omitted the request should not be - * sent. - */ - std::optional willSaveWaitUntil; - /** - * If present save notifications are sent to the server. If omitted the notification should not be - * sent. - */ - std::optional save; - - /** - * Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full - * and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None. - */ - std::optional change; - - static text_document_sync_options from_json(const nlohmann::json& node) - { - text_document_sync_options res; - data::from_json(node, "openClose", res.openClose); - data::from_json(node, "willSave", res.willSave); - data::from_json(node, "willSaveWaitUntil", res.willSaveWaitUntil); - data::from_json(node, "save", res.save); - data::from_json(node, "change", res.change); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "openClose", openClose); - data::set_json(json, "willSave", willSave); - data::set_json(json, "willSaveWaitUntil", willSaveWaitUntil); - data::set_json(json, "change", change); - return json; - } - }; - struct completion_options - { - /** - * Most tools trigger 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. - * - * If code complete should automatically be trigger on characters not being valid inside - * an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. - */ - std::optional> triggerCharacters; - - /** - * The list of all possible characters that commit a completion. This field can be used - * if clients don't support individual commit characters per completion item. See - * `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport`. - * - * If a server provides both `allCommitCharacters` and commit characters on an individual - * completion item the ones on the completion item win. - * - * @since 3.2.0 - */ - std::optional> allCommitCharacters; - - /** - * The server provides support to resolve additional - * information for a completion item. - */ - std::optional resolveProvider; - - static completion_options from_json(const nlohmann::json& node) - { - completion_options res; - data::from_json(node, "triggerCharacters", res.triggerCharacters); - data::from_json(node, "allCommitCharacters", res.allCommitCharacters); - data::from_json(node, "resolveProvider", res.resolveProvider); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "triggerCharacters", triggerCharacters); - data::set_json(json, "allCommitCharacters", allCommitCharacters); - data::set_json(json, "resolveProvider", resolveProvider); - return json; - } - }; - struct hover_options - { - std::optional workDoneProgress; - - static hover_options from_json(const nlohmann::json& node) - { - hover_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct signature_help_options - { - std::optional workDoneProgress; - /** - * The characters that trigger signature help - * automatically. - */ - std::optional> 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. - * - * @since 3.15.0 - */ - std::optional> retriggerCharacters; - - static signature_help_options from_json(const nlohmann::json& node) - { - signature_help_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "triggerCharacters", res.triggerCharacters); - data::from_json(node, "retriggerCharacters", res.retriggerCharacters); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "triggerCharacters", triggerCharacters); - data::set_json(json, "retriggerCharacters", retriggerCharacters); - return json; - } - }; - struct declaration_registration_options - { - std::optional workDoneProgress; - /** - * The id used to register the request. The id can be used to deregister - * the request again. See also Registration#id. - */ - std::optional id; - /** - * A document selector to identify the scope of the registration. If set to null - * the document selector provided on the client side will be used. - */ - std::optional documentSelector; - - static declaration_registration_options from_json(const nlohmann::json& node) - { - declaration_registration_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "id", res.id); - data::from_json(node, "documentSelector", res.documentSelector); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "id", id); - data::set_json(json, "documentSelector", documentSelector); - return json; - } - }; - struct definition_options - { - std::optional workDoneProgress; - - static definition_options from_json(const nlohmann::json& node) - { - definition_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct type_definition_registration_options - { - std::optional workDoneProgress; - /** - * A document selector to identify the scope of the registration. If set to null - * the document selector provided on the client side will be used. - */ - std::optional documentSelector; - /** - * The id used to register the request. The id can be used to deregister - * the request again. See also Registration#id. - */ - std::optional id; - - static type_definition_registration_options from_json(const nlohmann::json& node) - { - type_definition_registration_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "documentSelector", res.documentSelector); - data::from_json(node, "id", res.id); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "documentSelector", documentSelector); - data::set_json(json, "id", id); - return json; - } - }; - struct implementation_registration_options - { - std::optional workDoneProgress; - /** - * A document selector to identify the scope of the registration. If set to null - * the document selector provided on the client side will be used. - */ - std::optional documentSelector; - /** - * The id used to register the request. The id can be used to deregister - * the request again. See also Registration#id. - */ - std::optional id; - - static implementation_registration_options from_json(const nlohmann::json& node) - { - implementation_registration_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "documentSelector", res.documentSelector); - data::from_json(node, "id", res.id); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "documentSelector", documentSelector); - data::set_json(json, "id", id); - return json; - } - }; - struct reference_options - { - std::optional workDoneProgress; - - static reference_options from_json(const nlohmann::json& node) - { - reference_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct document_highlight_options - { - std::optional workDoneProgress; - - static document_highlight_options from_json(const nlohmann::json& node) - { - document_highlight_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct document_symbol_options - { - std::optional workDoneProgress; - - static document_symbol_options from_json(const nlohmann::json& node) - { - document_symbol_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct code_action_options - { - std::optional workDoneProgress; - /** - * CodeActionKinds that this server may return. - * - * The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server - * may list out every specific kind they provide. - */ - std::optional> codeActionKinds; - - static code_action_options from_json(const nlohmann::json& node) - { - code_action_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "codeActionKinds", res.codeActionKinds); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "codeActionKinds", codeActionKinds); - return json; - } - }; - struct code_lens_options - { - std::optional workDoneProgress; - /** - * Code lens has a resolve provider as well. - */ - std::optional resolveProvider; - - static code_lens_options from_json(const nlohmann::json& node) - { - code_lens_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "resolveProvider", res.resolveProvider); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "resolveProvider", resolveProvider); - return json; - } - }; - struct document_link_options - { - std::optional workDoneProgress; - /** - * Code lens has a resolve provider as well. - */ - std::optional resolveProvider; - - static document_link_options from_json(const nlohmann::json& node) - { - document_link_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "resolveProvider", res.resolveProvider); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "resolveProvider", resolveProvider); - return json; - } - }; - struct document_color_registration_options - { - std::optional workDoneProgress; - /** - * A document selector to identify the scope of the registration. If set to null - * the document selector provided on the client side will be used. - */ - std::optional documentSelector; - /** - * The id used to register the request. The id can be used to deregister - * the request again. See also Registration#id. - */ - std::optional id; - - static document_color_registration_options from_json(const nlohmann::json& node) - { - document_color_registration_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "documentSelector", res.documentSelector); - data::from_json(node, "id", res.id); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "documentSelector", documentSelector); - data::set_json(json, "id", id); - return json; - } - }; - struct document_formatting_options - { - std::optional workDoneProgress; - - static document_formatting_options from_json(const nlohmann::json& node) - { - document_formatting_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct document_range_formatting_options - { - std::optional workDoneProgress; - - static document_range_formatting_options from_json(const nlohmann::json& node) - { - document_range_formatting_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct document_on_type_formatting_options { - /** - * A character on which formatting should be triggered, like `}`. - */ - std::string firstTriggerCharacter; - - /** - * More trigger characters. - */ - std::optional> moreTriggerCharacter; - - static document_on_type_formatting_options from_json(const nlohmann::json& node) - { - document_on_type_formatting_options res; - data::from_json(node, "firstTriggerCharacter", res.firstTriggerCharacter); - data::from_json(node, "moreTriggerCharacter", res.moreTriggerCharacter); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "firstTriggerCharacter", firstTriggerCharacter); - data::set_json(json, "moreTriggerCharacter", moreTriggerCharacter); - return json; - } - }; - struct rename_options - { - std::optional workDoneProgress; - /** - * Renames should be checked and tested before being executed. - */ - std::optional prepareProvider; - - static rename_options from_json(const nlohmann::json& node) - { - rename_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "prepareProvider", res.prepareProvider); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "prepareProvider", prepareProvider); - return json; - } - }; - struct folding_range_registration_options - { - std::optional workDoneProgress; - /** - * A document selector to identify the scope of the registration. If set to null - * the document selector provided on the client side will be used. - */ - std::optional documentSelector; - /** - * The id used to register the request. The id can be used to deregister - * the request again. See also Registration#id. - */ - std::optional id; - - static folding_range_registration_options from_json(const nlohmann::json& node) - { - folding_range_registration_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "documentSelector", res.documentSelector); - data::from_json(node, "id", res.id); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "documentSelector", documentSelector); - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - struct execute_command_options - { - std::optional workDoneProgress; - /** - * The commands to be executed on the server. - */ - std::optional> commands; - - static execute_command_options from_json(const nlohmann::json& node) - { - execute_command_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "commands", res.commands); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "commands", commands); - return json; - } - }; - struct selection_range_registration_options - { - std::optional workDoneProgress; - /** - * A document selector to identify the scope of the registration. If set to null - * the document selector provided on the client side will be used. - */ - std::optional documentSelector; - /** - * The id used to register the request. The id can be used to deregister - * the request again. See also Registration#id. - */ - std::optional id; - - static selection_range_registration_options from_json(const nlohmann::json& node) - { - selection_range_registration_options res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - data::from_json(node, "documentSelector", res.documentSelector); - data::from_json(node, "id", res.id); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - data::set_json(json, "documentSelector", documentSelector); - data::set_json(json, "id", id); - return json; - } - }; - struct workspace_folders_server_capabilities - { - /** - * The server has support for workspace folders - */ - std::optional supported; - - /** - * Whether the server wants to receive workspace folder - * change notifications. - * - * If a string is provided, the string is treated as an ID - * under which the notification is registered on the client - * side. The ID can be used to unregister for these events - * using the `client/unregisterCapability` request. - * - * Implementors note: For simplicity (on my end ...), the option to use a string here was removed. - */ - std::optional changeNotifications; - - static workspace_folders_server_capabilities from_json(const nlohmann::json& node) - { - workspace_folders_server_capabilities res; - data::from_json(node, "supported", res.supported); - data::from_json(node, "changeNotifications", res.changeNotifications); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "supported", supported); - data::set_json(json, "changeNotifications", changeNotifications); - return json; - } - }; - struct Workspace - { - /** - * The server supports workspace folder. - * - * @since 3.6.0 - */ - std::optional workspaceFolders; - - static Workspace from_json(const nlohmann::json& node) - { - Workspace res; - data::from_json(node, "workspaceFolders", res.workspaceFolders); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workspaceFolders", workspaceFolders); - return json; - } - }; - /** - * Defines how text documents are synced. Is either a detailed structure defining each notification or - * for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`. - * - * Implementors note: Technically, this should support `TextDocumentSyncOptions | number` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional textDocumentSync; - - /** - * The server provides completion support. - */ - std::optional completionProvider; - - /** - * The server provides hover support. - * - * Implementors note: Technically, this should support `boolean | HoverOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional hoverProvider; - - /** - * The server provides signature help support. - */ - std::optional signatureHelpProvider; - - /** - * The server provides go to declaration support. - * - * @since 3.14.0 - * - * Implementors note: Technically, this should support `boolean | DeclarationOptions | DeclarationRegistrationOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional declarationProvider; - - /** - * The server provides goto definition support. - * - * Implementors note: Technically, this should support `boolean | DefinitionOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional definitionProvider; - - /** - * The server provides goto type definition support. - * - * @since 3.6.0 - * - * Implementors note: Technically, this should support `boolean | TypeDefinitionOptions | TypeDefinitionRegistrationOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional typeDefinitionProvider;; - - /** - * The server provides goto implementation support. - * - * @since 3.6.0 - * - * Implementors note: Technically, this should support `boolean | ImplementationOptions | ImplementationRegistrationOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional implementationProvider; - - /** - * The server provides find references support. - * - * Implementors note: Technically, this should support `boolean | ReferenceOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional referencesProvider; - - /** - * The server provides document highlight support. - * - * Implementors note: Technically, this should support `boolean | DocumentHighlightOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional documentHighlightProvider; - - /** - * The server provides document symbol support. - * - * Implementors note: Technically, this should support `boolean | DocumentSymbolOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional 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`. - * - * Implementors note: Technically, this should support `boolean | CodeActionOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional codeActionProvider; - - /** - * The server provides code lens. - */ - std::optional codeLensProvider; - - /** - * The server provides document link support. - */ - std::optional documentLinkProvider; - - /** - * The server provides color provider support. - * - * @since 3.6.0 - * - * Implementors note: Technically, this should support `boolean | DocumentColorOptions | DocumentColorRegistrationOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional colorProvider; - - /** - * The server provides document formatting. - * - * Implementors note: Technically, this should support `boolean | DocumentFormattingOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional documentFormattingProvider; - - /** - * The server provides document range formatting. - * - * Implementors note: Technically, this should support `boolean | DocumentRangeFormattingOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional documentRangeFormattingProvider; - - /** - * The server provides document formatting on typing. - */ - std::optional documentOnTypeFormattingProvider; - - /** - * The server provides rename support. RenameOptions may only be - * specified if the client states that it supports - * `prepareSupport` in its initial `initialize` request. - * - * Implementors note: Technically, this should support `boolean | RenameOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional renameProvider; - - /** - * The server provides folding provider support. - * - * @since 3.10.0 - * - * Implementors note: Technically, this should support `boolean | FoldingRangeOptions | FoldingRangeRegistrationOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional foldingRangeProvider; - - /** - * The server provides execute command support. - */ - std::optional executeCommandProvider; - - /** - * The server provides selection range support. - * - * @since 3.15.0 - * - * Implementors note: Technically, this should support `boolean | SelectionRangeOptions | SelectionRangeRegistrationOptions` for backwards compatibility ... but we ignore that simply because - * it already is hard enough to provide this shitfest of a protocol. No need to make it even harder to implement - * a server. - */ - std::optional selectionRangeProvider; - - /** - * The server provides workspace symbol support. - */ - std::optional workspaceSymbolProvider; - - /** - * Workspace specific server capabilities - */ - std::optional workspace; - - /** - * Experimental server capabilities. - */ - std::optional experimental; - - static server_capabilities from_json(const nlohmann::json& node) - { - server_capabilities res; - data::from_json(node, "textDocumentSync", res.textDocumentSync); - data::from_json(node, "completionProvider", res.completionProvider); - data::from_json(node, "hoverProvider", res.hoverProvider); - data::from_json(node, "signatureHelpProvider", res.signatureHelpProvider); - data::from_json(node, "declarationProvider", res.declarationProvider); - data::from_json(node, "definitionProvider", res.definitionProvider); - data::from_json(node, "typeDefinitionProvider", res.typeDefinitionProvider); - data::from_json(node, "implementationProvider", res.implementationProvider); - data::from_json(node, "referencesProvider", res.referencesProvider); - data::from_json(node, "documentHighlightProvider", res.documentHighlightProvider); - data::from_json(node, "documentSymbolProvider", res.documentSymbolProvider); - data::from_json(node, "codeActionProvider", res.codeActionProvider); - data::from_json(node, "codeLensProvider", res.codeLensProvider); - data::from_json(node, "documentLinkProvider", res.documentLinkProvider); - data::from_json(node, "colorProvider", res.colorProvider); - data::from_json(node, "documentFormattingProvider", res.documentFormattingProvider); - data::from_json(node, "documentRangeFormattingProvider", res.documentRangeFormattingProvider); - data::from_json(node, "documentOnTypeFormattingProvider", res.documentOnTypeFormattingProvider); - data::from_json(node, "renameProvider", res.renameProvider); - data::from_json(node, "foldingRangeProvider", res.foldingRangeProvider); - data::from_json(node, "executeCommandProvider", res.executeCommandProvider); - data::from_json(node, "selectionRangeProvider", res.selectionRangeProvider); - data::from_json(node, "workspaceSymbolProvider", res.workspaceSymbolProvider); - data::from_json(node, "workspace", res.workspace); - res.experimental = node.contains("experimental") ? node["experimental"] : nlohmann::json(nullptr); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "textDocumentSync", textDocumentSync); - data::set_json(json, "completionProvider", completionProvider); - data::set_json(json, "hoverProvider", hoverProvider); - data::set_json(json, "signatureHelpProvider", signatureHelpProvider); - data::set_json(json, "declarationProvider", declarationProvider); - data::set_json(json, "definitionProvider", definitionProvider); - data::set_json(json, "typeDefinitionProvider", typeDefinitionProvider); - data::set_json(json, "implementationProvider", implementationProvider); - data::set_json(json, "referencesProvider", referencesProvider); - data::set_json(json, "documentHighlightProvider", documentHighlightProvider); - data::set_json(json, "documentSymbolProvider", documentSymbolProvider); - data::set_json(json, "codeActionProvider", codeActionProvider); - data::set_json(json, "codeLensProvider", codeLensProvider); - data::set_json(json, "documentLinkProvider", documentLinkProvider); - data::set_json(json, "colorProvider", colorProvider); - data::set_json(json, "documentFormattingProvider", documentFormattingProvider); - data::set_json(json, "documentRangeFormattingProvider", documentRangeFormattingProvider); - data::set_json(json, "documentOnTypeFormattingProvider", documentOnTypeFormattingProvider); - data::set_json(json, "renameProvider", renameProvider); - data::set_json(json, "foldingRangeProvider", foldingRangeProvider); - data::set_json(json, "executeCommandProvider", executeCommandProvider); - data::set_json(json, "selectionRangeProvider", selectionRangeProvider); - data::set_json(json, "workspaceSymbolProvider", workspaceSymbolProvider); - data::set_json(json, "workspace", workspace); - if (experimental.has_value()) { json["experimental"] = *experimental; } - return json; - } - }; - struct server_info - { - std::string name; - std::optional version; - - static server_info from_json(const nlohmann::json& node) - { - server_info res; - data::from_json(node, "name", res.name); - data::from_json(node, "version", res.version); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "name", name); - data::set_json(json, "version", version); - return json; - } - }; - /** - * The capabilities the language server provides. - */ - server_capabilities capabilities; - - /** - * Information about the server. - * - * @since 3.15.0 - */ - std::optional serverInfo; - - static initialize_result from_json(const nlohmann::json& node) - { - initialize_result res; - data::from_json(node, "capabilities", res.capabilities); - data::from_json(node, "serverInfo", res.serverInfo); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "capabilities", capabilities); - data::set_json(json, "serverInfo", serverInfo); - return json; - } - }; - struct initialize_params - { - struct client_info - { - std::string name; - std::string version; - - static client_info from_json(const nlohmann::json& node) - { - return - { - node["name"], - node["version"] - }; - } - nlohmann::json to_json() const - { - return - { - { "name", name }, - { "version", version } - }; - } - }; - struct workspace_edit_client_capabilities - { - /* - The client supports versioned document changes in `WorkspaceEdit`s - */ - std::optional documentChanges; - /* - The resource operations the client supports. Clients should at least - support 'create', 'rename' and 'delete' files and folders. - - @since 3.13.0 - */ - resource_operations resourceOperations; - /* - The failure handling strategy of a client if applying the workspace edit - fails. - - @since 3.13.0 - */ - failure_handling failureHandling; - - static workspace_edit_client_capabilities from_json(const nlohmann::json& node) - { - workspace_edit_client_capabilities cap; - data::from_json(node, "documentChanges", cap.documentChanges); - data::from_json(node, "resourceOperations", cap.resourceOperations); - data::from_json(node, "failureHandling", cap.failureHandling); - return cap; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "documentChanges", documentChanges); - data::set_json(json, "resourceOperations", resourceOperations); - data::set_json(json, "failureHandling", failureHandling); - return json; - } - }; - struct did_change_configuration_client_capabilities - { - /* - Did change configuration notification supports dynamic registration. - */ - std::optional dynamicRegistration; - - static did_change_configuration_client_capabilities from_json(const nlohmann::json& node) - { - did_change_configuration_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct did_change_watched_files_client_capabilities - { - /* - Did change watched files notification supports dynamic registration. Please note - that the current protocol doesn't support static configuration for file changes - from the server side. - */ - std::optional dynamicRegistration; - - static did_change_watched_files_client_capabilities from_json(const nlohmann::json& node) - { - did_change_watched_files_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct workspace_symbol_client_capabilities - { - struct SymbolKind - { - std::optional> valueSet; - - static SymbolKind from_json(const nlohmann::json& node) - { - SymbolKind res; - data::from_json(node, "valueSet", res.valueSet); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "valueSet", valueSet); - return json; - } - }; - /* - Symbol request supports dynamic registration. - */ - std::optional dynamicRegistration; - /* - Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. - */ - std::optional symbolKind; - - static workspace_symbol_client_capabilities from_json(const nlohmann::json& node) - { - workspace_symbol_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "symbolKind", res.symbolKind); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "symbolKind", symbolKind); - return json; - } - }; - struct execute_command_client_capabilities - { - /* - Execute command supports dynamic registration. - */ - std::optional dynamicRegistration; - - static execute_command_client_capabilities from_json(const nlohmann::json& node) - { - execute_command_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct text_document_sync_client_capabilities - { - /* - Whether text document synchronization supports dynamic registration. - */ - std::optional dynamicRegistration; - - /* - The client supports sending will save notifications. - */ - std::optional willSave; - - /* - The client supports sending a will save request and - waits for a response providing text edits which will - be applied to the document before it is saved. - */ - std::optional willSaveWaitUntil; - - /* - The client supports did save notifications. - */ - std::optional didSave; - - static text_document_sync_client_capabilities from_json(const nlohmann::json& node) - { - text_document_sync_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "willSave", res.willSave); - data::from_json(node, "willSaveWaitUntil", res.willSaveWaitUntil); - data::from_json(node, "didSave", res.didSave); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "willSave", willSave); - data::set_json(json, "willSaveWaitUntil", willSaveWaitUntil); - data::set_json(json, "didSave", didSave); - return json; - } - }; - struct completion_client_capabilities - { - struct CompletionItem - { - struct TagSupport - { - /** - * The tags supported by the client. - */ - std::optional> valueSet; - - static TagSupport from_json(const nlohmann::json& node) - { - TagSupport res; - data::from_json(node, "valueSet", res.valueSet); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "valueSet", valueSet); - return json; - } - }; - /** - * Client supports snippets as insert text. - * - * A snippet can define tab stops and placeholders with `$1`, `$2` - * and `${3:foo}`. `$0` defines the final tab stop, it defaults to - * the end of the snippet. Placeholders with equal identifiers are linked, - * that is typing in one will update others too. - */ - std::optional snippetSupport; - - /** - * Client supports commit characters on a completion item. - */ - std::optional commitCharactersSupport; - - /** - * Client supports the follow content formats for the documentation - * property. The order describes the preferred format of the client. - */ - std::optional> documentationFormat; - - /** - * Client supports the deprecated property on a completion item. - */ - std::optional deprecatedSupport; - - /** - * Client supports the preselect property on a completion item. - */ - std::optional preselectSupport; - - /** - * Client supports the tag property on a completion item. Clients supporting - * tags have to handle unknown tags gracefully. Clients especially need to - * preserve unknown tags when sending a completion item back to the server in - * a resolve call. - * - * @since 3.15.0 - */ - std::optional tagSupport; - - static CompletionItem from_json(const nlohmann::json& node) - { - CompletionItem res; - data::from_json(node, "snippetSupport", res.snippetSupport); - data::from_json(node, "commitCharactersSupport", res.commitCharactersSupport); - data::from_json(node, "documentationFormat", res.documentationFormat); - data::from_json(node, "deprecatedSupport", res.deprecatedSupport); - data::from_json(node, "preselectSupport", res.preselectSupport); - data::from_json(node, "tagSupport", res.tagSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "snippetSupport", snippetSupport); - data::set_json(json, "commitCharactersSupport", commitCharactersSupport); - data::set_json(json, "documentationFormat", documentationFormat); - data::set_json(json, "deprecatedSupport", deprecatedSupport); - data::set_json(json, "preselectSupport", preselectSupport); - data::set_json(json, "tagSupport", tagSupport); - return json; - } - }; - - struct CompletionItemKind { - /* - The completion item 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 completion items kinds from `Text` to `Reference` as defined in - the initial version of the protocol. - */ - std::optional> valueSet; - - static CompletionItemKind from_json(const nlohmann::json& node) - { - CompletionItemKind res; - data::from_json(node, "valueSet", res.valueSet); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "valueSet", valueSet); - return json; - } - }; - /* - The client supports the following `CompletionItem` specific - capabilities. - */ - std::optional dynamicRegistration; - /* - The client supports the following `CompletionItem` specific - capabilities. - */ - std::optional completionItem; - /* - The client supports the following `CompletionItem` specific - capabilities. - */ - std::optional completionItemKind; - /* - The client supports to send additional context information for a - `textDocument/completion` request. - */ - std::optional contextSupport; - - static completion_client_capabilities from_json(const nlohmann::json& node) - { - completion_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "completionItem", res.completionItem); - data::from_json(node, "completionItemKind", res.completionItemKind); - data::from_json(node, "contextSupport", res.contextSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "completionItem", completionItem); - data::set_json(json, "completionItemKind", completionItemKind); - data::set_json(json, "contextSupport", contextSupport); - return json; - } - }; - struct hover_client_capabilities - { - /** - * Whether hover supports dynamic registration. - */ - std::optional dynamicRegistration; - - /** - * Client supports the follow content formats for the content - * property. The order describes the preferred format of the client. - */ - std::optional> contentFormat; - - static hover_client_capabilities from_json(const nlohmann::json& node) - { - hover_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "contentFormat", res.contentFormat); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "contentFormat", contentFormat); - return json; - } - }; - struct signature_help_client_capabilities - { - struct SignatureInformation - { - struct ParameterInformation - { - /** - * The client supports processing label offsets instead of a - * simple label string. - * - * @since 3.14.0 - */ - std::optional labelOffsetSupport; - static ParameterInformation from_json(const nlohmann::json& node) - { - ParameterInformation res; - data::from_json(node, "labelOffsetSupport", res.labelOffsetSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "labelOffsetSupport", labelOffsetSupport); - return json; - } - }; - /** - * Client supports the follow content formats for the documentation - * property. The order describes the preferred format of the client. - */ - std::optional> documentationFormat; - - /** - * Client capabilities specific to parameter information. - */ - std::optional parameterInformation; - - static SignatureInformation from_json(const nlohmann::json& node) - { - SignatureInformation res; - data::from_json(node, "documentationFormat", res.documentationFormat); - data::from_json(node, "parameterInformation", res.parameterInformation); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "documentationFormat", documentationFormat); - data::set_json(json, "parameterInformation", parameterInformation); - return json; - } - }; - /** - * Whether signature help supports dynamic registration. - */ - std::optional dynamicRegistration; - - /** - * The client supports the following `SignatureInformation` - * specific properties. - */ - std::optional signatureInformation; - - /** - * The client supports to send additional context information for a - * `textDocument/signatureHelp` request. A client that opts into - * contextSupport will also support the `retriggerCharacters` on - * `SignatureHelpOptions`. - * - * @since 3.15.0 - */ - std::optional contextSupport; - - static signature_help_client_capabilities from_json(const nlohmann::json& node) - { - signature_help_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "signatureInformation", res.signatureInformation); - data::from_json(node, "contextSupport", res.contextSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "signatureInformation", signatureInformation); - data::set_json(json, "contextSupport", contextSupport); - return json; - } - }; - struct declaration_client_capabilities - { - /** - * Whether declaration supports dynamic registration. If this is set to `true` - * the client supports the new `DeclarationRegistrationOptions` return value - * for the corresponding server capability as well. - */ - std::optional dynamicRegistration; - - /** - * The client supports additional metadata in the form of declaration links. - */ - std::optional linkSupport; - - static declaration_client_capabilities from_json(const nlohmann::json& node) - { - declaration_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "linkSupport", res.linkSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "linkSupport", linkSupport); - return json; - } - }; - struct definition_client_capabilities - { - /** - * Whether definition supports dynamic registration. - */ - std::optional dynamicRegistration; - - /** - * The client supports additional metadata in the form of definition links. - * - * @since 3.14.0 - */ - std::optional linkSupport; - - static definition_client_capabilities from_json(const nlohmann::json& node) - { - definition_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "linkSupport", res.linkSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "linkSupport", linkSupport); - return json; - } - }; - struct type_definition_client_capabilities - { - /** - * Whether implementation supports dynamic registration. If this is set to `true` - * the client supports the new `TypeDefinitionRegistrationOptions` return value - * for the corresponding server capability as well. - */ - std::optional dynamicRegistration; - - /** - * The client supports additional metadata in the form of definition links. - * - * @since 3.14.0 - */ - std::optional linkSupport; - - static type_definition_client_capabilities from_json(const nlohmann::json& node) - { - type_definition_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "linkSupport", res.linkSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "linkSupport", linkSupport); - return json; - } - }; - struct implementation_client_capabilities - { - /** - * Whether implementation supports dynamic registration. If this is set to `true` - * the client supports the new `ImplementationRegistrationOptions` return value - * for the corresponding server capability as well. - */ - std::optional dynamicRegistration; - - /** - * The client supports additional metadata in the form of definition links. - * - * @since 3.14.0 - */ - std::optional linkSupport; - - static implementation_client_capabilities from_json(const nlohmann::json& node) - { - implementation_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "linkSupport", res.linkSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "linkSupport", linkSupport); - return json; - } - }; - struct reference_client_capabilities - { - /** - * Whether references supports dynamic registration. - */ - std::optional dynamicRegistration; - - static reference_client_capabilities from_json(const nlohmann::json& node) - { - reference_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct document_highlight_client_capabilities - { - /** - * Whether document highlight supports dynamic registration. - */ - std::optional dynamicRegistration; - - static document_highlight_client_capabilities from_json(const nlohmann::json& node) - { - document_highlight_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct document_symbol_client_capabilities - { - struct SymbolKind - { - /** - * 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. - */ - std::optional> valueSet; - - static SymbolKind from_json(const nlohmann::json& node) - { - SymbolKind res; - data::from_json(node, "valueSet", res.valueSet); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "valueSet", valueSet); - return json; - } - }; - - /** - * Whether document symbol supports dynamic registration. - */ - std::optional dynamicRegistration; - - /** - * Specific capabilities for the `SymbolKind` in the `textDocument/documentSymbol` request. - */ - std::optional symbolKind; - - /** - * The client supports hierarchical document symbols. - */ - std::optional hierarchicalDocumentSymbolSupport; - - static document_symbol_client_capabilities from_json(const nlohmann::json& node) - { - document_symbol_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "symbolKind", res.symbolKind); - data::from_json(node, "hierarchicalDocumentSymbolSupport", res.hierarchicalDocumentSymbolSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "symbolKind", symbolKind); - data::set_json(json, "hierarchicalDocumentSymbolSupport", hierarchicalDocumentSymbolSupport); - return json; - } - }; - struct code_action_client_capabilities - { - struct CodeActionLiteralSupport - { - /** - * The code action kind is supported with the following value - * set. - */ - struct - { - /** - * The code action 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. - */ - std::vector valueSet; - } codeActionKind; - - static CodeActionLiteralSupport from_json(const nlohmann::json& node) - { - CodeActionLiteralSupport res; - data::from_json(node["codeActionKind"], "valueSet", res.codeActionKind.valueSet); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - json["codeActionKind"] = { "valueSet", data::to_json(codeActionKind.valueSet) }; - return json; - } - }; - /** - * Whether code action supports dynamic registration. - */ - std::optional dynamicRegistration; - - /** - * The client supports code action literals as a valid - * response of the `textDocument/codeAction` request. - * - * @since 3.8.0 - */ - std::optional codeActionLiteralSupport; - - /** - * Whether code action supports the `isPreferred` property. - * @since 3.15.0 - */ - std::optional isPreferredSupport; - - static code_action_client_capabilities from_json(const nlohmann::json& node) - { - code_action_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "codeActionLiteralSupport", res.codeActionLiteralSupport); - data::from_json(node, "isPreferredSupport", res.isPreferredSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "codeActionLiteralSupport", codeActionLiteralSupport); - data::set_json(json, "isPreferredSupport", isPreferredSupport); - return json; - } - }; - struct code_lens_client_capabilities - { - /** - * Whether code lens supports dynamic registration. - */ - std::optional dynamicRegistration; - - static code_lens_client_capabilities from_json(const nlohmann::json& node) - { - code_lens_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct document_link_client_capabilities - { - /** - * Whether document link supports dynamic registration. - */ - std::optional dynamicRegistration; - - /** - * Whether the client supports the `tooltip` property on `DocumentLink`. - * - * @since 3.15.0 - */ - std::optional tooltipSupport; - - static document_link_client_capabilities from_json(const nlohmann::json& node) - { - document_link_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "tooltipSupport", res.tooltipSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "tooltipSupport", tooltipSupport); - return json; - } - }; - struct document_color_client_capabilities - { - /** - * Whether document color supports dynamic registration. - */ - std::optional dynamicRegistration; - - static document_color_client_capabilities from_json(const nlohmann::json& node) - { - document_color_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct document_formatting_client_capabilities - { - /** - * Whether formatting supports dynamic registration. - */ - std::optional dynamicRegistration; - - static document_formatting_client_capabilities from_json(const nlohmann::json& node) - { - document_formatting_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct document_range_formatting_client_capabilities - { - /** - * Whether formatting supports dynamic registration. - */ - std::optional dynamicRegistration; - - static document_range_formatting_client_capabilities from_json(const nlohmann::json& node) - { - document_range_formatting_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct document_on_type_formatting_client_capabilities - { - /** - * Whether on type formatting supports dynamic registration. - */ - std::optional dynamicRegistration; - - static document_on_type_formatting_client_capabilities from_json(const nlohmann::json& node) - { - document_on_type_formatting_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - struct rename_client_capabilities - { - /** - * Whether rename supports dynamic registration. - */ - std::optional dynamicRegistration; - - /** - * Client supports testing for validity of rename operations - * before execution. - * - * @since version 3.12.0 - */ - std::optional prepareSupport; - - static rename_client_capabilities from_json(const nlohmann::json& node) - { - rename_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "prepareSupport", res.prepareSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "prepareSupport", prepareSupport); - return json; - } - }; - struct publish_diagnostics_client_capabilities - { - struct TagSupport - { - /** - * The tags supported by the client. - */ - std::optional> valueSet; - - static TagSupport from_json(const nlohmann::json& node) - { - TagSupport res; - data::from_json(node, "valueSet", res.valueSet); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "valueSet", valueSet); - return json; - } - }; - /** - * Whether the clients accepts diagnostics with related information. - */ - std::optional relatedInformation; - - /** - * Client supports the tag property to provide meta data about a diagnostic. - * Clients supporting tags have to handle unknown tags gracefully. - * - * @since 3.15.0 - */ - std::optional tagSupport; - - /** - * Whether the client interprets the version property of the - * `textDocument/publishDiagnostics` notification's parameter. - * - * @since 3.15.0 - */ - std::optional versionSupport; - - static publish_diagnostics_client_capabilities from_json(const nlohmann::json& node) - { - publish_diagnostics_client_capabilities res; - data::from_json(node, "relatedInformation", res.relatedInformation); - data::from_json(node, "tagSupport", res.tagSupport); - data::from_json(node, "versionSupport", res.versionSupport); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "relatedInformation", relatedInformation); - data::set_json(json, "tagSupport", tagSupport); - data::set_json(json, "versionSupport", versionSupport); - return json; - } - }; - struct folding_range_client_capabilities - { - /** - * Whether implementation supports dynamic registration for folding range providers. If this is set to `true` - * the client supports the new `FoldingRangeRegistrationOptions` return value for the corresponding server - * capability as well. - */ - std::optional dynamicRegistration; - /** - * 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. - */ - std::optional 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. - */ - std::optional lineFoldingOnly; - - static folding_range_client_capabilities from_json(const nlohmann::json& node) - { - folding_range_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - data::from_json(node, "rangeLimit", res.rangeLimit); - data::from_json(node, "lineFoldingOnly", res.lineFoldingOnly); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - data::set_json(json, "rangeLimit", rangeLimit); - data::set_json(json, "lineFoldingOnly", lineFoldingOnly); - return json; - } - }; - struct selection_range_client_capabilities - { - /** - * Whether implementation supports dynamic registration for selection range providers. If this is set to `true` - * the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server - * capability as well. - */ - std::optional dynamicRegistration; - - static selection_range_client_capabilities from_json(const nlohmann::json& node) - { - selection_range_client_capabilities res; - data::from_json(node, "dynamicRegistration", res.dynamicRegistration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "dynamicRegistration", dynamicRegistration); - return json; - } - }; - /* - Text document specific client capabilities. - */ - struct text_document_client_capabilities - { - std::optional synchronization; - - /** - Capabilities specific to the `textDocument/completion` request. - */ - std::optional completion; - - /** - Capabilities specific to the `textDocument/hover` request. - */ - std::optional hover; - - /** - Capabilities specific to the `textDocument/signatureHelp` request. - */ - std::optional signatureHelp; - - /** - Capabilities specific to the `textDocument/declaration` request. - - @since 3.14.0 - */ - std::optional declaration; - - /** - Capabilities specific to the `textDocument/definition` request. - */ - std::optional definition; - - /** - Capabilities specific to the `textDocument/typeDefinition` request. - - @since 3.6.0 - */ - std::optional typeDefinition; - - /** - Capabilities specific to the `textDocument/implementation` request. - - @since 3.6.0 - */ - std::optional implementation; - - /** - Capabilities specific to the `textDocument/references` request. - */ - std::optional references; - - /** - Capabilities specific to the `textDocument/documentHighlight` request. - */ - std::optional documentHighlight; - - /** - Capabilities specific to the `textDocument/documentSymbol` request. - */ - std::optional documentSymbol; - - /** - Capabilities specific to the `textDocument/codeAction` request. - */ - std::optional codeAction; - - /** - Capabilities specific to the `textDocument/codeLens` request. - */ - std::optional codeLens; - - /** - Capabilities specific to the `textDocument/documentLink` request. - */ - std::optional documentLink; - - /** - Capabilities specific to the `textDocument/documentColor` and the - `textDocument/colorPresentation` request. - - @since 3.6.0 - */ - std::optional colorProvider; - - /** - Capabilities specific to the `textDocument/formatting` request. - */ - std::optional formatting; - - /** - Capabilities specific to the `textDocument/rangeFormatting` request. - */ - std::optional rangeFormatting; - - /** request. - Capabilities specific to the `textDocument/onTypeFormatting` request. - */ - std::optional onTypeFormatting; - - /** - Capabilities specific to the `textDocument/rename` request. - */ - std::optional rename; - - /** - Capabilities specific to the `textDocument/publishDiagnostics` notification. - */ - std::optional publishDiagnostics; - - /** - Capabilities specific to the `textDocument/foldingRange` request. - - @since 3.10.0 - */ - std::optional foldingRange; - - /** - Capabilities specific to the `textDocument/selectionRange` request. - - @since 3.15.0 - */ - std::optional selectionRange; - - static text_document_client_capabilities from_json(const nlohmann::json& node) - { - text_document_client_capabilities res; - data::from_json(node, "synchronization", res.synchronization); - data::from_json(node, "completion", res.completion); - data::from_json(node, "hover", res.hover); - data::from_json(node, "signatureHelp", res.signatureHelp); - data::from_json(node, "declaration", res.declaration); - data::from_json(node, "definition", res.definition); - data::from_json(node, "typeDefinition", res.typeDefinition); - data::from_json(node, "implementation", res.implementation); - data::from_json(node, "references", res.references); - data::from_json(node, "documentHighlight", res.documentHighlight); - data::from_json(node, "documentSymbol", res.documentSymbol); - data::from_json(node, "codeAction", res.codeAction); - data::from_json(node, "codeLens", res.codeLens); - data::from_json(node, "documentLink", res.documentLink); - data::from_json(node, "colorProvider", res.colorProvider); - data::from_json(node, "formatting", res.formatting); - data::from_json(node, "rangeFormatting", res.rangeFormatting); - data::from_json(node, "onTypeFormatting", res.onTypeFormatting); - data::from_json(node, "rename", res.rename); - data::from_json(node, "publishDiagnostics", res.publishDiagnostics); - data::from_json(node, "foldingRange", res.foldingRange); - data::from_json(node, "selectionRange", res.selectionRange); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "synchronization", synchronization); - data::set_json(json, "completion", completion); - data::set_json(json, "hover", hover); - data::set_json(json, "signatureHelp", signatureHelp); - data::set_json(json, "declaration", declaration); - data::set_json(json, "definition", definition); - data::set_json(json, "typeDefinition", typeDefinition); - data::set_json(json, "implementation", implementation); - data::set_json(json, "references", references); - data::set_json(json, "documentHighlight", documentHighlight); - data::set_json(json, "documentSymbol", documentSymbol); - data::set_json(json, "codeAction", codeAction); - data::set_json(json, "codeLens", codeLens); - data::set_json(json, "documentLink", documentLink); - data::set_json(json, "colorProvider", colorProvider); - data::set_json(json, "formatting", formatting); - data::set_json(json, "rangeFormatting", rangeFormatting); - data::set_json(json, "onTypeFormatting", onTypeFormatting); - data::set_json(json, "rename", rename); - data::set_json(json, "publishDiagnostics", publishDiagnostics); - data::set_json(json, "foldingRange", foldingRange); - data::set_json(json, "selectionRange", selectionRange); - return json; - } - }; - struct client_capabilities - { - struct Workspace - { - /* - The client supports applying batch edits - to the workspace by supporting the request - 'workspace/applyEdit' - */ - std::optional applyEdit; - /* - Capabilities specific to `WorkspaceEdit`s - */ - std::optional workspaceEdit; - /* - Capabilities specific to the `workspace/didChangeConfiguration` notification. - */ - std::optional didChangeConfiguration; - /* - Capabilities specific to the `workspace/didChangeWatchedFiles` notification. - */ - std::optional didChangeWatchedFiles; - /* - Capabilities specific to the `workspace/symbol` request. - */ - std::optional symbol; - /* - Capabilities specific to the `workspace/executeCommand` request. - */ - std::optional executeCommand; - /* - The client has support for workspace folders. - - Since 3.6.0 - */ - std::optional workspaceFolders; - /* - The client supports `workspace/configuration` requests. - - Since 3.6.0 - */ - std::optional configuration; - - static Workspace from_json(const nlohmann::json& node) - { - Workspace res; - data::from_json(node, "applyEdit", res.applyEdit); - data::from_json(node, "workspaceEdit", res.workspaceEdit); - data::from_json(node, "didChangeConfiguration", res.didChangeConfiguration); - data::from_json(node, "didChangeWatchedFiles", res.didChangeWatchedFiles); - data::from_json(node, "symbol", res.symbol); - data::from_json(node, "executeCommand", res.executeCommand); - data::from_json(node, "workspaceFolders", res.workspaceFolders); - data::from_json(node, "configuration", res.configuration); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "applyEdit", applyEdit); - data::set_json(json, "workspaceEdit", workspaceEdit); - data::set_json(json, "didChangeConfiguration", didChangeConfiguration); - data::set_json(json, "didChangeWatchedFiles", didChangeWatchedFiles); - data::set_json(json, "symbol", symbol); - data::set_json(json, "executeCommand", executeCommand); - data::set_json(json, "workspaceFolders", workspaceFolders); - data::set_json(json, "configuration", configuration); - return json; - } - }; - struct Window - { - /* - Whether client supports handling progress notifications. If set servers are allowed to - report in `workDoneProgress` property in the request specific server capabilities. - - Since 3.15.0 - */ - std::optional workDoneProgress; - - static Window from_json(const nlohmann::json& node) - { - Window res; - data::from_json(node, "workDoneProgress", res.workDoneProgress); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workDoneProgress", workDoneProgress); - return json; - } - }; - /* - Workspace specific client capabilities. - */ - std::optional workspace; - /* - Text document specific client capabilities. - */ - std::optional textDocument; - /* - Experimental client capabilities. - */ - std::optional experimental; - - static client_capabilities from_json(const nlohmann::json& node) - { - client_capabilities res; - data::from_json(node, "workspace", res.workspace); - data::from_json(node, "textDocument", res.textDocument); - res.experimental = node.contains("experimental") ? node["experimental"] : nlohmann::json(nullptr); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "workspace", workspace); - data::set_json(json, "textDocument", textDocument); - if (experimental.has_value()) { json["experimental"] = *experimental; } - return json; - } - }; - struct workspace_folder - { - /* - 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. - */ - std::string name; - - static workspace_folder from_json(const nlohmann::json& node) - { - workspace_folder res; - data::from_json(node, "uri", res.uri); - data::from_json(node, "name", res.name); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "uri", uri); - data::set_json(json, "name", name); - return json; - } - }; - /* - The process Id of the parent process that started - the server. Is null if the process has not been started by another process. - If the parent process is not alive then the server should exit (see exit notification) its process. - */ - std::optional processId; - /* - Information about the client - - @since 3.15.0 - */ - client_info clientInfo; - /* - The rootPath of the workspace. Is null - if no folder is open. - - @deprecated in favour of rootUri. - */ - std::optional rootPath; - /* - The rootUri of the workspace. Is null if no - folder is open. If both `rootPath` and `rootUri` are set - `rootUri` wins. - */ - std::optional rootUri; - /* - User provided initialization options. - */ - nlohmann::json initializationOptions; - /* - The capabilities provided by the client (editor or tool) - */ - client_capabilities capabilities; - /* - The initial trace setting. If omitted trace is disabled ('off'). - */ - trace_mode trace; - /** - 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. - - @since 3.6.0 - */ - std::optional> workspaceFolders; - - static initialize_params from_json(const nlohmann::json& node) - { - initialize_params res; - data::from_json(node, "processId", res.processId); - data::from_json(node, "clientInfo", res.clientInfo); - data::from_json(node, "rootPath", res.rootPath); - data::from_json(node, "rootUri", res.rootUri); - res.initializationOptions = node.contains("initializationOptions") ? node["initializationOptions"] : nlohmann::json(nullptr); - data::from_json(node, "capabilities", res.capabilities); - data::from_json(node, "trace", res.trace); - data::from_json(node, "workspaceFolders", res.workspaceFolders); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "processId", processId); - data::set_json(json, "clientInfo", clientInfo); - data::set_json(json, "rootPath", rootPath); - data::set_json(json, "rootUri", rootUri); - json["initializationOptions"] = initializationOptions; - data::set_json(json, "capabilities", capabilities); - data::set_json(json, "trace", trace); - data::set_json(json, "workspaceFolders", workspaceFolders); - return json; - } - }; - struct did_open_text_document_params - { - /** - * The document that was opened. - */ - text_document_item textDocument; - static did_open_text_document_params from_json(const nlohmann::json& node) - { - did_open_text_document_params res; - data::from_json(node, "textDocument", res.textDocument); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "textDocument", textDocument); - return json; - } - }; - struct did_change_text_document_params - { - struct text_document_content_change_event - { - /** - * The range of the document that changed. - */ - std::optional range; - - /** - * The optional length of the range that got replaced. - * - * @deprecated use range instead. - */ - std::optional rangeLength; - - /** - * The new text for the provided range. - */ - std::string text; - - static text_document_content_change_event from_json(const nlohmann::json& node) - { - text_document_content_change_event res; - data::from_json(node, "range", res.range); - data::from_json(node, "rangeLength", res.rangeLength); - data::from_json(node, "text", res.text); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "range", range); - data::set_json(json, "rangeLength", rangeLength); - data::set_json(json, "text", text); - return json; - } - }; - /** - * The document that did change. The version number points - * to the version after all provided content changes have - * been applied. - */ - versioned_text_document_identifier 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 recevie them. - * - apply the `TextDocumentContentChangeEvent`s in a single notification in the order - * you receive them. - */ - std::vector contentChanges; - - static did_change_text_document_params from_json(const nlohmann::json& node) - { - did_change_text_document_params res; - data::from_json(node, "textDocument", res.textDocument); - data::from_json(node, "contentChanges", res.contentChanges); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "textDocument", textDocument); - data::set_json(json, "contentChanges", contentChanges); - return json; - } - }; - struct will_save_text_document_params - { - /** - * The document that will be saved. - */ - text_document_identifier textDocument; - - /** - * The 'TextDocumentSaveReason'. - */ - text_document_save_reason reason; - - static will_save_text_document_params from_json(const nlohmann::json& node) - { - will_save_text_document_params res; - data::from_json(node, "textDocument", res.textDocument); - data::from_json(node, "reason", res.reason); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "textDocument", textDocument); - data::set_json(json, "reason", reason); - return json; - } - }; - struct did_save_text_document_params - { - /** - * The document that was saved. - */ - text_document_identifier textDocument; - - /** - * Optional the content when saved. Depends on the includeText value - * when the save notification was requested. - */ - std::optional text; - static did_save_text_document_params from_json(const nlohmann::json& node) - { - did_save_text_document_params res; - data::from_json(node, "textDocument", res.textDocument); - data::from_json(node, "text", res.text); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "textDocument", textDocument); - data::set_json(json, "text", text); - return json; - } - }; - struct did_close_text_document_params - { - /** - * The document that was closed. - */ - text_document_identifier textDocument; - static did_close_text_document_params from_json(const nlohmann::json& node) - { - did_close_text_document_params res; - data::from_json(node, "textDocument", res.textDocument); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "textDocument", textDocument); - return json; - } - }; - struct completion_params - { - struct CompletionContext - { - /** - * How the completion was triggered. - */ - completion_trigger_kind triggerKind; - - /** - * The trigger character (a single character) that has trigger code complete. - * Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` - */ - std::optional triggerCharacter; - - static CompletionContext from_json(const nlohmann::json& node) - { - CompletionContext res; - data::from_json(node, "triggerKind", res.triggerKind); - data::from_json(node, "triggerCharacter", res.triggerCharacter); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "triggerKind", triggerKind); - data::set_json(json, "triggerCharacter", triggerCharacter); - return json; - } - }; - /** - * An optional token that a server can use to report partial results (e.g. streaming) to - * the client. - */ - std::optional partialResultToken; - /** - * An optional token that a server can use to report work done progress. - */ - std::optional workDoneToken; - /** - * The text document. - */ - text_document_identifier textDocument; - - /** - * The position inside the text document. - */ - position position; - /** - * The completion context. This is only available if the client specifies - * to send this using `ClientCapabilities.textDocument.completion.contextSupport === true` - */ - std::optional context; - - static completion_params from_json(const nlohmann::json& node) - { - completion_params res; - data::from_json(node, "partialResultToken", res.partialResultToken); - data::from_json(node, "workDoneToken", res.workDoneToken); - data::from_json(node, "textDocument", res.textDocument); - data::from_json(node, "position", res.position); - data::from_json(node, "context", res.context); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "partialResultToken", partialResultToken); - data::set_json(json, "workDoneToken", workDoneToken); - data::set_json(json, "textDocument", textDocument); - data::set_json(json, "position", position); - data::set_json(json, "context", context); - return json; - } - }; - struct publish_diagnostics_params - { - /** - * The URI for which diagnostic information is reported. - */ - uri uri; - - /** - * Optional the version number of the document the diagnostics are published for. - * - * @since 3.15.0 - */ - std::optional version; - - /** - * An array of diagnostic information items. - */ - std::vector diagnostics; - - static publish_diagnostics_params from_json(const nlohmann::json& node) - { - publish_diagnostics_params res; - data::from_json(node, "uri", res.uri); - data::from_json(node, "version", res.version); - data::from_json(node, "diagnostics", res.diagnostics); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "uri", uri); - data::set_json(json, "version", version); - data::set_json(json, "diagnostics", diagnostics); - return json; - } - }; - struct folding_range_params - { - /** - * An optional token that a server can use to report partial results (e.g. streaming) to - * the client. - */ - std::optional partialResultToken; - /** - * An optional token that a server can use to report work done progress. - */ - std::optional workDoneToken; - /** - * The text document. - */ - text_document_identifier textDocument; - - static folding_range_params from_json(const nlohmann::json& node) - { - folding_range_params res; - data::from_json(node, "partialResultToken", res.partialResultToken); - data::from_json(node, "workDoneToken", res.workDoneToken); - data::from_json(node, "textDocument", res.textDocument); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "partialResultToken", partialResultToken); - data::set_json(json, "workDoneToken", workDoneToken); - data::set_json(json, "textDocument", textDocument); - return json; - } - }; - - /** - * Represents a color in RGBA space. - */ - struct color { - - /** - * The red component of this color in the range [0-1]. - */ - float red; - - /** - * The green component of this color in the range [0-1]. - */ - float green; - - /** - * The blue component of this color in the range [0-1]. - */ - float blue; - - /** - * The alpha component of this color in the range [0-1]. - */ - float alpha; - - static color from_json(const nlohmann::json& node) - { - color res; - data::from_json(node, "red", res.red); - data::from_json(node, "green", res.green); - data::from_json(node, "blue", res.blue); - data::from_json(node, "alpha", res.alpha); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "red", red); - data::set_json(json, "green", green); - data::set_json(json, "blue", blue); - data::set_json(json, "alpha", alpha); - return json; - } - }; - struct color_information { - /** - * The range in the document where this color appears. - */ - range range; - - /** - * The actual color value for this color range. - */ - color color; - - static color_information from_json(const nlohmann::json& node) - { - color_information res; - data::from_json(node, "range", res.range); - data::from_json(node, "color", res.color); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "range", range); - data::set_json(json, "color", color); - return json; - } - }; - struct document_color_params { - /** - * An optional token that a server can use to report partial results (e.g. streaming) to - * the client. - */ - std::optional partialResultToken; - /** - * An optional token that a server can use to report work done progress. - */ - std::optional workDoneToken; - /** - * The text document. - */ - text_document_identifier textDocument; - - static document_color_params from_json(const nlohmann::json& node) - { - document_color_params res; - data::from_json(node, "partialResultToken", res.partialResultToken); - data::from_json(node, "workDoneToken", res.workDoneToken); - data::from_json(node, "textDocument", res.textDocument); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "partialResultToken", partialResultToken); - data::set_json(json, "workDoneToken", workDoneToken); - data::set_json(json, "textDocument", textDocument); - return json; - } - }; - struct did_change_configuration_params - { - /** - * The actual changed settings - */ - std::optional settings; - - static did_change_configuration_params from_json(const nlohmann::json& node) - { - did_change_configuration_params res; - res.settings = node.contains("settings") ? node["settings"] : nlohmann::json(nullptr); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - if (settings.has_value()) { json["settings"] = *settings; } - return json; - } - }; - struct color_presentation { - /** - * The label of this color presentation. It will be shown on the color - * picker header. By default this is also the text that is inserted when selecting - * this color presentation. - */ - std::string label; - /** - * An [edit](#TextEdit) which is applied to a document when selecting - * this presentation for the color. When `falsy` the [label](#ColorPresentation.label) - * is used. - */ - std::optional textEdit; - /** - * An optional array of additional [text edits](#TextEdit) that are applied when - * selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves. - */ - std::optional> additionalTextEdits; - - static color_presentation from_json(const nlohmann::json& node) - { - color_presentation res; - data::from_json(node, "label", res.label); - data::from_json(node, "textEdit", res.textEdit); - data::from_json(node, "additionalTextEdits", res.additionalTextEdits); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "label", label); - data::set_json(json, "textEdit", textEdit); - data::set_json(json, "additionalTextEdits", additionalTextEdits); - return json; - } - }; - struct color_presentation_params { - /** - * An optional token that a server can use to report partial results (e.g. streaming) to - * the client. - */ - std::optional partialResultToken; - /** - * An optional token that a server can use to report work done progress. - */ - std::optional workDoneToken; - /** - * The text document. - */ - text_document_identifier textDocument; - - /** - * The color information to request presentations for. - */ - color color; - - /** - * The range where the color would be inserted. Serves as a context. - */ - range range; - - static color_presentation_params from_json(const nlohmann::json& node) - { - color_presentation_params res; - data::from_json(node, "partialResultToken", res.partialResultToken); - data::from_json(node, "workDoneToken", res.workDoneToken); - data::from_json(node, "textDocument", res.textDocument); - data::from_json(node, "color", res.color); - data::from_json(node, "range", res.range); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "partialResultToken", partialResultToken); - data::set_json(json, "workDoneToken", workDoneToken); - data::set_json(json, "textDocument", textDocument); - data::set_json(json, "color", color); - data::set_json(json, "range", range); - return json; - } - }; - - struct log_message_params { - /** - * The message type. See {@link MessageType} - */ - message_type type; - - /** - * The actual message - */ - std::string message; - - static log_message_params from_json(const nlohmann::json& node) - { - log_message_params res; - data::from_json(node, "type", res.type); - data::from_json(node, "message", res.message); - return res; - } - nlohmann::json to_json() const - { - nlohmann::json json; - data::set_json(json, "type", type); - data::set_json(json, "message", message); - return json; - } - }; - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class server - { - - private: - bool m_die; - public: - jsonrpc rpc; - server() : rpc(std::cin, std::cout, jsonrpc::detach, jsonrpc::skip), m_die(false) - { - rpc.register_method("initialize", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::initialize_params::from_json(msg.params.value()); - auto res = on_initialize(params); - rpc.send({ msg.id, res.to_json() }); - after_initialize(params); - }); - rpc.register_method("shutdown", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - kill(); - on_shutdown(); - }); - rpc.register_method("textDocument/didOpen", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::did_open_text_document_params::from_json(msg.params.value()); - on_textDocument_didOpen(params); - }); - rpc.register_method("textDocument/didChange", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::did_change_text_document_params::from_json(msg.params.value()); - on_textDocument_didChange(params); - }); - rpc.register_method("textDocument/willSave", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::will_save_text_document_params::from_json(msg.params.value()); - on_textDocument_willSave(params); - }); - rpc.register_method("textDocument/willSaveWaitUntil", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::will_save_text_document_params::from_json(msg.params.value()); - auto res = on_textDocument_willSaveWaitUntil(params); - rpc.send({ msg.id, res.has_value() ? res->to_json() : nlohmann::json(nullptr) }); - }); - rpc.register_method("textDocument/didSave", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::did_save_text_document_params::from_json(msg.params.value()); - on_textDocument_didSave(params); - }); - rpc.register_method("textDocument/didClose", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::did_close_text_document_params::from_json(msg.params.value()); - on_textDocument_didClose(params); - }); - rpc.register_method("textDocument/completion", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::completion_params::from_json(msg.params.value()); - auto res = on_textDocument_completion(params); - rpc.send({ msg.id, res.has_value() ? res->to_json() : nlohmann::json(nullptr) }); - }); - rpc.register_method("textDocument/foldingRange", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::folding_range_params::from_json(msg.params.value()); - auto res = on_textDocument_foldingRange(params); - rpc.send({ msg.id, res.has_value() ? to_json(*res) : nlohmann::json(nullptr) }); - }); - rpc.register_method("textDocument/documentColor", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::document_color_params::from_json(msg.params.value()); - auto res = on_textDocument_documentColor(params); - rpc.send({ msg.id, to_json(res) }); - }); - rpc.register_method("textDocument/colorPresentation", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::color_presentation_params::from_json(msg.params.value()); - auto res = on_textDocument_colorPresentation(params); - rpc.send({ msg.id, to_json(res) }); - }); - rpc.register_method("workspace/didChangeConfiguration", - [&](jsonrpc& rpc, const jsonrpc::rpcmessage& msg) - { - auto params = data::did_change_configuration_params::from_json(msg.params.value()); - on_workspace_didChangeConfiguration(params); - }); - } - - void listen() - { - while (!m_die) - { - if (!rpc.handle_single_message()) - { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - continue; - } - } - } - - void kill() { m_die = true; } - - // Methods that must be overriden by clients - protected: - virtual lsp::data::initialize_result on_initialize(const lsp::data::initialize_params& params) = 0; - virtual void after_initialize(const lsp::data::initialize_params& params) {} - virtual void on_shutdown() = 0; - - - // Methods that can be overriden by implementing clients - protected: - virtual void on_textDocument_didOpen(const lsp::data::did_open_text_document_params& params) {} - virtual void on_textDocument_didChange(const lsp::data::did_change_text_document_params& params) {} - virtual void on_textDocument_willSave(const lsp::data::will_save_text_document_params& params) {} - virtual std::optional on_textDocument_willSaveWaitUntil(const lsp::data::will_save_text_document_params& params) { return {}; } - virtual void on_textDocument_didSave(const lsp::data::did_save_text_document_params& params) {} - virtual void on_textDocument_didClose(const lsp::data::did_close_text_document_params& params) {} - virtual std::optional on_textDocument_completion(const lsp::data::completion_params& params) { return {}; } - virtual std::optional> on_textDocument_foldingRange(const lsp::data::folding_range_params& params) { return {}; } - virtual std::vector on_textDocument_documentColor(const lsp::data::document_color_params& params) { return {}; } - virtual std::vector on_textDocument_colorPresentation(const lsp::data::color_presentation_params& params) { return {}; } - virtual void on_workspace_didChangeConfiguration(const lsp::data::did_change_configuration_params& params) {} - - public: - void textDocument_publishDiagnostics(const lsp::data::publish_diagnostics_params& params) { rpc.send({ {}, "textDocument/publishDiagnostics", params.to_json() }); } - void window_logMessage(lsp::data::message_type type, std::string message) { rpc.send({ {}, "window/logMessage", lsp::data::log_message_params{ type, message }.to_json() }); } - void window_logMessage(const lsp::data::log_message_params& params) { rpc.send({ {}, "window/logMessage", params.to_json() }); } - }; -} \ No newline at end of file diff --git a/server/src/main.cpp b/server/src/main.cpp deleted file mode 100644 index 266cc19..0000000 --- a/server/src/main.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "sqf_language_server.h" - - -int main(int argc, char** argv) -{ -#ifdef _DEBUG - _CrtDbgReport(_CRT_ASSERT, "", 0, "", "Waiting for vs."); -#endif // _DEBUG - - // x39::uri a("aba:///aba:aba@aba:aba/aba?aba#aba"); - // x39::uri b("aba:///aba:aba@aba:aba?aba#aba"); - // x39::uri c("aba:///aba:aba@aba?aba#aba"); - // x39::uri d("aba:///aba@aba?aba#aba"); - // x39::uri e("aba:///aba?aba#aba"); - // x39::uri f("aba:///aba?aba"); - // x39::uri g("aba:///aba"); - // x39::uri h("file:///D%3A/Git/Sqfvm/vm/tests/"); - // x39::uri i("file:///c%3A/%40X39/vscode/clients/vscode/sample/sample.sqf"); - // x39::uri j("https://www.google.com/search?rlz=1C1CHBF_deDE910DE910&sxsrf=ALeKk02J_XcmnGpP0UfYPa2S-usVtUnZXw%3A1597937338384&ei=upY-X4TzFpHikgWc7pXwBQ&q=file%3A%2F%2F%2FD%3A%2Fasdasd"); - sqf_language_server ls; - ls.listen(); - return 0; -} \ No newline at end of file diff --git a/server/src/sqf_language_server.cpp b/server/src/sqf_language_server.cpp deleted file mode 100644 index 9d78709..0000000 --- a/server/src/sqf_language_server.cpp +++ /dev/null @@ -1,326 +0,0 @@ -#include "sqf_language_server.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include - -void sqf_language_server::scan_documents_recursive_at(std::string directory) -{ - std::filesystem::recursive_directory_iterator dir_end; - std::vector files; - for (std::filesystem::recursive_directory_iterator it(directory, std::filesystem::directory_options::skip_permission_denied); it != dir_end; it++) - { - auto path = it->path(); - if (it->is_directory() || !path.has_extension()) - { - continue; - } - else - { - files.push_back(path); - } - } - - // Lazy "Max-Files-per-Thread" - size_t files_per_thread = 0; - size_t thread_count; - do - { - files_per_thread += 20; - thread_count = files.size() / files_per_thread; - } while (thread_count > 12); - { - std::stringstream sstream; - sstream << "Launching " << thread_count << " worker threads to handle parsing the whole project."; - window_logMessage(lsp::data::message_type::Log, sstream.str()); - } - std::vector threads; - auto start = files.begin(); - // First round, Globals might not be known here correctly - for (size_t i = 0; i < thread_count + 1; i++) - { - const auto localstart = start; - start += i == thread_count ? files.size() % files_per_thread : files_per_thread; - const auto localend = start; - threads.emplace_back([this, localstart, localend, i]() - { - size_t files = localend - localstart; - for (auto it = localstart; it < localend; ++it) - { - auto path = *it; - auto uri = sanitize_to_uri(path.string()); - auto fpath = sanitize_to_string(uri); - if (path.extension() == ".sqf") - { - text_documents[fpath] = { *this, sqfvm, fpath, text_document::document_type::SQF }; - std::stringstream sstream; - sstream << "[WORKER-" << i << "][" << std::setw(3) << (it - localstart) << "/" << std::setw(3) << files << "] Analyzing " << fpath << " ... "; - window_logMessage(lsp::data::message_type::Log, sstream.str()); - text_documents[fpath].analyze(*this, sqfvm, {}); - } - else - { - std::stringstream sstream; - sstream << "[WORKER-" << i << "][" << std::setw(3) << (it - localstart) << "/" << std::setw(3) << files << "] Skipping " << fpath << "."; - window_logMessage(lsp::data::message_type::Log, sstream.str()); - } - } - }); - } - for (size_t i = 0; i < threads.size(); i++) - { - if (threads[i].joinable()) - { - threads[i].join(); - } - } -} - -void sqf_language_server::after_initialize(const lsp::data::initialize_params& params) -{ - // Prepare sqfvm - sqfvm.fileio(std::make_unique(logger)); - sqfvm.parser_config(std::make_unique(logger)); - sqfvm.parser_preprocessor(std::make_unique(logger)); - sqfvm.parser_sqf(std::make_unique(logger)); - sqf::operators::ops(sqfvm); - - // Setup Pathing & Parse every file inside the workspace - if (params.workspaceFolders.has_value()) - { - for (auto workspaceFolder : params.workspaceFolders.value()) - { - auto workspacePath = sanitize_to_string(workspaceFolder.uri); - m_workspace_folders.push_back(workspacePath); - if (!std::filesystem::exists(workspacePath)) - { - std::stringstream sstream; - sstream << "Cannot analyze workspace folder " << workspacePath << " as it is not existing."; - window_logMessage(lsp::data::message_type::Error, sstream.str()); - continue; - } - { - std::stringstream sstream; - sstream << "Mapping " << workspacePath << " onto '/'"; - window_logMessage(lsp::data::message_type::Log, sstream.str()); - sqfvm.fileio().add_mapping(workspacePath, "/"); - } - std::filesystem::recursive_directory_iterator dir_end; - size_t sqf_files_total = 0; - // Read-In all $PBOPREFIX$ files - for (std::filesystem::recursive_directory_iterator it(workspacePath, std::filesystem::directory_options::skip_permission_denied); it != dir_end; it++) - { - auto path = it->path(); - if (!it->is_directory() && !path.has_extension() && path.filename() == "$PBOPREFIX$") - { - // ToDo: If modified, remove existing mapping and reparse as whole. (or message the user that a reload is required) - // ToDo: If created, add mapping and reparse as whole. (or message the user that a reload is required) - auto pboprefix_path = path.parent_path().string(); - auto pboprefix_contents_o = sqfvm.fileio().read_file_from_disk(path.string()); - if (pboprefix_contents_o.has_value()) - { - auto pboprefix_contents = pboprefix_contents_o.value(); - add_mapping_to_sqf_vm(pboprefix_path, pboprefix_contents); - } - else - { - std::stringstream sstream; - sstream << "Failed to read " << pboprefix_path << ". Skipping."; - window_logMessage(lsp::data::message_type::Error, sstream.str()); - } - } - else if (path.has_extension() && path.extension() == ".sqf") - { - sqf_files_total++; - } - } - // Scan twice to "fix" undeclared gloabls - scan_documents_recursive_at(workspacePath); - scan_documents_recursive_at(workspacePath); - } - } - window_logMessage(lsp::data::message_type::Log, "SQF-VM Language Server is ready."); -} - -void sqf_language_server::on_workspace_didChangeConfiguration(const lsp::data::did_change_configuration_params& params) -{ - if (params.settings.has_value()) - { - m_sqc_support = (*params.settings)["sqfVmLanguageServer"]["ls"]["sqcSupport"]; - if (m_sqc_support) - { - window_logMessage(lsp::data::message_type::Log, "SQC Auto-Compilation support enabled."); - } - - logger.setEnabled(loglevel::verbose, (*params.settings)["sqfVmLanguageServer"]["ls"]["logLevel"]["verbose"]); - logger.setEnabled(loglevel::trace, (*params.settings)["sqfVmLanguageServer"]["ls"]["logLevel"]["trace"]); - - - if (m_read_config) { return; } - m_read_config = true; - auto res = (*params.settings)["sqfVmLanguageServer"]["ls"]["additionalMappings"]; - if (res.is_array()) - { - for (auto el : res.items()) - { - if (el.value().is_string()) - { - add_mapping_to_sqf_vm(el.value().get(), el.key()); - } - } - } - } -} - -void sqf_language_server::on_textDocument_didChange(const lsp::data::did_change_text_document_params& params) -{ - auto& doc = get_or_create(params.textDocument.uri); - auto path = std::filesystem::path(sanitize_to_string(params.textDocument.uri)).lexically_normal(); - if (path.extension().string() == ".sqc") - { - if (!sqc_support()) { return; } - { - std::stringstream sstream; - sstream << "Compiling file '" << path.string() << "'." << std::endl; - window_logMessage(lsp::data::message_type::Info, sstream.str()); - } - doc.diagnostics.diagnostics.clear(); - auto preprocessed = sqfvm.parser_preprocessor().preprocess(sqfvm, params.contentChanges.front().text, { path.string(), {} }); - if (preprocessed.has_value()) - { - sqf::sqc::parser sqcParser(logger); - auto set = sqcParser.parse(sqfvm, preprocessed.value(), { path.string(), {} }); - - if (set.has_value()) - { - path.replace_extension(".sqf"); - std::ofstream out_file(path, std::ios_base::trunc); - if (out_file.good()) - { - auto dcode = std::make_shared(*set); - auto str = dcode->to_string_sqf(); - if (str.length() > 2) - { - std::string_view view(str.data() + 1, str.length() - 2); - view = sqf::runtime::util::trim(view, " \t\r\n"); - out_file << view; - } - } - else - { - std::stringstream sstream; - sstream << "Failed to open file '" << path.string() << "' for writing." << std::endl; - window_logMessage(lsp::data::message_type::Error, sstream.str()); - } - } - else - { - std::stringstream sstream; - sstream << "Failed to parse file '" << path.string() << "'." << std::endl; - window_logMessage(lsp::data::message_type::Error, sstream.str()); - } - } - else - { - std::stringstream sstream; - sstream << "Failed to preprocess file '" << path.string() << "'." << std::endl; - window_logMessage(lsp::data::message_type::Error, sstream.str()); - } - textDocument_publishDiagnostics(doc.diagnostics); - } - else - { - doc.analyze(*this, sqfvm, params.contentChanges.front().text); - } -} - -std::optional> sqf_language_server::on_textDocument_foldingRange(const lsp::data::folding_range_params& params) -{ - auto& doc = get_or_create(params.textDocument.uri); - return doc.foldings(); -} - -std::optional sqf_language_server::on_textDocument_completion(const lsp::data::completion_params& params) -{ - - // Get navigation token - auto& doc = get_or_create(params.textDocument.uri); - auto nav_o = doc.navigate(params.position.line, params.position.character); - if (!nav_o.has_value()) { /* ToDo: Return default completion_list instead of empty results */ return {}; } - - // ToDo: Handle the different astnode tokens for extended completion (eg. for `addAction [@p1, @p2, @p3, @p4, ...]` the different parameters inside of the list) - - // ToDo: Return slide of default completion_list instead of empty results - return {}; -} - - -text_document& sqf_language_server::get_or_create(lsp::data::uri uri) -{ - using namespace std::string_view_literals; - auto fpath = sanitize_to_string(uri); - std::filesystem::path path(fpath); - - // Check if file already exists - auto findRes = text_documents.find(fpath); - if (findRes != text_documents.end()) - { - // Only perform analysis again on the text provided by params - return findRes->second; - } - else - { - auto ext = path.extension(); - - // Create file at fpath only if file is an actual sqf file and perform analysis. - text_documents[fpath] = { - *this, - sqfvm, - fpath, - ext == ".sqc" ? text_document::document_type::SQC - : text_document::document_type::SQF }; - return text_documents[fpath]; - } -} - -void sqf_language_server::add_mapping_to_sqf_vm(std::string phys, std::string virt) -{ - using namespace std::string_view_literals; - { - std::replace(phys.begin(), phys.end(), '\\', '/'); - phys = std::string(sqf::runtime::util::trim(phys, " \t\r\n")); - } - { - virt = virt[0] != '/' ? "/" + virt : virt; - std::replace(virt.begin(), virt.end(), '\\', '/'); - virt = std::string(sqf::runtime::util::trim(virt, " \t\r\n")); - } - - std::string msg; - msg.reserve( - "Mapping "sv.length() + - phys.length() + - " onto '"sv.length() + - virt.length() + - "'"sv.length() - ); - msg.append("Mapping "sv); - msg.append(phys); - msg.append(" onto '"); - msg.append(virt); - msg.append("'"sv); - window_logMessage(lsp::data::message_type::Log, msg); - sqfvm.fileio().add_mapping(phys, virt); -} diff --git a/server/src/sqf_language_server.h b/server/src/sqf_language_server.h deleted file mode 100644 index 60e2151..0000000 --- a/server/src/sqf_language_server.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include "lspserver.h" -#include "language_server_logger.h" -#include "text_document.h" -#include "git_sha1.h" - -#include - -#include -#include - -class sqf_language_server : public lsp::server -{ -public: -protected: - - virtual lsp::data::initialize_result on_initialize(const lsp::data::initialize_params& params) override - { - client = params; - lsp::data::initialize_result res; - res.serverInfo = lsp::data::initialize_result::server_info{}; - res.serverInfo->name = "SQF-VM Language Server"; - res.serverInfo->version = std::string(g_GIT_SHA1); - res.capabilities.textDocumentSync = lsp::data::initialize_result::server_capabilities::text_document_sync_options{}; - res.capabilities.textDocumentSync->change = lsp::data::text_document_sync_kind::Full; - res.capabilities.textDocumentSync->openClose = true; - res.capabilities.textDocumentSync->save = lsp::data::initialize_result::server_capabilities::text_document_sync_options::SaveOptions{}; - res.capabilities.textDocumentSync->save->includeText = true; - res.capabilities.textDocumentSync->willSave = false; - res.capabilities.foldingRangeProvider = lsp::data::initialize_result::server_capabilities::folding_range_registration_options{}; - res.capabilities.foldingRangeProvider->documentSelector = lsp::data::document_filter{ }; - res.capabilities.foldingRangeProvider->documentSelector->language = "sqf"; - // res.capabilities.completionProvider = lsp::data::initialize_result::server_capabilities::completion_options{}; - - return res; - } - virtual void on_shutdown() override {} - - // After Initialize - virtual void after_initialize(const lsp::data::initialize_params& params) override; - virtual void on_workspace_didChangeConfiguration(const lsp::data::did_change_configuration_params& params) override; - virtual void on_textDocument_didChange(const lsp::data::did_change_text_document_params& params) override; - virtual std::optional> on_textDocument_foldingRange(const lsp::data::folding_range_params& params) override; - virtual std::optional on_textDocument_completion(const lsp::data::completion_params& params); - - // ToDo: Add the ability to reload the config instead of just disabling it using this workaround - bool m_read_config; - bool m_sqc_support; - std::vector m_global_declarations; - std::vector m_workspace_folders; - std::mutex m_mutex_global_declarations; -public: - - void scan_documents_recursive_at(std::string directory); - - std::unordered_map text_documents; - lsp::data::initialize_params client; - language_server_logger logger; - sqf::runtime::runtime sqfvm; - sqf_language_server() : logger(*this), sqfvm(logger, {}), m_read_config(false), m_sqc_support(false) {} - sqf_language_server(const sqf_language_server& copy) = delete; - - bool sqc_support() const { return m_sqc_support; } - - text_document& get_or_create(lsp::data::uri uri); - - std::vector global_declarations() - { - std::scoped_lock scoped(m_mutex_global_declarations); - return m_global_declarations; - } - void with_global_declarations_do(std::function&)> func) - { - std::scoped_lock scoped(m_mutex_global_declarations); - func(m_global_declarations); - } - - // Adds the provided physical - virtual mapping onto SQF-VM - // and cleans up both strings before inserting them (trim, replace \ with /, append / if first char != /) - void add_mapping_to_sqf_vm(std::string phys, std::string virt); -}; \ No newline at end of file diff --git a/server/src/text_document.cpp b/server/src/text_document.cpp deleted file mode 100644 index f003b6d..0000000 --- a/server/src/text_document.cpp +++ /dev/null @@ -1,474 +0,0 @@ -#include "text_document.h" -#include "sqf_language_server.h" - -#include -#include -#include -#include -#include -#include - -using namespace sqf::types; -using namespace sqf::runtime; - -void text_document::recalculate_ast( - sqf_language_server& language_server, - sqf::runtime::runtime& sqfvm, - std::optional contents_override) -{ - auto parser = dynamic_cast(sqfvm.parser_sqf()); - bool errflag = false; - auto preprocessed = contents_override.has_value() ? - sqfvm.parser_preprocessor().preprocess(sqfvm, *contents_override, { m_path, {} }) : - sqfvm.parser_preprocessor().preprocess(sqfvm, { m_path, {} }); - if (preprocessed.has_value()) - { - m_contents = preprocessed.value(); - sqf::parser::sqf::tokenizer t(m_contents.begin(), m_contents.end(), m_path); - if (!parser.get_tree(sqfvm, t, &m_root_ast)) - { - m_root_ast = {}; - } - } - else - { - lsp::data::diagnostics diag; - diag.code = "FATAL"; - diag.range.start.line = 0; - diag.range.start.character = 0; - diag.range.end.line = 0; - diag.range.end.character = 0; - diag.message = "Failed to preprocess (or read) file."; - diag.severity = lsp::data::diagnostic_severity::Error; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } -} - -void text_document::analysis_ensure_L0001_L0003(sqf_language_server& language_server, std::vector& known, size_t level, sqf::parser::sqf::bison::astnode& node, const std::string& orig, bool private_check, variable_declaration::sptr* out_var_decl) -{ - using sqf::parser::sqf::parser; - std::string variable = orig; - std::transform(variable.begin(), variable.end(), variable.begin(), [](char c) { return (char)std::tolower(c); }); - auto findRes = std::find_if(known.begin(), known.end(), - [&variable](variable_declaration::sptr& it) { return it->variable == variable; }); - if (findRes == known.end()) - { - auto var = std::make_shared(level, node.token.line, node.token.column, variable); - known.push_back(var); - if (var->variable[0] == '_') // safe as empty std string `0` is `\0` - { - auto ref = m_private_declarations.emplace_back(var); - if (out_var_decl) - { - *out_var_decl = ref; - } - } - else - { - var->owner = m_path; - auto ref = m_global_declarations.emplace_back(var); - if (out_var_decl) - { - *out_var_decl = ref; - } - } - } - else if (variable[0] != '_' && (*findRes)->owner == m_path) - { - if (std::find(m_global_declarations.begin(), m_global_declarations.end(), *findRes) == m_global_declarations.end()) - { - m_global_declarations.push_back(*findRes); - } - if (out_var_decl) - { - *out_var_decl = *findRes; - } - } - else if (node.kind == sqf::parser::sqf::bison::astkind::ASSIGNMENT_LOCAL || private_check) - { - analysis_raise_L0001(node, orig); - private_check = true; - if (out_var_decl) - { - *out_var_decl = *findRes; - } - } - if (private_check) - { - if (variable[0] != '_') - { - analysis_raise_L0003(node, orig); - } - } -} - -void text_document::analysis_params(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, sqf::parser::sqf::bison::astnode& current, size_t level, std::vector& known) -{ - if (current.children.empty()) { /* only handle if we have children */ return; } - for (auto child : current.children) - { - if (child.kind == sqf::parser::sqf::bison::astkind::STRING) - { // Simple parsing - Add variable - auto variable = sqf::types::d_string::from_sqf(child.token.contents); - if (variable != "") - { - analysis_ensure_L0001_L0003(language_server, known, level, child, variable, true, nullptr); - } - } - else - { // "Complex parsing" - Ensure correctness and add variable from first child - if (child.children.size() < 2 || child.children.size() > 4) - { // Ensure at least 2 children - analysis_raise_L0006_array_size_missmatch(child, 2, 4, child.children.size()); - } - else - { - if (child.children.size() >= 1 && child.children[0].kind != sqf::parser::sqf::bison::astkind::STRING) - { - analysis_raise_L0007_type_error<1>(child.children[0], { t_string() }, {}); - } - else - { - auto variable = sqf::types::d_string::from_sqf(child.children[0].token.contents); - if (variable != "") - { - analysis_ensure_L0001_L0003(language_server, known, level, child.children[0], variable, true, nullptr); - } - } - if (child.children.size() >= 3 && child.children[2].kind != sqf::parser::sqf::bison::astkind::ARRAY) - { - analysis_raise_L0007_type_error<1>(child.children[2], { t_array() }, {}); - } - if (child.children.size() >= 4 && - child.children[3].kind != sqf::parser::sqf::bison::astkind::ARRAY && - child.children[3].kind != sqf::parser::sqf::bison::astkind::NUMBER && - child.children[3].kind != sqf::parser::sqf::bison::astkind::HEXNUMBER) - { - analysis_raise_L0007_type_error<2>(child.children[3], { t_string(), t_scalar() }, {}); - } - } - } - } -} - -void text_document::recalculate_analysis_helper( - sqf_language_server& language_server, - sqf::runtime::runtime& sqfvm, - sqf::parser::sqf::bison::astnode& current, - size_t level, - std::vector& known, - analysis_info parent_type, - const std::vector& actual_globals - ) -{ - using sqf::parser::sqf::parser; - using namespace sqf::parser::sqf::bison; - m_asthints.push_back({ ¤t, current.token.offset, current.token.line, current.token.column }); - - - switch (current.kind) - { - /* - Error Codes: - - L-0001 - - L-0003 - Handles: - - Duplicate-Declaration detection - - Adding variables to the known-stack - */ - case astkind::ASSIGNMENT_LOCAL: - case astkind::ASSIGNMENT: { - std::string variable(current.token.contents.begin(), current.token.contents.end()); - analysis_ensure_L0001_L0003(language_server, known, level, current, variable, false, nullptr); - recalculate_analysis_helper_ident(language_server, sqfvm, current, level, known, analysis_info::NA, actual_globals); - recalculate_analysis_helper(language_server, sqfvm, current.children[0], level, known, analysis_info::NA, actual_globals); - } break; - - /* - Handles: - - Add built-in variables `_x`, `_foreachindex` of foreach - - Add built-in variables `_x` of count - - Cleanup of "known" variables after leaving code-block - */ - case astkind::CODE: { - switch (parent_type) - { - case text_document::analysis_info::DECLARE_FOREACHINDEX_AND_X: - known.push_back(std::make_shared( level, current.token.line, current.token.column, "_foreachindex")); - /* fallthrough */ - case text_document::analysis_info::DECLARE_X: - known.push_back(std::make_shared(level, current.token.line, current.token.column, "_x")); - break; - } - for (auto child : current.children) - { - recalculate_analysis_helper(language_server, sqfvm, child, level + 1, known, analysis_info::NA, actual_globals); - } - - // Erase lower known variables - for (size_t i = 0; i < known.size(); i++) - { - if (known[i]->level == level + 1) - { - known.at(i) = known.back(); - known.erase(known.begin() + known.size() - 1); - i--; - } - } - } break; - - /* - Error Codes: - - L-0002 - Handles: - - Undefined variable warnings - - Variable-Usage reference updating - */ - case astkind::IDENT: { - recalculate_analysis_helper_ident(language_server, sqfvm, current, level, known, parent_type, actual_globals); - } goto l_default; - - /* - Handles: - - Passing analysis_info to lower method - - Applying clean `known` vector for `spawn` - */ - case astkind::EXP1: - case astkind::EXP2: - case astkind::EXP3: - case astkind::EXP4: - case astkind::EXP5: - case astkind::EXP6: - case astkind::EXP7: - case astkind::EXP8: - case astkind::EXP9: - case astkind::EXP0: { - auto op = std::string(current.token.contents); - std::transform(op.begin(), op.end(), op.begin(), [](char& c) { return (char)std::tolower((int)c); }); - - if (op == "spawn") - { - for (auto child : current.children) - { - std::vector known2 = language_server.global_declarations(); - known2.push_back(std::make_shared(0, 0, 0, "_this")); - recalculate_analysis_helper(language_server, sqfvm, child, level + 1, known2, analysis_info::NA, actual_globals); - } - - break; - } - else if (op == "params") - { - analysis_params(language_server, sqfvm, current.children[1], level, known); - goto l_default; - } - else if (op == "foreach") - { - for (auto child : current.children) - { - recalculate_analysis_helper(language_server, sqfvm, child, level + 1, known, analysis_info::DECLARE_FOREACHINDEX_AND_X, actual_globals); - } - - break; - } - else if (op == "count" || op == "select" || op == "apply" || op == "findif") - { - for (auto child : current.children) - { - recalculate_analysis_helper(language_server, sqfvm, child, level + 1, known, analysis_info::DECLARE_X, actual_globals); - } - - break; - } - else - { - goto l_default; - } - } - /* - Handles: - - Applying clean `known` vector for `spawn` - */ - case astkind::EXPU: { - auto op = std::string(current.token.contents); - std::transform(op.begin(), op.end(), op.begin(), [](char& c) { return (char)std::tolower((int)c); }); - - if (op == "spawn") - { - for (auto child : current.children) - { - std::vector known2 = language_server.global_declarations(); - known2.push_back(std::make_shared(0, 0, 0, "_this")); - recalculate_analysis_helper(language_server, sqfvm, child, level + 1, known2, analysis_info::NA, actual_globals); - } - - break; - } - else if (op == "private") - { - for (auto child : current.children) - { - recalculate_analysis_helper(language_server, sqfvm, child, level, known, analysis_info::PRIVATE, actual_globals); - } - - break; - } - else if (op == "params") - { - analysis_params(language_server, sqfvm, current.children[0], level, known); - goto l_default; - } - else if (op == "for" && current.children[0].kind == astkind::STRING) - { - auto variable = sqf::types::d_string::from_sqf(current.children[0].token.contents); - auto var = std::make_shared(level, current.children[0].token.line, current.children[0].token.column, variable); - known.push_back(var); - if (var->variable[0] == '_') // safe as empty std string `0` is `\0` - { - m_private_declarations.push_back(var); - } - else - { - analysis_raise_L0003(current.children[0], var->variable); - } - goto l_default; - } - else - { - goto l_default; - } - } - /* - Error Codes: - - L-0001 - - L-0003 - Handles: - - On parent_type == PRIVATE: - - Duplicate-Declaration detection - - Adding variables to the known-stack - */ - case astkind::STRING: { - if (parent_type == analysis_info::PRIVATE) - { - auto variable = sqf::types::d_string::from_sqf(current.token.contents); - analysis_ensure_L0001_L0003(language_server, known, level, current, variable, true, nullptr); - } - } break; - - l_default: - default: { - for (auto child : current.children) - { - recalculate_analysis_helper(language_server, sqfvm, child, level, known, parent_type, actual_globals); - } - } break; - } -} - -void text_document::recalculate_analysis_helper_ident(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, sqf::parser::sqf::bison::astnode& current, size_t level, std::vector& known, analysis_info parent_type, const std::vector& actual_globals) -{ - std::string variable(current.token.contents.begin(), current.token.contents.end()); - - - std::transform(variable.begin(), variable.end(), variable.begin(), [](char c) { return (char)std::tolower(c); }); - auto findRes = std::find_if(known.begin(), known.end(), - [&variable](variable_declaration::sptr it) { return it->variable == variable; }); - if (findRes == known.end()) - { - std::string content(current.token.contents.begin(), current.token.contents.end()); - analysis_raise_L0002(current, content); - } - else - { - if (variable[0] == '_') // safe as empty std string `0` is `\0` - { - auto it = std::find_if(m_private_declarations.begin(), m_private_declarations.end(), - [&variable](variable_declaration::sptr it) -> bool { return it->variable == variable; }); - if (it != m_private_declarations.end()) - { - (*it)->usages.push_back({ current.token.line, current.token.column }); - } - } - else - { - auto it = std::find_if(actual_globals.begin(), actual_globals.end(), - [&variable](variable_declaration::sptr it) -> bool { return it->variable == variable; }); - if (it != actual_globals.end()) - { - (*it)->usages.push_back({ current.token.line, current.token.column }); - } - } - } -} - -void text_document::recalculate_analysis(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm) -{ - m_private_declarations.clear(); - m_asthints.clear(); - std::vector actual = language_server.global_declarations(); - std::vector known = actual; - known.push_back(std::make_shared(0, 0, 0, "_this" )); - recalculate_analysis_helper(language_server, sqfvm, m_root_ast, 0, known, analysis_info::NA, actual); -} - -void text_document::analyze(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, std::optional contents_override) -{ - // Preparation - bool had_diagnostics = !diagnostics.diagnostics.empty(); - diagnostics.diagnostics.clear(); - auto prev_gl = m_global_declarations; - m_global_declarations.clear(); - - if (type == document_type::SQF) - { - // Perform different analysis steps - recalculate_ast(language_server, sqfvm, contents_override); - recalculate_foldings(sqfvm); - recalculate_analysis(language_server, sqfvm); - } - // calculate delta in the globals - std::vector added; - for (auto it : m_global_declarations) - { - auto res = std::find(prev_gl.begin(), prev_gl.end(), it); - if (res == prev_gl.end()) - { - added.push_back(it); - } - } - std::vector removed; - for (auto it : prev_gl) - { - auto res = std::find(m_global_declarations.begin(), m_global_declarations.end(), it); - if (res == m_global_declarations.end()) - { - removed.push_back(it); - } - } - - // Apply removal - language_server.with_global_declarations_do( - [&](auto& global_declarations) { - for (auto it : removed) - { - auto res = std::find(global_declarations.begin(), global_declarations.end(), it); - if (res != global_declarations.end()) - { - global_declarations.erase(res); - } - } - - // Apply additions - for (auto it : added) - { - global_declarations.push_back(it); - } - }); - - // Send diagnostics - if (had_diagnostics || !diagnostics.diagnostics.empty()) - { - language_server.textDocument_publishDiagnostics(diagnostics); - } -} \ No newline at end of file diff --git a/server/src/text_document.h b/server/src/text_document.h deleted file mode 100644 index d3bafc3..0000000 --- a/server/src/text_document.h +++ /dev/null @@ -1,329 +0,0 @@ -#pragma once -#include "variable_declaration.h" -#include "util.h" - -#include -#include - -#include -#include -#include -#include -#include - -class sqf_language_server; - -class text_document -{ -public: - enum class document_type - { - NA, - SQF, - SQC - }; - struct asthint - { - sqf::parser::sqf::bison::astnode* actual; - size_t offset; - size_t line; - size_t column; - }; - - // Helper class to navigate an ast linear - class astnav - { - private: - size_t m_index; - std::vector& m_hints; - public: - astnav(size_t index, std::vector& hints) : - m_index(index), - m_hints(hints) - { - - } - - // Navigate to next asthint - bool next() - { - m_index++; - if (m_index >= m_hints.size()) - { - m_index--; - return false; - } - return true; - } - // Navigate to previous asthint - bool previous() - { - if (m_index == 0) - { - return false; - } - m_index--; - return true; - } - asthint& operator*() const { return m_hints[m_index]; } - asthint* operator->() const { return &m_hints[m_index]; } - }; -private: - enum class analysis_info - { - NA, - DECLARE_FOREACHINDEX_AND_X, - DECLARE_X, - PRIVATE, - }; - std::string m_path; - std::string m_contents; - sqf::parser::sqf::bison::astnode m_root_ast; - std::vector m_foldings; - std::vector m_asthints; - - std::vector m_private_declarations; - std::vector m_global_declarations; - - void recalculate_ast(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, std::optional contents_override); - void recalculate_foldings_recursive(sqf::runtime::runtime& sqfvm, sqf::parser::sqf::bison::astnode& current) - { - switch (current.kind) - { - case sqf::parser::sqf::bison::astkind::ARRAY: - case sqf::parser::sqf::bison::astkind::CODE: - { - // todo: find a way to force vscode into using offsets instead of lines - lsp::data::folding_range frange; - frange.startCharacter = current.token.offset; - frange.startLine = current.token.line - 1; // lines start at 0 - frange.endCharacter = current.token.offset + current.token.contents.length(); - - // find current nodes, last child in tree and set its line as end. - sqf::parser::sqf::bison::astnode* prev = ¤t; - sqf::parser::sqf::bison::astnode* node = ¤t; - while (node) - { - prev = node; - node = node->children.empty() ? nullptr : &node->children.back(); - } - frange.endLine = prev->token.line; - m_foldings.push_back(frange); - } break; - } - for (auto child : current.children) - { - recalculate_foldings_recursive(sqfvm, child); - } - } - void recalculate_foldings(sqf::runtime::runtime& sqfvm) - { - m_foldings.clear(); - recalculate_foldings_recursive(sqfvm, m_root_ast); - } - void analysis_raise_L0001(sqf::parser::sqf::bison::astnode& node, const std::string& variable) - { - lsp::data::diagnostics diag; - diag.code = "L-0001"; - diag.range.start.line = node.token.line - 1; - diag.range.start.character = node.token.column; - diag.range.end.line = node.token.line - 1; - diag.range.end.character = node.token.column; - diag.message = "'" + variable + "' hides previous declaration."; - diag.severity = lsp::data::diagnostic_severity::Warning; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } - void analysis_raise_L0002(sqf::parser::sqf::bison::astnode& node, const std::string& variable) - { - lsp::data::diagnostics diag; - diag.code = "L-0002"; - diag.range.start.line = node.token.line - 1; - diag.range.start.character = node.token.column; - diag.range.end.line = node.token.line - 1; - diag.range.end.character = node.token.column; - diag.message = "Variable '" + variable + "' not defined."; - diag.severity = lsp::data::diagnostic_severity::Warning; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } - void analysis_raise_L0003(sqf::parser::sqf::bison::astnode& node, const std::string& variable) - { - lsp::data::diagnostics diag; - diag.code = "L-0003"; - diag.range.start.line = node.token.line - 1; - diag.range.start.character = node.token.column; - diag.range.end.line = node.token.line - 1; - diag.range.end.character = node.token.column; - diag.message = "'" + variable + "' is not starting with an underscore ('_')."; - diag.severity = lsp::data::diagnostic_severity::Error; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } - void analysis_raise_L0004(sqf::parser::sqf::bison::astnode& node, const std::string& variable) - { - lsp::data::diagnostics diag; - diag.code = "L-0004"; - diag.range.start.line = node.token.line - 1; - diag.range.start.character = node.token.column; - diag.range.end.line = node.token.line - 1; - diag.range.end.character = node.token.column; - diag.message = "Missing variable string."; - diag.severity = lsp::data::diagnostic_severity::Error; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } - void analysis_raise_L0005_format_error(sqf::parser::sqf::bison::astnode& node, const char* additional) - { - lsp::data::diagnostics diag; - diag.code = "L-0005"; - diag.range.start.line = node.token.line - 1; - diag.range.start.character = node.token.column; - diag.range.end.line = node.token.line - 1; - diag.range.end.character = node.token.column; - diag.message = "Format Error: "; - diag.message.append(additional); - diag.severity = lsp::data::diagnostic_severity::Error; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } - void analysis_raise_L0006_array_size_missmatch(sqf::parser::sqf::bison::astnode& node, const std::optional min_inclusive, std::optional max_inclusive, size_t actual) - { - std::stringstream sstream; - sstream << "Array Size Missmatch. Got " << actual << "."; - if (min_inclusive.has_value() || max_inclusive.has_value()) - { - if (!min_inclusive.has_value()) - { - sstream << " " << "Value was expected to be greater then " << min_inclusive.value(); - } - else if (!max_inclusive.has_value()) - { - sstream << " " << "Value was expected to be less then or equal to " << max_inclusive.value(); - } - else - { - sstream << " " << "Value was expected to be inbetween " << min_inclusive.value() << " - " << max_inclusive.value(); - } - } - - lsp::data::diagnostics diag; - diag.code = "L-0006"; - diag.range.start.line = node.token.line - 1; - diag.range.start.character = node.token.column; - diag.range.end.line = node.token.line - 1; - diag.range.end.character = node.token.column; - diag.message = sstream.str(); - diag.severity = lsp::data::diagnostic_severity::Error; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } - template - void analysis_raise_L0007_type_error(sqf::parser::sqf::bison::astnode& node, std::array<::sqf::runtime::type, size> expected, std::optional <::sqf::runtime::type> got) - { - std::stringstream sstream; - sstream << "Type Missmatch "; - if (got.has_value()) - { - sstream << ". Got " << got->to_string(); - } - if (size == 1) - { - sstream << ". Expected " << expected[0].to_string() << "."; - } - else - { - sstream << ". Expected one of { "; - for (size_t i = 0; i < size; i++) - { - if (i != 0) - { - sstream << ", "; - } - sstream << expected[i].to_string(); - } - sstream << " }."; - } - lsp::data::diagnostics diag; - diag.code = "L-0006"; - diag.range.start.line = node.token.line - 1; - diag.range.start.character = node.token.column; - diag.range.end.line = node.token.line - 1; - diag.range.end.character = node.token.column; - diag.message = sstream.str(); - diag.severity = lsp::data::diagnostic_severity::Error; - diag.source = "SQF-VM LS"; - diagnostics.diagnostics.push_back(diag); - } - - // Performs the checks for L-0001 & L-0003. - // - // @param known: The list of known variable as reference - // @param level: The current variable level - // @param node: The actual variable node, handled - // @param variable: The variable name (should be tolowered first) - // @param private_check: Wether the variable should be treated as private declaration - void analysis_ensure_L0001_L0003( - sqf_language_server& language_server, std::vector& known, - size_t level, sqf::parser::sqf::bison::astnode& node, const std::string& orig, bool private_check, variable_declaration::sptr* out_var_decl); - void analysis_params(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, sqf::parser::sqf::bison::astnode& current, size_t level, std::vector& known); - void recalculate_analysis_helper(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, sqf::parser::sqf::bison::astnode& current, size_t level, std::vector& known, analysis_info parent_type, const std::vector& actual_globals); - void recalculate_analysis_helper_ident(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, sqf::parser::sqf::bison::astnode& current, size_t level, std::vector& known, analysis_info parent_type, const std::vector& actual_globals); - void recalculate_analysis(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm); -public: - lsp::data::publish_diagnostics_params diagnostics; - document_type type; - text_document() : type(document_type::NA) {} - text_document(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, std::string path, document_type type) : m_path(path), type(type) - { - diagnostics.uri = sanitize_to_uri(path); - } - - std::string_view contents() const { return m_contents; } - void analyze(sqf_language_server& language_server, sqf::runtime::runtime& sqfvm, std::optional contents_override); - - std::vector& foldings() { return m_foldings; } - - // Finds the closest astnode to the position provided and gives it back. - // Might return empty astnav if line is not existing, no astnode exists on that line or - // the file failed to parse - std::optional navigate(size_t line, size_t column) - { - size_t i; - - // Look for the perfect match, linewise - for (i = 0; i < m_asthints.size(); i++) - { - if (m_asthints[i].line == line) - { - // Found match. Break loop. - break; - } - if (m_asthints[i].line > line) - { - // Current line > targeted line. Match not found. - return {}; - } - } - if (i == m_asthints.size()) - { - return {}; - } - - // Look for the best match, columnwise (prioritize left) - size_t j; - for (j = i; j < m_asthints.size() && m_asthints[j].line == line; j++) - { - if (m_asthints[j].column == line) - { - // Perfect match. Return immediate - return astnav(j, m_asthints); - } - if (m_asthints[j].column > column) - { // Astnodes column > lookup column. Break loop. - break; - } - } - return astnav(j - 1, m_asthints); - } -}; \ No newline at end of file diff --git a/server/src/uri.h b/server/src/uri.h deleted file mode 100644 index 5f799c3..0000000 --- a/server/src/uri.h +++ /dev/null @@ -1,638 +0,0 @@ -#pragma once -#include -#include -#include -#include - -namespace x39 -{ - class uri - { - private: - std::string m_data; - size_t m_schema_start; - size_t m_schema_length; - size_t m_user_start; - size_t m_user_length; - size_t m_password_start; - size_t m_password_length; - size_t m_host_start; - size_t m_host_length; - size_t m_port_start; - size_t m_port_length; - size_t m_path_start; - size_t m_path_length; - size_t m_query_start; - size_t m_query_length; - size_t m_fragment_start; - size_t m_fragment_length; - protected: - uri() : - m_data(), - m_schema_start(0), - m_schema_length(0), - m_user_start(0), - m_user_length(0), - m_password_start(0), - m_password_length(0), - m_host_start(0), - m_host_length(0), - m_port_start(0), - m_port_length(0), - m_path_start(0), - m_path_length(0), - m_query_start(0), - m_query_length(0), - m_fragment_start(0), - m_fragment_length(0) {} - public: - uri(const char* input) : uri(std::string_view(input)) {} - uri(const std::string& input) : uri(std::string_view(input)) {} - uri(std::string_view input) : uri() - { - auto& output = m_data; - output.reserve(input.size()); // parsed string is always at max as long as input - - enum estate { schema_read, schema_wait_length, schema_wait_length_slash, user, password, host, port, path, query, fragment }; - estate state = schema_read; - size_t start = 0; - size_t current = 0; - int specialCharacter = 0; - char specialBuffer[4] = { ' ', '\0', '\0', '\0' }; - for (auto c : input) - { - if (specialCharacter != 0) - { - specialBuffer[specialCharacter++] = c; - if (specialCharacter == 3) - { - specialCharacter = 0; - switch (specialBuffer[1]) - { - case '0': - c = static_cast(0x00); - break; - case '1': - c = static_cast(0x10); - break; - case '2': - c = static_cast(0x20); - break; - case '3': - c = static_cast(0x30); - break; - case '4': - c = static_cast(0x40); - break; - case '5': - c = static_cast(0x50); - break; - case '6': - c = static_cast(0x60); - break; - case '7': - c = static_cast(0x70); - break; - case '8': - c = static_cast(0x80); - break; - case '9': - c = static_cast(0x90); - break; - case 'a': case 'A': - c = static_cast(0xA0); - break; - case 'b': case 'B': - c = static_cast(0xB0); - break; - case 'c': case 'C': - c = static_cast(0xC0); - break; - case 'd': case 'D': - c = static_cast(0xD0); - break; - case 'e': case 'E': - c = static_cast(0xE0); - break; - case 'f': case 'F': - c = static_cast(0xF0); - break; - } - switch (specialBuffer[2]) - { - case '0': - c |= static_cast(0x00); - break; - case '1': - c |= static_cast(0x01); - break; - case '2': - c |= static_cast(0x02); - break; - case '3': - c |= static_cast(0x03); - break; - case '4': - c |= static_cast(0x04); - break; - case '5': - c |= static_cast(0x05); - break; - case '6': - c |= static_cast(0x06); - break; - case '7': - c |= static_cast(0x07); - break; - case '8': - c |= static_cast(0x08); - break; - case '9': - c |= static_cast(0x09); - break; - case 'a': case 'A': - c |= static_cast(0x0A); - break; - case 'b': case 'B': - c |= static_cast(0x0B); - break; - case 'c': case 'C': - c |= static_cast(0x0C); - break; - case 'd': case 'D': - c |= static_cast(0x0D); - break; - case 'e': case 'E': - c |= static_cast(0x0E); - break; - case 'f': case 'F': - c |= static_cast(0x0F); - break; - } - current++; - output.append(&c, &c + 1); - continue; - } - else - { - continue; - } - } - else if (c == '%') - { - specialCharacter++; - continue; - } - current++; - output.append(&c, &c + 1); - switch (state) - { - case schema_read: - if (c == ':') - { - state = schema_wait_length; - m_schema_start = start; - m_schema_length = current - start - 1; - } - break; - case schema_wait_length: - if (c == '/') - { - state = schema_wait_length_slash; - } - else if (c != ':') - { // invalid uri. Still try to parse - state = user; - start = current - 1; - } - break; - case schema_wait_length_slash: - if (c != '/') - { // invalid uri. Still try to parse - state = user; - start = current - 1; - goto l_user; - } - else // char c is '/' here - { - state = user; - start = current; // Skip current '/' - } - break; - l_user: - case user: - if (c == ':') - { - auto atLocation = input.find("@"); - if (atLocation == std::string::npos) - { // no user present, pass to host state - state = host; - goto case_host; - } - m_user_start = start; - m_user_length = current - start - 1; - state = password; - start = current; - } - else if (c == '@') - { - m_user_start = start; - m_user_length = current - start - 1; - state = host; - start = current; - } - else if (c == '/') - { - m_host_start = start; - m_host_length = current - start - 1; - state = path; - start = current; - } - else if (c == '?') - { - m_host_start = start; - m_host_length = current - start - 1; - state = query; - start = current; - } - else if (c == '#') - { - m_host_start = start; - m_host_length = current - start - 1; - state = fragment; - start = current; - } - else if (c == ':') - { - m_host_start = start; - m_host_length = current - start - 1; - state = port; - start = current; - } - break; - case password: - if (c == '@') - { - m_password_start = start; - m_password_length = current - start - 1; - state = host; - start = current; - } - else if (c == '/') - { - m_host_start = start; - m_host_length = current - start - 1; - state = path; - start = current; - } - else if (c == '?') - { - m_host_start = start; - m_host_length = current - start - 1; - state = query; - start = current; - } - else if (c == '#') - { - m_host_start = start; - m_host_length = current - start - 1; - state = fragment; - start = current; - } - else if (c == ':') - { - m_host_start = start; - m_host_length = current - start - 1; - state = port; - start = current; - } - break; - case host: - case_host: - if (c == '/') - { - m_host_start = start; - m_host_length = current - start - 1; - state = path; - start = current; - } - else if (c == '?') - { - m_host_start = start; - m_host_length = current - start - 1; - state = query; - start = current; - } - else if (c == '#') - { - m_host_start = start; - m_host_length = current - start - 1; - state = fragment; - start = current; - } - else if (c == ':') - { - m_host_start = start; - m_host_length = current - start - 1; - state = port; - start = current; - } - break; - case port: - if (c == '/') - { - m_port_start = start; - m_port_length = current - start - 1; - if (m_port_length == 0) - { - m_host_length++; - } - state = path; - start = current; - } - else if (c == '?') - { - m_port_start = start; - m_port_length = current - start - 1; - state = query; - start = current; - } - else if (c == '#') - { - m_port_start = start; - m_port_length = current - start - 1; - state = fragment; - start = current; - } - break; - case path: - if (c == '?') - { - m_path_start = start; - m_path_length = current - start - 1; - state = query; - start = current; - } - else if (c == '#') - { - m_path_start = start; - m_path_length = current - start - 1; - state = fragment; - start = current; - } - break; - case query: - if (c == '#') - { - m_query_start = start; - m_query_length = current - start - 1; - state = fragment; - start = current; - } - break; - case fragment: - break; - } - } - switch (state) - { - case user: - case password: - case host: - m_host_start = start; - m_host_length = current - start; - break; - case port: - m_port_start = start; - m_port_length = current - start; - break; - case path: - m_path_start = start; - m_path_length = current - start; - break; - case query: - m_query_start = start; - m_query_length = current - start; - break; - case fragment: - m_fragment_start = start; - m_fragment_length = current - start - 1; - break; - default: break; - } - } - uri(std::string_view schema, - std::string_view user, - std::string_view password, - std::string_view host, - std::string_view port, - std::string_view path, - std::string_view query, - std::string_view fragment) : uri() - { - using namespace std::string_view_literals; - m_data.reserve( - schema.length() + - "://"sv.length() + - (user.empty() ? 0 : user.length() + "@"sv.length() + (password.empty() ? 0 : password.length() + ":"sv.length())) + - host.length() + - "/"sv.length() + - (port.empty() ? 0 : port.length() + ":"sv.length()) + - path.length() + - (query.empty() ? 0 : query.length() + "?"sv.length()) + - (fragment.empty() ? 0 : fragment.length() + "#"sv.length()) - ); - size_t cur = 0; - - m_data.append(schema); - m_schema_start = cur; - cur += m_schema_length = schema.length(); - - m_data.append("://"sv); - cur += "://"sv.length(); - - if (!user.empty()) - { - m_data.append(user); - m_user_start = cur; - cur += m_user_length = user.length(); - - if (!password.empty()) - { - m_data.append(":"sv); - cur += ":"sv.length(); - - m_data.append(password); - m_password_start = cur; - cur += m_password_length = password.length(); - } - - m_data.append("@"sv); - cur += "@"sv.length(); - } - - m_data.append(host); - m_host_start = cur; - cur += m_host_length = host.length(); - - if (!port.empty()) - { - m_data.append(":"sv); - cur += ":"sv.length(); - - m_data.append(port); - m_port_start = cur; - cur += m_port_length = port.length(); - } - - m_data.append("/"sv); - cur += "/"sv.length(); - - m_data.append(path); - m_path_start = cur; - cur += m_path_length = path.length(); - - if (!query.empty()) - { - m_data.append("?"sv); - cur += "?"sv.length(); - - m_data.append(query); - m_query_start = cur; - cur += m_query_length = query.length(); - } - - if (!fragment.empty()) - { - m_data.append("#"sv); - cur += "#"sv.length(); - - m_data.append(fragment); - m_fragment_start = cur; - cur += m_fragment_length = fragment.length(); - } - } - - - std::string_view full() const { return m_data; } - std::string_view schema() const { return std::string_view(m_data.data() + m_schema_start, m_schema_length); } - std::string_view user() const { return std::string_view(m_data.data() + m_user_start, m_user_length); } - std::string_view password() const { return std::string_view(m_data.data() + m_password_start, m_password_length); } - std::string_view host() const { return std::string_view(m_data.data() + m_host_start, m_host_length); } - std::string_view port() const { return std::string_view(m_data.data() + m_port_start, m_port_length); } - std::string_view path() const { return std::string_view(m_data.data() + m_path_start, m_path_length); } - std::string_view query() const { return std::string_view(m_data.data() + m_query_start, m_query_length); } - std::string_view fragment() const { return std::string_view(m_data.data() + m_fragment_start, m_fragment_length); } - - static std::array escape(char c) - { - std::array arr = { '%', '\0', '\0' }; - switch ((c & 0xF0) >> 4) - { - case 0: arr[1] = '0'; break; - case 1: arr[1] = '1'; break; - case 2: arr[1] = '2'; break; - case 3: arr[1] = '3'; break; - case 4: arr[1] = '4'; break; - case 5: arr[1] = '5'; break; - case 6: arr[1] = '6'; break; - case 7: arr[1] = '7'; break; - case 8: arr[1] = '8'; break; - case 9: arr[1] = '9'; break; - case 10: arr[1] = 'A'; break; - case 11: arr[1] = 'B'; break; - case 12: arr[1] = 'C'; break; - case 13: arr[1] = 'D'; break; - case 14: arr[1] = 'E'; break; - case 15: arr[1] = 'F'; break; - } - switch (c & 0x0F) - { - default: - case 0: arr[2] = '0'; break; - case 1: arr[2] = '1'; break; - case 2: arr[2] = '2'; break; - case 3: arr[2] = '3'; break; - case 4: arr[2] = '4'; break; - case 5: arr[2] = '5'; break; - case 6: arr[2] = '6'; break; - case 7: arr[2] = '7'; break; - case 8: arr[2] = '8'; break; - case 9: arr[2] = '9'; break; - case 10: arr[2] = 'A'; break; - case 11: arr[2] = 'B'; break; - case 12: arr[2] = 'C'; break; - case 13: arr[2] = 'D'; break; - case 14: arr[2] = 'E'; break; - case 15: arr[2] = 'F'; break; - } - - return arr; - } - - private: - void encode_helper(std::stringstream& sstream, std::string_view selected_view, const char* allowed) const - { - for (auto c : selected_view) - { - bool flag = false; - for (const char* it = allowed; *it != '\0'; it++) - { - if (*it == c) - { - flag = true; - break; - } - } - if (flag) - { - sstream << c; - } - else - { - auto res = escape(c); - sstream << std::string_view(res.data(), res.size()); - } - } - } - public: - std::string encoded() const - { - std::stringstream sstream; - - // ToDo: Parse into https://de.wikipedia.org/wiki/URL-Encoding - encode_helper(sstream, schema(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); - sstream << "://"; - if (!user().empty()) - { - encode_helper(sstream, user(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); - if (!password().empty()) - { - sstream << ":"; - encode_helper(sstream, password(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); - } - sstream << "@"; - } - encode_helper(sstream, host(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); - if (!port().empty()) - { - sstream << ":"; - encode_helper(sstream, port(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); - } - sstream << "/"; - encode_helper(sstream, path(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~/"); - if (!query().empty()) - { - sstream << "?"; - encode_helper(sstream, query(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~&"); - } - if (!fragment().empty()) - { - sstream << "#"; - encode_helper(sstream, fragment(), "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0987654321-._~"); - } - - return sstream.str(); - } - }; -} \ No newline at end of file diff --git a/server/src/util.h b/server/src/util.h deleted file mode 100644 index 5e9fa7a..0000000 --- a/server/src/util.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "lspserver.h" -#include -#include -#include - -// Method to get a clear & clean uri string out of the uri provided by vscode. -inline static std::string sanitize_to_string(const lsp::data::uri& uri) -{ - std::string dpath; - dpath.reserve(uri.path().length()); - dpath.append(uri.path()); - std::filesystem::path data_path(dpath); - data_path = data_path.lexically_normal(); - dpath = data_path.string(); - std::replace(dpath.begin(), dpath.end(), '\\', '/'); - return dpath; -} -// Method to get a clear & clean uri out of the string provided by sqfvm. -inline static lsp::data::uri sanitize_to_uri(const std::string_view sv) -{ - auto path = std::filesystem::path(sv).lexically_normal(); - auto str = path.string(); - std::replace(str.begin(), str.end(), '\\', '/'); - return lsp::data::uri("file", {}, {}, {}, {}, str, {}, {}); -} -// Method to get a clear & clean uri out of the string provided by sqfvm. -inline static lsp::data::uri sanitize_to_uri(const std::string str) { return sanitize_to_uri(std::string_view(str)); } \ No newline at end of file diff --git a/server/src/variable_declaration.h b/server/src/variable_declaration.h deleted file mode 100644 index ebaf2da..0000000 --- a/server/src/variable_declaration.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "lspserver.h" - -#include - -#include -#include -#include - -struct variable_declaration -{ - using sptr = std::shared_ptr; - struct parameter - { - // The types accepted by this parameter - std::vector types; - - // Wether this parameter is optional or not - bool optional; - }; - // The ast-level this was created at - size_t level; - - // Position info for the initial variable declaration - lsp::data::position position; - - // The actual variable name - std::string variable; - - // The different positions where this is being used - std::vector usages; - - // The different types, known for this variable - std::vector types; - - // If one known type is code, this will contain the params expected by this piece of code. - std::vector args; - - // The uri of the owning file. Will be empty for private variables. - std::string owner; - - variable_declaration(size_t layer, size_t line, size_t column, std::string variable) : - level(layer), - position({ line, column }), - variable(variable), - usages() { } -}; \ No newline at end of file diff --git a/server/vcpkg.json b/server/vcpkg.json new file mode 100644 index 0000000..b871c3d --- /dev/null +++ b/server/vcpkg.json @@ -0,0 +1,19 @@ +{ + "name" : "sqfvm-language-server", + "version-string" : "1.0.0", + "builtin-baseline" : "8b04a7bd93bef991818fc372bb83ce00ec1c1c16", + "supports" : "windows & (x64 | x86) ", + "dependencies" : [ { + "name" : "nlohmann-json", + "version>=" : "3.11.2" + }, { + "name" : "sqlite-orm", + "version>=" : "1.8.2" + }, { + "name" : "sqlite3", + "version>=" : "3.42.0#1" + }, { + "name" : "poco", + "version>=" : "1.12.4#4" + } ] +} \ No newline at end of file