├── .cargo └── config.toml ├── .github ├── actions │ └── setup-rust-env │ │ └── action.yml ├── banner.png ├── code_actions.png ├── completion.png ├── completion2.png ├── completion3.png ├── diagnostics1.png ├── diagnostics2.png ├── diagnostics3.png ├── document_symbols.png ├── hover.png ├── hover2.png ├── jinja-lsp.mp4 ├── snippets.png └── workflows │ ├── CI-nodejs.yml │ ├── build-extension.yml │ ├── ci.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── act_script.sh ├── editors └── code │ ├── .gitignore │ ├── .vscode │ ├── launch.json │ └── settings.json │ ├── .vscodeignore │ ├── LICENSE │ ├── README.md │ ├── build-all.sh │ ├── build.sh │ ├── client │ ├── package.json │ ├── src │ │ ├── binaryName.ts │ │ └── extension.ts │ └── tsconfig.json │ ├── media │ └── icon.png │ ├── package-lock.json │ ├── package.json │ ├── tsconfig.json │ └── webpack.config.js ├── example ├── .gitignore ├── .python-version ├── Cargo.toml ├── README.md ├── pyproject.toml ├── requirements-dev.lock ├── requirements.lock ├── src │ ├── main.py │ └── main.rs └── templates │ ├── account.jinja │ ├── header.jinja │ └── hello.jinja ├── jinja-lsp-nodejs ├── .cargo │ └── config.toml ├── .gitignore ├── .npmignore ├── .yarn │ └── releases │ │ └── yarn-4.2.2.cjs ├── .yarnrc.yml ├── Cargo.toml ├── __test__ │ └── index.spec.mjs ├── build.rs ├── example.mjs ├── index.d.ts ├── index.js ├── npm │ ├── android-arm-eabi │ │ ├── README.md │ │ └── package.json │ ├── android-arm64 │ │ ├── README.md │ │ └── package.json │ ├── darwin-arm64 │ │ ├── README.md │ │ └── package.json │ ├── darwin-universal │ │ ├── README.md │ │ └── package.json │ ├── freebsd-x64 │ │ ├── README.md │ │ └── package.json │ ├── gnu-linux │ │ ├── README.md │ │ └── package.json │ ├── linux-arm-gnueabihf │ │ ├── README.md │ │ └── package.json │ ├── linux-arm-musleabihf │ │ ├── README.md │ │ └── package.json │ ├── linux-arm64-gnu │ │ ├── README.md │ │ └── package.json │ ├── linux-arm64-musl │ │ ├── README.md │ │ └── package.json │ ├── linux-riscv64-gnu │ │ ├── README.md │ │ └── package.json │ ├── linux-x64-gnu │ │ ├── README.md │ │ └── package.json │ ├── linux-x64-musl │ │ ├── README.md │ │ └── package.json │ ├── win32-arm64-msvc │ │ ├── README.md │ │ └── package.json │ ├── win32-ia32-msvc │ │ ├── README.md │ │ └── package.json │ └── win32-x64-msvc │ │ ├── README.md │ │ └── package.json ├── package-lock.json ├── package.json ├── rustfmt.toml ├── src │ └── lib.rs └── yarn.lock ├── jinja-lsp-queries ├── Cargo.toml └── src │ ├── lib.rs │ ├── lsp_helper.rs │ ├── parsers.rs │ ├── search │ ├── definition.rs │ ├── mod.rs │ ├── objects.rs │ ├── python_identifiers.rs │ ├── queries.rs │ ├── rust_identifiers.rs │ ├── rust_template_completion.rs │ ├── snippets_completion.rs │ ├── templates.rs │ └── test_queries.rs │ ├── to_input_edit.rs │ ├── tree_builder.rs │ └── types.rs ├── jinja-lsp ├── Cargo.toml ├── README.md └── src │ ├── backend.rs │ ├── channels │ ├── diagnostics.rs │ ├── lsp.rs │ └── mod.rs │ ├── config.rs │ ├── filter │ ├── md │ │ └── filters │ │ │ ├── abs.md │ │ │ ├── attr.md │ │ │ ├── batch.md │ │ │ ├── bool.md │ │ │ ├── capitalize.md │ │ │ ├── contrib │ │ │ ├── dateformat.md │ │ │ ├── datetimeformat.md │ │ │ ├── pluralize.md │ │ │ └── timeformat.md │ │ │ ├── default.md │ │ │ ├── dictsort.md │ │ │ ├── escape.md │ │ │ ├── first.md │ │ │ ├── float.md │ │ │ ├── indent.md │ │ │ ├── int.md │ │ │ ├── items.md │ │ │ ├── join.md │ │ │ ├── last.md │ │ │ ├── length.md │ │ │ ├── list.md │ │ │ ├── lower.md │ │ │ ├── map.md │ │ │ ├── max.md │ │ │ ├── min.md │ │ │ ├── pprint.md │ │ │ ├── reject.md │ │ │ ├── rejectattr.md │ │ │ ├── replace.md │ │ │ ├── reverse.md │ │ │ ├── round.md │ │ │ ├── safe.md │ │ │ ├── select.md │ │ │ ├── selectattr.md │ │ │ ├── slice.md │ │ │ ├── sort.md │ │ │ ├── title.md │ │ │ ├── tojson.md │ │ │ ├── trim.md │ │ │ ├── unique.md │ │ │ ├── upper.md │ │ │ └── urlencode.md │ └── mod.rs │ ├── lib.rs │ ├── lsp_files.rs │ ├── main.rs │ └── template_tests │ ├── md │ ├── is_boolean.md │ ├── is_defined.md │ ├── is_divisibleby.md │ ├── is_endingwith.md │ ├── is_eq.md │ ├── is_even.md │ ├── is_false.md │ ├── is_filter.md │ ├── is_float.md │ ├── is_ge.md │ ├── is_gt.md │ ├── is_in.md │ ├── is_integer.md │ ├── is_iterable.md │ ├── is_le.md │ ├── is_lower.md │ ├── is_lt.md │ ├── is_mapping.md │ ├── is_ne.md │ ├── is_none.md │ ├── is_number.md │ ├── is_odd.md │ ├── is_safe.md │ ├── is_sameas.md │ ├── is_sequence.md │ ├── is_startingwith.md │ ├── is_string.md │ ├── is_test.md │ ├── is_true.md │ ├── is_undefined.md │ └── is_upper.md │ └── mod.rs └── rust-toolchains.toml /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | lint = "clippy --workspace --all-targets -- --deny warnings" 3 | -------------------------------------------------------------------------------- /.github/actions/setup-rust-env/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup Rust Environment" 2 | 3 | description: "Setup the Rust CI environment for GitHub Action runners" 4 | 5 | runs: 6 | using: composite 7 | steps: 8 | - name: Setup Rust toolchain 9 | uses: dtolnay/rust-toolchain@master 10 | with: 11 | toolchain: stable 12 | targets: > 13 | x86_64-unknown-linux-gnu, 14 | x86_64-pc-windows-msvc, 15 | aarch64-pc-windows-msvc, 16 | x86_64-apple-darwin, 17 | aarch64-apple-darwin 18 | components: rustfmt, clippy 19 | 20 | - name: Setup rust cache 21 | uses: Swatinem/rust-cache@v2 22 | -------------------------------------------------------------------------------- /.github/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/banner.png -------------------------------------------------------------------------------- /.github/code_actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/code_actions.png -------------------------------------------------------------------------------- /.github/completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/completion.png -------------------------------------------------------------------------------- /.github/completion2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/completion2.png -------------------------------------------------------------------------------- /.github/completion3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/completion3.png -------------------------------------------------------------------------------- /.github/diagnostics1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/diagnostics1.png -------------------------------------------------------------------------------- /.github/diagnostics2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/diagnostics2.png -------------------------------------------------------------------------------- /.github/diagnostics3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/diagnostics3.png -------------------------------------------------------------------------------- /.github/document_symbols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/document_symbols.png -------------------------------------------------------------------------------- /.github/hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/hover.png -------------------------------------------------------------------------------- /.github/hover2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/hover2.png -------------------------------------------------------------------------------- /.github/jinja-lsp.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/jinja-lsp.mp4 -------------------------------------------------------------------------------- /.github/snippets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/.github/snippets.png -------------------------------------------------------------------------------- /.github/workflows/CI-nodejs.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | env: 3 | DEBUG: napi:* 4 | APP_NAME: functions 5 | MACOSX_DEPLOYMENT_TARGET: '10.13' 6 | permissions: 7 | contents: write 8 | id-token: write 9 | 'on': 10 | push: 11 | branches: 12 | - nodejs-completion-duplicate 13 | tags-ignore: 14 | - '**' 15 | paths-ignore: 16 | - '**/*.md' 17 | - LICENSE 18 | - '**/*.gitignore' 19 | - .editorconfig 20 | - docs/** 21 | pull_request: null 22 | jobs: 23 | build: 24 | defaults: 25 | run: 26 | working-directory: jinja-lsp-nodejs 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | settings: 31 | - host: macos-latest 32 | target: x86_64-apple-darwin 33 | build: yarn build --target x86_64-apple-darwin 34 | - host: windows-latest 35 | build: | 36 | yarn build --target i686-pc-windows-msvc && yarn test 37 | target: i686-pc-windows-msvc 38 | - host: windows-latest 39 | build: | 40 | yarn build --target x86_64-pc-windows-msvc && yarn test 41 | target: x86_64-pc-windows-msvc 42 | code-target: win32-x64 43 | - host: ubuntu-latest 44 | target: x86_64-unknown-linux-musl 45 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 46 | build: cd jinja-lsp-nodejs && yarn build --target x86_64-unknown-linux-musl 47 | - host: macos-latest 48 | target: aarch64-apple-darwin 49 | build: yarn build --target aarch64-apple-darwin 50 | - host: ubuntu-latest 51 | target: aarch64-unknown-linux-gnu 52 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 53 | build: cd jinja-lsp-nodejs && yarn build --target aarch64-unknown-linux-gnu 54 | - host: ubuntu-latest 55 | target: armv7-unknown-linux-gnueabihf 56 | setup: | 57 | sudo apt-get update 58 | sudo apt-get install gcc-arm-linux-gnueabihf -y 59 | build: yarn build --target armv7-unknown-linux-gnueabihf 60 | - host: ubuntu-latest 61 | target: armv7-unknown-linux-musleabihf 62 | build: yarn build --target armv7-unknown-linux-musleabihf 63 | - host: ubuntu-latest 64 | target: aarch64-linux-android 65 | build: yarn build --target aarch64-linux-android 66 | - host: ubuntu-latest 67 | target: armv7-linux-androideabi 68 | build: yarn build --target armv7-linux-androideabi 69 | - host: ubuntu-latest 70 | target: aarch64-unknown-linux-musl 71 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 72 | build: |- 73 | cd jinja-lsp-nodejs && set -e && 74 | rustup target add aarch64-unknown-linux-musl && 75 | yarn build --target aarch64-unknown-linux-musl 76 | - host: windows-latest 77 | target: aarch64-pc-windows-msvc 78 | build: yarn build --target aarch64-pc-windows-msvc 79 | - host: ubuntu-latest 80 | target: riscv64gc-unknown-linux-gnu 81 | setup: | 82 | sudo apt-get update 83 | sudo apt-get install gcc-riscv64-linux-gnu -y 84 | build: yarn build --target riscv64gc-unknown-linux-gnu 85 | - host: ubuntu-latest 86 | build: | 87 | yarn build --target x86_64-unknown-linux-gnu 88 | target: x86_64-unknown-linux-gnu 89 | setup: | 90 | sudo apt-get install gcc-aarch64-linux-gnu 91 | rustup target add aarch64-unknown-linux-gnu 92 | name: stable - ${{ matrix.settings.target }} - node@20 93 | runs-on: ${{ matrix.settings.host }} 94 | steps: 95 | - uses: actions/checkout@v4 96 | - name: Setup node 97 | uses: actions/setup-node@v4 98 | if: ${{ !matrix.settings.docker }} 99 | with: 100 | node-version: 20 101 | cache: yarn 102 | cache-dependency-path: ./jinja-lsp-nodejs/yarn.lock 103 | - name: Install 104 | uses: dtolnay/rust-toolchain@stable 105 | if: ${{ !matrix.settings.docker }} 106 | with: 107 | toolchain: stable 108 | targets: ${{ matrix.settings.target }} 109 | - name: Cache cargo 110 | uses: actions/cache@v4 111 | with: 112 | path: | 113 | ~/.cargo/registry/index/ 114 | ~/.cargo/registry/cache/ 115 | ~/.cargo/git/db/ 116 | .cargo-cache 117 | target/ 118 | key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} 119 | - uses: goto-bus-stop/setup-zig@v2 120 | if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' || matrix.settings.target == 'armv7-unknown-linux-musleabihf' }} 121 | with: 122 | version: 0.11.0 123 | - name: Setup toolchain 124 | run: ${{ matrix.settings.setup }} 125 | if: ${{ matrix.settings.setup }} 126 | shell: bash 127 | - name: Setup node x86 128 | if: matrix.settings.target == 'i686-pc-windows-msvc' 129 | run: yarn config set supportedArchitectures.cpu "ia32" 130 | shell: bash 131 | - name: Install dependencies 132 | run: yarn install 133 | - name: Setup node x86 134 | uses: actions/setup-node@v4 135 | if: matrix.settings.target == 'i686-pc-windows-msvc' 136 | with: 137 | node-version: 20 138 | cache: yarn 139 | architecture: x86 140 | cache-dependency-path: ./jinja-lsp-nodejs/yarn.lock 141 | - name: Build in docker 142 | uses: addnab/docker-run-action@v3 143 | if: ${{ matrix.settings.docker }} 144 | with: 145 | image: ${{ matrix.settings.docker }} 146 | options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build/' 147 | run: ${{ matrix.settings.build }} 148 | - name: Build 149 | run: ${{ matrix.settings.build }} 150 | if: ${{ !matrix.settings.docker }} 151 | shell: bash 152 | - name: Upload artifact 153 | uses: actions/upload-artifact@v4 154 | with: 155 | name: bindings-${{ matrix.settings.target }} 156 | path: jinja-lsp-nodejs/${{ env.APP_NAME }}.*.node 157 | if-no-files-found: error 158 | # build-freebsd: 159 | # runs-on: macos-13 160 | # name: Build FreeBSD 161 | # steps: 162 | # - uses: actions/checkout@v4 163 | # - name: Build 164 | # id: build 165 | # uses: cross-platform-actions/action@v0.24.0 166 | # env: 167 | # DEBUG: napi:* 168 | # RUSTUP_IO_THREADS: 1 169 | # with: 170 | # operating_system: freebsd 171 | # version: '13.2' 172 | # memory: 8G 173 | # cpu_count: 3 174 | # environment_variables: DEBUG RUSTUP_IO_THREADS 175 | # shell: bash 176 | # run: | 177 | # sudo pkg install -y -f curl node libnghttp2 npm 178 | # sudo npm install -g yarn --ignore-scripts 179 | # curl https://sh.rustup.rs -sSf --output rustup.sh 180 | # sh rustup.sh -y --profile minimal --default-toolchain stable 181 | # source "$HOME/.cargo/env" 182 | # echo "~~~~ rustc --version ~~~~" 183 | # rustc --version 184 | # echo "~~~~ node -v ~~~~" 185 | # node -v 186 | # echo "~~~~ yarn --version ~~~~" 187 | # yarn --version 188 | # pwd 189 | # ls -lah 190 | # whoami 191 | # env 192 | # freebsd-version 193 | # yarn install 194 | # yarn build 195 | # yarn test 196 | # rm -rf node_modules 197 | # rm -rf target 198 | # rm -rf .yarn/cache 199 | # - name: Upload artifact 200 | # uses: actions/upload-artifact@v4 201 | # with: 202 | # name: bindings-freebsd 203 | # path: jinja-lsp-nodejs/${{ env.APP_NAME }}.*.node 204 | # if-no-files-found: error 205 | 206 | test-macOS-windows-binding: 207 | defaults: 208 | run: 209 | working-directory: jinja-lsp-nodejs 210 | name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} 211 | needs: 212 | - build 213 | strategy: 214 | fail-fast: false 215 | matrix: 216 | settings: 217 | - host: macos-latest 218 | target: x86_64-apple-darwin 219 | node: 220 | - '18' 221 | - '20' 222 | runs-on: ${{ matrix.settings.host }} 223 | steps: 224 | - uses: actions/checkout@v4 225 | - name: Setup node 226 | uses: actions/setup-node@v4 227 | with: 228 | node-version: ${{ matrix.node }} 229 | cache: yarn 230 | architecture: x64 231 | cache-dependency-path: ./jinja-lsp-nodejs/yarn.lock 232 | - name: Install dependencies 233 | run: yarn install 234 | - name: Download artifacts 235 | uses: actions/download-artifact@v4 236 | with: 237 | name: bindings-${{ matrix.settings.target }} 238 | path: jinja-lsp-nodejs 239 | - name: List packages 240 | run: ls -R . 241 | shell: bash 242 | - name: Test bindings 243 | run: yarn test 244 | test-linux-x64-musl-binding: 245 | defaults: 246 | run: 247 | working-directory: jinja-lsp-nodejs 248 | name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} 249 | needs: 250 | - build 251 | strategy: 252 | fail-fast: false 253 | matrix: 254 | node: 255 | - '18' 256 | - '20' 257 | runs-on: ubuntu-latest 258 | steps: 259 | - uses: actions/checkout@v4 260 | - name: Setup node 261 | uses: actions/setup-node@v4 262 | with: 263 | node-version: ${{ matrix.node }} 264 | cache: yarn 265 | cache-dependency-path: ./jinja-lsp-nodejs/yarn.lock 266 | - name: Install dependencies 267 | run: | 268 | yarn config set supportedArchitectures.libc "musl" 269 | yarn install 270 | - name: Download artifacts 271 | uses: actions/download-artifact@v4 272 | with: 273 | name: bindings-x86_64-unknown-linux-musl 274 | path: jinja-lsp-nodejs 275 | - name: List packages 276 | run: ls -R . 277 | shell: bash 278 | - name: Test bindings 279 | run: docker run --rm -v $(pwd):/build -w /build/jinja-lsp-nodejs node:${{ matrix.node }}-alpine yarn test 280 | test-linux-aarch64-gnu-binding: 281 | defaults: 282 | run: 283 | working-directory: jinja-lsp-nodejs 284 | name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }} 285 | needs: 286 | - build 287 | strategy: 288 | fail-fast: false 289 | matrix: 290 | node: 291 | - '18' 292 | - '20' 293 | runs-on: ubuntu-latest 294 | steps: 295 | - uses: actions/checkout@v4 296 | - name: Download artifacts 297 | uses: actions/download-artifact@v4 298 | with: 299 | name: bindings-aarch64-unknown-linux-gnu 300 | path: jinja-lsp-nodejs 301 | - name: List packages 302 | run: ls -R . 303 | shell: bash 304 | - name: Install dependencies 305 | run: | 306 | yarn config set supportedArchitectures.cpu "arm64" 307 | yarn config set supportedArchitectures.libc "glibc" 308 | yarn install 309 | - name: Set up QEMU 310 | uses: docker/setup-qemu-action@v3 311 | with: 312 | platforms: arm64 313 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 314 | - name: Setup and run tests 315 | uses: addnab/docker-run-action@v3 316 | with: 317 | image: node:${{ matrix.node }}-slim 318 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build/jinja-lsp-nodejs' 319 | run: | 320 | set -e 321 | yarn test 322 | ls -la 323 | test-linux-aarch64-musl-binding: 324 | defaults: 325 | run: 326 | working-directory: jinja-lsp-nodejs 327 | name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }} 328 | needs: 329 | - build 330 | runs-on: ubuntu-latest 331 | steps: 332 | - uses: actions/checkout@v4 333 | - name: Download artifacts 334 | uses: actions/download-artifact@v4 335 | with: 336 | name: bindings-aarch64-unknown-linux-musl 337 | path: jinja-lsp-nodejs 338 | - name: List packages 339 | run: ls -R . 340 | shell: bash 341 | - name: Install dependencies 342 | run: | 343 | yarn config set supportedArchitectures.cpu "arm64" 344 | yarn config set supportedArchitectures.libc "musl" 345 | yarn install 346 | - name: Set up QEMU 347 | uses: docker/setup-qemu-action@v3 348 | with: 349 | platforms: arm64 350 | - run: 351 | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 352 | - name: Setup and run tests 353 | uses: addnab/docker-run-action@v3 354 | with: 355 | image: node:lts-alpine 356 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build/jinja-lsp-nodejs' 357 | run: | 358 | set -e 359 | yarn test 360 | test-linux-arm-gnueabihf-binding: 361 | defaults: 362 | run: 363 | working-directory: jinja-lsp-nodejs 364 | name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }} 365 | needs: 366 | - build 367 | strategy: 368 | fail-fast: false 369 | matrix: 370 | node: 371 | - '18' 372 | - '20' 373 | runs-on: ubuntu-latest 374 | steps: 375 | - uses: actions/checkout@v4 376 | - name: Download artifacts 377 | uses: actions/download-artifact@v4 378 | with: 379 | name: bindings-armv7-unknown-linux-gnueabihf 380 | path: jinja-lsp-nodejs 381 | - name: List packages 382 | run: ls -R . 383 | shell: bash 384 | - name: Install dependencies 385 | run: | 386 | yarn config set supportedArchitectures.cpu "arm" 387 | yarn install 388 | - name: Set up QEMU 389 | uses: docker/setup-qemu-action@v3 390 | with: 391 | platforms: arm 392 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 393 | - name: Setup and run tests 394 | uses: addnab/docker-run-action@v3 395 | with: 396 | image: node:${{ matrix.node }}-bullseye-slim 397 | options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build/ -w /build/jinja-lsp-nodejs' 398 | run: | 399 | set -e 400 | yarn test 401 | ls -la 402 | universal-macOS: 403 | defaults: 404 | run: 405 | working-directory: jinja-lsp-nodejs 406 | name: Build universal macOS binary 407 | needs: 408 | - build 409 | runs-on: macos-latest 410 | steps: 411 | - uses: actions/checkout@v4 412 | - name: Setup node 413 | uses: actions/setup-node@v4 414 | with: 415 | node-version: 20 416 | cache: yarn 417 | cache-dependency-path: ./jinja-lsp-nodejs/yarn.lock 418 | - name: Install dependencies 419 | run: yarn install 420 | - name: Download macOS x64 artifact 421 | uses: actions/download-artifact@v4 422 | with: 423 | name: bindings-x86_64-apple-darwin 424 | path: jinja-lsp-nodejs/artifacts 425 | - name: Download macOS arm64 artifact 426 | uses: actions/download-artifact@v4 427 | with: 428 | name: bindings-aarch64-apple-darwin 429 | path: jinja-lsp-nodejs/artifacts 430 | - name: Combine binaries 431 | run: yarn universal 432 | - name: Upload artifact 433 | uses: actions/upload-artifact@v4 434 | with: 435 | name: bindings-universal-apple-darwin 436 | path: jinja-lsp-nodejs/${{ env.APP_NAME }}.*.node 437 | if-no-files-found: error 438 | publish: 439 | name: Publish 440 | runs-on: ubuntu-latest 441 | defaults: 442 | run: 443 | working-directory: jinja-lsp-nodejs 444 | needs: 445 | - test-macOS-windows-binding 446 | - test-linux-x64-musl-binding 447 | - test-linux-aarch64-gnu-binding 448 | - test-linux-aarch64-musl-binding 449 | - test-linux-arm-gnueabihf-binding 450 | - universal-macOS 451 | steps: 452 | - uses: actions/checkout@v4 453 | - name: Setup node 454 | uses: actions/setup-node@v4 455 | with: 456 | node-version: 20 457 | cache: yarn 458 | cache-dependency-path: ./jinja-lsp-nodejs/yarn.lock 459 | - name: Install dependencies 460 | run: yarn install 461 | - name: Download all artifacts 462 | uses: actions/download-artifact@v4 463 | with: 464 | path: jinja-lsp-nodejs/artifacts 465 | - name: Move artifacts 466 | run: yarn artifacts 467 | - name: List packages 468 | run: ls -R ./npm 469 | shell: bash 470 | - name: Publish 471 | run: | 472 | npm config set provenance true 473 | if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; 474 | then 475 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN2" >> ~/.npmrc 476 | npm publish --access public 477 | elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+"; 478 | then 479 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN2" >> ~/.npmrc 480 | npm publish --tag next --access public 481 | else 482 | echo "Not a release, skipping publish" 483 | fi 484 | env: 485 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 486 | NPM_TOKEN2: ${{ secrets.NPM_TOKEN2 }} 487 | -------------------------------------------------------------------------------- /.github/workflows/build-extension.yml: -------------------------------------------------------------------------------- 1 | name: build-extension 2 | 3 | on: 4 | workflow_dispatch 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | 11 | 12 | - name: Get latest release version 13 | run: | 14 | LATEST_TAG=$(curl -s https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r .tag_name) 15 | echo "Latest release is: $LATEST_TAG" 16 | echo "LATEST_TAG=$LATEST_TAG" 17 | echo "version=$LATEST_TAG" >> $GITHUB_ENV 18 | 19 | 20 | - name: Checkout repository 21 | uses: actions/checkout@v4 22 | 23 | 24 | - name: Install Node.js 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 22 28 | 29 | - name: Download and extract extension 30 | run: | 31 | echo $LATEST_TAG 32 | wget "https://github.com/uros-5/jinja-lsp/releases/download/${{ env.version }}/jinja-lsp.zip" 33 | mkdir binaries 34 | unzip jinja-lsp -d binaries 35 | mkdir extensions 36 | 37 | - name: Npm install 38 | run: | 39 | npm install 40 | npm install @vscode/vsce -g 41 | npm install vscode-languageclient 42 | working-directory: editors/code 43 | 44 | - name: Build extensions 45 | run: | 46 | sh build-all.sh 47 | working-directory: editors/code 48 | 49 | - name: Upload artifacts 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: jinja-lsp-extension 53 | path: ./extensions 54 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | 8 | jobs: 9 | clippy: 10 | name: Clippy 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v3 15 | 16 | - name: Setup Rust env 17 | uses: ./.github/actions/setup-rust-env 18 | 19 | - name: Rust clippy 20 | run: cargo clippy -- -Dclippy::all -D warnings 21 | 22 | rustfmt: 23 | name: Format 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout code 27 | uses: actions/checkout@v3 28 | 29 | - name: Setup Rust env 30 | uses: ./.github/actions/setup-rust-env 31 | 32 | - name: Rust fmt 33 | run: cargo fmt --check 34 | 35 | test: 36 | name: Test 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout code 40 | uses: actions/checkout@v3 41 | 42 | - name: Setup Rust env 43 | uses: "./.github/actions/setup-rust-env" 44 | 45 | - name: Rust test 46 | run: cargo test --workspace 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build & Release 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ${{ matrix.build.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | build: 18 | - { 19 | NAME: linux-x64, 20 | OS: ubuntu-22.04, 21 | TARGET: x86_64-unknown-linux-gnu, 22 | } 23 | - { 24 | NAME: windows-x64, 25 | OS: windows-2022, 26 | TARGET: x86_64-pc-windows-msvc, 27 | } 28 | - { 29 | NAME: windows-arm64, 30 | OS: windows-2022, 31 | TARGET: aarch64-pc-windows-msvc, 32 | } 33 | - { 34 | NAME: darwin-x64, 35 | OS: macos-latest, 36 | TARGET: x86_64-apple-darwin, 37 | } 38 | - { 39 | NAME: darwin-arm64, 40 | OS: macos-latest, 41 | TARGET: aarch64-apple-darwin, 42 | } 43 | steps: 44 | - name: Checkout code 45 | uses: actions/checkout@v3 46 | 47 | - name: Setup Rust env 48 | uses: "./.github/actions/setup-rust-env" 49 | 50 | - name: Build 51 | run: cargo build --release --locked --target ${{ matrix.build.TARGET }} 52 | 53 | - name: Rename jinja-lsp binary 54 | shell: bash 55 | run: | 56 | binary_name="jinja-lsp" 57 | 58 | extension="" 59 | # windows binaries have ".exe" extension 60 | if [[ "${{ matrix.build.OS }}" == *"windows"* ]]; then 61 | extension=".exe" 62 | fi 63 | 64 | mkdir -p bin 65 | cp "target/${{ matrix.build.TARGET }}/release/${binary_name}" "bin/${binary_name}-${{ matrix.build.NAME }}${extension}" 66 | 67 | - name: Upload binary 68 | uses: actions/upload-artifact@v4 69 | with: 70 | name: jinja-lsp-${{ matrix.build.NAME }} 71 | path: bin/* 72 | 73 | release: 74 | name: Release 75 | runs-on: ubuntu-22.04 76 | needs: build 77 | if: github.ref == 'refs/heads/main' 78 | steps: 79 | - name: Checkout code 80 | uses: actions/checkout@v4 81 | with: 82 | fetch-depth: 2 83 | 84 | - name: Check if release should be created 85 | shell: bash 86 | run: | 87 | set -o pipefail 88 | 89 | RELEASE_VERSION=$(awk -F ' = ' '$1 ~ /version/ { gsub(/["]/, "", $2); printf("%s",$2) }' Cargo.toml) 90 | OLD_VERSION=$( curl -s --fail-with-body https://api.github.com/repos/uros-5/jinja-lsp/releases/latest | jq -r '.tag_name' ) 91 | 92 | echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV 93 | echo "$OLD_VERSION -> $RELEASE_VERSION" 94 | 95 | if [[ "$RELEASE_VERSION" == "$OLD_VERSION" ]] || ! [[ "$OLD_VERSION" =~ ^[0-9]\.[0-9]\.[0-9]$ ]]; then 96 | echo "SHOULD_RELEASE=no" >> $GITHUB_ENV 97 | else 98 | git tag "$RELEASE_VERSION" 99 | git push -u origin "$RELEASE_VERSION" 100 | echo "SHOULD_RELEASE=yes" >> $GITHUB_ENV 101 | fi 102 | 103 | - name: Download binaries 104 | uses: actions/download-artifact@v4 105 | if: env.SHOULD_RELEASE == 'yes' 106 | with: 107 | name: jinja-lsp 108 | path: bin 109 | 110 | - name: Publish release 111 | uses: softprops/action-gh-release@v1 112 | if: env.SHOULD_RELEASE == 'yes' 113 | with: 114 | files: bin/* 115 | tag_name: ${{ env.RELEASE_VERSION }} 116 | fail_on_unmatched_files: true 117 | generate_release_notes: true 118 | env: 119 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 120 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | node_modules 3 | out/ 4 | .pnpm-debug.log 5 | *.ast 6 | *.nrs 7 | dist/ 8 | *.vsix 9 | editors/code/pnpm-lock.yaml 10 | editors/code/client/pnpm-lock.yaml 11 | editors/code/.vscode/tasks.json 12 | editors/code/client/package-lock.json 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "jinja-lsp-queries", 5 | "jinja-lsp", 6 | "jinja-lsp-nodejs" 7 | ] 8 | 9 | [profile.dev] 10 | opt-level = 1 11 | 12 | [profile.release] 13 | strip = true 14 | opt-level = 3 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 IWANABETHATGUY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | banner 2 | 3 | jinja-lsp enhances minijinja development experience by providing Helix/Nvim users with advanced features such as autocomplete, syntax highlighting, hover, goto definition, code actions and linting. 4 | 5 |
6 | crates.io 7 | visualstudio.com 8 |
9 | 10 | ## Installation 11 | 12 | ```sh 13 | cargo install jinja-lsp 14 | ``` 15 | 16 | 17 | ## Features 18 | 19 | ### Autocomplete 20 | 21 | Intelligent suggestions for variables in current template, as well as variables, templates and filters defined on backend side. 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ### Linting 30 | 31 | Highlights errors and potential bugs in your jinja templates. 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ### Hover Preview 40 | 41 | See the complete filter or variable description by hovering over it. 42 | 43 | 44 | 45 | 46 | 47 | ### Code Actions 48 | 49 | It's recommended to reset variables on server in case you rename/delete file. 50 | 51 | 52 | 53 | ### Goto Definition 54 | 55 | Quickly jump to definition. Works for Rust identifiers as well. 56 | 57 | https://github.com/uros-5/jinja-lsp/assets/59397844/015e47b4-b6f6-47c0-8504-5ce79ebafb00 58 | 59 | ### Snippets 60 | 61 | 62 | 63 | ### Document symbols 64 | 65 | 66 | 67 | ## Configuration 68 | 69 | Language server configuration(all fields are optional) 70 | 71 | ```json 72 | { "templates": "./TEMPLATES_DIR", "backend": ["./BACKEND_DIR"], "lang": "rust"} 73 | ```` 74 | 75 | Helix configuration 76 | 77 | ```toml 78 | [language-server.jinja-lsp] 79 | command = "jinja-lsp" 80 | config = { templates = "./templates", backend = ["./src"], lang = "rust"} 81 | timeout = 5 82 | 83 | [[language]] 84 | name = "jinja" 85 | language-servers = ["jinja-lsp"] 86 | ``` 87 | 88 | Neovim configuration 89 | 90 | `init.lua`, I use [kickstarter.nvim](https://github.com/nvim-lua/kickstart.nvim), it uses Mason.nvim for installing language servers. 91 | 92 | ```lua 93 | 94 | -- if you want to debug 95 | vim.lsp.set_log_level("debug") 96 | 97 | 98 | require('lazy').setup( 99 | -- your other configs 100 | { 101 | -- Main LSP Configuration 102 | 'neovim/nvim-lspconfig', 103 | dependencies = { 104 | -- dependencies 105 | }, 106 | config = function() { 107 | -- keybindings etc. 108 | 109 | vim.filetype.add { 110 | extension = { 111 | jinja = 'jinja', 112 | jinja2 = 'jinja', 113 | j2 = 'jinja', 114 | py = 'python' 115 | }, 116 | } 117 | local servers = { 118 | jinja_lsp = { 119 | filetypes = { 'jinja', 'rust', 'python' }, 120 | }, 121 | -- other servers 122 | } 123 | end 124 | } 125 | ) 126 | 127 | ``` 128 | 129 | ## Adding custom template extensions: 130 | 131 | ``` 132 | template_extensions = ["j2", "tex"] 133 | ``` 134 | 135 | You can also write configuration in: `pyproject.toml`, `Cargo.toml`, `jinja-lsp.toml`. 136 | 137 | Python 138 | 139 | ```toml 140 | [tool.jinja-lsp] 141 | templates = "./templates" 142 | backend = ["./src"] 143 | ``` 144 | 145 | Rust 146 | 147 | ```toml 148 | [metadata.jinja-lsp] 149 | templates = "./templates" 150 | backend = ["./src"] 151 | ``` 152 | 153 | Supported languages: Python, Rust 154 | -------------------------------------------------------------------------------- /act_script.sh: -------------------------------------------------------------------------------- 1 | 2 | # act -j show -P macos-latest=sickcodes/docker-osx -P windows-latest=dockurr/windows 3 | act --env-file .env -W .github/workflows/CI-nodejs.yaml -P macos-latest=sickcodes/docker-osx -P windows-latest=dockurr/windows 4 | -------------------------------------------------------------------------------- /editors/code/.gitignore: -------------------------------------------------------------------------------- 1 | .vsix 2 | -------------------------------------------------------------------------------- /editors/code/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "name": "Launch Client", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceRoot}" 12 | ], 13 | "outFiles": [ 14 | "${workspaceRoot}/client/out/**/*.js" 15 | ], 16 | "preLaunchTask": { 17 | "type": "npm", 18 | "script": "esbuild" 19 | }, 20 | "env": { 21 | "SERVER_PATH": "/home/uros/.cache/cargo/target/debug/jinja-lsp" 22 | } 23 | }, 24 | // { 25 | // "type": "node", 26 | // "request": "attach", 27 | // "name": "Attach to Server", 28 | // "port": 6009, 29 | // "restart": true, 30 | // "outFiles": ["${workspaceRoot}/server/out/**/*.js"] 31 | // }, 32 | { 33 | "name": "Language Server E2E Test", 34 | "type": "extensionHost", 35 | "request": "launch", 36 | "runtimeExecutable": "${execPath}", 37 | "args": [ 38 | "--extensionDevelopmentPath=${workspaceRoot}", 39 | "--extensionTestsPath=${workspaceRoot}/client/out/test/index", 40 | "${workspaceRoot}/client/testFixture" 41 | ], 42 | "outFiles": [ 43 | "${workspaceRoot}/client/out/test/**/*.js" 44 | ] 45 | } 46 | ], 47 | "compounds": [ 48 | { 49 | "name": "Client + Server", 50 | "configurations": [ 51 | "Launch Client" 52 | ] 53 | } 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /editors/code/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off", 11 | "files.associations": { 12 | "*.yaml": "yaml" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /editors/code/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | client/node_modules 4 | out/ 5 | src/ 6 | tsconfig.json 7 | build.sh 8 | build-all.sh 9 | -------------------------------------------------------------------------------- /editors/code/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 IWANABETHATGUY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /editors/code/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /editors/code/build-all.sh: -------------------------------------------------------------------------------- 1 | 2 | sh ./build.sh jinja-lsp-darwin-arm64 jinja-lsp-darwin-arm64 darwin-arm64 3 | sh ./build.sh jinja-lsp-darwin-x64 jinja-lsp-darwin-x64 darwin-x64 4 | sh ./build.sh jinja-lsp-linux-x64 jinja-lsp-linux-x64 linux-x64 5 | sh ./build.sh jinja-lsp-windows-arm64.exe jinja-lsp-windows-arm64 win32-arm64 6 | sh ./build.sh jinja-lsp-windows-x64.exe jinja-lsp-windows-x64 win32-x64 7 | -------------------------------------------------------------------------------- /editors/code/build.sh: -------------------------------------------------------------------------------- 1 | 2 | echo "export const binaryName = '$1';" > client/src/binaryName.ts 3 | 4 | cp -r "../../binaries/$1" ./media/ 5 | 6 | npm run vscode:prepublish && vsce package -o "$2.vsix" --target $3 7 | 8 | rm -rf "./media/$1" 9 | mv "$2.vsix" ../../extensions 10 | -------------------------------------------------------------------------------- /editors/code/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jinja-lsp-client", 3 | "description": "VSCode part of a language server", 4 | "author": "uros-5", 5 | "license": "MIT", 6 | "version": "0.1.83", 7 | "publisher": "uros", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/uros-5/jinja-lsp" 11 | }, 12 | "engines": { 13 | "vscode": "^1.75.0" 14 | }, 15 | "dependencies": { 16 | "vscode-languageclient": "^9.0.1" 17 | }, 18 | "devDependencies": { 19 | "@types/vscode": "^1.75.1", 20 | "@vscode/test-electron": "^2.3.9" 21 | } 22 | } -------------------------------------------------------------------------------- /editors/code/client/src/binaryName.ts: -------------------------------------------------------------------------------- 1 | export const binaryName = 'jinja-lsp-windows-x64.exe'; 2 | -------------------------------------------------------------------------------- /editors/code/client/src/extension.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import * as path from 'path'; 7 | import { workspace, ExtensionContext } from 'vscode'; 8 | 9 | import { 10 | LanguageClient, 11 | LanguageClientOptions, 12 | ServerOptions, 13 | } from 'vscode-languageclient/node'; 14 | import * as child_process from 'child_process'; 15 | import * as vscode from "vscode"; 16 | import { binaryName } from './binaryName'; 17 | let client: LanguageClient; 18 | 19 | export function activate(context: ExtensionContext) { 20 | let output = vscode.window.createOutputChannel("jinja-lsp"); 21 | // The server is implemented in node 22 | const serverModule = getServer(output, context.extensionPath); 23 | if (!serverModule.valid) { 24 | throw new Error(serverModule.name); 25 | } 26 | 27 | let config: Record = JSON.parse( 28 | JSON.stringify(workspace.getConfiguration("jinja-lsp")) 29 | ); 30 | 31 | // If the extension is launched in debug mode then the debug server options are used 32 | // Otherwise the run options are used 33 | const serverOptions: ServerOptions = { 34 | run: { command: serverModule.name }, 35 | debug: { 36 | command: serverModule.name, 37 | args: [], 38 | } 39 | }; 40 | 41 | // Options to control the language client 42 | const clientOptions: LanguageClientOptions = { 43 | // Register the server for plain text documents 44 | documentSelector: [{ scheme: 'file', language: 'jinja-html' }, { scheme: 'file', language: 'rust' }, { scheme: 'file', language: 'python' }], 45 | initializationOptions: config, 46 | synchronize: { 47 | // Notify the server about file changes to '.clientrc files contained in the workspace 48 | fileEvents: workspace.createFileSystemWatcher('**/.{jinja, rs, py}') 49 | } 50 | }; 51 | 52 | // Create the language client and start the client. 53 | client = new LanguageClient( 54 | 'jinja-lsp', 55 | 'Jinja language server', 56 | serverOptions, 57 | clientOptions 58 | ); 59 | 60 | // Start the client. This will also launch the server 61 | client.start(); 62 | } 63 | 64 | export function deactivate(): Thenable | undefined { 65 | if (!client) { 66 | return undefined; 67 | } 68 | return client.stop(); 69 | } 70 | 71 | function getServer(_output: vscode.OutputChannel, cwd: string): { valid: boolean, name: string } { 72 | try { 73 | // let name = "/home/uros/.cache/cargo/target/debug/jinja-lsp"; 74 | let name = path.join(cwd, "media", binaryName); 75 | const validation = child_process.spawnSync(name); 76 | if (validation.status === 0) { 77 | return { valid: true, name: name }; 78 | } 79 | else { 80 | return { valid: false, name: "Jinja language server not installed." } 81 | } 82 | 83 | } 84 | catch (e) { 85 | return { valid: false, name: "Jinja language server not installed." } 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /editors/code/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2020", 5 | "lib": [ 6 | "es2020" 7 | ], 8 | "outDir": "out", 9 | "rootDir": "src", 10 | "sourceMap": true 11 | }, 12 | "include": [ 13 | "src" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | ".vscode-test" 18 | ] 19 | } -------------------------------------------------------------------------------- /editors/code/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/editors/code/media/icon.png -------------------------------------------------------------------------------- /editors/code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jinja-lsp", 3 | "description": "jinja-lsp", 4 | "license": "MIT", 5 | "version": "0.1.86", 6 | "repository": { 7 | "url": "https://github.com/uros-5/jinja-lsp" 8 | }, 9 | "icon": "media/icon.png", 10 | "publisher": "urosmrkobrada", 11 | "categories": [ 12 | "Linters", 13 | "Programming Languages", 14 | "Other" 15 | ], 16 | "keywords": [ 17 | "multi-root ready", 18 | "jinja", 19 | "minijinja", 20 | "rust", 21 | "jinja-lsp", 22 | "python" 23 | ], 24 | "engines": { 25 | "vscode": "^1.75.0" 26 | }, 27 | "activationEvents": [ 28 | "onLanguage:jinja-html", 29 | "onLanguage:rust", 30 | "onLanguage:python" 31 | ], 32 | "main": "dist/extension", 33 | "contributes": { 34 | "configuration": { 35 | "type": "object", 36 | "title": "Example configuration", 37 | "properties": { 38 | "jinja-lsp.templates": { 39 | "type": "string", 40 | "description": "Templates directory" 41 | }, 42 | "jinja-lsp.backend": { 43 | "type": "array", 44 | "description": "Backend directories" 45 | }, 46 | "jinja-lsp.lang": { 47 | "type": "string", 48 | "enum": [ 49 | "rust", 50 | "python" 51 | ], 52 | "description": "Language that is used on backend" 53 | }, 54 | "jinja-lsp.hide_undefined": { 55 | "type": "boolean", 56 | "description": "Disable warnings about undefined variables" 57 | }, 58 | "jinja-lsp.template_extension": { 59 | "type": "array", 60 | "default": [ 61 | "html", 62 | "jinja", 63 | "j2" 64 | ], 65 | "description": "Add custom template extension" 66 | } 67 | } 68 | } 69 | }, 70 | "scripts": { 71 | "vscode:prepublish": "npm run esbuild-base -- --minify", 72 | "esbuild-base": "esbuild ./client/src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node", 73 | "esbuild": "npm run esbuild-base -- --sourcemap", 74 | "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", 75 | "test-compile": "tsc -p ./", 76 | "compile": "tsc -b", 77 | "watch": "tsc -b -w", 78 | "lint": "eslint ./client/src --ext .ts,.tsx", 79 | "test": "sh ./scripts/e2e.sh", 80 | "test2": "vscode-test" 81 | }, 82 | "devDependencies": { 83 | "@types/mocha": "^10.0.6", 84 | "@types/node": "^18.14.6", 85 | "@typescript-eslint/eslint-plugin": "^7.1.0", 86 | "@typescript-eslint/parser": "^7.1.0", 87 | "@vscode/test-cli": "^0.0.9", 88 | "@vscode/test-electron": "^2.3.9", 89 | "esbuild": "0.20.2", 90 | "eslint": "^8.57.0", 91 | "mocha": "^10.3.0", 92 | "typescript": "5.3.3" 93 | } 94 | } -------------------------------------------------------------------------------- /editors/code/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2020", 4 | "target": "es2020", 5 | "lib": [ 6 | "ES2020" 7 | ], 8 | "outDir": "out", 9 | "rootDir": "src", 10 | "sourceMap": true 11 | }, 12 | "include": [ 13 | "src" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | ".vscode-test" 18 | ], 19 | "references": [ 20 | { 21 | "path": "./client" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /editors/code/webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | "use strict"; 4 | 5 | const path = require("path"); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: "webworker", // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target 10 | 11 | entry: "./client/src/extension.ts", // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 12 | output: { 13 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 14 | path: path.resolve(__dirname, "dist"), 15 | filename: "extension.js", 16 | libraryTarget: "commonjs2", 17 | devtoolModuleFilenameTemplate: "../[resource-path]", 18 | }, 19 | devtool: "hidden-source-map", 20 | externals: { 21 | vscode: 22 | "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 23 | }, 24 | resolve: { 25 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 26 | mainFields: ["browser", "module", "main"], // look for `browser` entry point in imported node modules 27 | extensions: [".ts", ".js"], 28 | alias: {}, 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: "ts-loader", 38 | }, 39 | ], 40 | }, 41 | ], 42 | }, 43 | }; 44 | module.exports = config; 45 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # python generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | 9 | # venv 10 | .venv 11 | venv 12 | -------------------------------------------------------------------------------- /example/.python-version: -------------------------------------------------------------------------------- 1 | 3.10.13 2 | -------------------------------------------------------------------------------- /example/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "example" 4 | version = "0.1.0" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | minijinja = "1.0.10" 9 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | # from base directory(jinja-lsp) 3 | cd example 4 | git init . 5 | ``` 6 | -------------------------------------------------------------------------------- /example/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "example" 3 | version = "0.1.0" 4 | description = "Simple jinja-lsp example for Python" 5 | authors = [ 6 | { name = "uros-5"} 7 | ] 8 | dependencies = [ 9 | "jinja2>=3.1.4", 10 | ] 11 | readme = "README.md" 12 | requires-python = ">= 3.10" 13 | 14 | [build-system] 15 | requires = ["hatchling"] 16 | build-backend = "hatchling.build" 17 | 18 | [tool.rye] 19 | managed = true 20 | dev-dependencies = [] 21 | 22 | [tool.hatch.metadata] 23 | allow-direct-references = true 24 | 25 | [tool.hatch.build.targets.wheel] 26 | packages = ["src"] 27 | 28 | [tool.pyright] 29 | extraPaths = ["example/.venv/lib/python3.10/site-packages"] 30 | 31 | 32 | [tool.jinja-lsp] 33 | hide_undefined = false 34 | -------------------------------------------------------------------------------- /example/requirements-dev.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | 10 | -e file:. 11 | jinja2==3.1.4 12 | # via example 13 | markupsafe==2.1.5 14 | # via jinja2 15 | -------------------------------------------------------------------------------- /example/requirements.lock: -------------------------------------------------------------------------------- 1 | # generated by rye 2 | # use `rye lock` or `rye sync` to update this lockfile 3 | # 4 | # last locked with the following flags: 5 | # pre: false 6 | # features: [] 7 | # all-features: false 8 | # with-sources: false 9 | 10 | -e file:. 11 | jinja2==3.1.4 12 | # via example 13 | markupsafe==2.1.5 14 | # via jinja2 15 | -------------------------------------------------------------------------------- /example/src/main.py: -------------------------------------------------------------------------------- 1 | from jinja2 import Environment 2 | 3 | 4 | def main(): 5 | jinja_env = Environment() 6 | 7 | template = jinja_env.get_template("account.jinja") 8 | template.render( 9 | first_name="John", 10 | last_name="Doe", 11 | email="johndoe@example.com", 12 | phone_number="(123) 456-7890", 13 | street="123 Main St", 14 | city="Dallas", 15 | header_info="This is some information about the user.", 16 | ) 17 | 18 | jinja_env.globals["PROJECT_NAME"] = "example" 19 | -------------------------------------------------------------------------------- /example/src/main.rs: -------------------------------------------------------------------------------- 1 | use minijinja::{context, Environment}; 2 | 3 | fn main() { 4 | let mut jinja = Environment::new(); 5 | let _user = context! { 6 | first_name => "John", 7 | last_name => "Doe", 8 | email => "johndoe@example.com", 9 | phone_number => "(123) 456-7890", 10 | street => "123 Main St", 11 | city => "Dallas", 12 | header_info => "This is some information about the user.", 13 | }; 14 | jinja.add_global("PROJECT_NAME", "Example"); 15 | } 16 | -------------------------------------------------------------------------------- /example/templates/account.jinja: -------------------------------------------------------------------------------- 1 |
2 | {% include './templates/header.jinja' %} 3 | {{ hello_world() }} 4 | {% set abc = 55 %} 5 |
6 |
7 |
8 |
9 | Full name 10 |
11 |
12 | {{ first_name }} {{ last_name }} 13 |
14 |
15 |
16 |
17 | Email address 18 |
19 |
20 | {{ email }} 21 |
22 |
23 |
24 |
25 | Phone number 26 |
27 |
28 | {{ phone_number }} 29 |
30 |
31 |
32 |
33 | Address 34 |
35 |
36 | {{ street }}
37 | {{ city | capitalize }}, USA 12345 38 |
39 |
40 |
41 |
42 |
43 | -------------------------------------------------------------------------------- /example/templates/header.jinja: -------------------------------------------------------------------------------- 1 |
2 |

3 | User Profile 4 |

5 |

6 | {{ header_info | capitalize }} 7 | {{ additional_info }} 8 |

9 |
10 | -------------------------------------------------------------------------------- /example/templates/hello.jinja: -------------------------------------------------------------------------------- 1 | {% macro hello_world() -%} 2 |

hello world

3 | {{ PROJECT_NAME | length }} 4 | {% endmacro %} 5 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | linker = "aarch64-linux-musl-gcc" 3 | rustflags = ["-C", "target-feature=-crt-static"] -------------------------------------------------------------------------------- /jinja-lsp-nodejs/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # End of https://www.toptal.com/developers/gitignore/api/node 114 | 115 | # Created by https://www.toptal.com/developers/gitignore/api/macos 116 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos 117 | 118 | ### macOS ### 119 | # General 120 | .DS_Store 121 | .AppleDouble 122 | .LSOverride 123 | 124 | # Icon must end with two 125 | Icon 126 | 127 | 128 | # Thumbnails 129 | ._* 130 | 131 | # Files that might appear in the root of a volume 132 | .DocumentRevisions-V100 133 | .fseventsd 134 | .Spotlight-V100 135 | .TemporaryItems 136 | .Trashes 137 | .VolumeIcon.icns 138 | .com.apple.timemachine.donotpresent 139 | 140 | # Directories potentially created on remote AFP share 141 | .AppleDB 142 | .AppleDesktop 143 | Network Trash Folder 144 | Temporary Items 145 | .apdisk 146 | 147 | ### macOS Patch ### 148 | # iCloud generated files 149 | *.icloud 150 | 151 | # End of https://www.toptal.com/developers/gitignore/api/macos 152 | 153 | # Created by https://www.toptal.com/developers/gitignore/api/windows 154 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows 155 | 156 | ### Windows ### 157 | # Windows thumbnail cache files 158 | Thumbs.db 159 | Thumbs.db:encryptable 160 | ehthumbs.db 161 | ehthumbs_vista.db 162 | 163 | # Dump file 164 | *.stackdump 165 | 166 | # Folder config file 167 | [Dd]esktop.ini 168 | 169 | # Recycle Bin used on file shares 170 | $RECYCLE.BIN/ 171 | 172 | # Windows Installer files 173 | *.cab 174 | *.msi 175 | *.msix 176 | *.msm 177 | *.msp 178 | 179 | # Windows shortcuts 180 | *.lnk 181 | 182 | # End of https://www.toptal.com/developers/gitignore/api/windows 183 | 184 | #Added by cargo 185 | 186 | /target 187 | Cargo.lock 188 | 189 | .pnp.* 190 | .yarn/* 191 | !.yarn/patches 192 | !.yarn/plugins 193 | !.yarn/releases 194 | !.yarn/sdks 195 | !.yarn/versions 196 | 197 | *.node 198 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/.npmignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .cargo 4 | .github 5 | npm 6 | .eslintrc 7 | .prettierignore 8 | rustfmt.toml 9 | yarn.lock 10 | *.node 11 | .yarn 12 | __test__ 13 | renovate.json 14 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | yarnPath: .yarn/releases/yarn-4.2.2.cjs 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "jinja-lsp-nodejs" 4 | version = "0.1.85" 5 | license = "MIT" 6 | authors = ["uros-5"] 7 | description = "Bindings for jinja-lsp" 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix 14 | napi = { version = "2.12.2", default-features = false, features = ["napi4"] } 15 | napi-derive = "2.12.2" 16 | jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.85"} 17 | jinja-lsp = { path = "../jinja-lsp", version = "0.1.83"} 18 | tree-sitter = "0.23.0" 19 | tower-lsp = { version = "0.20.0", features = ["proposed"] } 20 | ropey = "1.5.0" 21 | tree-sitter-language = "0.1.0" 22 | 23 | [build-dependencies] 24 | napi-build = "2.0.1" 25 | 26 | [profile.release] 27 | lto = true 28 | strip = "symbols" 29 | opt-level = 3 30 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/__test__/index.spec.mjs: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import { NodejsLspFiles } from '../index.js' 4 | 5 | test('main class', (t) => { 6 | t.is(new NodejsLspFiles().getVariables("id", 11), null); 7 | }) 8 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/example.mjs: -------------------------------------------------------------------------------- 1 | import { sum } from './index.js' 2 | console.log(sum(1,2)) 3 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/index.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | /* auto-generated by NAPI-RS */ 5 | 6 | export function basic(content: string): number | null 7 | export interface JsPosition { 8 | line: number 9 | character: number 10 | } 11 | export const enum JsIdentifierType { 12 | ForLoopKey = 0, 13 | ForLoopValue = 1, 14 | ForLoopCount = 2, 15 | SetVariable = 3, 16 | WithVariable = 4, 17 | MacroName = 5, 18 | MacroParameter = 6, 19 | TemplateBlock = 7, 20 | BackendVariable = 8, 21 | UndefinedVariable = 9, 22 | JinjaTemplate = 10, 23 | Link = 11 24 | } 25 | export interface JsIdentifier { 26 | start: JsPosition 27 | end: JsPosition 28 | name: string 29 | identifierType: JsIdentifierType 30 | error?: string 31 | } 32 | export interface JsHover { 33 | kind: string 34 | value: string 35 | range?: JsRange 36 | label?: string 37 | documentaion?: string 38 | } 39 | export interface JsRange { 40 | start: JsPosition 41 | end: JsPosition 42 | } 43 | export interface JsLocation { 44 | uri: string 45 | range: JsRange 46 | isBackend: boolean 47 | name: string 48 | } 49 | export interface JsCompletionItem { 50 | completionType: JsCompletionType 51 | label: string 52 | kind: Kind2 53 | description: string 54 | newText?: string 55 | insert?: JsRange 56 | replace?: JsRange 57 | } 58 | export const enum Kind2 { 59 | VARIABLE = 0, 60 | FIELD = 1, 61 | FUNCTION = 2, 62 | MODULE = 3, 63 | CONSTANT = 4, 64 | FILE = 5, 65 | TEXT = 6 66 | } 67 | export const enum JsCompletionType { 68 | Filter = 0, 69 | Identifier = 1, 70 | Snippets = 2 71 | } 72 | export interface Action { 73 | name: string 74 | description: string 75 | } 76 | export class NodejsLspFiles { 77 | constructor() 78 | /** Actions can come from unsaved context. */ 79 | addLinkHints(uri: string, actions?: Array | undefined | null): void 80 | saveLinkHint(actions?: Array | undefined | null, hint?: string | undefined | null): void 81 | removeTempLinkHint(hint?: string | undefined | null): void 82 | deleteAll(filename: string): void 83 | addOne(id: number, filename: string, content: string, line: number, ext: string, col?: number | undefined | null): Array 84 | getVariables(id: string, line: number): Array | null 85 | hover(id: number, filename: string, line: number, position: JsPosition): JsHover | null 86 | complete(id: number, filename: string, line: number, position: JsPosition): Array | null 87 | gotoDefinition(id: number, filename: string, line: number, position: JsPosition): Array | null 88 | } 89 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/index.js: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /* prettier-ignore */ 4 | 5 | /* auto-generated by NAPI-RS */ 6 | 7 | const { existsSync, readFileSync } = require('fs') 8 | const { join } = require('path') 9 | 10 | const { platform, arch } = process 11 | 12 | let nativeBinding = null 13 | let localFileExisted = false 14 | let loadError = null 15 | 16 | function isMusl() { 17 | // For Node 10 18 | if (!process.report || typeof process.report.getReport !== 'function') { 19 | try { 20 | const lddPath = require('child_process').execSync('which ldd').toString().trim() 21 | return readFileSync(lddPath, 'utf8').includes('musl') 22 | } catch (e) { 23 | return true 24 | } 25 | } else { 26 | const { glibcVersionRuntime } = process.report.getReport().header 27 | return !glibcVersionRuntime 28 | } 29 | } 30 | 31 | switch (platform) { 32 | case 'android': 33 | switch (arch) { 34 | case 'arm64': 35 | localFileExisted = existsSync(join(__dirname, 'functions.android-arm64.node')) 36 | try { 37 | if (localFileExisted) { 38 | nativeBinding = require('./functions.android-arm64.node') 39 | } else { 40 | nativeBinding = require('@jinja-lsp/functions-android-arm64') 41 | } 42 | } catch (e) { 43 | loadError = e 44 | } 45 | break 46 | case 'arm': 47 | localFileExisted = existsSync(join(__dirname, 'functions.android-arm-eabi.node')) 48 | try { 49 | if (localFileExisted) { 50 | nativeBinding = require('./functions.android-arm-eabi.node') 51 | } else { 52 | nativeBinding = require('@jinja-lsp/functions-android-arm-eabi') 53 | } 54 | } catch (e) { 55 | loadError = e 56 | } 57 | break 58 | default: 59 | throw new Error(`Unsupported architecture on Android ${arch}`) 60 | } 61 | break 62 | case 'win32': 63 | switch (arch) { 64 | case 'x64': 65 | localFileExisted = existsSync( 66 | join(__dirname, 'functions.win32-x64-msvc.node') 67 | ) 68 | try { 69 | if (localFileExisted) { 70 | nativeBinding = require('./functions.win32-x64-msvc.node') 71 | } else { 72 | nativeBinding = require('@jinja-lsp/functions-win32-x64-msvc') 73 | } 74 | } catch (e) { 75 | loadError = e 76 | } 77 | break 78 | case 'ia32': 79 | localFileExisted = existsSync( 80 | join(__dirname, 'functions.win32-ia32-msvc.node') 81 | ) 82 | try { 83 | if (localFileExisted) { 84 | nativeBinding = require('./functions.win32-ia32-msvc.node') 85 | } else { 86 | nativeBinding = require('@jinja-lsp/functions-win32-ia32-msvc') 87 | } 88 | } catch (e) { 89 | loadError = e 90 | } 91 | break 92 | case 'arm64': 93 | localFileExisted = existsSync( 94 | join(__dirname, 'functions.win32-arm64-msvc.node') 95 | ) 96 | try { 97 | if (localFileExisted) { 98 | nativeBinding = require('./functions.win32-arm64-msvc.node') 99 | } else { 100 | nativeBinding = require('@jinja-lsp/functions-win32-arm64-msvc') 101 | } 102 | } catch (e) { 103 | loadError = e 104 | } 105 | break 106 | default: 107 | throw new Error(`Unsupported architecture on Windows: ${arch}`) 108 | } 109 | break 110 | case 'darwin': 111 | localFileExisted = existsSync(join(__dirname, 'functions.darwin-universal.node')) 112 | try { 113 | if (localFileExisted) { 114 | nativeBinding = require('./functions.darwin-universal.node') 115 | } else { 116 | nativeBinding = require('@jinja-lsp/functions-darwin-universal') 117 | } 118 | break 119 | } catch {} 120 | switch (arch) { 121 | case 'x64': 122 | localFileExisted = existsSync(join(__dirname, 'functions.darwin-x64.node')) 123 | try { 124 | if (localFileExisted) { 125 | nativeBinding = require('./functions.darwin-x64.node') 126 | } else { 127 | nativeBinding = require('@jinja-lsp/functions-darwin-x64') 128 | } 129 | } catch (e) { 130 | loadError = e 131 | } 132 | break 133 | case 'arm64': 134 | localFileExisted = existsSync( 135 | join(__dirname, 'functions.darwin-arm64.node') 136 | ) 137 | try { 138 | if (localFileExisted) { 139 | nativeBinding = require('./functions.darwin-arm64.node') 140 | } else { 141 | nativeBinding = require('@jinja-lsp/functions-darwin-arm64') 142 | } 143 | } catch (e) { 144 | loadError = e 145 | } 146 | break 147 | default: 148 | throw new Error(`Unsupported architecture on macOS: ${arch}`) 149 | } 150 | break 151 | case 'freebsd': 152 | if (arch !== 'x64') { 153 | throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) 154 | } 155 | localFileExisted = existsSync(join(__dirname, 'functions.freebsd-x64.node')) 156 | try { 157 | if (localFileExisted) { 158 | nativeBinding = require('./functions.freebsd-x64.node') 159 | } else { 160 | nativeBinding = require('@jinja-lsp/functions-freebsd-x64') 161 | } 162 | } catch (e) { 163 | loadError = e 164 | } 165 | break 166 | case 'linux': 167 | switch (arch) { 168 | case 'x64': 169 | if (isMusl()) { 170 | localFileExisted = existsSync( 171 | join(__dirname, 'functions.linux-x64-musl.node') 172 | ) 173 | try { 174 | if (localFileExisted) { 175 | nativeBinding = require('./functions.linux-x64-musl.node') 176 | } else { 177 | nativeBinding = require('@jinja-lsp/functions-linux-x64-musl') 178 | } 179 | } catch (e) { 180 | loadError = e 181 | } 182 | } else { 183 | localFileExisted = existsSync( 184 | join(__dirname, 'functions.linux-x64-gnu.node') 185 | ) 186 | try { 187 | if (localFileExisted) { 188 | nativeBinding = require('./functions.linux-x64-gnu.node') 189 | } else { 190 | nativeBinding = require('@jinja-lsp/functions-linux-x64-gnu') 191 | } 192 | } catch (e) { 193 | loadError = e 194 | } 195 | } 196 | break 197 | case 'arm64': 198 | if (isMusl()) { 199 | localFileExisted = existsSync( 200 | join(__dirname, 'functions.linux-arm64-musl.node') 201 | ) 202 | try { 203 | if (localFileExisted) { 204 | nativeBinding = require('./functions.linux-arm64-musl.node') 205 | } else { 206 | nativeBinding = require('@jinja-lsp/functions-linux-arm64-musl') 207 | } 208 | } catch (e) { 209 | loadError = e 210 | } 211 | } else { 212 | localFileExisted = existsSync( 213 | join(__dirname, 'functions.linux-arm64-gnu.node') 214 | ) 215 | try { 216 | if (localFileExisted) { 217 | nativeBinding = require('./functions.linux-arm64-gnu.node') 218 | } else { 219 | nativeBinding = require('@jinja-lsp/functions-linux-arm64-gnu') 220 | } 221 | } catch (e) { 222 | loadError = e 223 | } 224 | } 225 | break 226 | case 'arm': 227 | if (isMusl()) { 228 | localFileExisted = existsSync( 229 | join(__dirname, 'functions.linux-arm-musleabihf.node') 230 | ) 231 | try { 232 | if (localFileExisted) { 233 | nativeBinding = require('./functions.linux-arm-musleabihf.node') 234 | } else { 235 | nativeBinding = require('@jinja-lsp/functions-linux-arm-musleabihf') 236 | } 237 | } catch (e) { 238 | loadError = e 239 | } 240 | } else { 241 | localFileExisted = existsSync( 242 | join(__dirname, 'functions.linux-arm-gnueabihf.node') 243 | ) 244 | try { 245 | if (localFileExisted) { 246 | nativeBinding = require('./functions.linux-arm-gnueabihf.node') 247 | } else { 248 | nativeBinding = require('@jinja-lsp/functions-linux-arm-gnueabihf') 249 | } 250 | } catch (e) { 251 | loadError = e 252 | } 253 | } 254 | break 255 | case 'riscv64': 256 | if (isMusl()) { 257 | localFileExisted = existsSync( 258 | join(__dirname, 'functions.linux-riscv64-musl.node') 259 | ) 260 | try { 261 | if (localFileExisted) { 262 | nativeBinding = require('./functions.linux-riscv64-musl.node') 263 | } else { 264 | nativeBinding = require('@jinja-lsp/functions-linux-riscv64-musl') 265 | } 266 | } catch (e) { 267 | loadError = e 268 | } 269 | } else { 270 | localFileExisted = existsSync( 271 | join(__dirname, 'functions.linux-riscv64-gnu.node') 272 | ) 273 | try { 274 | if (localFileExisted) { 275 | nativeBinding = require('./functions.linux-riscv64-gnu.node') 276 | } else { 277 | nativeBinding = require('@jinja-lsp/functions-linux-riscv64-gnu') 278 | } 279 | } catch (e) { 280 | loadError = e 281 | } 282 | } 283 | break 284 | case 's390x': 285 | localFileExisted = existsSync( 286 | join(__dirname, 'functions.linux-s390x-gnu.node') 287 | ) 288 | try { 289 | if (localFileExisted) { 290 | nativeBinding = require('./functions.linux-s390x-gnu.node') 291 | } else { 292 | nativeBinding = require('@jinja-lsp/functions-linux-s390x-gnu') 293 | } 294 | } catch (e) { 295 | loadError = e 296 | } 297 | break 298 | default: 299 | throw new Error(`Unsupported architecture on Linux: ${arch}`) 300 | } 301 | break 302 | default: 303 | throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) 304 | } 305 | 306 | if (!nativeBinding) { 307 | if (loadError) { 308 | throw loadError 309 | } 310 | throw new Error(`Failed to load native binding`) 311 | } 312 | 313 | const { basic, NodejsLspFiles, JsIdentifierType, Kind2, JsCompletionType } = nativeBinding 314 | 315 | module.exports.basic = basic 316 | module.exports.NodejsLspFiles = NodejsLspFiles 317 | module.exports.JsIdentifierType = JsIdentifierType 318 | module.exports.Kind2 = Kind2 319 | module.exports.JsCompletionType = JsCompletionType 320 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/android-arm-eabi/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-android-arm-eabi` 2 | 3 | This is the **armv7-linux-androideabi** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/android-arm-eabi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-android-arm-eabi", 3 | "version": "0.0.22", 4 | "os": [ 5 | "android" 6 | ], 7 | "cpu": [ 8 | "arm" 9 | ], 10 | "main": "functions.android-arm-eabi.node", 11 | "files": [ 12 | "functions.android-arm-eabi.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/android-arm64/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-android-arm64` 2 | 3 | This is the **aarch64-linux-android** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/android-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-android-arm64", 3 | "version": "0.0.22", 4 | "os": [ 5 | "android" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "functions.android-arm64.node", 11 | "files": [ 12 | "functions.android-arm64.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/darwin-arm64/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-darwin-arm64` 2 | 3 | This is the **aarch64-apple-darwin** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/darwin-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-darwin-arm64", 3 | "version": "0.0.22", 4 | "os": [ 5 | "darwin" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "functions.darwin-arm64.node", 11 | "files": [ 12 | "functions.darwin-arm64.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/darwin-universal/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-darwin-universal` 2 | 3 | This is the **universal-apple-darwin** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/darwin-universal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-darwin-universal", 3 | "version": "0.0.22", 4 | "os": [ 5 | "darwin" 6 | ], 7 | "main": "functions.darwin-universal.node", 8 | "files": [ 9 | "functions.darwin-universal.node" 10 | ], 11 | "description": "Bindings for jinja-lsp", 12 | "license": "MIT", 13 | "engines": { 14 | "node": ">= 10" 15 | }, 16 | "repository": "https://github.com/uros-5/jinja-lsp" 17 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/freebsd-x64/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-freebsd-x64` 2 | 3 | This is the **x86_64-unknown-freebsd** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/freebsd-x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-freebsd-x64", 3 | "version": "0.0.22", 4 | "os": [ 5 | "freebsd" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "functions.freebsd-x64.node", 11 | "files": [ 12 | "functions.freebsd-x64.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/gnu-linux/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-gnu-linux` 2 | 3 | This is the **linux-x64-gnu** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/gnu-linux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-gnu-linux", 3 | "version": "0.0.6", 4 | "os": [ 5 | "gnu", 6 | "linux" 7 | ], 8 | "cpu": [ 9 | "linux", 10 | "x64" 11 | ], 12 | "main": "functions.gnu-linux.node", 13 | "files": [ 14 | "functions.gnu-linux.node" 15 | ], 16 | "description": "Bindings for jinja-lsp", 17 | "license": "MIT", 18 | "engines": { 19 | "node": ">= 10" 20 | }, 21 | "repository": "https://github.com/uros-5/jinja-lsp" 22 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm-gnueabihf/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-linux-arm-gnueabihf` 2 | 3 | This is the **armv7-unknown-linux-gnueabihf** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm-gnueabihf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-linux-arm-gnueabihf", 3 | "version": "0.0.22", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm" 9 | ], 10 | "main": "functions.linux-arm-gnueabihf.node", 11 | "files": [ 12 | "functions.linux-arm-gnueabihf.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm-musleabihf/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-linux-arm-musleabihf` 2 | 3 | This is the **armv7-unknown-linux-musleabihf** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm-musleabihf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-linux-arm-musleabihf", 3 | "version": "0.0.22", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm" 9 | ], 10 | "main": "functions.linux-arm-musleabihf.node", 11 | "files": [ 12 | "functions.linux-arm-musleabihf.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-linux-arm64-gnu` 2 | 3 | This is the **aarch64-unknown-linux-gnu** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-linux-arm64-gnu", 3 | "version": "0.0.22", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "functions.linux-arm64-gnu.node", 11 | "files": [ 12 | "functions.linux-arm64-gnu.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp", 20 | "libc": [ 21 | "glibc" 22 | ] 23 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-linux-arm64-musl` 2 | 3 | This is the **aarch64-unknown-linux-musl** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-arm64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-linux-arm64-musl", 3 | "version": "0.0.22", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "functions.linux-arm64-musl.node", 11 | "files": [ 12 | "functions.linux-arm64-musl.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp", 20 | "libc": [ 21 | "musl" 22 | ] 23 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-riscv64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-linux-riscv64-gnu` 2 | 3 | This is the **riscv64gc-unknown-linux-gnu** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-riscv64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-linux-riscv64-gnu", 3 | "version": "0.0.22", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "riscv64" 9 | ], 10 | "main": "functions.linux-riscv64-gnu.node", 11 | "files": [ 12 | "functions.linux-riscv64-gnu.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp", 20 | "libc": [ 21 | "glibc" 22 | ] 23 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-x64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `@uros/jinja-lsp-nodejs-linux-x64-musl` 2 | 3 | This is the **x86_64-unknown-linux-musl** binary for `@uros/jinja-lsp-nodejs` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-x64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-linux-x64-gnu", 3 | "version": "0.0.22", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "functions.linux-x64-gnu.node", 11 | "files": [ 12 | "functions.linux-x64-gnu.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "libc": [ 20 | "gnu", 21 | "glibc" 22 | ], 23 | "repository": "https://github.com/uros-5/jinja-lsp" 24 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-x64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-linux-x64-musl` 2 | 3 | This is the **x86_64-unknown-linux-musl** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/linux-x64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-linux-x64-musl", 3 | "version": "0.0.22", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "functions.linux-x64-musl.node", 11 | "files": [ 12 | "functions.linux-x64-musl.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp", 20 | "libc": [ 21 | "musl" 22 | ] 23 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/win32-arm64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-win32-arm64-msvc` 2 | 3 | This is the **aarch64-pc-windows-msvc** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/win32-arm64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-win32-arm64-msvc", 3 | "version": "0.0.22", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "functions.win32-arm64-msvc.node", 11 | "files": [ 12 | "functions.win32-arm64-msvc.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/win32-ia32-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-win32-ia32-msvc` 2 | 3 | This is the **i686-pc-windows-msvc** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/win32-ia32-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-win32-ia32-msvc", 3 | "version": "0.0.22", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "ia32" 9 | ], 10 | "main": "functions.win32-ia32-msvc.node", 11 | "files": [ 12 | "functions.win32-ia32-msvc.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/win32-x64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `@jinja-lsp/functions-win32-x86_64-pc-windows-msvc` 2 | 3 | This is the **win32-x86_64-pc-windows-msvc** binary for `@jinja-lsp/functions` 4 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/npm/win32-x64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions-win32-x64-msvc", 3 | "version": "0.0.22", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "functions.win32-x64-msvc.node", 11 | "files": [ 12 | "functions.win32-x64-msvc.node" 13 | ], 14 | "description": "Bindings for jinja-lsp", 15 | "license": "MIT", 16 | "engines": { 17 | "node": ">= 10" 18 | }, 19 | "repository": "https://github.com/uros-5/jinja-lsp" 20 | } -------------------------------------------------------------------------------- /jinja-lsp-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jinja-lsp/functions", 3 | "version": "0.0.22", 4 | "main": "index.js", 5 | "types": "index.d.ts", 6 | "napi": { 7 | "name": "functions", 8 | "triples": { 9 | "defaults": false, 10 | "additional": [ 11 | "aarch64-apple-darwin", 12 | "aarch64-linux-android", 13 | "aarch64-unknown-linux-gnu", 14 | "aarch64-unknown-linux-musl", 15 | "aarch64-pc-windows-msvc", 16 | "armv7-unknown-linux-gnueabihf", 17 | "armv7-unknown-linux-musleabihf", 18 | "x86_64-unknown-linux-musl", 19 | "x86_64-unknown-freebsd", 20 | "i686-pc-windows-msvc", 21 | "x86_64-pc-windows-msvc", 22 | "armv7-linux-androideabi", 23 | "universal-apple-darwin", 24 | "riscv64gc-unknown-linux-gnu", 25 | "x86_64-unknown-linux-gnu" 26 | ] 27 | } 28 | }, 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@napi-rs/cli": "^2.18.3", 32 | "ava": "^6.0.1" 33 | }, 34 | "ava": { 35 | "timeout": "3m" 36 | }, 37 | "engines": { 38 | "node": ">= 10" 39 | }, 40 | "scripts": { 41 | "artifacts": "napi artifacts", 42 | "build": "napi build --platform --release", 43 | "build:debug": "napi build --platform", 44 | "prepublishOnly": "napi prepublish -t npm", 45 | "test": "ava", 46 | "universal": "napi universal", 47 | "version": "napi version" 48 | }, 49 | "packageManager": "yarn@4.2.2", 50 | "repository": "https://github.com/uros-5/jinja-lsp", 51 | "description": "Bindings for jinja-lsp" 52 | } 53 | -------------------------------------------------------------------------------- /jinja-lsp-nodejs/rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | edition = "2021" 3 | -------------------------------------------------------------------------------- /jinja-lsp-queries/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jinja-lsp-queries" 3 | version = "0.1.85" 4 | edition = "2021" 5 | description = "TreeSitter queries for jinja-lsp" 6 | license = "MIT" 7 | 8 | [dependencies] 9 | tree-sitter = "0.23.0" 10 | tree-sitter-jinja2 = "0.0.12" 11 | tree-sitter-rust = "0.23.0" 12 | tower-lsp = { version = "0.20.0", features = ["proposed"] } 13 | ropey = "1.5.0" 14 | tree-sitter-python = "=0.23.0" 15 | tree-sitter-language = "0.1.0" 16 | 17 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod lsp_helper; 2 | pub mod parsers; 3 | pub mod search; 4 | pub mod to_input_edit; 5 | pub mod tree_builder; 6 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/lsp_helper.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, io::ErrorKind, path::PathBuf}; 2 | 3 | use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; 4 | use tree_sitter::{Point, Tree}; 5 | 6 | use crate::{ 7 | search::{ 8 | objects::objects_query, queries::Queries, templates::templates_query, Identifier, 9 | IdentifierType, 10 | }, 11 | tree_builder::{JinjaDiagnostic, LangType}, 12 | }; 13 | 14 | #[allow(clippy::too_many_arguments)] 15 | pub fn search_errors( 16 | root: &Tree, 17 | source: &str, 18 | queries: &Queries, 19 | variables: &HashMap>, 20 | file_name: &String, 21 | templates: PathBuf, 22 | lang_type: LangType, 23 | ignore_globals: bool, 24 | ) -> Option> { 25 | let mut diagnostics = vec![]; 26 | match lang_type { 27 | LangType::Template => { 28 | let trigger_point = Point::new(0, 0); 29 | let query = &queries.jinja_objects; 30 | let objects = objects_query(query, root, trigger_point, source, true); 31 | let objects = objects.show(); 32 | let this_file = variables.get(file_name)?; 33 | for object in objects { 34 | if object.is_filter || object.is_test { 35 | continue; 36 | } 37 | let mut exist = false; 38 | let mut err_type = JinjaDiagnostic::Undefined; 39 | let mut to_warn = false; 40 | let located = this_file 41 | .iter() 42 | .filter(|variable| { 43 | variable.name == object.name 44 | && variable.identifier_type != IdentifierType::TemplateBlock 45 | }) 46 | .filter(|variable| { 47 | exist = true; 48 | let bigger = object.location.1 >= variable.start; 49 | let global = variable.scope_ends.1 == Point::default(); 50 | let in_scope = object.location.0 < variable.scope_ends.1; 51 | if bigger && global { 52 | true 53 | } else { 54 | bigger && in_scope 55 | } 56 | }); 57 | let empty = located.count() == 0; 58 | if empty && exist { 59 | to_warn = true; 60 | } else if empty { 61 | if ignore_globals { 62 | continue; 63 | } 64 | to_warn = true; 65 | for file in variables { 66 | let temp = file 67 | .1 68 | .iter() 69 | .filter(|variable| variable.name == object.name); 70 | if temp.count() != 0 { 71 | err_type = JinjaDiagnostic::DefinedSomewhere; 72 | to_warn = true; 73 | break; 74 | } 75 | } 76 | } 77 | if to_warn { 78 | if ignore_globals { 79 | continue; 80 | } 81 | let diagnostic = (err_type, Identifier::from(&object)); 82 | diagnostics.push(diagnostic); 83 | } 84 | } 85 | 86 | let mut variables = vec![]; 87 | let query_templates = &queries.jinja_imports; 88 | let jinja_imports = templates_query(query_templates, root, trigger_point, source, true); 89 | jinja_imports.collect(&mut variables); 90 | 91 | let id_templates = variables 92 | .iter() 93 | .filter(|identifier| identifier.identifier_type == IdentifierType::JinjaTemplate); 94 | for i in id_templates { 95 | let err_type = JinjaDiagnostic::TemplateNotFound; 96 | if i.name.is_empty() { 97 | let diagnostic = (err_type, i.to_owned()); 98 | diagnostics.push(diagnostic); 99 | } else { 100 | let mut templates = templates.clone(); 101 | templates.push(path_items(&i.name)); 102 | if let Err(err) = std::fs::canonicalize(templates) { 103 | if err.kind() == ErrorKind::NotFound { 104 | let diagnostic = (err_type, i.to_owned()); 105 | diagnostics.push(diagnostic); 106 | } 107 | } 108 | } 109 | } 110 | Some(diagnostics) 111 | } 112 | LangType::Backend => { 113 | let all_variables = variables.get(file_name)?; 114 | let templates2 = all_variables 115 | .iter() 116 | .filter(|id| id.identifier_type == IdentifierType::JinjaTemplate); 117 | for template in templates2 { 118 | let mut templates = templates.clone(); 119 | templates.push(path_items(&template.name)); 120 | if let Err(err) = std::fs::canonicalize(templates) { 121 | if err.kind() == ErrorKind::NotFound { 122 | let diagnostic = (JinjaDiagnostic::TemplateNotFound, template.to_owned()); 123 | diagnostics.push(diagnostic); 124 | } 125 | } 126 | } 127 | Some(diagnostics) 128 | } 129 | } 130 | } 131 | 132 | pub fn create_diagnostic( 133 | template: &Identifier, 134 | severity: DiagnosticSeverity, 135 | message: String, 136 | ) -> Diagnostic { 137 | Diagnostic { 138 | range: Range::new( 139 | Position::new(template.start.row as u32, template.start.column as u32), 140 | Position::new(template.end.row as u32, template.end.column as u32), 141 | ), 142 | severity: Some(severity), 143 | message, 144 | source: Some(String::from("jinja-lsp")), 145 | ..Default::default() 146 | } 147 | } 148 | 149 | pub fn path_items(template: &str) -> PathBuf { 150 | template.split('/').collect() 151 | } 152 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/parsers.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Parser, Tree}; 2 | 3 | use crate::tree_builder::LangType; 4 | 5 | pub struct Parsers { 6 | jinja: Parser, 7 | backend: Parser, 8 | } 9 | 10 | impl Parsers { 11 | pub fn parse( 12 | &mut self, 13 | lang_type: LangType, 14 | text: &str, 15 | old_tree: Option<&Tree>, 16 | ) -> Option { 17 | match lang_type { 18 | LangType::Template => self.jinja.parse(text, old_tree), 19 | LangType::Backend => self.backend.parse(text, old_tree), 20 | } 21 | } 22 | 23 | pub fn update_backend(&mut self, lang: &str) { 24 | if lang == "python" { 25 | self.backend = Parser::new(); 26 | let _ = self 27 | .backend 28 | .set_language(&tree_sitter_python::LANGUAGE.into()); 29 | } 30 | } 31 | } 32 | 33 | impl Default for Parsers { 34 | fn default() -> Self { 35 | let mut jinja = Parser::new(); 36 | let _ = jinja.set_language(&tree_sitter_jinja2::LANGUAGE.into()); 37 | let mut backend = Parser::new(); 38 | let _ = backend.set_language(&tree_sitter_rust::LANGUAGE.into()); 39 | Self { jinja, backend } 40 | } 41 | } 42 | 43 | impl Clone for Parsers { 44 | fn clone(&self) -> Self { 45 | Self::default() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/definition.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, LinkedList}; 2 | 3 | use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; 4 | 5 | use super::{Identifier, IdentifierType}; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum Definition { 9 | ForLoop { 10 | key: Identifier, 11 | value: Option, 12 | }, 13 | Set { 14 | key: Identifier, 15 | equals: bool, 16 | }, 17 | With { 18 | keys: Vec, 19 | }, 20 | Macro { 21 | keys: Vec, 22 | scope: usize, 23 | }, 24 | Block { 25 | name: Identifier, 26 | }, 27 | } 28 | 29 | impl Definition { 30 | fn collect(self, ids: &mut Vec) { 31 | match self { 32 | Definition::ForLoop { mut key, value, .. } => { 33 | key.identifier_type = IdentifierType::ForLoopKey; 34 | ids.push(key); 35 | if let Some(mut value) = value { 36 | value.identifier_type = IdentifierType::ForLoopValue; 37 | ids.push(value); 38 | } 39 | } 40 | Definition::Set { mut key, .. } => { 41 | key.identifier_type = IdentifierType::SetVariable; 42 | ids.push(key); 43 | } 44 | Definition::With { keys, .. } => { 45 | for mut key in keys { 46 | key.identifier_type = IdentifierType::WithVariable; 47 | ids.push(key); 48 | } 49 | } 50 | Definition::Macro { keys, .. } => { 51 | for mut key in keys.into_iter().enumerate() { 52 | if key.0 == 0 { 53 | key.1.identifier_type = IdentifierType::MacroName; 54 | } else { 55 | key.1.identifier_type = IdentifierType::MacroParameter; 56 | } 57 | ids.push(key.1); 58 | } 59 | } 60 | Definition::Block { mut name, .. } => { 61 | name.identifier_type = IdentifierType::TemplateBlock; 62 | ids.push(name); 63 | } 64 | } 65 | } 66 | } 67 | 68 | impl From<&str> for Definition { 69 | fn from(value: &str) -> Self { 70 | match value { 71 | "for" => Self::ForLoop { 72 | key: Identifier::default(), 73 | value: None, 74 | }, 75 | "set" => Self::Set { 76 | key: Identifier::default(), 77 | equals: false, 78 | }, 79 | "with" => Self::With { keys: vec![] }, 80 | "macro" => Definition::Macro { 81 | keys: vec![], 82 | scope: 0, 83 | }, 84 | "block" => Self::Block { 85 | name: Identifier::default(), 86 | }, 87 | _ => Self::ForLoop { 88 | key: Identifier::default(), 89 | value: None, 90 | }, 91 | } 92 | } 93 | } 94 | 95 | #[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)] 96 | pub enum Current { 97 | For, 98 | Set, 99 | With, 100 | Macro, 101 | Block, 102 | If, 103 | Else, 104 | Filter, 105 | Autoescape, 106 | Raw, 107 | #[default] 108 | NoDefinition, 109 | } 110 | 111 | impl Current { 112 | fn _from_end(name: &str) -> Self { 113 | match name { 114 | "endfor" => Self::For, 115 | "endset" => Self::Set, 116 | "endwith" => Self::With, 117 | "endmacro" => Self::Macro, 118 | "endblock" => Self::Block, 119 | "endif" => Self::If, 120 | "endelse" => Self::Else, 121 | "endfilter" => Self::Filter, 122 | "endautoescape" => Self::Autoescape, 123 | "endraw" => Self::Raw, 124 | _ => Self::NoDefinition, 125 | } 126 | } 127 | } 128 | 129 | #[derive(Default, Debug)] 130 | pub struct Scope { 131 | pub id: usize, 132 | pub start: Point, 133 | pub end: Point, 134 | } 135 | 136 | impl Scope { 137 | pub fn new(end: Point) -> Self { 138 | Self { 139 | id: 0, 140 | start: Point::default(), 141 | end, 142 | } 143 | } 144 | } 145 | 146 | #[derive(Default, Debug)] 147 | pub struct JinjaDefinitions { 148 | pub definitions: Vec, 149 | can_close_scope: bool, 150 | can_open_scope: bool, 151 | can_add_id: bool, 152 | is_end: bool, 153 | pub current_scope: LinkedList, 154 | pub all_scopes: Vec, 155 | all_ids: HashSet, 156 | } 157 | 158 | impl JinjaDefinitions { 159 | fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option { 160 | let id = capture.node.id(); 161 | match name { 162 | "definition" => { 163 | if self.all_ids.contains(&id) { 164 | return Some(false); 165 | } 166 | let content = capture.node.utf8_text(text.as_bytes()).unwrap(); 167 | self.all_ids.insert(id); 168 | let mut add_new_scope = true; 169 | let mut definition = Definition::from(content); 170 | if let Definition::Set { .. } = definition { 171 | add_new_scope = false; 172 | } else if let Definition::Macro { ref mut scope, .. } = &mut definition { 173 | let current_scope = self.current_scope.front().unwrap_or(&Scope::default()).id; 174 | *scope = current_scope; 175 | } 176 | self.definitions.push(definition); 177 | if add_new_scope { 178 | self.current_scope.push_front(Scope { 179 | id, 180 | ..Default::default() 181 | }); 182 | self.can_close_scope = false; 183 | self.can_open_scope = true; 184 | self.is_end = false; 185 | self.can_add_id = true; 186 | } else { 187 | self.can_add_id = true; 188 | } 189 | } 190 | "scope" => { 191 | self.can_close_scope = false; 192 | self.can_open_scope = true; 193 | self.is_end = false; 194 | self.can_add_id = false; 195 | self.current_scope.push_front(Scope { 196 | id, 197 | ..Default::default() 198 | }); 199 | } 200 | "endblock" => { 201 | self.is_end = true; 202 | self.can_close_scope = true; 203 | self.can_open_scope = false; 204 | } 205 | "equals" => { 206 | let last = self.definitions.last_mut(); 207 | if let Some(Definition::Set { equals, .. }) = last { 208 | *equals = true; 209 | self.can_open_scope = false; 210 | } 211 | } 212 | "error" => { 213 | return None; 214 | } 215 | "id" => { 216 | if !self.can_add_id { 217 | return Some(false); 218 | } 219 | let mut identifier = Identifier::default(); 220 | let start = capture.node.start_position(); 221 | let end = capture.node.end_position(); 222 | let content = capture.node.utf8_text(text.as_bytes()).ok()?; 223 | let current_scope = self.current_scope.front().unwrap_or(&Scope::default()).id; 224 | identifier.start = start; 225 | identifier.end = end; 226 | content.to_owned().clone_into(&mut identifier.name); 227 | identifier.scope_ends.0 = current_scope; 228 | let last = self.definitions.last_mut(); 229 | if let Some(last) = last { 230 | match last { 231 | Definition::ForLoop { key, value } => { 232 | if key.name.is_empty() { 233 | *key = identifier; 234 | } else if let Some(value) = value { 235 | if value.name.is_empty() { 236 | *value = identifier; 237 | self.can_add_id = false; 238 | } 239 | } 240 | } 241 | Definition::Set { key, .. } => { 242 | if key.name.is_empty() { 243 | *key = identifier; 244 | self.can_add_id = false; 245 | } 246 | } 247 | Definition::With { keys } => { 248 | keys.push(identifier); 249 | } 250 | Definition::Macro { keys, scope } => { 251 | if keys.is_empty() { 252 | identifier.scope_ends.0 = *scope; 253 | } 254 | keys.push(identifier); 255 | } 256 | Definition::Block { name } => { 257 | if name.name.is_empty() { 258 | *name = identifier; 259 | self.can_add_id = false; 260 | } 261 | } 262 | } 263 | } 264 | } 265 | "scope_end" => { 266 | if self.can_close_scope && self.is_end { 267 | self.can_close_scope = false; 268 | self.can_add_id = false; 269 | self.is_end = false; 270 | if let Some(mut last) = self.current_scope.pop_front() { 271 | last.end = capture.node.start_position(); 272 | self.all_scopes.push(last); 273 | } 274 | } 275 | } 276 | "scope_start" => { 277 | if self.can_open_scope { 278 | self.can_open_scope = false; 279 | self.can_add_id = false; 280 | if let Some(last) = self.current_scope.front_mut() { 281 | last.start = capture.node.end_position(); 282 | } 283 | } 284 | } 285 | _ => {} 286 | } 287 | Some(true) 288 | } 289 | 290 | pub fn identifiers(self) -> Vec { 291 | let mut ids = vec![]; 292 | for id in self.definitions { 293 | id.collect(&mut ids); 294 | } 295 | 296 | ids 297 | } 298 | 299 | fn fix_end(&mut self, last_line: Point) { 300 | let global_scope = Scope::new(last_line); 301 | for def in self.definitions.iter_mut() { 302 | match def { 303 | Definition::ForLoop { key, value } => { 304 | let scope = self 305 | .all_scopes 306 | .iter() 307 | .find(|item| item.id == key.scope_ends.0); 308 | let scope = scope.unwrap_or(&global_scope); 309 | key.scope_ends.1 = scope.end; 310 | if let Some(value) = value { 311 | value.scope_ends.1 = scope.end; 312 | } 313 | } 314 | Definition::Set { key, .. } => { 315 | let scope = self 316 | .all_scopes 317 | .iter() 318 | .find(|item| item.id == key.scope_ends.0); 319 | let scope = scope.unwrap_or(&global_scope); 320 | key.scope_ends.1 = scope.end; 321 | } 322 | Definition::With { keys } => { 323 | for key in keys { 324 | let scope = self 325 | .all_scopes 326 | .iter() 327 | .find(|item| item.id == key.scope_ends.0); 328 | let scope = scope.unwrap_or(&global_scope); 329 | key.scope_ends.1 = scope.end; 330 | } 331 | } 332 | Definition::Macro { keys, scope } => { 333 | let scope = self.all_scopes.iter().find(|item| item.id == *scope); 334 | let scope = scope.unwrap_or(&global_scope); 335 | for (index, key) in keys.iter_mut().enumerate() { 336 | if index == 0 { 337 | key.scope_ends.1 = scope.end; 338 | } else { 339 | let scope = self 340 | .all_scopes 341 | .iter() 342 | .find(|item| item.id == key.scope_ends.0); 343 | let scope = scope.unwrap_or(&global_scope); 344 | key.scope_ends.1 = scope.end; 345 | } 346 | } 347 | } 348 | Definition::Block { name } => { 349 | let scope = self 350 | .all_scopes 351 | .iter() 352 | .find(|item| item.id == name.scope_ends.0); 353 | let scope = scope.unwrap_or(&global_scope); 354 | name.scope_ends.1 = scope.end; 355 | } 356 | } 357 | // let id = self.all_scopes.iter().find(|scope| scope.id == ) 358 | } 359 | } 360 | } 361 | 362 | pub fn definition_query( 363 | query: &Query, 364 | tree: &Tree, 365 | trigger_point: Point, 366 | text: &str, 367 | all: bool, 368 | ) -> JinjaDefinitions { 369 | let closest_node = tree.root_node(); 370 | let mut definitions = JinjaDefinitions::default(); 371 | let mut cursor_qry = QueryCursor::new(); 372 | let capture_names = query.capture_names(); 373 | let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); 374 | let captures = matches.into_iter().flat_map(|m| { 375 | m.captures 376 | .iter() 377 | .filter(|capture| all || capture.node.start_position() <= trigger_point) 378 | }); 379 | for capture in captures { 380 | let name = &capture_names[capture.index as usize]; 381 | let err = definitions.check(name, capture, text); 382 | if err.is_none() { 383 | break; 384 | } 385 | } 386 | let root = tree.root_node().end_position(); 387 | definitions.fix_end(root); 388 | definitions 389 | } 390 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/mod.rs: -------------------------------------------------------------------------------- 1 | use tower_lsp::lsp_types::{CompletionItemKind, Position, Range, SymbolKind}; 2 | use tree_sitter::Point; 3 | 4 | use self::objects::JinjaObject; 5 | 6 | pub mod definition; 7 | pub mod objects; 8 | pub mod python_identifiers; 9 | pub mod queries; 10 | pub mod rust_identifiers; 11 | pub mod rust_template_completion; 12 | pub mod snippets_completion; 13 | pub mod templates; 14 | pub mod test_queries; 15 | 16 | #[derive(Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] 17 | pub struct Identifier { 18 | pub start: Point, 19 | pub end: Point, 20 | pub name: String, 21 | pub scope_ends: (usize, Point), 22 | pub identifier_type: IdentifierType, 23 | pub fields: Vec<(String, (Point, Point))>, 24 | } 25 | 26 | impl Identifier { 27 | pub fn new(name: &str, start: Point, end: Point) -> Self { 28 | Self { 29 | name: String::from(name), 30 | start, 31 | end, 32 | scope_ends: (0, Point::default()), 33 | identifier_type: IdentifierType::UndefinedVariable, 34 | fields: Vec::new(), 35 | } 36 | } 37 | 38 | pub fn merge(&self) -> String { 39 | let mut merged = self.name.to_string(); 40 | for field in &self.fields { 41 | merged.push('.'); 42 | merged.push_str(&field.0); 43 | } 44 | merged 45 | } 46 | } 47 | 48 | impl From<&JinjaObject> for Identifier { 49 | fn from(value: &JinjaObject) -> Self { 50 | let mut identifier = Identifier::new(&value.name, value.location.0, value.location.1); 51 | identifier.fields.clone_from(&value.fields); 52 | identifier 53 | } 54 | } 55 | 56 | pub fn completion_start(mut trigger_point: Point, identifier: &Identifier) -> Option<&str> { 57 | if trigger_point.column > 0 { 58 | trigger_point.column -= 1; 59 | } 60 | let len = identifier.name.len(); 61 | if len == 0 { 62 | return Some(""); 63 | } 64 | let diff = identifier.end.column - trigger_point.column; 65 | if diff == 0 || diff == 1 { 66 | return Some(&identifier.name); 67 | } 68 | if diff > len { 69 | return Some(&identifier.name); 70 | // return None; 71 | } 72 | let to = len - diff; 73 | let s = identifier.name.get(0..to + 1); 74 | s 75 | } 76 | pub fn to_range(points: (Point, Point)) -> Range { 77 | let start = Position::new(points.0.row as u32, points.0.column as u32); 78 | let end = Position::new(points.1.row as u32, points.1.column as u32); 79 | Range::new(start, end) 80 | } 81 | 82 | pub fn to_point(position: Position) -> Point { 83 | Point::new(position.line as usize, position.character as usize) 84 | } 85 | 86 | pub fn to_range2(range: Range, point: Point) -> bool { 87 | let start = to_point(range.start); 88 | let end = to_point(range.end); 89 | point >= start && point <= end 90 | } 91 | 92 | #[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 93 | pub enum IdentifierType { 94 | ForLoopKey, 95 | ForLoopValue, 96 | ForLoopCount, 97 | SetVariable, 98 | WithVariable, 99 | MacroName, 100 | MacroParameter, 101 | TemplateBlock, 102 | BackendVariable, 103 | #[default] 104 | UndefinedVariable, 105 | JinjaTemplate, 106 | } 107 | 108 | impl IdentifierType { 109 | pub fn completion_detail(&self) -> &'static str { 110 | match self { 111 | IdentifierType::ForLoopKey => "For loop key", 112 | IdentifierType::ForLoopValue => "For loop value", 113 | IdentifierType::ForLoopCount => "For loop count", 114 | IdentifierType::SetVariable => "Set variable", 115 | IdentifierType::WithVariable => "With variable", 116 | IdentifierType::MacroName => "Macro", 117 | IdentifierType::MacroParameter => "Macro parameter", 118 | IdentifierType::TemplateBlock => "Template block", 119 | IdentifierType::BackendVariable => "Backend variable", 120 | IdentifierType::UndefinedVariable => "Undefined variable", 121 | IdentifierType::JinjaTemplate => "Jinja template", 122 | } 123 | } 124 | 125 | pub fn completion_kind(&self) -> CompletionItemKind { 126 | match self { 127 | IdentifierType::ForLoopKey => CompletionItemKind::VARIABLE, 128 | IdentifierType::ForLoopValue => CompletionItemKind::VARIABLE, 129 | IdentifierType::ForLoopCount => CompletionItemKind::FIELD, 130 | IdentifierType::SetVariable => CompletionItemKind::VARIABLE, 131 | IdentifierType::WithVariable => CompletionItemKind::VARIABLE, 132 | IdentifierType::MacroName => CompletionItemKind::FUNCTION, 133 | IdentifierType::MacroParameter => CompletionItemKind::FIELD, 134 | IdentifierType::TemplateBlock => CompletionItemKind::MODULE, 135 | IdentifierType::BackendVariable => CompletionItemKind::VARIABLE, 136 | IdentifierType::UndefinedVariable => CompletionItemKind::CONSTANT, 137 | IdentifierType::JinjaTemplate => CompletionItemKind::FILE, 138 | } 139 | } 140 | 141 | pub fn symbol_kind(&self) -> SymbolKind { 142 | match self { 143 | IdentifierType::ForLoopKey => SymbolKind::VARIABLE, 144 | IdentifierType::ForLoopValue => SymbolKind::VARIABLE, 145 | IdentifierType::ForLoopCount => SymbolKind::FIELD, 146 | IdentifierType::SetVariable => SymbolKind::VARIABLE, 147 | IdentifierType::WithVariable => SymbolKind::VARIABLE, 148 | IdentifierType::MacroName => SymbolKind::FUNCTION, 149 | IdentifierType::MacroParameter => SymbolKind::FIELD, 150 | IdentifierType::TemplateBlock => SymbolKind::MODULE, 151 | IdentifierType::BackendVariable => SymbolKind::VARIABLE, 152 | IdentifierType::UndefinedVariable => SymbolKind::CONSTANT, 153 | IdentifierType::JinjaTemplate => SymbolKind::FILE, 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/objects.rs: -------------------------------------------------------------------------------- 1 | use tower_lsp::lsp_types::Range; 2 | use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; 3 | 4 | use super::{completion_start, to_range, to_range2, Identifier}; 5 | 6 | #[derive(Default, Debug, Clone)] 7 | pub struct JinjaObject { 8 | pub name: String, 9 | pub location: (Point, Point), 10 | pub is_filter: bool, 11 | pub is_test: bool, 12 | pub fields: Vec<(String, (Point, Point))>, 13 | pub capture_first: bool, 14 | } 15 | 16 | impl JinjaObject { 17 | pub fn new(name: String, start: Point, end: Point, is_filter: bool, is_test: bool) -> Self { 18 | Self { 19 | name, 20 | location: (start, end), 21 | fields: vec![], 22 | is_filter, 23 | capture_first: false, 24 | is_test, 25 | } 26 | } 27 | 28 | pub fn add_field(&mut self, field: String, start: Point, end: Point) { 29 | self.fields.push((field, (start, end))); 30 | } 31 | 32 | pub fn last_field_end(&self) -> Point { 33 | let last = self.fields.last().map_or(self.location.1, |v| v.1 .1); 34 | last 35 | } 36 | 37 | pub fn full_range(&self) -> Range { 38 | let start = self.location.0; 39 | let end = self.last_field_end(); 40 | to_range((start, end)) 41 | } 42 | } 43 | 44 | #[derive(Default, Debug)] 45 | pub struct JinjaObjects { 46 | objects: Vec, 47 | dot: (Point, Point), 48 | pipe: (Point, Point), 49 | test: (Point, Point, bool), 50 | expr: (Point, Point, ExpressionRange), 51 | ident: (Point, Point), 52 | } 53 | 54 | impl JinjaObjects { 55 | fn check( 56 | &mut self, 57 | name: &str, 58 | capture: &QueryCapture<'_>, 59 | source: &str, 60 | ) -> Option { 61 | let start = capture.node.start_position(); 62 | let end = capture.node.end_position(); 63 | match name { 64 | "error" => { 65 | return None; 66 | } 67 | "just_id" => { 68 | return Some(self.build_object(capture, source)); 69 | } 70 | "dot" => { 71 | self.dot = (start, end); 72 | return Some(ObjectAction::NewField); 73 | } 74 | "pipe" => { 75 | let content = capture.node.utf8_text(source.as_bytes()).ok()?; 76 | if content.starts_with('|') { 77 | self.pipe = (start, end); 78 | } 79 | return Some(ObjectAction::NewFilter); 80 | } 81 | "is" => { 82 | self.test = (start, end, true); 83 | return Some(ObjectAction::NewTest); 84 | } 85 | "expr" => { 86 | let mut cursor = capture.node.walk(); 87 | cursor.goto_first_child(); 88 | let first = cursor.node(); 89 | cursor.reset(capture.node); 90 | cursor.goto_last_child(); 91 | let last = cursor.node(); 92 | let expr = ExpressionRange { 93 | begin: (first.start_position(), first.end_position()), 94 | end: (last.start_position(), last.end_position()), 95 | }; 96 | self.expr = (start, end, expr); 97 | return Some(ObjectAction::Expression); 98 | } 99 | _ => (), 100 | } 101 | Some(ObjectAction::Invalid) 102 | } 103 | 104 | pub fn build_object(&mut self, capture: &QueryCapture<'_>, source: &str) -> ObjectAction { 105 | let value = capture.node.utf8_text(source.as_bytes()); 106 | let start = capture.node.start_position(); 107 | let end = capture.node.end_position(); 108 | if let Ok(value) = value { 109 | if start.row == self.dot.1.row && start.column == self.dot.1.column { 110 | let last_object = self.objects.last_mut().map(|last| { 111 | last.fields.push((String::from(value), (start, end))); 112 | self.ident = (start, end); 113 | }); 114 | match last_object { 115 | Some(_) => {} 116 | None => { 117 | // TODO: in future add those to main library 118 | if VALID_IDENTIFIERS.contains(&value) { 119 | return ObjectAction::Invalid; 120 | } 121 | self.ident = (start, end); 122 | let is_test = self.test.2; 123 | let is_filter = self.is_hover(start) && self.is_filter(); 124 | let obj = 125 | JinjaObject::new(String::from(value), start, end, is_filter, is_test); 126 | self.objects.push(obj); 127 | self.test.2 = false; 128 | return ObjectAction::NewObject; 129 | } 130 | } 131 | } else { 132 | // TODO: in future add those to main library 133 | if VALID_IDENTIFIERS.contains(&value) { 134 | return ObjectAction::Invalid; 135 | } 136 | self.ident = (start, end); 137 | let is_test = self.test.2; 138 | let is_filter = self.is_hover(start) && self.is_filter(); 139 | self.objects.push(JinjaObject::new( 140 | String::from(value), 141 | start, 142 | end, 143 | is_filter, 144 | is_test, 145 | )); 146 | self.test.2 = false; 147 | return ObjectAction::NewObject; 148 | } 149 | } 150 | ObjectAction::Invalid 151 | } 152 | 153 | pub fn completion(&self, trigger_point: Point) -> Option<(CompletionType, bool)> { 154 | let autoclose = self.should_autoclose(); 155 | if self.in_pipe(trigger_point) { 156 | return Some((CompletionType::Filter, autoclose)); 157 | } else if self.in_expr(trigger_point) { 158 | if trigger_point == self.expr.2.begin.1 && trigger_point == self.expr.2.end.0 { 159 | return Some((CompletionType::Identifier, autoclose)); 160 | } 161 | if trigger_point > self.ident.1 { 162 | return Some((CompletionType::Identifier, autoclose)); 163 | } 164 | if let Some(ident_value) = self.is_ident(trigger_point) { 165 | // if let Some(ident2) = self.objects.last().map(|last| last) { 166 | let identifier = Identifier::new(&ident_value, self.ident.0, self.ident.1); 167 | let start = completion_start(trigger_point, &identifier); 168 | // let range = to_range((self.ident.0, self.ident.1)); 169 | let range = self.full_range(); 170 | return Some(( 171 | CompletionType::IncompleteIdentifier { 172 | name: start?.to_string(), 173 | range, 174 | }, 175 | autoclose, 176 | )); 177 | // } 178 | } 179 | return Some((CompletionType::Identifier, autoclose)); 180 | } else if self.is_test(trigger_point) { 181 | return Some((CompletionType::Test, false)); 182 | } 183 | None 184 | } 185 | 186 | pub fn in_pipe(&self, trigger_point: Point) -> bool { 187 | trigger_point >= self.pipe.0 && trigger_point <= self.pipe.1 188 | } 189 | 190 | pub fn in_expr(&self, trigger_point: Point) -> bool { 191 | let in_expr = trigger_point >= self.expr.0 && trigger_point < self.expr.1; 192 | let after_ident = trigger_point > self.ident.0; 193 | let no_ident = self.expr.2.begin.1 == self.expr.2.end.0; 194 | if !in_expr { 195 | return false; 196 | } 197 | in_expr && after_ident || no_ident 198 | } 199 | 200 | pub fn should_autoclose(&self) -> bool { 201 | self.expr.2.end.0 == self.expr.2.end.1 202 | } 203 | 204 | pub fn is_ident(&self, trigger_point: Point) -> Option { 205 | if trigger_point >= self.ident.0 && trigger_point <= self.ident.1 { 206 | self.objects.last().map(|last| last.name.to_string()) 207 | } else { 208 | None 209 | } 210 | } 211 | 212 | pub fn is_hover(&self, trigger_point: Point) -> bool { 213 | let full_range = self.full_range(); 214 | trigger_point >= self.ident.0 && trigger_point <= self.ident.1 215 | || to_range2(full_range, trigger_point) 216 | } 217 | 218 | pub fn is_filter(&self) -> bool { 219 | self.pipe.1 == self.ident.0 220 | } 221 | 222 | pub fn get_last_id(&self) -> Option<&JinjaObject> { 223 | self.objects.last() 224 | } 225 | 226 | pub fn show(&self) -> Vec { 227 | self.objects.clone() 228 | } 229 | 230 | pub fn full_range(&self) -> Range { 231 | self.objects 232 | .last() 233 | .map_or(Range::default(), |item| item.full_range()) 234 | } 235 | 236 | pub fn is_test(&self, trigger_point: Point) -> bool { 237 | self.test.2 && trigger_point >= self.test.1 && trigger_point.row == self.test.1.row 238 | } 239 | } 240 | 241 | pub fn objects_query( 242 | query: &Query, 243 | tree: &Tree, 244 | trigger_point: Point, 245 | text: &str, 246 | all: bool, 247 | ) -> JinjaObjects { 248 | let closest_node = tree.root_node(); 249 | let mut objects = JinjaObjects::default(); 250 | let mut cursor_qry = QueryCursor::new(); 251 | let capture_names = query.capture_names(); 252 | let mut continued = false; 253 | let mut my_id = 0; 254 | let mut my_expr = ( 255 | Point::default(), 256 | Point::default(), 257 | ExpressionRange::default(), 258 | ); 259 | let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); 260 | 'loop1: for m in matches { 261 | for capture in m.captures { 262 | let smaller = trigger_point <= capture.node.start_position(); 263 | if all || trigger_point >= capture.node.start_position() { 264 | let name = &capture_names[capture.index as usize]; 265 | let checked = objects.check(name, capture, text); 266 | if checked.is_none() { 267 | break 'loop1; 268 | } 269 | } else if smaller { 270 | let name = capture_names[capture.index as usize]; 271 | if objects.is_filter() || name == "expr" { 272 | break 'loop1; 273 | } else if !continued { 274 | if objects.is_hover(trigger_point) { 275 | continued = true; 276 | my_id = objects.objects.len() - 1; 277 | my_expr = objects.expr; 278 | continue; 279 | } else { 280 | break 'loop1; 281 | } 282 | } else if continued { 283 | let name = &capture_names[capture.index as usize]; 284 | let checked = objects.check(name, capture, text); 285 | if checked.is_none() { 286 | break 'loop1; 287 | } else if checked.is_some_and(|item| { 288 | matches!( 289 | item, 290 | ObjectAction::Expression 291 | | ObjectAction::NewObject 292 | | ObjectAction::Invalid 293 | ) 294 | }) { 295 | objects 296 | .objects 297 | .get_mut(my_id) 298 | .and_then(|obj| -> Option<()> { 299 | objects.ident = obj.location; 300 | obj.capture_first = true; 301 | None 302 | }); 303 | if my_id != objects.objects.len() - 1 { 304 | objects.objects.pop(); 305 | objects.expr = my_expr; 306 | } 307 | break 'loop1; 308 | } 309 | } 310 | } 311 | } 312 | } 313 | objects 314 | } 315 | 316 | #[derive(PartialEq, Debug)] 317 | pub enum CompletionType { 318 | Filter, 319 | Test, 320 | Identifier, 321 | IncludedTemplate { name: String, range: Range }, 322 | Snippets { range: Range }, 323 | IncompleteIdentifier { name: String, range: Range }, 324 | IncompleteFilter { name: String, range: Range }, 325 | } 326 | 327 | static VALID_IDENTIFIERS: [&str; 8] = [ 328 | "loop", "true", "false", "not", "as", "module", "super", "url_for", 329 | ]; 330 | 331 | #[derive(Default, Debug, Clone, Copy)] 332 | pub struct ExpressionRange { 333 | begin: (Point, Point), 334 | end: (Point, Point), 335 | } 336 | 337 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] 338 | pub enum ObjectAction { 339 | Expression, 340 | Invalid, 341 | NewField, 342 | NewFilter, 343 | NewTest, 344 | NewObject, 345 | } 346 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/python_identifiers.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use tree_sitter::{Point, Query, QueryCursor, Tree}; 4 | 5 | pub struct PythonAttributes { 6 | pub attributes: HashMap>, 7 | } 8 | 9 | impl PythonAttributes { 10 | pub fn merge(&self, line: u32) -> Vec { 11 | let mut identifiers = vec![]; 12 | for i in &self.attributes { 13 | let mut start = i.0.to_owned(); 14 | start.row += line as usize; 15 | let mut end = i.0; 16 | let mut name = String::new(); 17 | let len = i.1.len(); 18 | for (index, identifier) in i.1.iter().enumerate() { 19 | name.push_str(&identifier.field); 20 | end = &identifier.end; 21 | if index != len - 1 { 22 | name.push('.'); 23 | } 24 | } 25 | let mut end = end.to_owned(); 26 | end.row += line as usize; 27 | let identifier = PythonIdentifier { 28 | id: 0, 29 | start, 30 | end, 31 | field: name, 32 | }; 33 | identifiers.push(identifier); 34 | } 35 | identifiers 36 | } 37 | } 38 | 39 | #[derive(Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] 40 | pub struct PythonIdentifier { 41 | pub id: usize, 42 | pub start: Point, 43 | pub end: Point, 44 | pub field: String, 45 | } 46 | 47 | pub fn python_identifiers( 48 | query: &Query, 49 | tree: &Tree, 50 | mut _trigger_point: Point, 51 | text: &str, 52 | line: u32, 53 | ) -> Vec { 54 | let closest_node = tree.root_node(); 55 | let mut cursor_qry = QueryCursor::new(); 56 | let _capture_names = query.capture_names(); 57 | let mut attributes = PythonAttributes { 58 | attributes: HashMap::new(), 59 | }; 60 | let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); 61 | for i in matches { 62 | for capture in i.captures { 63 | if let Some(parent) = capture.node.parent() { 64 | let attribute = attributes 65 | .attributes 66 | .entry(parent.start_position()) 67 | .or_default(); 68 | let field = capture.node.utf8_text(text.as_bytes()).unwrap_or_default(); 69 | let identifier = PythonIdentifier { 70 | id: capture.node.id(), 71 | start: capture.node.start_position(), 72 | end: capture.node.end_position(), 73 | field: field.to_string(), 74 | }; 75 | attribute.push(identifier); 76 | } 77 | } 78 | } 79 | attributes.merge(line) 80 | } 81 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/queries.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::Query; 2 | 3 | #[derive(Debug)] 4 | pub struct Queries { 5 | pub jinja_definitions: Query, 6 | pub jinja_objects: Query, 7 | pub jinja_imports: Query, 8 | pub backend_definitions: Query, 9 | pub backend_templates: Query, 10 | pub jinja_snippets: Query, 11 | pub python_identifiers: Query, 12 | } 13 | 14 | impl Clone for Queries { 15 | fn clone(&self) -> Self { 16 | Self::default() 17 | } 18 | } 19 | 20 | impl Default for Queries { 21 | fn default() -> Self { 22 | Self { 23 | jinja_definitions: Query::new(&tree_sitter_jinja2::LANGUAGE.into(), DEFINITIONS) 24 | .unwrap(), 25 | jinja_objects: Query::new(&tree_sitter_jinja2::LANGUAGE.into(), OBJECTS).unwrap(), 26 | backend_definitions: Query::new(&tree_sitter_rust::LANGUAGE.into(), RUST_DEFINITIONS) 27 | .unwrap(), 28 | jinja_imports: Query::new(&tree_sitter_jinja2::LANGUAGE.into(), JINJA_IMPORTS).unwrap(), 29 | backend_templates: Query::new(&tree_sitter_rust::LANGUAGE.into(), RUST_TEMPLATES) 30 | .unwrap(), 31 | jinja_snippets: Query::new(&tree_sitter_jinja2::LANGUAGE.into(), JINJA_SNIPPETS) 32 | .unwrap(), 33 | python_identifiers: Query::new( 34 | &tree_sitter_python::LANGUAGE.into(), 35 | PYTHON_IDENTIFIERS, 36 | ) 37 | .unwrap(), 38 | } 39 | } 40 | } 41 | 42 | impl Queries { 43 | pub fn update_backend(&mut self, lang: &str) { 44 | if lang == "python" { 45 | self.backend_templates = 46 | Query::new(&tree_sitter_python::LANGUAGE.into(), PYTHON_TEMPLATES).unwrap(); 47 | self.backend_definitions = 48 | Query::new(&tree_sitter_python::LANGUAGE.into(), PYTHON_DEFINITIONS).unwrap(); 49 | self.python_identifiers = 50 | Query::new(&tree_sitter_python::LANGUAGE.into(), PYTHON_IDENTIFIERS).unwrap(); 51 | } 52 | } 53 | } 54 | 55 | const OBJECTS: &str = r#" 56 | ( 57 | [ 58 | ( 59 | (operator) @dot 60 | (#eq? @dot "\.") 61 | ) 62 | 63 | ( 64 | (identifier) @just_id 65 | (#not-match? @just_id "(^\\d+$)") 66 | ) 67 | 68 | ( 69 | (operator) @pipe 70 | ) 71 | 72 | (expression) @expr 73 | 74 | ( 75 | (keyword) @is 76 | (#eq? @is "is") 77 | ) 78 | 79 | (ERROR) @error 80 | 81 | ] 82 | ) 83 | "#; 84 | 85 | pub static RUST_DEFINITIONS: &str = r#" 86 | ([ 87 | (macro_invocation 88 | (identifier) @context 89 | (#eq? @context "context") 90 | ) @macro 91 | 92 | (token_tree 93 | (identifier) @key_id 94 | (#not-eq? @key_id "context") 95 | ) 96 | 97 | ( 98 | (field_expression 99 | (identifier) @jinja 100 | (field_identifier) @method 101 | ) 102 | (arguments 103 | (string_literal)+ @name 104 | ) 105 | 106 | (#eq? @jinja "jinja") 107 | (#match? @method "(add_global|add_filter|add_function)") 108 | 109 | ) @function 110 | 111 | (ERROR) @error 112 | ]) 113 | "#; 114 | 115 | const JINJA_IMPORTS: &str = r#" 116 | 117 | ( 118 | [ 119 | 120 | (statement 121 | (statement_begin) 122 | (keyword) @extends_keyword 123 | (string) @template_name 124 | (statement_end) 125 | (#eq? @extends_keyword "extends") 126 | ) @extends 127 | 128 | 129 | (statement 130 | (statement_begin) 131 | (keyword) @include_keyword 132 | (string) @template_name 133 | (statement_end) 134 | (#eq? @include_keyword "include") 135 | ) @include 136 | 137 | (statement 138 | (statement_begin) 139 | (keyword) @from_keyword 140 | (string) @template_name 141 | (keyword)? @import_keyword 142 | (identifier)? @import_identifier 143 | (#not-match? @import_identifier "(^\\d)") 144 | (statement_end) 145 | (#eq? @from_keyword "from") 146 | (#eq? @import_keyword "import") 147 | ) @from 148 | 149 | 150 | (statement 151 | (statement_begin) 152 | (keyword) @import_keyword 153 | (string) @template_name 154 | (identifier)? @as_keyword 155 | (identifier)? @import_identifier 156 | (#not-match? @import_identifier "(^\\d)") 157 | (#eq? @import_keyword "import") 158 | (#eq? @as_keyword "as") 159 | (statement_end) 160 | ) @import 161 | 162 | (ERROR) @error 163 | ] 164 | ) 165 | "#; 166 | 167 | const RUST_TEMPLATES: &str = r#" 168 | (call_expression 169 | [ 170 | (field_expression 171 | (field_identifier) @method_name 172 | ) 173 | (identifier) @method_name 174 | (#any-of? @method_name "render_jinja" "get_template") 175 | ;;(#match? @method_name "(render_jinja|get_template)") 176 | ] 177 | (arguments 178 | (string_literal)+ @template_name 179 | ) 180 | ) 181 | "#; 182 | 183 | const JINJA_SNIPPETS: &str = r#" 184 | [ 185 | (statement_begin) @start 186 | (statement_end) @end 187 | (ERROR 188 | (ERROR)? @error 189 | ) @error_block 190 | 191 | ( 192 | (keyword) @keyword 193 | ) 194 | ] 195 | "#; 196 | 197 | const DEFINITIONS: &str = r#" 198 | (statement 199 | (statement_begin) @scope_end 200 | 201 | (statement_end) @scope_start 202 | ) 203 | 204 | ( 205 | (identifier) @id 206 | (#not-match? @id "^(\\d+)$") 207 | ) 208 | ( 209 | (keyword) @definition 210 | (#match? @definition "^(for|set|with|macro|block)$") 211 | ) 212 | 213 | ( 214 | (keyword) @scope 215 | (#match? @scope "^(if|elif|else|filter|autoescape|raw)$") 216 | ) 217 | 218 | ( 219 | (keyword) @endblock 220 | (#match? @endblock "^end") 221 | ) 222 | 223 | ( 224 | (operator) @equals 225 | (#match? @equals "=") 226 | ) 227 | 228 | (ERROR) @error 229 | "#; 230 | 231 | const PYTHON_TEMPLATES: &str = r#" 232 | (call 233 | [ 234 | (attribute 235 | (identifier) @method_name 236 | ) 237 | (identifier) @method_name 238 | (#any-of? @method_name "render_jinja" "get_template") 239 | ] 240 | (argument_list 241 | (string)+ @template_name 242 | ) 243 | ) 244 | "#; 245 | 246 | pub static PYTHON_DEFINITIONS: &str = r#" 247 | 248 | ( 249 | (subscript 250 | (attribute 251 | object: (identifier)* @object 252 | attribute: (identifier) @field 253 | (#match? @field "^(globals|filters)$") 254 | (#eq? @object "jinja_env") 255 | ) 256 | (string 257 | (string_content) @key_id 258 | ) 259 | ) 260 | ) 261 | 262 | ( 263 | [ 264 | (call 265 | function: (identifier) @method 266 | arguments: (argument_list 267 | (keyword_argument 268 | name: (identifier) @key_id 269 | ) 270 | ) 271 | ) 272 | 273 | (call 274 | function: (attribute 275 | object: (identifier) 276 | attribute: (identifier) @method 277 | ) 278 | arguments: (argument_list 279 | (keyword_argument 280 | name: (identifier) @key_id 281 | ) 282 | ) 283 | ) 284 | ] 285 | (#match? @method "^(render_template|render)$") 286 | ) 287 | 288 | (ERROR) @error 289 | 290 | "#; 291 | 292 | const PYTHON_IDENTIFIERS: &str = r#" 293 | (attribute 294 | (identifier) @identifier 295 | ) 296 | 297 | (ERROR) @error 298 | "#; 299 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/rust_identifiers.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; 2 | 3 | use super::{Identifier, IdentifierType}; 4 | 5 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 6 | pub enum Current { 7 | InMacro(Point), 8 | #[default] 9 | Free, 10 | } 11 | 12 | #[derive(Default, Debug, Clone)] 13 | pub struct BackendIdentifiers { 14 | variables: Vec, 15 | } 16 | 17 | impl BackendIdentifiers { 18 | pub fn show(self) -> Vec { 19 | self.variables 20 | } 21 | 22 | pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { 23 | match name { 24 | "key_id" => { 25 | let start = capture.node.start_position(); 26 | let end = capture.node.end_position(); 27 | let name = capture.node.utf8_text(text.as_bytes()).ok()?; 28 | let mut identifier = Identifier::new(name, start, end); 29 | identifier.identifier_type = IdentifierType::BackendVariable; 30 | self.variables.push(identifier); 31 | } 32 | "name" => { 33 | let start = capture.node.start_position(); 34 | let end = capture.node.end_position(); 35 | let name = capture.node.utf8_text(text.as_bytes()).ok()?; 36 | let name = name.replace(['\"', '\''], ""); 37 | let mut identifier = Identifier::new(&name, start, end); 38 | identifier.identifier_type = IdentifierType::BackendVariable; 39 | self.variables.push(identifier); 40 | } 41 | "error" => { 42 | return None; 43 | } 44 | _ => (), 45 | } 46 | Some(()) 47 | } 48 | } 49 | 50 | pub fn backend_definition_query( 51 | query: &Query, 52 | tree: &Tree, 53 | trigger_point: Point, 54 | text: &str, 55 | all: bool, 56 | ) -> BackendIdentifiers { 57 | let closest_node = tree.root_node(); 58 | let mut cursor_qry = QueryCursor::new(); 59 | let mut rust = BackendIdentifiers::default(); 60 | let capture_names = query.capture_names(); 61 | let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); 62 | let captures = matches.into_iter().flat_map(|m| { 63 | m.captures 64 | .iter() 65 | .filter(|capture| all || capture.node.start_position() <= trigger_point) 66 | }); 67 | for capture in captures { 68 | let name = &capture_names[capture.index as usize]; 69 | if rust.check(name, capture, text).is_none() { 70 | break; 71 | } 72 | } 73 | rust 74 | } 75 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/rust_template_completion.rs: -------------------------------------------------------------------------------- 1 | use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; 2 | 3 | use super::{Identifier, IdentifierType}; 4 | 5 | #[derive(Default, Debug, Clone, PartialEq, Eq)] 6 | pub struct BackendTemplates { 7 | pub templates: Vec, 8 | in_method: bool, 9 | } 10 | 11 | impl BackendTemplates { 12 | pub fn in_template(&self, trigger_point: Point) -> Option<&Identifier> { 13 | let last = self.templates.last()?; 14 | if trigger_point >= last.start && trigger_point <= last.end { 15 | Some(last) 16 | } else { 17 | None 18 | } 19 | } 20 | 21 | pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { 22 | if name == "template_name" && self.in_method { 23 | let template = capture.node.utf8_text(text.as_bytes()).ok()?; 24 | let template = template.replace(['\"', '\''], ""); 25 | let start = capture.node.start_position(); 26 | let end = capture.node.end_position(); 27 | let mut identifer = Identifier::new(&template, start, end); 28 | identifer.identifier_type = IdentifierType::JinjaTemplate; 29 | self.templates.push(identifer); 30 | self.in_method = false; 31 | } else if name == "method_name" { 32 | let content = capture.node.utf8_text(text.as_bytes()).ok()?; 33 | if !METHODS.contains(&content) { 34 | return None; 35 | } 36 | self.in_method = true; 37 | } 38 | None 39 | } 40 | 41 | pub fn collect(self) -> Vec { 42 | self.templates 43 | } 44 | } 45 | 46 | pub fn backend_templates_query( 47 | query: &Query, 48 | tree: &Tree, 49 | trigger_point: Point, 50 | text: &str, 51 | all: bool, 52 | ) -> BackendTemplates { 53 | let mut templates = BackendTemplates::default(); 54 | let closest_node = tree.root_node(); 55 | let mut cursor_qry = QueryCursor::new(); 56 | let capture_names = query.capture_names(); 57 | let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); 58 | let captures = matches.into_iter().flat_map(|m| { 59 | m.captures 60 | .iter() 61 | .filter(|capture| all || capture.node.start_position() <= trigger_point) 62 | }); 63 | for capture in captures { 64 | let name = &capture_names[capture.index as usize]; 65 | templates.check(name, capture, text); 66 | } 67 | templates 68 | } 69 | 70 | static METHODS: [&str; 2] = ["render_jinja", "get_template"]; 71 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/snippets_completion.rs: -------------------------------------------------------------------------------- 1 | use crate::to_input_edit::to_position2; 2 | use tower_lsp::lsp_types::{ 3 | CompletionItem, CompletionItemKind, CompletionTextEdit, Range, TextEdit, 4 | }; 5 | use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; 6 | 7 | #[derive(Default, Debug)] 8 | pub struct Snippets { 9 | start: Point, 10 | end: Point, 11 | keyword: Point, 12 | pub is_error: bool, 13 | } 14 | 15 | impl Snippets { 16 | pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>) -> Option<()> { 17 | match name { 18 | "start" => { 19 | let start = capture.node.start_position(); 20 | self.start = start; 21 | } 22 | "end" => { 23 | let end = capture.node.end_position(); 24 | self.end = end; 25 | } 26 | "error_block" => { 27 | self.is_error = true; 28 | self.end = capture.node.end_position(); 29 | return None; 30 | } 31 | "keyword" => { 32 | self.keyword = capture.node.start_position(); 33 | } 34 | _ => (), 35 | } 36 | Some(()) 37 | } 38 | 39 | pub fn to_complete(&self, trigger_point: Point) -> Option { 40 | if self.is_error && trigger_point <= self.end { 41 | return Some(Range::default()); 42 | } 43 | if self.is_error && trigger_point >= self.start && trigger_point <= self.end { 44 | if self.keyword >= self.start && self.keyword <= self.end { 45 | return None; 46 | } 47 | let start_position = to_position2(trigger_point); 48 | let mut end_position = to_position2(trigger_point); 49 | end_position.character += 1; 50 | return Some(Range::new(start_position, end_position)); 51 | } 52 | None 53 | } 54 | } 55 | 56 | pub fn snippets_query( 57 | query: &Query, 58 | tree: &Tree, 59 | mut trigger_point: Point, 60 | text: &str, 61 | all: bool, 62 | ) -> Snippets { 63 | let closest_node = tree.root_node(); 64 | let mut snippets = Snippets::default(); 65 | let mut cursor_qry = QueryCursor::new(); 66 | let capture_names = query.capture_names(); 67 | let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); 68 | trigger_point.column += 2; 69 | let captures = matches.into_iter().flat_map(|m| { 70 | m.captures 71 | .iter() 72 | .filter(|capture| all || capture.node.start_position() <= trigger_point) 73 | }); 74 | for capture in captures { 75 | let name = &capture_names[capture.index as usize]; 76 | let check = snippets.check(name, capture); 77 | if check.is_none() { 78 | break; 79 | } 80 | } 81 | snippets 82 | } 83 | 84 | /// Range will be updated 85 | pub fn snippets() -> Vec { 86 | // label detail text 87 | let all = [ 88 | ( 89 | "for1", 90 | "Basic for loop", 91 | r#" for ${1:i} in ${2:items} %} 92 | {% endfor %} 93 | "#, 94 | ), 95 | ( 96 | "for2", 97 | "For loop with key and value", 98 | r#" for (${1:key}, ${2:value}) in ${3:items} %} 99 | {% endfor %} 100 | "#, 101 | ), 102 | ( 103 | "with", 104 | "With block", 105 | r#" with $1 %} 106 | {% endwith %} 107 | "#, 108 | ), 109 | ( 110 | "set1", 111 | "Set variable that is current scope", 112 | r#" set ${1:key} = ${2:value} %} 113 | "#, 114 | ), 115 | ( 116 | "set2", 117 | "Set with scope", 118 | r#" set ${1:data} %} 119 | {% endset %} 120 | 121 | "#, 122 | ), 123 | ( 124 | "include", 125 | "Include template", 126 | r#" include "$1" %} 127 | "#, 128 | ), 129 | ( 130 | "from", 131 | "Import from other template", 132 | r#" from "$1" import ${2:module} %} 133 | "#, 134 | ), 135 | ( 136 | "import", 137 | "Import entire template as module", 138 | r#" import "$1" as ${2:module} %} 139 | "#, 140 | ), 141 | ( 142 | "extends", 143 | "Extend parent template", 144 | r#" extends "$1" %} 145 | "#, 146 | ), 147 | ( 148 | "if1", 149 | "If statement", 150 | r#" if $1 %} 151 | {% endif %} 152 | "#, 153 | ), 154 | ( 155 | "if2", 156 | "If statement", 157 | r#" if $1 %} 158 | {% elif $2 %} 159 | {% endif %} 160 | "#, 161 | ), 162 | ]; 163 | 164 | let mut snippets = vec![]; 165 | 166 | for snippet in all { 167 | let edit = TextEdit { 168 | new_text: snippet.2.to_owned(), 169 | ..Default::default() 170 | }; 171 | let text_edit = CompletionTextEdit::Edit(edit); 172 | let item = CompletionItem { 173 | label: snippet.0.to_owned(), 174 | detail: Some(snippet.1.to_owned()), 175 | kind: Some(CompletionItemKind::SNIPPET), 176 | text_edit: Some(text_edit), 177 | ..Default::default() 178 | }; 179 | snippets.push(item); 180 | } 181 | snippets 182 | } 183 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/search/templates.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; 4 | 5 | use super::{Identifier, IdentifierType}; 6 | 7 | #[derive(Debug)] 8 | pub enum Import { 9 | Extends { 10 | template: Identifier, 11 | }, 12 | Include { 13 | templates: Vec, 14 | }, 15 | From { 16 | template: Identifier, 17 | identifiers: Vec, 18 | }, 19 | Import { 20 | template: Identifier, 21 | identifier: Identifier, 22 | }, 23 | } 24 | 25 | impl Import { 26 | pub fn get_identifier(&self, trigger_point: Point) -> Option<&Identifier> { 27 | match &self { 28 | Import::Extends { template } 29 | | Import::From { template, .. } 30 | | Import::Import { template, .. } => { 31 | if trigger_point >= template.start && trigger_point <= template.end { 32 | Some(template) 33 | } else { 34 | None 35 | } 36 | } 37 | Import::Include { templates } => { 38 | let template = templates.iter().find(|template| { 39 | trigger_point >= template.start && trigger_point <= template.end 40 | })?; 41 | Some(template) 42 | } 43 | } 44 | } 45 | 46 | fn collect(self, ids: &mut Vec) { 47 | match self { 48 | Import::Extends { template } => ids.push(template), 49 | Import::Include { templates } => { 50 | for i in templates { 51 | ids.push(i); 52 | } 53 | } 54 | Import::From { 55 | template, 56 | identifiers, 57 | } => { 58 | ids.push(template); 59 | for i in identifiers { 60 | ids.push(i); 61 | } 62 | } 63 | Import::Import { template, .. } => { 64 | ids.push(template); 65 | } 66 | } 67 | } 68 | } 69 | 70 | #[derive(Default, Debug)] 71 | pub enum Current { 72 | Id(usize), 73 | #[default] 74 | Nothing, 75 | } 76 | 77 | #[derive(Default)] 78 | pub struct JinjaImports { 79 | pub imports: HashMap, 80 | pub current: Current, 81 | pub last_id: usize, 82 | } 83 | 84 | impl JinjaImports { 85 | pub fn in_template(&self, _: Point) -> Option<&Import> { 86 | if let Current::Id(id) = self.current { 87 | let last = self.imports.get(&id)?; 88 | return Some(last); 89 | } 90 | None 91 | } 92 | pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { 93 | match name { 94 | "error" => { 95 | return None; 96 | } 97 | "extends" => { 98 | let id = capture.node.id(); 99 | let last = self.imports.get_mut(&id); 100 | if last.is_some() { 101 | self.current = Current::Nothing; 102 | } else { 103 | let import = Import::Extends { 104 | template: Identifier::default(), 105 | }; 106 | self.imports.insert(id, import); 107 | self.current = Current::Id(id); 108 | self.last_id = id; 109 | } 110 | } 111 | "include" => { 112 | let id = capture.node.id(); 113 | let last = self.imports.get_mut(&id); 114 | if last.is_some() { 115 | self.current = Current::Id(id); 116 | } else { 117 | let import = Import::Include { templates: vec![] }; 118 | self.imports.insert(id, import); 119 | self.current = Current::Id(id); 120 | self.last_id = id; 121 | } 122 | } 123 | "import" => { 124 | let id = capture.node.id(); 125 | let last = self.imports.get_mut(&id); 126 | if last.is_some() { 127 | self.current = Current::Id(id); 128 | } else { 129 | let import = Import::Import { 130 | template: Identifier::default(), 131 | identifier: Identifier::default(), 132 | }; 133 | self.imports.insert(id, import); 134 | self.current = Current::Id(id); 135 | self.last_id = id; 136 | } 137 | } 138 | "from" => { 139 | let id = capture.node.id(); 140 | let last = self.imports.get_mut(&id); 141 | if last.is_some() { 142 | self.current = Current::Id(id); 143 | } else { 144 | let import = Import::From { 145 | template: Identifier::default(), 146 | identifiers: vec![], 147 | }; 148 | self.imports.insert(id, import); 149 | self.current = Current::Id(id); 150 | self.last_id = id; 151 | } 152 | } 153 | "template_name" => { 154 | if let Current::Id(id) = self.current { 155 | let last = self.imports.get_mut(&id)?; 156 | let name = capture.node.utf8_text(text.as_bytes()).ok()?; 157 | let name = name.replace(['\"', '\''], ""); 158 | let start = capture.node.start_position(); 159 | let end = capture.node.end_position(); 160 | match last { 161 | Import::Extends { template } => { 162 | if template.name.is_empty() { 163 | template.name = name; 164 | template.start = start; 165 | template.end = end; 166 | template.identifier_type = IdentifierType::JinjaTemplate; 167 | } 168 | } 169 | Import::Include { templates } => { 170 | let mut template = Identifier::new(&name, start, end); 171 | template.identifier_type = IdentifierType::JinjaTemplate; 172 | templates.push(template); 173 | } 174 | Import::From { template, .. } => { 175 | if template.name.is_empty() { 176 | template.name = name; 177 | template.start = start; 178 | template.end = end; 179 | template.identifier_type = IdentifierType::JinjaTemplate; 180 | } 181 | } 182 | Import::Import { template, .. } => { 183 | if template.name.is_empty() { 184 | template.name = name; 185 | template.start = start; 186 | template.end = end; 187 | template.identifier_type = IdentifierType::JinjaTemplate; 188 | } 189 | } 190 | } 191 | } 192 | } 193 | "import_identifier" => { 194 | if let Current::Id(id) = self.current { 195 | let name = capture.node.utf8_text(text.as_bytes()).ok()?; 196 | let start = capture.node.start_position(); 197 | let end = capture.node.end_position(); 198 | let last = self.imports.get_mut(&id)?; 199 | match last { 200 | Import::From { identifiers, .. } => { 201 | let identifier = Identifier::new(name, start, end); 202 | identifiers.push(identifier); 203 | } 204 | Import::Import { identifier, .. } => { 205 | if identifier.name.is_empty() { 206 | identifier.name = String::from(name); 207 | self.current = Current::Nothing; 208 | } 209 | } 210 | _ => self.current = Current::Nothing, 211 | } 212 | } 213 | } 214 | 215 | _ => (), 216 | } 217 | Some(()) 218 | } 219 | 220 | pub fn collect(self, ids: &mut Vec) { 221 | for i in self.imports { 222 | i.1.collect(ids); 223 | } 224 | } 225 | } 226 | pub fn templates_query( 227 | query: &Query, 228 | tree: &Tree, 229 | trigger_point: Point, 230 | text: &str, 231 | all: bool, 232 | ) -> JinjaImports { 233 | let closest_node = tree.root_node(); 234 | let mut imports = JinjaImports::default(); 235 | let mut cursor_qry = QueryCursor::new(); 236 | let capture_names = query.capture_names(); 237 | let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); 238 | let captures = matches.into_iter().flat_map(|m| { 239 | m.captures 240 | .iter() 241 | .filter(|capture| all || capture.node.start_position() <= trigger_point) 242 | }); 243 | for capture in captures { 244 | let name = &capture_names[capture.index as usize]; 245 | let res = imports.check(name, capture, text); 246 | if res.is_none() { 247 | break; 248 | } 249 | } 250 | imports 251 | } 252 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/to_input_edit.rs: -------------------------------------------------------------------------------- 1 | use ropey::Rope; 2 | use tower_lsp::lsp_types::{Position, Range}; 3 | use tree_sitter::{InputEdit, Point}; 4 | 5 | // use crate::lsp_files::JinjaVariable; 6 | 7 | pub trait ToInputEdit { 8 | fn to_point(&self, position: Position) -> Point; 9 | fn to_byte(&self, position: Position) -> usize; 10 | fn to_position(&self, offset: usize) -> Position; 11 | fn to_input_edit(&self, range: Range, text: &str) -> InputEdit; 12 | } 13 | 14 | impl ToInputEdit for Rope { 15 | fn to_point(&self, position: Position) -> Point { 16 | Point::new(position.line as usize, position.character as usize) 17 | } 18 | 19 | fn to_byte(&self, position: Position) -> usize { 20 | let start_line = self.line_to_byte(position.line as usize); 21 | start_line + position.character as usize 22 | } 23 | 24 | fn to_position(&self, mut offset: usize) -> Position { 25 | offset = offset.min(self.len_bytes()); 26 | let mut low = 0usize; 27 | let mut high = self.len_lines(); 28 | if high == 0 { 29 | return Position { 30 | line: 0, 31 | character: offset as u32, 32 | }; 33 | } 34 | while low < high { 35 | let mid = low + (high - low) / 2; 36 | if self.line_to_byte(mid) > offset { 37 | high = mid; 38 | } else { 39 | low = mid + 1; 40 | } 41 | } 42 | let line = low - 1; 43 | let character = offset - self.line_to_byte(line); 44 | Position::new(line as u32, character as u32) 45 | } 46 | 47 | fn to_input_edit(&self, range: Range, text: &str) -> InputEdit { 48 | let start = range.start; 49 | let end = range.end; 50 | 51 | let start_byte = self.to_byte(start); 52 | let start_position = self.to_point(start); 53 | 54 | let new_end_byte = start_byte + text.len(); 55 | let new_end_position = self.to_position(new_end_byte); 56 | let new_end_position = self.to_point(new_end_position); 57 | 58 | let old_end_byte = self.to_byte(end); 59 | let old_end_position = self.to_point(end); 60 | 61 | InputEdit { 62 | start_byte, 63 | old_end_byte, 64 | new_end_byte, 65 | start_position, 66 | old_end_position, 67 | new_end_position, 68 | } 69 | } 70 | } 71 | 72 | pub fn to_position2(point: Point) -> Position { 73 | Position::new(point.row as u32, point.column as u32) 74 | } 75 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/tree_builder.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use tower_lsp::lsp_types::DiagnosticSeverity; 4 | 5 | #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] 6 | pub enum LangType { 7 | Template, 8 | Backend, 9 | } 10 | 11 | #[derive(PartialEq, Eq, Debug)] 12 | pub enum JinjaDiagnostic { 13 | DefinedSomewhere, 14 | Undefined, 15 | TemplateNotFound, 16 | } 17 | 18 | impl JinjaDiagnostic { 19 | pub fn severity(&self) -> DiagnosticSeverity { 20 | match &self { 21 | JinjaDiagnostic::DefinedSomewhere => DiagnosticSeverity::INFORMATION, 22 | JinjaDiagnostic::Undefined => DiagnosticSeverity::WARNING, 23 | JinjaDiagnostic::TemplateNotFound => DiagnosticSeverity::WARNING, 24 | } 25 | } 26 | } 27 | 28 | impl Display for JinjaDiagnostic { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | match self { 31 | JinjaDiagnostic::Undefined => f.write_str("Undefined variable"), 32 | JinjaDiagnostic::DefinedSomewhere => f.write_str("Variable is defined in other file."), 33 | JinjaDiagnostic::TemplateNotFound => f.write_str("Template not found"), 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jinja-lsp-queries/src/types.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uros-5/jinja-lsp/ef8025bed811a2b282c50edfd53a4230fc93b4f9/jinja-lsp-queries/src/types.rs -------------------------------------------------------------------------------- /jinja-lsp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jinja-lsp" 3 | version = "0.1.86" 4 | edition = "2021" 5 | license = "MIT" 6 | authors = ["uros-5"] 7 | description = "Language server for jinja2" 8 | homepage = "https://github.com/uros-5/jinja-lsp" 9 | repository = "https://github.com/uros-5/jinja-lsp" 10 | keywords = ["jinja", "templates", "lsp"] 11 | readme = "README.md" 12 | 13 | [profile.release] 14 | strip = true 15 | opt-level = 3 16 | 17 | [[bin]] 18 | name = "jinja-lsp" 19 | path = "src/main.rs" 20 | 21 | 22 | [dependencies] 23 | env_logger = "0.9.0" 24 | ropey = "1.5.0" 25 | serde_json = "1.0.78" 26 | tokio = { version = "1.17.0", features = ["full"] } 27 | tower-lsp = { version = "0.20.0", features = ["proposed"]} 28 | serde = { version = "1.0", features = ["derive"] } 29 | tree-sitter = "^0.23.0" 30 | walkdir = "2.4.0" 31 | anyhow = "1.0.75" 32 | tree-sitter-jinja2 = "0.0.12" 33 | tree-sitter-rust = "0.23.0" 34 | jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.85"} 35 | tree-sitter-language = "0.1.0" 36 | toml = {version = "0.8.19", features = ["display"]} 37 | clap = { version = "4.5.39", features = ["derive"] } 38 | -------------------------------------------------------------------------------- /jinja-lsp/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /jinja-lsp/src/backend.rs: -------------------------------------------------------------------------------- 1 | use serde_json::Value; 2 | use tokio::sync::{ 3 | mpsc::{self, Sender}, 4 | oneshot, 5 | }; 6 | use tower_lsp::{ 7 | jsonrpc::Result, 8 | lsp_types::{ 9 | CompletionParams, CompletionResponse, DidChangeConfigurationParams, 10 | DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, 11 | DocumentSymbolResponse, InitializeParams, InitializeResult, 12 | }, 13 | Client, LanguageServer, 14 | }; 15 | 16 | use tower_lsp::lsp_types::{ 17 | CodeActionParams, CodeActionResponse, DidChangeTextDocumentParams, DidSaveTextDocumentParams, 18 | ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverParams, 19 | InitializedParams, 20 | }; 21 | 22 | use crate::channels::{ 23 | diagnostics::diagnostics_task, 24 | lsp::{lsp_task, LspMessage}, 25 | }; 26 | 27 | pub struct Backend { 28 | main_channel: Sender, 29 | } 30 | 31 | #[tower_lsp::async_trait] 32 | impl LanguageServer for Backend { 33 | async fn initialize(&self, params: InitializeParams) -> Result { 34 | let (sender, rx) = oneshot::channel(); 35 | let _ = self 36 | .main_channel 37 | .send(LspMessage::Initialize(Box::new(params), sender)) 38 | .await; 39 | if let Ok(msg) = rx.await { 40 | Ok(msg) 41 | } else { 42 | Ok(InitializeResult::default()) 43 | } 44 | } 45 | 46 | async fn initialized(&self, _params: InitializedParams) { 47 | let (sender, _) = oneshot::channel(); 48 | let _ = self 49 | .main_channel 50 | .send(LspMessage::Initialized(sender)) 51 | .await; 52 | } 53 | 54 | async fn did_open(&self, params: DidOpenTextDocumentParams) { 55 | let _ = self.main_channel.send(LspMessage::DidOpen(params)).await; 56 | } 57 | 58 | async fn did_close(&self, _params: DidCloseTextDocumentParams) {} 59 | 60 | async fn did_change(&self, params: DidChangeTextDocumentParams) { 61 | let _ = self.main_channel.send(LspMessage::DidChange(params)).await; 62 | } 63 | 64 | async fn did_save(&self, params: DidSaveTextDocumentParams) { 65 | let _ = self.main_channel.send(LspMessage::DidSave(params)).await; 66 | } 67 | 68 | async fn completion(&self, params: CompletionParams) -> Result> { 69 | let (sender, tx) = oneshot::channel(); 70 | let _ = self 71 | .main_channel 72 | .send(LspMessage::Completion(params, sender)) 73 | .await; 74 | if let Ok(completion) = tx.await { 75 | return Ok(completion); 76 | } 77 | Ok(None) 78 | } 79 | 80 | async fn hover(&self, params: HoverParams) -> Result> { 81 | let (sender, tx) = oneshot::channel(); 82 | let _ = self 83 | .main_channel 84 | .send(LspMessage::Hover(params, sender)) 85 | .await; 86 | if let Ok(hover) = tx.await { 87 | return Ok(hover); 88 | } 89 | Ok(None) 90 | } 91 | 92 | async fn goto_definition( 93 | &self, 94 | params: GotoDefinitionParams, 95 | ) -> Result> { 96 | let (sender, tx) = oneshot::channel(); 97 | let _ = self 98 | .main_channel 99 | .send(LspMessage::GoToDefinition(params, sender)) 100 | .await; 101 | if let Ok(definition) = tx.await { 102 | return Ok(definition); 103 | } 104 | Ok(None) 105 | } 106 | 107 | async fn code_action(&self, params: CodeActionParams) -> Result> { 108 | let (sender, tx) = oneshot::channel(); 109 | let _ = self 110 | .main_channel 111 | .send(LspMessage::CodeAction(params, sender)) 112 | .await; 113 | if let Ok(code_action) = tx.await { 114 | return Ok(code_action); 115 | } 116 | Ok(None) 117 | } 118 | 119 | async fn execute_command(&self, params: ExecuteCommandParams) -> Result> { 120 | let (sender, _) = oneshot::channel(); 121 | let _ = self 122 | .main_channel 123 | .send(LspMessage::ExecuteCommand(params, sender)) 124 | .await; 125 | Ok(None) 126 | } 127 | 128 | async fn document_symbol( 129 | &self, 130 | params: DocumentSymbolParams, 131 | ) -> Result> { 132 | let (sender, tx) = oneshot::channel(); 133 | let _ = self 134 | .main_channel 135 | .send(LspMessage::DocumentSymbol(params, sender)) 136 | .await; 137 | if let Ok(symbols) = tx.await { 138 | return Ok(symbols); 139 | } 140 | 141 | Ok(None) 142 | } 143 | 144 | async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { 145 | let _ = self 146 | .main_channel 147 | .send(LspMessage::DidChangeConfiguration(params)) 148 | .await; 149 | } 150 | 151 | async fn shutdown(&self) -> Result<()> { 152 | Ok(()) 153 | } 154 | } 155 | 156 | impl Backend { 157 | pub fn _new(client: Client) -> Self { 158 | let (lsp_sender, lsp_recv) = mpsc::channel(50); 159 | let (diagnostic_sender, diagnostic_recv) = mpsc::channel(20); 160 | lsp_task( 161 | client.clone(), 162 | diagnostic_sender, 163 | lsp_sender.clone(), 164 | lsp_recv, 165 | ); 166 | diagnostics_task(client.clone(), diagnostic_recv, lsp_sender.clone()); 167 | Self { 168 | main_channel: lsp_sender, 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /jinja-lsp/src/channels/diagnostics.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use jinja_lsp_queries::{ 4 | lsp_helper::create_diagnostic, search::Identifier, tree_builder::JinjaDiagnostic, 5 | }; 6 | use tokio::sync::mpsc::{Receiver, Sender}; 7 | use tower_lsp::{ 8 | lsp_types::{MessageType, Url}, 9 | Client, 10 | }; 11 | 12 | use super::lsp::LspMessage; 13 | 14 | pub fn diagnostics_task( 15 | client: Client, 16 | mut receiver: Receiver, 17 | lsp_channel: Sender, 18 | ) { 19 | tokio::spawn(async move { 20 | while let Some(msg) = receiver.recv().await { 21 | match msg { 22 | DiagnosticMessage::Str(msg) => client.log_message(MessageType::INFO, msg).await, 23 | DiagnosticMessage::Errors(all_errors) => { 24 | let mut code_actions = HashMap::new(); 25 | for (uri, errors) in all_errors.into_iter() { 26 | let template_errors = errors 27 | .iter() 28 | .filter(|err| err.0 == JinjaDiagnostic::TemplateNotFound); 29 | let mut v = vec![]; 30 | for err in template_errors { 31 | v.push(err.1.to_owned()); 32 | } 33 | code_actions.insert(uri.to_owned(), v); 34 | let mut v = vec![]; 35 | for error in errors { 36 | let diagnostic = create_diagnostic( 37 | &error.1, 38 | error.0.severity(), 39 | error.0.to_string(), 40 | ); 41 | v.push(diagnostic); 42 | } 43 | let uri = Url::parse(&uri).unwrap(); 44 | client.publish_diagnostics(uri, v, None).await; 45 | } 46 | let _ = lsp_channel 47 | .send(LspMessage::CodeActions(code_actions)) 48 | .await; 49 | } 50 | } 51 | } 52 | }); 53 | } 54 | 55 | #[derive(Debug)] 56 | pub enum DiagnosticMessage { 57 | Errors(HashMap>), 58 | Str(String), 59 | } 60 | -------------------------------------------------------------------------------- /jinja-lsp/src/channels/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod diagnostics; 2 | pub mod lsp; 3 | -------------------------------------------------------------------------------- /jinja-lsp/src/config.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::{HashMap, HashSet}, 3 | fs::read_to_string, 4 | path::{Path, PathBuf}, 5 | }; 6 | 7 | use jinja_lsp_queries::{ 8 | search::Identifier, 9 | tree_builder::{JinjaDiagnostic, LangType}, 10 | }; 11 | use serde::{Deserialize, Serialize}; 12 | use walkdir::WalkDir; 13 | 14 | use crate::lsp_files::LspFiles; 15 | use clap::Parser; 16 | 17 | /// Jinja configuration 18 | /// `templates` can be absolute and relative path 19 | #[derive(Serialize, Deserialize, Debug, Clone)] 20 | pub struct JinjaConfig { 21 | pub templates: PathBuf, 22 | pub backend: Vec, 23 | pub lang: String, 24 | #[serde(skip)] 25 | pub user_defined: bool, 26 | pub hide_undefined: Option, 27 | pub template_extensions: Vec, 28 | } 29 | 30 | #[derive(Serialize, Deserialize, Debug, Clone)] 31 | pub struct OptionalJinjaConfig { 32 | pub templates: Option, 33 | pub backend: Option>, 34 | #[serde(skip)] 35 | pub lang: Option, 36 | #[serde(skip)] 37 | pub user_defined: Option, 38 | pub hide_undefined: Option>, 39 | pub template_extensions: Option>, 40 | } 41 | 42 | impl Default for JinjaConfig { 43 | fn default() -> Self { 44 | Self { 45 | templates: PathBuf::from("./"), 46 | backend: vec![".".to_string()], 47 | lang: "python".to_string(), 48 | user_defined: false, 49 | hide_undefined: Some(false), 50 | template_extensions: vec!["html".to_string(), "jinja".to_string(), "j2".to_string()], 51 | } 52 | } 53 | } 54 | 55 | impl From for JinjaConfig { 56 | fn from(value: OptionalJinjaConfig) -> Self { 57 | let mut config = Self::default(); 58 | if let Some(templates) = value.templates { 59 | config.templates = templates; 60 | } 61 | if let Some(backend) = value.backend { 62 | config.backend = backend; 63 | } 64 | if let Some(lang) = value.lang { 65 | config.lang = lang; 66 | } 67 | if let Some(hide_undefined) = value.hide_undefined { 68 | config.hide_undefined = hide_undefined; 69 | } 70 | 71 | if let Some(new_extensions) = value.template_extensions { 72 | new_template_extensions(&mut config, Some(new_extensions)); 73 | } 74 | 75 | config 76 | } 77 | } 78 | 79 | pub fn new_template_extensions(config: &mut JinjaConfig, new_extensions: Option>) { 80 | let existing: HashSet<_> = config.template_extensions.iter().cloned().collect(); 81 | let new_extensions = new_extensions.unwrap_or(vec![ 82 | "html".to_string(), 83 | "jinja".to_string(), 84 | "j2".to_string(), 85 | ]); 86 | for new_ext in new_extensions { 87 | if !existing.contains(&new_ext) { 88 | config.template_extensions.push(new_ext); 89 | } 90 | } 91 | } 92 | 93 | pub fn search_config() -> Option { 94 | let configs = [ 95 | ("pyproject.toml", "tool", "python"), 96 | ("Cargo.toml", "metadata", "rust"), 97 | ("jinja-lsp.toml", "tool", "python"), 98 | ]; 99 | for i in configs { 100 | let contents = read_to_string(i.0).unwrap_or_default(); 101 | if contents.is_empty() { 102 | continue; 103 | } 104 | let config = get_config(&contents, i.1); 105 | if let Some(config) = config { 106 | let mut config = JinjaConfig::from(config); 107 | config.user_defined = true; 108 | config.lang = i.2.to_string(); 109 | return Some(config); 110 | } 111 | } 112 | None 113 | } 114 | 115 | impl JinjaConfig { 116 | pub fn file_ext(&self, path: &&Path) -> Option { 117 | let extension = match path.extension()?.to_str() { 118 | Some(e) => { 119 | if self.template_extensions.contains(&e.to_string()) { 120 | Some(LangType::Template) 121 | } else if e == "rs" || e == "py" { 122 | Some(LangType::Backend) 123 | } else { 124 | None 125 | } 126 | } 127 | None => None, 128 | }; 129 | extension 130 | } 131 | 132 | pub fn user_defined(&mut self, def: bool) -> Option<()> { 133 | self.user_defined = def; 134 | None 135 | } 136 | } 137 | 138 | pub type InitLsp = ( 139 | HashMap>, 140 | LspFiles, 141 | ); 142 | 143 | pub fn walkdir(config: &JinjaConfig) -> anyhow::Result { 144 | let mut all = vec![config 145 | .clone() 146 | .templates 147 | .to_str() 148 | .unwrap() 149 | .to_string() 150 | .clone()]; 151 | let mut backend = config.backend.clone(); 152 | all.append(&mut backend); 153 | let mut lsp_files = LspFiles::default(); 154 | lsp_files.config = config.clone(); 155 | lsp_files.ignore_globals = config.hide_undefined.unwrap_or(false); 156 | if config.lang == "python" { 157 | lsp_files.queries.update_backend(&config.lang); 158 | lsp_files.parsers.update_backend(&config.lang); 159 | } 160 | let mut diags = HashMap::new(); 161 | for dir in all { 162 | let walk = WalkDir::new(dir); 163 | for entry in walk.into_iter() { 164 | let entry = entry?; 165 | let metadata = entry.metadata()?; 166 | if metadata.is_file() { 167 | let path = &entry.path(); 168 | let ext = config.file_ext(path); 169 | if let Some(ext) = ext { 170 | lsp_files.read_file(path, ext); 171 | } 172 | } 173 | } 174 | } 175 | 176 | lsp_files.read_trees(&mut diags); 177 | Ok((diags, lsp_files)) 178 | } 179 | 180 | #[derive(Parser, Debug)] 181 | #[command(version, about, long_about = None)] 182 | pub struct JinjaLspArgs { 183 | /// Run language server. 184 | #[arg(long)] 185 | pub stdio: bool, 186 | } 187 | 188 | pub fn get_config(contents: &str, tools: &str) -> Option { 189 | let toml_value: toml::Value = toml::from_str(contents).ok()?; 190 | let tools = toml_value.get(tools)?; 191 | let config = tools.get("jinja-lsp")?; 192 | let toml_value: OptionalJinjaConfig = 193 | toml::from_str(&toml::to_string_pretty(config).ok()?).ok()?; 194 | 195 | Some(toml_value) 196 | } 197 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/abs.md: -------------------------------------------------------------------------------- 1 | **abs** 2 | 3 | Returns the absolute value of a number. 4 | 5 | ```jinja 6 | |a - b| = {{ (a - b)|abs }} 7 | -> |2 - 4| = 2 8 | ``` 9 | 10 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/attr.md: -------------------------------------------------------------------------------- 1 | **attr** 2 | 3 | Looks up an attribute. 4 | 5 | In MiniJinja this is the same as the `[]` operator. In Jinja2 there is a 6 | small difference which is why this filter is sometimes used in Jinja2 7 | templates. For compatibility it s provided here as well. 8 | 9 | ```jinja 10 | 11 | {{ value["key"] == value|attr("key") }} -> true 12 | 13 | ``` 14 | 15 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/batch.md: -------------------------------------------------------------------------------- 1 | **batch** 2 | 3 | Batch items. 4 | 5 | This filter works pretty much like `slice` just the other way round. It 6 | 7 | returns a list of lists with the given number of items. If you provide a 8 | 9 | second parameter this is used to fill up missing items. 10 | 11 | ```jinja 12 | 13 | 14 | 15 | {% for row in items|batch(3, " ") %} 16 | 17 | {% for column in row %} 18 | 19 | {% endfor %} 20 | 21 | {% endfor %} 22 |
{{ column }}
23 | ``` 24 | 25 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/bool.md: -------------------------------------------------------------------------------- 1 | **bool** 2 | 3 | Converts the value into a boolean value. 4 | 5 | This behaves the same as the if statement does with regards to 6 | handling of boolean values. 7 | 8 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/capitalize.md: -------------------------------------------------------------------------------- 1 | **capitalize** 2 | 3 | Convert the string with all its characters lowercased 4 | 5 | apart from the first char which is uppercased. 6 | 7 | ```jinja 8 |

{{ chapter.title|capitalize }}

9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/contrib/dateformat.md: -------------------------------------------------------------------------------- 1 | dateformat 2 | Formats a timestamp as date. 3 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/contrib/datetimeformat.md: -------------------------------------------------------------------------------- 1 | datetimeformat 2 | Formats a timestamp as date and time. 3 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/contrib/pluralize.md: -------------------------------------------------------------------------------- 1 | pluralize 2 | Returns a plural suffix if the value is not 1, ‘1’, or an object of length 1. 3 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/contrib/timeformat.md: -------------------------------------------------------------------------------- 1 | timeformat 2 | Formats a timestamp as time. 3 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/default.md: -------------------------------------------------------------------------------- 1 | **default** 2 | 3 | If the value is undefined it will return the passed default value, 4 | otherwise the value of the variable: 5 | 6 | ```jinja 7 | |a - b| = {{ (a - b)|abs }} 8 | -> |2 - 4| = 2 9 | ``` 10 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/dictsort.md: -------------------------------------------------------------------------------- 1 | **dictsort** 2 | 3 | Dict sorting functionality. 4 | 5 | This filter works like `|items` but sorts the pairs by key first. 6 | The filter accepts a few keyword arguments: 7 | * `case_sensitive`: set to `true` to make the sorting of strings case sensitive. 8 | * `by`: set to `"value"` to sort by value. Defaults to `"key"`. 9 | * `reverse`: set to `true` to sort in reverse. 10 | 11 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/escape.md: -------------------------------------------------------------------------------- 1 | **escape** 2 | 3 | Escapes a string. By default to HTML. 4 | 5 | By default this filter is also registered under the alias `e`. Note that 6 | this filter escapes with the format that is native to the format or HTML 7 | otherwise. This means that if the auto escape setting is set to 8 | `Json` for instance then this filter will serialize to JSON instead. 9 | 10 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/first.md: -------------------------------------------------------------------------------- 1 | **first** 2 | 3 | Returns the first item from a list. 4 | 5 | If the list is empty `undefined` is returned. 6 | ```jinja 7 |
8 |
primary email 9 |
{{ user.email_addresses|first|default(no userecho first 10 | ) }} 11 |
12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/float.md: -------------------------------------------------------------------------------- 1 | **float** 2 | 3 | Converts a value into a float. 4 | 5 | ```jinja 6 | {{ "42.5"|float == 42.5 }} -> true 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/indent.md: -------------------------------------------------------------------------------- 1 | **indent** 2 | 3 | indents Value with spaces 4 | 5 | The first optional parameter to the filter can be set to `true` to 6 | indent the first line. The parameter defaults to false. 7 | the second optional parameter to the filter can be set to `true` 8 | to indent blank lines. The parameter defaults to false. 9 | This filter is useful, if you want to template yaml-files 10 | 11 | ```jinja 12 | example: 13 | config: 14 | {{ global_conifg|indent(2) }} # does not indent first line 15 | {{ global_config|indent(2,true) }} # indent whole Value with two spaces 16 | {{ global_config|indent(2,true,true)}} # indent whole Value and all blank lines 17 | ``` 18 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/int.md: -------------------------------------------------------------------------------- 1 | **int** 2 | 3 | Converts a value into an integer. 4 | 5 | ```jinja 6 | {{ "42"|int == 42 }} -> true 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/items.md: -------------------------------------------------------------------------------- 1 | **items** 2 | 3 | Returns a list of pairs (items) from a mapping. 4 | 5 | This can be used to iterate over keys and values of a mapping 6 | at once. 7 | 8 | Note that this will use the original order of the map 9 | which is typically arbitrary unless the `preserve_order` feature 10 | is used in which case the original order of the map is retained. 11 | It s generally better to use `|dictsort` which sorts the map by 12 | key before iterating. 13 | 14 | ```jinja 15 |
16 | {% for key, value in my_dict|items %} 17 |
{{ key }} 18 |
{{ value }} 19 | {% endfor %} 20 |
21 | ```" 22 | 23 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/join.md: -------------------------------------------------------------------------------- 1 | **join** 2 | 3 | Joins a sequence by a character 4 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/last.md: -------------------------------------------------------------------------------- 1 | **last** 2 | 3 | Returns the last item from a list. 4 | 5 | If the list is empty `undefined` is returned. 6 | 7 | ```jinja 8 |

Most Recent Update

9 | {% with update = updates|last %} 10 |
11 |
Location 12 |
{{ update.location }} 13 |
Status 14 |
{{ update.status }} 15 |
16 | {% endwith %} 17 | ``` 18 | 19 | 20 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/length.md: -------------------------------------------------------------------------------- 1 | **length** 2 | 3 | Returns the "length" of the value 4 | 5 | By default this filter is also registered under the alias `count`. 6 | 7 | ```jinja 8 |

Search results: {{ results|length }} 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/list.md: -------------------------------------------------------------------------------- 1 | **list** 2 | 3 | Converts the input value into a list. 4 | 5 | If the value is already a list, then it s returned unchanged. 6 | 7 | Applied to a map this returns the list of keys, applied to a 8 | string this returns the characters. 9 | 10 | If the value is undefined 11 | an empty list is returned. 12 | 13 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/lower.md: -------------------------------------------------------------------------------- 1 | **lower** 2 | 3 | Converts a value to lowercase. 4 | 5 | ```jinja 6 |

{{ chapter.title|lower }}

7 | ``` 8 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/map.md: -------------------------------------------------------------------------------- 1 | **map** 2 | 3 | Applies a filter to a sequence of objects or looks up an attribute. 4 | 5 | This is useful when dealing with lists of objects but you are really 6 | only interested in a certain value of it. 7 | The basic usage is mapping on an attribute. Given a list of users 8 | you can for instance quickly select the username and join on it: 9 | 10 | ```jinja 11 | {{ users|map(attribute="username")|join(, ) }} 12 | ``` 13 | You can specify a `default` value to use if an object in the list does 14 | not have the given attribute. 15 | ```jinja 16 | {{ users|map(attribute="username", default="Anonymous")|join(", ") }} 17 | ``` 18 | 19 | Alternatively you can have `map` invoke a filter by passing the name of the 20 | filter and the arguments afterwards. A good example would be applying a 21 | text conversion filter on a sequence: 22 | ```jinja 23 | Users on this page: {{ titles|map("lower")|join(, ) }} 24 | ``` 25 | 26 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/max.md: -------------------------------------------------------------------------------- 1 | **max** 2 | 3 | Returns the largest item from the list. 4 | 5 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/min.md: -------------------------------------------------------------------------------- 1 | **min** 2 | 3 | Returns the smallest item from the list. 4 | 5 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/pprint.md: -------------------------------------------------------------------------------- 1 | **pprint** 2 | 3 | Pretty print a variable. 4 | 5 | This is useful for debugging as it better shows what s inside an object. 6 | 7 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/reject.md: -------------------------------------------------------------------------------- 1 | **reject** 2 | 3 | Creates a new sequence of values that don t pass a test. 4 | 5 | This is the inverse of [`select`]. 6 | 7 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/rejectattr.md: -------------------------------------------------------------------------------- 1 | **rejectattr** 2 | 3 | Creates a new sequence of values of which an attribute does not pass a test. 4 | This functions like [`select`] but it will test an attribute of the 5 | object itself: 6 | 7 | ```jinja 8 | {{ users|rejectattr("is_active") }} -> all users where x.is_active is false 9 | {{ users|rejectattr("id", "even") }} -> returns all users with an odd id 10 | ``` 11 | 12 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/replace.md: -------------------------------------------------------------------------------- 1 | **replace** 2 | 3 | Does a string replace. 4 | 5 | It replaces all occurrences of the first parameter with the second. 6 | 7 | ```jinja 8 | {{ "Hello World"|replace("Hello", "Goodbye") }} 9 | -> Goodbye World 10 | ``` 11 | 12 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/reverse.md: -------------------------------------------------------------------------------- 1 | **reverse** 2 | 3 | Reverses a list or string 4 | 5 | ```jinja 6 | {% for user in users|reverse %} 7 |
  • {{ user.name }} 8 | {% endfor %} 9 | ``` 10 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/round.md: -------------------------------------------------------------------------------- 1 | **round** 2 | 3 | Round the number to a given precision. 4 | 5 | Round the number to a given precision. The first parameter specifies the 6 | precision (default is 0). 7 | 8 | ```jinja 9 | {{ 42.55|round }} 10 | -> 43.0 11 | ``` 12 | 13 | 14 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/safe.md: -------------------------------------------------------------------------------- 1 | **safe** 2 | 3 | Marks a value as safe. This converts it into a string. 4 | 5 | When a value is marked as safe, no further auto escaping will take place. 6 | 7 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/select.md: -------------------------------------------------------------------------------- 1 | **select** 2 | 3 | Creates a new sequence of values that pass a test. 4 | 5 | Filters a sequence of objects by applying a test to each object. 6 | Only values that pass the test are included. 7 | If no test is specified, each object will be evaluated as a boolean. 8 | 9 | ```jinja 10 | {{ [1, 2, 3, 4]|select("odd") }} -> [1, 3] 11 | {{ [false, null, 42]|select }} -> [42] 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/selectattr.md: -------------------------------------------------------------------------------- 1 | **selectattr** 2 | 3 | Creates a new sequence of values of which an attribute passes a test. 4 | 5 | This functions like [`select`] but it will test an attribute of the 6 | object itself: 7 | 8 | ```jinja 9 | {{ users|selectattr("is_active") }} -> all users where x.is_active is true 10 | {{ users|selectattr("id", "even") }} -> returns all users with an even id 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/slice.md: -------------------------------------------------------------------------------- 1 | **slice** 2 | 3 | Slice an iterable and return a list of lists containing 4 | those items. 5 | 6 | Useful if you want to create a div containing three ul tags that 7 | represent columns: 8 | 9 | ```jinja 10 | 11 |
    12 | {% for column in items|slice(3) %} 13 |
      14 | {% for item in column %} 15 |
    • {{ item }}
    • 16 | {% endfor %} 17 |
    18 | {% endfor %} 19 |
    20 | ``` 21 | If you pass it a second argument it’s used to fill missing values on the 22 | 23 | last iteration. 24 | 25 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/sort.md: -------------------------------------------------------------------------------- 1 | **sort** 2 | 3 | Returns the sorted version of the given list. 4 | 5 | The filter accepts a few keyword arguments: 6 | * `case_sensitive`: set to `true` to make the sorting of strings case sensitive. 7 | * `attribute`: can be set to an attribute or dotted path to sort by that attribute 8 | * `reverse`: set to `true` to sort in reverse. 9 | 10 | ```jinja 11 | {{ [1, 3, 2, 4]|sort }} -> [4, 3, 2, 1] 12 | {{ [1, 3, 2, 4]|sort(reverse=true) }} -> [1, 2, 3, 4] 13 | # Sort users by age attribute in descending order. 14 | {{ users|sort(attribute="age") }} 15 | # Sort users by age attribute in ascending order. 16 | {{ users|sort(attribute="age", reverse=true) }} 17 | ``` 18 | 19 | 20 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/title.md: -------------------------------------------------------------------------------- 1 | **title** 2 | 3 | Converts a value to title case. 4 | 5 | ```jinja 6 |

    {{ chapter.title|title }}

    7 | ``` 8 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/tojson.md: -------------------------------------------------------------------------------- 1 | **tojson** 2 | 3 | Dumps a value to JSON. 4 | 5 | This filter is only available if the `json` feature is enabled. The resulting 6 | value is safe to use in HTML as well as it will not contain any special HTML 7 | characters. The optional parameter to the filter can be set to `true` to enable 8 | pretty printing. Not that the `"` character is left unchanged as it s the 9 | JSON string delimiter. If you want to pass JSON serialized this way into an 10 | HTTP attribute use single quoted HTML attributes: 11 | 12 | ```jinja 13 | 16 | ... 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/trim.md: -------------------------------------------------------------------------------- 1 | **trim** 2 | 3 | Trims a value 4 | 5 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/unique.md: -------------------------------------------------------------------------------- 1 | **unique** 2 | 3 | Returns a list of unique items from the given iterable. 4 | 5 | ```jinja 6 | {{ ["foo", "bar", "foobar", "foobar"]|unique|list }} 7 | -> ["foo", "bar", "foobar"] 8 | ``` 9 | The unique items are yielded in the same order as their first occurrence 10 | in the iterable passed to the filter. The filter will not detect 11 | duplicate objects or arrays, only primitives such as strings or numbers. 12 | 13 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/upper.md: -------------------------------------------------------------------------------- 1 | **upper** 2 | 3 | Converts a value to uppercase. 4 | 5 | ```jinja 6 |

    {{ chapter.title|upper }}

    7 | ``` 8 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/md/filters/urlencode.md: -------------------------------------------------------------------------------- 1 | **urlencode** 2 | 3 | URL encodes a value. 4 | 5 | If given a map it encodes the parameters into a query set, otherwise it 6 | encodes the stringified value. If the value is none or undefined, an 7 | empty string is returned. 8 | 9 | ```jinja 10 | Search 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /jinja-lsp/src/filter/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Clone, Debug, Serialize, Deserialize)] 4 | pub struct FilterCompletion { 5 | pub name: String, 6 | pub desc: String, 7 | } 8 | 9 | impl From<(&str, &str)> for FilterCompletion { 10 | fn from((name, desc): (&str, &str)) -> Self { 11 | Self { 12 | name: name.to_string(), 13 | desc: desc.to_string(), 14 | } 15 | } 16 | } 17 | 18 | pub fn init_filter_completions() -> Vec { 19 | vec![ 20 | FilterCompletion::from(("abs", include_str!("md/filters/abs.md"))), 21 | FilterCompletion::from(("attr", include_str!("md/filters/attr.md"))), 22 | FilterCompletion::from(("batch", include_str!("md/filters/batch.md"))), 23 | FilterCompletion::from(("bool", include_str!("md/filters/bool.md"))), 24 | FilterCompletion::from(("capitalize", include_str!("md/filters/capitalize.md"))), 25 | FilterCompletion::from(("default", include_str!("md/filters/default.md"))), 26 | FilterCompletion::from(("dictsort", include_str!("md/filters/dictsort.md"))), 27 | FilterCompletion::from(("escape", include_str!("md/filters/escape.md"))), 28 | FilterCompletion::from(("first", include_str!("md/filters/first.md"))), 29 | FilterCompletion::from(("float", include_str!("md/filters/float.md"))), 30 | FilterCompletion::from(("indent", include_str!("md/filters/indent.md"))), 31 | FilterCompletion::from(("int", include_str!("md/filters/int.md"))), 32 | FilterCompletion::from(("items", include_str!("md/filters/items.md"))), 33 | FilterCompletion::from(("join", include_str!("md/filters/join.md"))), 34 | FilterCompletion::from(("last", include_str!("md/filters/last.md"))), 35 | FilterCompletion::from(("length", include_str!("md/filters/length.md"))), 36 | FilterCompletion::from(("list", include_str!("md/filters/list.md"))), 37 | FilterCompletion::from(("lower", include_str!("md/filters/lower.md"))), 38 | FilterCompletion::from(("map", include_str!("md/filters/map.md"))), 39 | FilterCompletion::from(("max", include_str!("md/filters/max.md"))), 40 | FilterCompletion::from(("min", include_str!("md/filters/min.md"))), 41 | FilterCompletion::from(("pprint", include_str!("md/filters/pprint.md"))), 42 | FilterCompletion::from(("rejectattr", include_str!("md/filters/rejectattr.md"))), 43 | FilterCompletion::from(("reject", include_str!("md/filters/reject.md"))), 44 | FilterCompletion::from(("replace", include_str!("md/filters/replace.md"))), 45 | FilterCompletion::from(("reverse", include_str!("md/filters/reverse.md"))), 46 | FilterCompletion::from(("round", include_str!("md/filters/round.md"))), 47 | FilterCompletion::from(("safe", include_str!("md/filters/safe.md"))), 48 | FilterCompletion::from(("selectattr", include_str!("md/filters/selectattr.md"))), 49 | FilterCompletion::from(("select", include_str!("md/filters/select.md"))), 50 | FilterCompletion::from(("slice", include_str!("md/filters/slice.md"))), 51 | FilterCompletion::from(("sort", include_str!("md/filters/sort.md"))), 52 | FilterCompletion::from(("title", include_str!("md/filters/title.md"))), 53 | FilterCompletion::from(("tojson", include_str!("md/filters/tojson.md"))), 54 | FilterCompletion::from(("trim", include_str!("md/filters/trim.md"))), 55 | FilterCompletion::from(("unique", include_str!("md/filters/unique.md"))), 56 | FilterCompletion::from(("upper", include_str!("md/filters/upper.md"))), 57 | FilterCompletion::from(("urlencode", include_str!("md/filters/urlencode.md"))), 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /jinja-lsp/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod backend; 2 | pub mod channels; 3 | mod config; 4 | pub mod filter; 5 | pub mod lsp_files; 6 | pub mod template_tests; 7 | -------------------------------------------------------------------------------- /jinja-lsp/src/main.rs: -------------------------------------------------------------------------------- 1 | mod backend; 2 | pub mod channels; 3 | mod config; 4 | mod filter; 5 | pub mod lsp_files; 6 | mod template_tests; 7 | 8 | use backend::Backend; 9 | use clap::Parser; 10 | use config::JinjaLspArgs; 11 | use tower_lsp::LspService; 12 | use tower_lsp::Server; 13 | 14 | #[tokio::main] 15 | async fn main() { 16 | let _ = JinjaLspArgs::parse(); 17 | 18 | env_logger::init(); 19 | 20 | let stdin = tokio::io::stdin(); 21 | let stdout = tokio::io::stdout(); 22 | 23 | let (service, socket) = LspService::build(Backend::_new).finish(); 24 | 25 | Server::new(stdin, stdout, socket).serve(service).await; 26 | } 27 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_boolean.md: -------------------------------------------------------------------------------- 1 | Return true if the object is a boolean value. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_defined.md: -------------------------------------------------------------------------------- 1 | Checks if a value is defined. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_divisibleby.md: -------------------------------------------------------------------------------- 1 | Return true if the value is divisible by another one. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_endingwith.md: -------------------------------------------------------------------------------- 1 | Checks if the value is ending with a string. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_eq.md: -------------------------------------------------------------------------------- 1 | Test version of `==`. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_even.md: -------------------------------------------------------------------------------- 1 | Checks if a value is even. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_false.md: -------------------------------------------------------------------------------- 1 | Checks if a value is false. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_filter.md: -------------------------------------------------------------------------------- 1 | Checks if a filter with a given name is available. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_float.md: -------------------------------------------------------------------------------- 1 | Checks if this value is a float 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_ge.md: -------------------------------------------------------------------------------- 1 | Test version of `>=`. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_gt.md: -------------------------------------------------------------------------------- 1 | Test version of `>`. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_in.md: -------------------------------------------------------------------------------- 1 | Test version of in. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_integer.md: -------------------------------------------------------------------------------- 1 | Checks if this value is an integer. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_iterable.md: -------------------------------------------------------------------------------- 1 | Checks if this value can be iterated over. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_le.md: -------------------------------------------------------------------------------- 1 | Test version of `<=`. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_lower.md: -------------------------------------------------------------------------------- 1 | Checks if a string is all lowercase. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_lt.md: -------------------------------------------------------------------------------- 1 | Test version of `<`. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_mapping.md: -------------------------------------------------------------------------------- 1 | Checks if this value is a mapping 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_ne.md: -------------------------------------------------------------------------------- 1 | Test version of `!=`. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_none.md: -------------------------------------------------------------------------------- 1 | Checks if a value is none. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_number.md: -------------------------------------------------------------------------------- 1 | Checks if this value is a number. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_odd.md: -------------------------------------------------------------------------------- 1 | Checks if a value is odd. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_safe.md: -------------------------------------------------------------------------------- 1 | Checks if a value is safe. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_sameas.md: -------------------------------------------------------------------------------- 1 | Checks if two values are identical. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_sequence.md: -------------------------------------------------------------------------------- 1 | Checks if this value is a sequence 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_startingwith.md: -------------------------------------------------------------------------------- 1 | Checks if the value is starting with a string. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_string.md: -------------------------------------------------------------------------------- 1 | Checks if this value is a string. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_test.md: -------------------------------------------------------------------------------- 1 | Checks if a test with a given name is available. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_true.md: -------------------------------------------------------------------------------- 1 | Checks if a value is true. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_undefined.md: -------------------------------------------------------------------------------- 1 | Checks if a value is undefined. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/md/is_upper.md: -------------------------------------------------------------------------------- 1 | Checks if a string is all uppercase. 2 | -------------------------------------------------------------------------------- /jinja-lsp/src/template_tests/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Clone, Debug, Serialize, Deserialize)] 4 | pub struct TemplateTestCompletion { 5 | pub name: String, 6 | pub desc: String, 7 | } 8 | 9 | impl From<(&str, &str)> for TemplateTestCompletion { 10 | fn from((name, desc): (&str, &str)) -> TemplateTestCompletion { 11 | Self { 12 | name: name.to_string(), 13 | desc: desc.to_string(), 14 | } 15 | } 16 | } 17 | 18 | pub fn init_template_test_completions() -> Vec { 19 | vec![ 20 | TemplateTestCompletion::from(("boolean", include_str!("md/is_boolean.md"))), 21 | TemplateTestCompletion::from(("defined", include_str!("md/is_defined.md"))), 22 | TemplateTestCompletion::from(("divisibleby", include_str!("md/is_divisibleby.md"))), 23 | TemplateTestCompletion::from(("endingwith", include_str!("md/is_endingwith.md"))), 24 | TemplateTestCompletion::from(("eq", include_str!("md/is_eq.md"))), 25 | TemplateTestCompletion::from(("even", include_str!("md/is_even.md"))), 26 | TemplateTestCompletion::from(("false", include_str!("md/is_false.md"))), 27 | TemplateTestCompletion::from(("filter", include_str!("md/is_filter.md"))), 28 | TemplateTestCompletion::from(("float", include_str!("md/is_float.md"))), 29 | TemplateTestCompletion::from(("ge", include_str!("md/is_ge.md"))), 30 | TemplateTestCompletion::from(("gt", include_str!("md/is_gt.md"))), 31 | TemplateTestCompletion::from(("in", include_str!("md/is_in.md"))), 32 | TemplateTestCompletion::from(("integer", include_str!("md/is_integer.md"))), 33 | TemplateTestCompletion::from(("iterable", include_str!("md/is_iterable.md"))), 34 | TemplateTestCompletion::from(("le", include_str!("md/is_le.md"))), 35 | TemplateTestCompletion::from(("lower", include_str!("md/is_lower.md"))), 36 | TemplateTestCompletion::from(("lt", include_str!("md/is_lt.md"))), 37 | TemplateTestCompletion::from(("mapping", include_str!("md/is_mapping.md"))), 38 | TemplateTestCompletion::from(("ne", include_str!("md/is_ne.md"))), 39 | TemplateTestCompletion::from(("none", include_str!("md/is_none.md"))), 40 | TemplateTestCompletion::from(("number", include_str!("md/is_number.md"))), 41 | TemplateTestCompletion::from(("odd", include_str!("md/is_odd.md"))), 42 | TemplateTestCompletion::from(("safe", include_str!("md/is_safe.md"))), 43 | TemplateTestCompletion::from(("sameas", include_str!("md/is_sameas.md"))), 44 | TemplateTestCompletion::from(("sequence", include_str!("md/is_sequence.md"))), 45 | TemplateTestCompletion::from(("startingwith", include_str!("md/is_startingwith.md"))), 46 | TemplateTestCompletion::from(("string", include_str!("md/is_string.md"))), 47 | TemplateTestCompletion::from(("test", include_str!("md/is_test.md"))), 48 | TemplateTestCompletion::from(("true", include_str!("md/is_true.md"))), 49 | TemplateTestCompletion::from(("undefined", include_str!("md/is_undefined.md"))), 50 | TemplateTestCompletion::from(("upper", include_str!("md/is_upper.md"))), 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /rust-toolchains.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.78.0" 3 | # channel = "nightly-2024-06-11-x86_64-unknown-linux-gnu" 4 | --------------------------------------------------------------------------------