├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── actions
│ └── setup
│ │ └── action.yml
├── dependabot.yml
└── workflows
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .rusty-hook.toml
├── .vscode
├── launch.json
└── tasks.json
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── DOCS.md
├── LICENSE
├── README.md
├── TEST_CASES.md
├── Taskfile.yml
├── demo.gif
├── language-server
├── Cargo.toml
└── src
│ ├── analysis.rs
│ ├── builtins.rs
│ ├── capabilities.rs
│ ├── config.rs
│ ├── helpers.rs
│ ├── lsp.rs
│ ├── main.rs
│ └── update_checker.rs
├── package-lock.json
├── pest.tmLanguage
├── sublime-text
└── pest.tmLanguage
└── vscode
├── .eslintrc.js
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .vscodeignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── client
├── src
│ ├── index.ts
│ └── server.ts
└── tsconfig.json
├── icon.png
├── language-configuration.json
├── package-lock.json
├── package.json
├── syntaxes
└── pest.tmLanguage.json
├── tests
├── .vscode
│ └── settings.json
├── cjk.pest
├── json.pest
├── misc.pest
└── no_consume.pest
└── tsconfig.json
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @pest-parser/pest-vscode
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## **Describe the bug**
11 |
12 | A clear and concise description of what the bug is.
13 |
14 | ## **To Reproduce**
15 |
16 | Steps to reproduce the behavior:
17 |
18 | 1. Type '...'
19 | 2. Run '....'
20 | 3. See error
21 |
22 | ### **Reproduction File**
23 |
24 | If relevant, please include the reproduction file inside a code block below.
25 |
26 | ### **Log**
27 |
28 | If you think it will help, please include the server log in a drop-down below. It can be found, for example in vscode, in the `Output` tab.
29 |
30 | ## **Expected behavior**
31 |
32 | A clear and concise description of what you expected to happen.
33 |
34 | ## **Screenshots**
35 |
36 | If applicable, add screenshots or GIFs to help explain your problem.
37 |
38 | ## **Environment (please complete the following information):**
39 |
40 | - OS: [e.g. Windows]
41 | - Editor [e.g. vscode, vim]
42 | - Server Version [e.g. 0.2.0]
43 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## **Is your feature request related to a problem? Please describe.**
11 |
12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
13 |
14 | ## **Describe the solution you'd like**
15 |
16 | A clear and concise description of what you want to happen.
17 |
--------------------------------------------------------------------------------
/.github/actions/setup/action.yml:
--------------------------------------------------------------------------------
1 | name: Setup
2 | description: Initial setup for workflows
3 |
4 | inputs:
5 | kind:
6 | description: Job kind (for cache key)
7 | required: false
8 | secret:
9 | description: GitHub Token
10 | required: true
11 | rust-target:
12 | description: Target to install using rustup, or nothing for a default target
13 | required: false
14 | outputs:
15 | cache-key:
16 | description: Cache key
17 | value: ${{ inputs.kind }}-${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}
18 |
19 | runs:
20 | using: composite
21 | steps:
22 | - name: Install Wasmpack
23 | shell: bash
24 | run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
25 |
26 | - name: Setup Node
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version: 18
30 | cache: npm
31 | cache-dependency-path: vscode/package-lock.json
32 |
33 | - name: NPM Install
34 | shell: bash
35 | working-directory: vscode
36 | run: npm install
37 |
38 | - name: Setup Task
39 | uses: arduino/setup-task@v2
40 | with:
41 | repo-token: ${{ inputs.secret }}
42 |
43 | - name: Install Rust Target
44 | shell: bash
45 | if: ${{ inputs.rust-target }}
46 | run: rustup target add ${{ inputs.rust-target }}
47 |
48 | - name: Set up cache
49 | uses: Swatinem/rust-cache@v2
50 | with:
51 | shared-key: ${{ inputs.kind }}-${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}
52 | workspaces: language-server
53 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "cargo" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 | assignees:
13 | - "Jamalam360"
14 | groups:
15 | rust-dependencies:
16 | applies-to: version-updates
17 | patterns:
18 | - "*"
19 |
20 | - package-ecosystem: "npm" # See documentation for possible values
21 | directory: "/vscode" # Location of package manifests
22 | schedule:
23 | interval: "weekly"
24 | assignees:
25 | - "Jamalam360"
26 | groups:
27 | dev-dependencies:
28 | applies-to: version-updates
29 | patterns:
30 | - "@trivago/prettier-plugin-sort-imports"
31 | - "@typescript-eslint/*"
32 | - "esbuild"
33 | - "eslint-*"
34 | - "gts"
35 | - "ovsx"
36 | - "@vscode/vsce"
37 | ignore:
38 | # we only want to update these when they have a new feature we want
39 | - dependency-name: "vscode-languageclient"
40 | - dependency-name: "@types/node"
41 | - dependency-name: "@types/vscode"
42 |
43 | - package-ecosystem: "github-actions"
44 | directory: "/"
45 | schedule:
46 | interval: "weekly"
47 | assignees:
48 | - "Jamalam360"
49 | groups:
50 | gha-dependencies:
51 | applies-to: version-updates
52 | patterns:
53 | - "*"
54 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - '**'
7 | pull_request:
8 |
9 | jobs:
10 | check:
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | os: [ubuntu-latest, windows-latest, macos-latest]
16 | steps:
17 | - name: Checkout Repository
18 | uses: actions/checkout@v4
19 |
20 | - name: Setup
21 | uses: ./.github/actions/setup
22 | with:
23 | kind: check
24 | secret: ${{ secrets.GITHUB_TOKEN }}
25 |
26 | - name: Format and Lint
27 | if: contains(matrix.os, 'ubuntu')
28 | run: task fmt-and-lint
29 |
30 | - name: Cargo Check
31 | run: cargo check
32 |
33 | package-vscode:
34 | runs-on: ubuntu-latest
35 | steps:
36 | - name: Checkout Repository
37 | uses: actions/checkout@v4
38 |
39 | - name: Setup
40 | uses: ./.github/actions/setup
41 | with:
42 | kind: package-vscode
43 | secret: ${{ secrets.GITHUB_TOKEN }}
44 |
45 | - name: Package Extension
46 | working-directory: vscode
47 | run: npm run package
48 |
49 | - name: Upload Extension
50 | uses: actions/upload-artifact@v4
51 | with:
52 | name: pest-vscode
53 | path: vscode/pest.vsix
54 |
55 | package-sublime-text:
56 | runs-on: ubuntu-latest
57 | steps:
58 | - name: Checkout Repository
59 | uses: actions/checkout@v4
60 |
61 | - name: Package Sublime Text Package
62 | working-directory: sublime-text
63 | run: zip pest.sublime-package *
64 |
65 | - name: Upload Extension
66 | uses: actions/upload-artifact@v4
67 | with:
68 | name: pest-sublime-text
69 | path: sublime-text/pest.sublime-package
70 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Extensions
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*"
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | # Gives access to the VSCE_PAT, OVSX_TOKEN secret
12 | environment: vsce
13 | permissions:
14 | # Allows creation of releases
15 | contents: write
16 | outputs:
17 | release_id: ${{ steps.create_release.outputs.id }}
18 | steps:
19 | - name: Checkout Repository
20 | uses: actions/checkout@v4
21 |
22 | - name: Setup
23 | uses: ./.github/actions/setup
24 | with:
25 | kind: release
26 | secret: ${{ secrets.GITHUB_TOKEN }}
27 |
28 | - name: Install VSCode NPM Packages
29 | working-directory: vscode
30 | run: npm install
31 |
32 | - name: Package VSCode Extension
33 | working-directory: vscode
34 | run: npm run package
35 |
36 | - name: Publish to VSCode Marketplace
37 | working-directory: vscode
38 | run: npm run publish:vsce
39 | env:
40 | VSCE_PAT: ${{ secrets.VSCE_PAT }}
41 |
42 | - name: Publish to OpenVSX
43 | working-directory: vscode
44 | run: npm run publish:ovsx
45 | env:
46 | OPENVSX_PAT: ${{ secrets.OVSX_TOKEN }}
47 |
48 | - name: Package Sublime Text Package
49 | working-directory: sublime-text
50 | run: zip pest.sublime-package *
51 |
52 | - name: Publish to crates.io
53 | working-directory: language-server
54 | run: cargo publish --token ${{ secrets.CRATES_TOKEN }}
55 |
56 | - name: Get Changelog
57 | id: get_changelog
58 | run: |
59 | EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
60 | echo "COMMITS<<$EOF" >> $GITHUB_OUTPUT
61 | echo "COMMITS=\"$(awk -v latest="$(grep -Eo '^## v[0-9]+\.[0-9]+\.[0-9]+$' CHANGELOG.md | head -n1)" '/^## v/ {if (header) exit; header=1} /^## v'${latest}'/{print; next} header && !/^## v/{print}' CHANGELOG.md)\"" >> $GITHUB_OUTPUT
62 | echo "$EOF" >> $GITHUB_OUTPUT
63 |
64 | - name: Create Release
65 | id: create_release
66 | uses: softprops/action-gh-release@v2
67 | env:
68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69 | with:
70 | tag_name: ${{ github.ref }}
71 | name: ${{ github.ref_name }}
72 | body: |
73 | # Checklist Before Publishing
74 |
75 | - [ ] Check [VSCode extension](https://marketplace.visualstudio.com/items?itemName=pest.pest-ide-tools) was published correctly.
76 | - [ ] Check [OpenVSX extension](https://open-vsx.org/extension/pest/pest-ide-tools) was published correctly.
77 | - [ ] Check [crates.io release](https://crates.io/crates/pest-language-server/versions) was published correctly.
78 | - [ ] Check artifacts were uploaded to this release.
79 | - [ ] Update release body.
80 |
81 | ${{ steps.get_changelog.outputs.COMMITS }}
82 | draft: true
83 | prerelease: false
84 | files: |
85 | vscode/pest.vsix
86 | sublime-text/pest.sublime-package
87 |
88 | build-binaries:
89 | needs: release
90 | runs-on: ${{ matrix.target.runner }}
91 |
92 | permissions:
93 | # So we can upload to the release
94 | contents: write
95 |
96 | strategy:
97 | matrix:
98 | target:
99 | [
100 | { runner: "macos-14", target: "aarch64-apple-darwin", os: darwin, arch: aarch64 }, # Apple silicon
101 | { runner: "ubuntu-latest", target: "aarch64-unknown-linux-gnu", os: linux, arch: aarch64 },
102 | { runner: "macos-12", target: "x86_64-apple-darwin", os: darwin, arch: x86_64 }, # Intel Mac
103 | { runner: "ubuntu-latest", target: "x86_64-pc-windows-gnu", os: windows, arch: x86_64 }, # It's trivial to cross-compile to Windows (less so for Mac)
104 | { runner: "ubuntu-latest", target: "x86_64-unknown-linux-gnu", os: linux, arch: x86_64 },
105 | ]
106 |
107 | steps:
108 | - name: Checkout code
109 | uses: actions/checkout@v4
110 |
111 | - name: Setup
112 | uses: ./.github/actions/setup
113 | with:
114 | kind: release-compile-binaries
115 | secret: ${{ secrets.GITHUB_TOKEN }}
116 | rust-target: ${{ matrix.target.target }}
117 |
118 | - name: Set up Windows
119 | if: matrix.target.os == 'windows'
120 | run: sudo apt-get install -y --no-install-recommends mingw-w64 musl-tools gcc-mingw-w64-x86-64-win32
121 |
122 | - name: Set up aarch64 Linux
123 | if: matrix.target.target == 'aarch64-unknown-linux-gnu'
124 | run: |
125 | sudo apt-get install gcc-aarch64-linux-gnu
126 | echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
127 |
128 | - name: Build binary
129 | run: cargo build --release --target=${{ matrix.target.target }}
130 |
131 | - name: Package binary (Linux and Mac)
132 | if: matrix.target.os != 'windows'
133 | run: tar -zcvf pest-language-server-${{ matrix.target.os }}-${{ matrix.target.arch }}.tar.gz -C target/${{ matrix.target.target }}/release pest-language-server
134 |
135 | - name: Package binary (Windows)
136 | if: matrix.target.os == 'windows'
137 | run: tar -zcvf pest-language-server-${{ matrix.target.os }}-${{ matrix.target.arch }}.tar.gz -C target/${{ matrix.target.target }}/release pest-language-server.exe
138 |
139 | - uses: xresloader/upload-to-github-release@v1
140 | env:
141 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
142 | with:
143 | file: "./pest-language-server-${{ matrix.target.os }}-${{ matrix.target.arch }}.tar.gz"
144 | release_id: ${{ needs.release.outputs.release_id }}
145 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .task/
2 | node_modules
3 | target
4 | *.sublime-package
5 |
--------------------------------------------------------------------------------
/.rusty-hook.toml:
--------------------------------------------------------------------------------
1 | [hooks]
2 | pre-commit = "task fmt-and-lint"
3 |
4 | [logging]
5 | verbose = true
6 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | {
6 | "version": "0.2.0",
7 | "configurations": [
8 | {
9 | "name": "Run Extension",
10 | "type": "extensionHost",
11 | "request": "launch",
12 | "debugWebWorkerHost": true,
13 | "args": [
14 | "${workspaceFolder}/vscode/tests",
15 | "--disable-extensions",
16 | "--extensionDevelopmentPath=${workspaceFolder}/vscode"
17 | ],
18 | "outFiles": [
19 | "${workspaceFolder}/vscode/build/**/*"
20 | ],
21 | "preLaunchTask": "npm: esbuild-with-rust - vscode"
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "npm",
6 | "script": "esbuild-with-rust",
7 | "path": "vscode",
8 | "group": "build",
9 | "problemMatcher": [],
10 | "label": "npm: esbuild-with-rust - vscode",
11 | "detail": "task rust-build -d .. && npm run esbuild-client"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes will be documented in this file.
4 |
5 |
6 |
7 | ## v0.3.11
8 |
9 | - fix(ci): update Node version for OpenVSX publishing.
10 |
11 | ## v0.3.10
12 |
13 | - [[sbillig](https://github.com/sbillig)] feat(analysis): don't emit unused rule warning if the rule name begins with an underscore (#88)
14 |
15 | ## v0.3.9
16 |
17 | - fix(lsp): use rustls rather than native-tls for reqwest
18 |
19 | ## v0.3.8
20 |
21 | - feat(ci): publish binaries to releases, fixes #51
22 | - chore(lsp): update dependencies
23 |
24 | ## v0.3.7
25 |
26 | - feat(deps): update all deps, fixes #50
27 |
28 | ## v0.3.6
29 |
30 | - [[Fluffyalien1422](https://github.com/Fluffyalien1422)] fix(vscode): add quotes around path when invoking the LS (#41)
31 |
32 | ## v0.3.5
33 |
34 | - fix(ci): fix the publish workflow
35 |
36 | ## v0.3.4
37 |
38 | - [[notpeter](https://github.com/notpeter)] fix(server): off-by-one error (#37)
39 | - [[notpeter](https://github.com/notpeter)] fix(grammar): issues with highlighting rules that started with a built-in's name (#38)
40 |
41 | ## v0.3.3
42 |
43 | - fix(server): hotfix for #28, ranges (along with other tokens like insensitive strings) no longer crash the server
44 |
45 | A proper fix for the code causing the crash in #28 will be released, but I do not have the time at the moment.
46 |
47 | ## v0.3.2
48 |
49 | - fix(vscode): update checker is now enabled by default, and some of its logic
50 | has been modified and fixed. It also supports cancellation of the install task
51 | - fix(vscode): give defaults to all config options
52 | - fix(server): fix crash with code actions
53 |
54 | ## v0.3.1
55 |
56 | - revert(server): revert performance marks temporarily, while they are
57 | refactored into a more generic crate
58 |
59 | ## v0.3.0
60 |
61 | - feat(server): add performance marks for debugging
62 | - feat(server): simple rule extraction support
63 | - fix(server): validate AST to catch errors like non-progressing expressions
64 |
65 | ## v0.2.2
66 |
67 | - feat(vscode): allow relative paths in `pestIdeTools.serverPath`
68 | - fix(vscode): allow `pestIdeTools.serverPath` to be `null` in the schema
69 | - fix(server): CJK/non-ascii characters no longer crash the server
70 | - fix(server): add a CJK test case to the manual testing recommendations
71 |
72 | ## v0.2.1
73 |
74 | - fix(vscode): scan both stdout and stderr of Cargo commands, fixes some issues
75 | with installation flow
76 | - feat(*): documentation, issue templates
77 | - feat(sublime): begin publishing a sublime text package
78 | - fix(server, vscode): server now hot-reloads config updates more reliably
79 | - fix(server, vscode): bump problematic dependencies (love the JS ecosystem...a
80 | CVE a day keeps the doctor away)
81 | - feat(server): add rule inlining code action
82 | - feat(server): ignore unused rule name analysis if there is only one unused
83 | rule (hack fix)
84 |
85 | ## v0.2.0
86 |
87 | - feat(*): port to tower lsp
88 | - This will allow the usage of this LS by other IDEs.
89 | - The vscode extension will prompt you to download the server.
90 | - Other IDEs will have to have the LS installed via `cargo install`.
91 | - feat(*): add configuration options
92 | - feat(server, #6): diagnostic for unused rules
93 | - feat(server, #7): show rule docs (`///`) on hover
94 | - fix(server, #8): solve issue relating to 0 vs 1 indexing causing diagnostics
95 | to occur at the wrong locations
96 | - feat(server): add a version checker
97 | - feat(readme, #2): update readme and add demo gif
98 | - feat(ci, #4): automatically populate changelog
99 | - fix(ci): lint all rust code
100 |
101 | ## v0.1.2
102 |
103 | - feat: upgrade pest v2.5.6, pest-fmt v0.2.3. See
104 | [Release Notes](https://github.com/pest-parser/pest/releases/tag/v2.5.6).
105 | - fix(server): solve issue relating to 0 vs 1 indexing.
106 | - feat(server): suggest user-defined rule names in intellisense.
107 |
108 | ## v0.1.1
109 |
110 | - feat(server): add hover information for `SOI` and `EOI`
111 | - fix(ci): allow the release workflow to create releases.
112 | - fix(vscode): add a readme for the vscode extension.
113 | - fix(vscode): add a changelog.
114 |
115 | ## v0.1.0
116 |
117 | - Initial release
118 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "2"
3 | members = ["language-server"]
4 |
--------------------------------------------------------------------------------
/DOCS.md:
--------------------------------------------------------------------------------
1 | # Pest IDE Tools Documentation
2 |
3 | This document contains instructions for setting up Pest support for all of the supported editors.
4 |
5 | ## Contents
6 |
7 | - [Server Configuration](#config)
8 | - [VSCode](#vscode)
9 | - [Sublime Text](#sublime-text)
10 |
11 | ## Config
12 |
13 | The method of updating your config is editor specific.
14 |
15 | The available options for all editors are:
16 |
17 | ```jsonc
18 | {
19 | // Check for updates to the Pest LS binary via crates.io
20 | "pestIdeTools.checkForUpdates": true,
21 | // Ignore specific rule names for the unused rules diagnostics (useful for specifying root rules)
22 | "pestIdeTools.alwaysUsedRuleNames": [
23 | "rule_one",
24 | "rule_two"
25 | ]
26 | }
27 | ```
28 |
29 | ## VSCode
30 |
31 | 1. Download [the extension](https://marketplace.visualstudio.com/items?itemName=pest.pest-ide-tools).
32 | 2. Await the prompt which will ask you if you want to install a suitable binary, and accept.
33 | 3. Wait for it to install the server.
34 | - If the server fails to install, you can install it manually using `cargo install pest-language-server`, then use the configuration `pestIdeTools.serverPath` to point the extension to the installed binary.
35 | 4. (_Optional_) You may need to execute the command `Pest: Restart server` or reload your window for the server to activate.
36 |
37 | ### VSCode Specific Configs
38 |
39 | These config options are specific to VSCode.
40 |
41 | ```jsonc
42 | {
43 | // Set a custom path to a Pest LS binary
44 | "pestIdeTools.serverPath": "/path/to/binary",
45 | // Custom arguments to pass to the Pest LS binary
46 | "pestIdeTools.customArgs": []
47 | }
48 | ```
49 |
50 | ## Sublime Text
51 |
52 | 1. Download the `pest.sublime-package` file from the [latest release](https://github.com/pest-parser/pest-ide-tools/releases/latest)'s assets page.
53 | - This gives you syntax highlighting for Pest flies.
54 | 2. Place the downloaded `pest.sublime-package` file in the `path/to/sublime-text/Installed Packages` directory.
55 | 3. Install the server using `cargo install pest-language-server`.
56 | 3. Execute the `Preferences: LSP Settings` command and add a new key to the `clients` object.
57 | ```json
58 | // LSP.sublime-settings
59 | "clients": {
60 | "pest": {
61 | "enabled": true,
62 | // This is usually something like /home/username/.cargo/bin/pest-language-server
63 | "command": ["/path/to/language/server/binary"],
64 | "selector": "source.pest",
65 | },
66 | // ...other LSPs
67 | }
68 | ```
69 | 4. You may have to restart your Sublime Text to get the LSP to start.
70 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pest IDE Tools
2 |
3 | _IDE support for [Pest](https://pest.rs), via the LSP._
4 |
5 | This repository contains an implementation of the _Language Server Protocol_ in
6 | Rust, for the Pest parser generator.
7 |
8 |
9 |
10 |
11 |
12 | ## Features
13 |
14 | - Error reporting.
15 | - Warnings for unused rules.
16 | - Syntax highlighting definitions available.
17 | - Rename rules.
18 | - Go to rule declaration, definition, or references.
19 | - Hover information for built-in rules and documented rules.
20 | - Autocompletion of rule names.
21 | - Inline and extract rules.
22 | - Full-unicode support.
23 | - Formatting.
24 | - Update checking.
25 |
26 | Please see the
27 | [issues page](https://github.com/pest-parser/pest-ide-tools/issues) to suggest
28 | features or view previous suggestions.
29 |
30 | ## Usage
31 |
32 | You can find documentation on how to set up the server for in the `DOCS.md`
33 | file.
34 |
35 | ### Supported IDEs
36 |
37 | - [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=pest.pest-ide-tools)
38 | - VSCode has a pre-built extension that can compile, update, and start up the
39 | language server. It also includes syntax highlighting definitions.
40 | - The extension is also available on [OpenVSX](https://open-vsx.org/extension/pest/pest-ide-tools)
41 | - Sublime Text
42 | - Sublime Text packages can be obtained from [the latest release](https://github.com/pest-parser/pest-ide-tools/releases/latest)
43 | - Vim
44 | - Vim support is provided via the [pest.vim](https://github.com/pest-parser/pest.vim) package.
45 | - Zed
46 | - Zed support is provided via the [Zed Pest extension](https://github.com/pest-parser/zed-pest).
47 |
48 | Due to the usage of the LSP by this project, adding support for new IDEs should
49 | be far more achievable than a custom implementation for each editor. Please see
50 | the [tracking issue](https://github.com/pest-parser/pest-ide-tools/issues/10) to
51 | request support for another IDE or view the current status of IDE support.
52 |
53 | ## Development
54 |
55 | This repository uses a [Taskfile](https://taskfile.dev); install the `task`
56 | command for a better experience developing in this repository.
57 |
58 | The task `fmt-and-lint` can be used to check the formatting and lint your code
59 | to ensure it fits with the rest of the repository.
60 |
61 | In VSCode, press `F5` to build and debug the VSCode extension. This is the only
62 | method of debugging that we have pre set-up.
63 |
64 | ### Architecture
65 |
66 | The server itself is implemented in Rust using `tower-lsp`. It communicates with
67 | editors via JSON-RPC through standard input/output, according to the language
68 | server protocol.
69 |
70 | ### Contributing
71 |
72 | We appreciate contributions! I recommend reaching out on Discord (the invite to
73 | which can be found at [pest.rs](https://pest.rs)) before contributing, to check
74 | with us.
75 |
76 | ## Credits
77 |
78 | - [OsoHQ](https://github.com/osohq), for their
79 | [blog post](https://www.osohq.com/post/building-vs-code-extension-with-rust-wasm-typescript),
80 | and open source code which was used as inspiration.
81 | - [Stef Gijsberts](https://github.com/Stef-Gijsberts) for their
82 | [Pest syntax highlighting TextMate bundle](https://github.com/Stef-Gijsberts/pest-Syntax-Highlighting-for-vscode)
83 | which is used in this extension under the MIT license.
84 |
--------------------------------------------------------------------------------
/TEST_CASES.md:
--------------------------------------------------------------------------------
1 | # Test Cases
2 |
3 | These are manual tests that should be performed to ensure the server is working correctly.
4 |
5 | - Check error reporting works as expected.
6 | - Check autocompletion works as expected.
7 | - Check formatting works as expected.
8 | - Check that rules with documentation show that documentation on hover.
9 | - Check that builtins show documentation on hover.
10 | - Check that the unused rule diagnostic works, with and without the `pestIdeTools.alwaysUsedRuleNames` configuration.
11 | - Check go to definition and find references works correctly.
12 | - Check that renaming rules works as expected.
13 | - Check that inlining and extracting rules works.
14 | - Check the CJK characters example works. Attempt to hover over a CJK rule to see if the server crashes.
15 |
16 |
--------------------------------------------------------------------------------
/Taskfile.yml:
--------------------------------------------------------------------------------
1 | version: 3
2 |
3 | tasks:
4 | rust-fmt-and-lint:
5 | desc: Format and lint the Rust code
6 | internal: true
7 |
8 | method: checksum
9 | sources:
10 | - language-server/src/**/*.rs
11 |
12 | cmds:
13 | - cargo fmt --check
14 | - cargo clippy
15 |
16 | vscode-fmt-and-lint:
17 | desc: Format and lint the VSCode package
18 | dir: vscode
19 | internal: true
20 |
21 | method: checksum
22 | sources:
23 | - client/**/*
24 | - server/**/*
25 | - syntaxes/*
26 | - package.json
27 |
28 | cmds:
29 | - npm run fmt-check
30 | - npm run lint
31 |
32 | fmt-and-lint:
33 | desc: Format and lint all code.
34 |
35 | deps:
36 | - rust-fmt-and-lint
37 | - vscode-fmt-and-lint
38 |
39 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pest-parser/pest-ide-tools/6344e2a0fdb4af42dfdc106980fe730b818204c5/demo.gif
--------------------------------------------------------------------------------
/language-server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pest-language-server"
3 | version = "0.3.11"
4 | authors = ["Jamalam "]
5 | description = "A language server for Pest."
6 | edition = "2021"
7 | homepage = "https://pest.rs"
8 | license = "Apache-2.0"
9 | readme = "../README.md"
10 | repository = "https://github.com/pest-parser/pest-ide-tools"
11 |
12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13 |
14 | [dependencies]
15 | pest = "2.7.9"
16 | pest_fmt = "0.2.5"
17 | pest_meta = "2.7.8"
18 | reqwest = { version = "0.12.4", features = [
19 | "json",
20 | "rustls-tls",
21 | ], default-features = false }
22 | serde = { version = "1.0.197", features = ["derive"] }
23 | serde_json = "1.0.116"
24 | tokio = { version = "1.37.0", features = ["full"] }
25 | tower-lsp = "0.20.0"
26 | unicode-segmentation = "1.11.0"
27 |
28 | [dev-dependencies]
29 | rusty-hook = "0.11"
30 |
--------------------------------------------------------------------------------
/language-server/src/analysis.rs:
--------------------------------------------------------------------------------
1 | use pest::{iterators::Pairs, Span};
2 | use pest_meta::parser::Rule;
3 | use reqwest::Url;
4 | use std::collections::HashMap;
5 | use tower_lsp::lsp_types::Location;
6 |
7 | use crate::{
8 | builtins::BUILTINS,
9 | helpers::{FindOccurrences, IntoLocation},
10 | };
11 |
12 | #[derive(Debug, Clone)]
13 | /// Stores analysis information for a rule.
14 | pub struct RuleAnalysis {
15 | /// The location of the entire definition of the rule (i.e. `rule = { "hello" }`).
16 | pub definition_location: Location,
17 | /// The location of the name definition of the rule.
18 | pub identifier_location: Location,
19 | /// The tokens that make up the rule.
20 | pub tokens: Vec<(String, Location)>,
21 | /// The rules expression, in [String] form.
22 | pub expression: String,
23 | /// The occurrences of the rule, including its definition.
24 | pub occurrences: Vec,
25 | /// The rules documentation, in markdown.
26 | pub doc: String,
27 | }
28 |
29 | #[derive(Debug)]
30 | /// Stores analysis information for a document.
31 | pub struct Analysis {
32 | /// The URL of the document that this analysis is for.
33 | pub doc_url: Url,
34 | /// Holds analyses for individual rules.
35 | /// [RuleAnalysis] is [None] for builtins.
36 | pub rules: HashMap>,
37 | }
38 |
39 | impl Analysis {
40 | /// Updates this analysis from the given pairs.
41 | pub fn update_from(&mut self, pairs: Pairs) {
42 | self.rules = HashMap::new();
43 |
44 | for builtin in BUILTINS.iter() {
45 | self.rules.insert(builtin.to_string(), None);
46 | }
47 |
48 | let mut preceding_docs = Vec::new();
49 | let mut current_span: Option;
50 |
51 | for pair in pairs.clone() {
52 | if pair.as_rule() == Rule::grammar_rule {
53 | current_span = Some(pair.as_span());
54 | let mut inner_pairs = pair.into_inner();
55 | let inner = inner_pairs.next().unwrap();
56 |
57 | match inner.as_rule() {
58 | Rule::line_doc => {
59 | preceding_docs.push(inner.into_inner().next().unwrap().as_str());
60 | }
61 | Rule::identifier => {
62 | let expression_pair = inner_pairs
63 | .find(|r| r.as_rule() == Rule::expression)
64 | .expect("rule should contain expression");
65 | let expression = expression_pair.as_str().to_owned();
66 | let expressions = expression_pair
67 | .into_inner()
68 | .map(|e| {
69 | (
70 | e.as_str().to_owned(),
71 | e.as_span().into_location(&self.doc_url),
72 | )
73 | })
74 | .collect();
75 | let occurrences = pairs.find_occurrences(&self.doc_url, inner.as_str());
76 | let mut docs = None;
77 |
78 | if !preceding_docs.is_empty() {
79 | let mut buf = String::new();
80 |
81 | if preceding_docs.len() == 1 {
82 | buf.push_str(preceding_docs.first().unwrap());
83 | } else {
84 | buf.push_str("- ");
85 | buf.push_str(preceding_docs.join("\n- ").as_str());
86 | }
87 |
88 | docs = Some(buf);
89 | preceding_docs.clear();
90 | }
91 |
92 | self.rules.insert(
93 | inner.as_str().to_owned(),
94 | Some(RuleAnalysis {
95 | identifier_location: inner.as_span().into_location(&self.doc_url),
96 | definition_location: current_span
97 | .expect("rule should have a defined span")
98 | .into_location(&self.doc_url),
99 | tokens: expressions,
100 | expression,
101 | occurrences,
102 | doc: docs.unwrap_or_else(|| "".to_owned()),
103 | }),
104 | );
105 | }
106 | _ => {}
107 | }
108 | }
109 | }
110 | }
111 |
112 | pub fn get_unused_rules(&self) -> Vec<(&String, &Location)> {
113 | self.rules
114 | .iter()
115 | .filter(|(_, ra)| {
116 | if let Some(ra) = ra {
117 | ra.occurrences.len() == 1
118 | } else {
119 | false
120 | }
121 | })
122 | .filter(|(name, _)| !BUILTINS.contains(&name.as_str()) && !name.starts_with('_'))
123 | .map(|(name, ra)| {
124 | (
125 | name,
126 | ra.as_ref().unwrap().occurrences.first().unwrap_or_else(|| {
127 | panic!("Expected at least one occurrence for rule {}", name)
128 | }),
129 | )
130 | })
131 | .collect()
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/language-server/src/builtins.rs:
--------------------------------------------------------------------------------
1 | pub const BUILTINS: &[&str] = &[
2 | "ANY",
3 | "WHITESPACE",
4 | "COMMENT",
5 | "SOI",
6 | "EOI",
7 | "ASCII_DIGIT",
8 | "ASCII_NONZERO_DIGIT",
9 | "ASCII_BIN_DIGIT",
10 | "ASCII_OCT_DIGIT",
11 | "ASCII_HEX_DIGIT",
12 | "ASCII_ALPHA_LOWER",
13 | "ASCII_ALPHA_UPPER",
14 | "ASCII_ALPHA",
15 | "ASCII_ALPHANUMERIC",
16 | "NEWLINE",
17 | "LETTER",
18 | "CASED_LETTER",
19 | "UPPERCASE_LETTER",
20 | "LOWERCASE_LETTER",
21 | "TITLECASE_LETTER",
22 | "MODIFIER_LETTER",
23 | "OTHER_LETTER",
24 | "MARK",
25 | "NON_SPACING_MARK",
26 | "SPACING_MARK",
27 | "ENCLOSING_MARK",
28 | "NUMBER",
29 | "DECIMAL_NUMBER",
30 | "LETTER_NUMBER",
31 | "OTHER_NUMBER",
32 | "PUNCTUATION",
33 | "CONNECTOR_PUNCTUATION",
34 | "DASH_PUNCTUATION",
35 | "OPEN_PUNCTUATION",
36 | "CLOSE_PUNCTUATION",
37 | "INITIAL_PUNCTUATION",
38 | "FINAL_PUNCTUATION",
39 | "OTHER_PUNCTUATION",
40 | "SYMBOL",
41 | "MATH_SYMBOL",
42 | "CURRENCY_SYMBOL",
43 | "MODIFIER_SYMBOL",
44 | "OTHER_SYMBOL",
45 | "SEPARATOR",
46 | "SPACE_SEPARATOR",
47 | "LINE_SEPARATOR",
48 | "PARAGRAPH_SEPARATOR",
49 | "CONTROL",
50 | "FORMAT",
51 | "PRIVATE_USE",
52 | "SURROGATE",
53 | "UNASSIGNED",
54 | "ALPHABETIC",
55 | "BIDI_CONTROL",
56 | "BIDI_MIRRORED",
57 | "CASE_IGNORABLE",
58 | "CASED",
59 | "CHANGES_WHEN_CASEFOLDED",
60 | "CHANGES_WHEN_CASEMAPPED",
61 | "CHANGES_WHEN_LOWERCASED",
62 | "CHANGES_WHEN_TITLECASED",
63 | "CHANGES_WHEN_UPPERCASED",
64 | "DASH",
65 | "DEFAULT_IGNORABLE_CODE_POINT",
66 | "DEPRECATED",
67 | "DIACRITIC",
68 | "EMOJI",
69 | "EMOJI_COMPONENT",
70 | "EMOJI_MODIFIER",
71 | "EMOJI_MODIFIER_BASE",
72 | "EMOJI_PRESENTATION",
73 | "EXTENDER",
74 | "GRAPHEME_BASE",
75 | "GRAPHEME_EXTEND",
76 | "GRAPHEME_LINK",
77 | "HEX_DIGIT",
78 | "HYPHEN",
79 | "IDS_BINARY_OPERATOR",
80 | "IDS_TRINARY_OPERATOR",
81 | "ID_CONTINUE",
82 | "ID_START",
83 | "IDEOGRAPHIC",
84 | "JOIN_CONTROL",
85 | "LOGICAL_ORDER_EXCEPTION",
86 | "LOWERCASE",
87 | "MATH",
88 | "NONCHARACTER_CODE_POINT",
89 | "OTHER_ALPHABETIC",
90 | "OTHER_DEFAULT_IGNORABLE_CODE_POINT",
91 | "OTHER_GRAPHEME_EXTEND",
92 | "OTHER_ID_CONTINUE",
93 | "OTHER_ID_START",
94 | "OTHER_LOWERCASE",
95 | "OTHER_MATH",
96 | "OTHER_UPPERCASE",
97 | "PATTERN_SYNTAX",
98 | "PATTERN_WHITE_SPACE",
99 | "PREPENDED_CONCATENATION_MARK",
100 | "QUOTATION_MARK",
101 | "RADICAL",
102 | "REGIONAL_INDICATOR",
103 | "SENTENCE_TERMINAL",
104 | "SOFT_DOTTED",
105 | "TERMINAL_PUNCTUATION",
106 | "UNIFIED_IDEOGRAPH",
107 | "UPPERCASE",
108 | "VARIATION_SELECTOR",
109 | "WHITE_SPACE",
110 | "XID_CONTINUE",
111 | "XID_START",
112 | "ADLAM",
113 | "AHOM",
114 | "ANATOLIAN_HIEROGLYPHS",
115 | "ARABIC",
116 | "ARMENIAN",
117 | "AVESTAN",
118 | "BALINESE",
119 | "BAMUM",
120 | "BASSA_VAH",
121 | "BATAK",
122 | "BENGALI",
123 | "BHAIKSUKI",
124 | "BOPOMOFO",
125 | "BRAHMI",
126 | "BRAILLE",
127 | "BUGINESE",
128 | "BUHID",
129 | "CANADIAN_ABORIGINAL",
130 | "CARIAN",
131 | "CAUCASIAN_ALBANIAN",
132 | "CHAKMA",
133 | "CHAM",
134 | "CHEROKEE",
135 | "CHORASMIAN",
136 | "COMMON",
137 | "COPTIC",
138 | "CUNEIFORM",
139 | "CYPRIOT",
140 | "CYPRO_MINOAN",
141 | "CYRILLIC",
142 | "DESERET",
143 | "DEVANAGARI",
144 | "DIVES_AKURU",
145 | "DOGRA",
146 | "DUPLOYAN",
147 | "EGYPTIAN_HIEROGLYPHS",
148 | "ELBASAN",
149 | "ELYMAIC",
150 | "ETHIOPIC",
151 | "GEORGIAN",
152 | "GLAGOLITIC",
153 | "GOTHIC",
154 | "GRANTHA",
155 | "GREEK",
156 | "GUJARATI",
157 | "GUNJALA_GONDI",
158 | "GURMUKHI",
159 | "HAN",
160 | "HANGUL",
161 | "HANIFI_ROHINGYA",
162 | "HANUNOO",
163 | "HATRAN",
164 | "HEBREW",
165 | "HIRAGANA",
166 | "IMPERIAL_ARAMAIC",
167 | "INHERITED",
168 | "INSCRIPTIONAL_PAHLAVI",
169 | "INSCRIPTIONAL_PARTHIAN",
170 | "JAVANESE",
171 | "KAITHI",
172 | "KANNADA",
173 | "KATAKANA",
174 | "KAWI",
175 | "KAYAH_LI",
176 | "KHAROSHTHI",
177 | "KHITAN_SMALL_SCRIPT",
178 | "KHMER",
179 | "KHOJKI",
180 | "KHUDAWADI",
181 | "LAO",
182 | "LATIN",
183 | "LEPCHA",
184 | "LIMBU",
185 | "LINEAR_A",
186 | "LINEAR_B",
187 | "LISU",
188 | "LYCIAN",
189 | "LYDIAN",
190 | "MAHAJANI",
191 | "MAKASAR",
192 | "MALAYALAM",
193 | "MANDAIC",
194 | "MANICHAEAN",
195 | "MARCHEN",
196 | "MASARAM_GONDI",
197 | "MEDEFAIDRIN",
198 | "MEETEI_MAYEK",
199 | "MENDE_KIKAKUI",
200 | "MEROITIC_CURSIVE",
201 | "MEROITIC_HIEROGLYPHS",
202 | "MIAO",
203 | "MODI",
204 | "MONGOLIAN",
205 | "MRO",
206 | "MULTANI",
207 | "MYANMAR",
208 | "NABATAEAN",
209 | "NAG_MUNDARI",
210 | "NANDINAGARI",
211 | "NEW_TAI_LUE",
212 | "NEWA",
213 | "NKO",
214 | "NUSHU",
215 | "NYIAKENG_PUACHUE_HMONG",
216 | "OGHAM",
217 | "OL_CHIKI",
218 | "OLD_HUNGARIAN",
219 | "OLD_ITALIC",
220 | "OLD_NORTH_ARABIAN",
221 | "OLD_PERMIC",
222 | "OLD_PERSIAN",
223 | "OLD_SOGDIAN",
224 | "OLD_SOUTH_ARABIAN",
225 | "OLD_TURKIC",
226 | "OLD_UYGHUR",
227 | "ORIYA",
228 | "OSAGE",
229 | "OSMANYA",
230 | "PAHAWH_HMONG",
231 | "PALMYRENE",
232 | "PAU_CIN_HAU",
233 | "PHAGS_PA",
234 | "PHOENICIAN",
235 | "PSALTER_PAHLAVI",
236 | "REJANG",
237 | "RUNIC",
238 | "SAMARITAN",
239 | "SAURASHTRA",
240 | "SHARADA",
241 | "SHAVIAN",
242 | "SIDDHAM",
243 | "SIGNWRITING",
244 | "SINHALA",
245 | "SOGDIAN",
246 | "SORA_SOMPENG",
247 | "SOYOMBO",
248 | "SUNDANESE",
249 | "SYLOTI_NAGRI",
250 | "SYRIAC",
251 | "TAGALOG",
252 | "TAGBANWA",
253 | "TAI_LE",
254 | "TAI_THAM",
255 | "TAI_VIET",
256 | "TAKRI",
257 | "TAMIL",
258 | "TANGSA",
259 | "TANGUT",
260 | "TELUGU",
261 | "THAANA",
262 | "THAI",
263 | "TIBETAN",
264 | "TIFINAGH",
265 | "TIRHUTA",
266 | "TOTO",
267 | "UGARITIC",
268 | "VAI",
269 | "VITHKUQI",
270 | "WANCHO",
271 | "WARANG_CITI",
272 | "YEZIDI",
273 | "YI",
274 | "ZANABAZAR_SQUARE",
275 | ];
276 |
277 | pub fn get_builtin_description(rule: &str) -> Option<&str> {
278 | match rule {
279 | "ANY" => Some("Matches any character."),
280 | "SOI" => Some("Matches the start of the input. Does not consume any characters."),
281 | "EOI" => Some("Matches the end of the input. Does not consume any characters."),
282 |
283 | "ASCII_DIGIT" => Some("Matches any ASCII digit (0-9)."),
284 | "ASCII_NONZERO_DIGIT" => Some("Matches any non-zero ASCII digit (1-9)."),
285 | "ASCII_BIN_DIGIT" => Some("Matches any ASCII binary digit (0-1)."),
286 | "ASCII_OCT_DIGIT" => Some("Matches any ASCII octal digit (0-7)."),
287 | "ASCII_HEX_DIGIT" => Some("Matches any ASCII hexadecimal digit (0-9, a-f, A-F)."),
288 |
289 | "ASCII_ALPHA_LOWER" => Some("Matches any ASCII lowercase letter (a-z)."),
290 | "ASCII_ALPHA_UPPER" => Some("Matches any ASCII uppercase letter (A-Z)."),
291 | "ASCII_ALPHA" => Some("Matches any ASCII letter (a-z, A-Z)."),
292 |
293 | "ASCII_ALPHANUMERIC" => Some("Matches any ASCII alphanumeric character (0-9, a-z, A-Z)."),
294 | "NEWLINE" => Some("Matches any newline character (\\n, \\r\\n, \\r)."),
295 |
296 | "LETTER" => Some("Matches any Unicode letter."),
297 | "CASED_LETTER" => Some("Matches any upper or lower case Unicode letter."),
298 | "UPPERCASE_LETTER" => Some("Matches any uppercase Unicode letter."),
299 | "LOWERCASE_LETTER" => Some("Matches any lowercase Unicode letter."),
300 | "TITLECASE_LETTER" => Some("Matches any titlecase Unicode letter."),
301 | "MODIFIER_LETTER" => Some("Matches any Unicode modifier letter."),
302 | "OTHER_LETTER" => {
303 | Some("Matches any Unicode letter that does not fit into any other defined categories.")
304 | }
305 |
306 | "MARK" => Some("Matches any Unicode mark."),
307 | "NON_SPACING_MARK" => Some("Matches any Unicode non-spacing mark."),
308 | "SPACING_MARK" => Some("Matches any Unicode spacing mark."),
309 | "ENCLOSING_MARK" => Some("Matches any Unicode enclosing mark."),
310 |
311 | "NUMBER" => Some("Matches any Unicode number."),
312 | "DECIMAL_NUMBER" => Some("Matches any Unicode decimal number."),
313 | "LETTER_NUMBER" => Some("Matches any Unicode letter number."),
314 | "OTHER_NUMBER" => {
315 | Some("Matches any Unicode number that does not fit into any other defined categories.")
316 | }
317 |
318 | "PUNCTUATION" => Some("Matches any Unicode punctuation."),
319 | "CONNECTOR_PUNCTUATION" => Some("Matches any Unicode connector punctuation."),
320 | "DASH_PUNCTUATION" => Some("Matches any Unicode dash punctuation."),
321 | "OPEN_PUNCTUATION" => Some("Matches any Unicode open punctuation."),
322 | "CLOSE_PUNCTUATION" => Some("Matches any Unicode close punctuation."),
323 | "INITIAL_PUNCTUATION" => Some("Matches any Unicode initial punctuation."),
324 | "FINAL_PUNCTUATION" => Some("Matches any Unicode final punctuation."),
325 | "OTHER_PUNCTUATION" => Some(
326 | "Matches any Unicode punctuation that does not fit into any other defined categories.",
327 | ),
328 |
329 | "SYMBOL" => Some("Matches any Unicode symbol."),
330 | "MATH_SYMBOL" => Some("Matches any Unicode math symbol."),
331 | "CURRENCY_SYMBOL" => Some("Matches any Unicode currency symbol."),
332 | "MODIFIER_SYMBOL" => Some("Matches any Unicode modifier symbol."),
333 | "OTHER_SYMBOL" => {
334 | Some("Matches any Unicode symbol that does not fit into any other defined categories.")
335 | }
336 |
337 | "SEPARATOR" => Some("Matches any Unicode separator."),
338 | "SPACE_SEPARATOR" => Some("Matches any Unicode space separator."),
339 | "LINE_SEPARATOR" => Some("Matches any Unicode line separator."),
340 | "PARAGRAPH_SEPARATOR" => Some("Matches any Unicode paragraph separator."),
341 |
342 | "OTHER" => Some(
343 | "Matches any Unicode character that does not fit into any other defined categories.",
344 | ),
345 | "CONTROL" => Some("Matches any Unicode control character."),
346 | "FORMAT" => Some("Matches any Unicode format character."),
347 | "SURROGATE" => Some("Matches any Unicode surrogate."),
348 | "PRIVATE_USE" => Some("Matches any Unicode private use character."),
349 | "UNASSIGNED" => Some("Matches any Unicode unassigned character."),
350 |
351 | "ALPHABETIC" => Some("Matches any Unicode alphabetic character."),
352 | "BIDI_CONTROL" => Some("Matches any Unicode bidirectional control character."),
353 | "BIDI_MIRRORED" => Some("Matches any Unicode bidirectional mirrored character."),
354 | "CASE_IGNORABLE" => Some("Matches any Unicode case-ignorable character."),
355 | "CASED" => Some("Matches any Unicode cased character."),
356 | "CHANGES_WHEN_CASEFOLDED" => {
357 | Some("Matches any Unicode character that changes when casefolded.")
358 | }
359 | "CHANGES_WHEN_CASEMAPPED" => {
360 | Some("Matches any Unicode character that changes when casemapped.")
361 | }
362 | "CHANGES_WHEN_LOWERCASED" => {
363 | Some("Matches any Unicode character that changes when lowercased.")
364 | }
365 | "CHANGES_WHEN_TITLECASED" => {
366 | Some("Matches any Unicode character that changes when titlecased.")
367 | }
368 | "CHANGES_WHEN_UPPERCASED" => {
369 | Some("Matches any Unicode character that changes when uppercased.")
370 | }
371 | "DASH" => Some("Matches any Unicode dash character."),
372 | "DEFAULT_IGNORABLE_CODE_POINT" => Some("Matches any Unicode default-ignorable code point."),
373 | "DEPRECATED" => Some("Matches any Unicode deprecated character."),
374 | "DIACRITIC" => Some("Matches any Unicode diacritic character."),
375 | "EMOJI" => Some("Matches any Unicode emoji character."),
376 | "EMOJI_COMPONENT" => Some("Matches any Unicode emoji component character."),
377 | "EMOJI_MODIFIER" => Some("Matches any Unicode emoji modifier character."),
378 | "EMOJI_MODIFIER_BASE" => Some("Matches any Unicode emoji modifier base character."),
379 | "EMOJI_PRESENTATION" => Some("Matches any Unicode emoji presentation character."),
380 | "EXTENDED_PICTOGRAPHIC" => Some("Matches any Unicode extended pictographic character."),
381 | "EXTENDER" => Some("Matches any Unicode extender character."),
382 | "GRAPHEME_BASE" => Some("Matches any Unicode grapheme base character."),
383 | "GRAPHEME_EXTEND" => Some("Matches any Unicode grapheme extend character."),
384 | "GRAPHEME_LINK" => Some("Matches any Unicode grapheme link character."),
385 | "HEX_DIGIT" => Some("Matches any Unicode hexadecimal digit character."),
386 | "HYPHEN" => Some("Matches any Unicode hyphen character."),
387 | "IDS_BINARY_OPERATOR" => Some("Matches any Unicode IDS binary operator character."),
388 | "IDS_TRINARY_OPERATOR" => Some("Matches any Unicode IDS trinary operator character."),
389 | "ID_CONTINUE" => Some("Matches any Unicode ID continue character."),
390 | "ID_START" => Some("Matches any Unicode ID start character."),
391 | "IDEOGRAPHIC" => Some("Matches any Unicode ideographic character."),
392 | "JOIN_CONTROL" => Some("Matches any Unicode join control character."),
393 | "LOGICAL_ORDER_EXCEPTION" => Some("Matches any Unicode logical order exception character."),
394 | "LOWERCASE" => Some("Matches any Unicode lowercase character."),
395 | "MATH" => Some("Matches any Unicode math character."),
396 | "NONCHARACTER_CODE_POINT" => Some("Matches any Unicode noncharacter code point."),
397 | "OTHER_ALPHABETIC" => Some("Matches any Unicode other alphabetic character."),
398 | "OTHER_DEFAULT_IGNORABLE_CODE_POINT" => {
399 | Some("Matches any Unicode other default-ignorable code point.")
400 | }
401 | "OTHER_GRAPHEME_EXTEND" => Some("Matches any Unicode other grapheme extend character."),
402 | "OTHER_ID_CONTINUE" => Some("Matches any Unicode other ID continue character."),
403 | "OTHER_ID_START" => Some("Matches any Unicode other ID start character."),
404 | "OTHER_LOWERCASE" => Some("Matches any Unicode other lowercase character."),
405 | "OTHER_MATH" => Some("Matches any Unicode other math character."),
406 | "OTHER_UPPERCASE" => Some("Matches any Unicode other uppercase character."),
407 | "PATTERN_SYNTAX" => Some("Matches any Unicode pattern syntax character."),
408 | "PATTERN_WHITE_SPACE" => Some("Matches any Unicode pattern white space character."),
409 | "PREPENDED_CONCATENATION_MARK" => {
410 | Some("Matches any Unicode prepended concatenation mark character.")
411 | }
412 | "QUOTATION_MARK" => Some("Matches any Unicode quotation mark character."),
413 | "RADICAL" => Some("Matches any Unicode radical character."),
414 | "REGIONAL_INDICATOR" => Some("Matches any Unicode regional indicator character."),
415 | "SENTENCE_TERMINAL" => Some("Matches any Unicode sentence terminal character."),
416 | "SOFT_DOTTED" => Some("Matches any Unicode soft-dotted character."),
417 | "TERMINAL_PUNCTUATION" => Some("Matches any Unicode terminal punctuation character."),
418 | "UNIFIED_IDEOGRAPH" => Some("Matches any Unicode unified ideograph character."),
419 | "UPPERCASE" => Some("Matches any Unicode uppercase character."),
420 | "VARIATION_SELECTOR" => Some("Matches any Unicode variation selector character."),
421 | "WHITE_SPACE" => Some("Matches any Unicode white space character."),
422 | "XID_CONTINUE" => Some("Matches any Unicode XID continue character."),
423 | "XID_START" => Some("Matches any Unicode XID start character."),
424 |
425 | "ADLAM" => Some("Matches any Unicode Adlam character."),
426 | "AHOM" => Some("Matches any Unicode Ahom character."),
427 | "ANATOLIAN_HIEROGLYPHS" => Some("Matches any Unicode Anatolian Hieroglyphs character."),
428 | "ARABIC" => Some("Matches any Unicode Arabic character."),
429 | "ARMENIAN" => Some("Matches any Unicode Armenian character."),
430 | "AVESTAN" => Some("Matches any Unicode Avestan character."),
431 | "BALINESE" => Some("Matches any Unicode Balinese character."),
432 | "BAMUM" => Some("Matches any Unicode Bamum character."),
433 | "BASSA_VAH" => Some("Matches any Unicode Bassa Vah character."),
434 | "BATAK" => Some("Matches any Unicode Batak character."),
435 | "BENGALI" => Some("Matches any Unicode Bengali character."),
436 | "BHAIKSUKI" => Some("Matches any Unicode Bhaiksuki character."),
437 | "BOPOMOFO" => Some("Matches any Unicode Bopomofo character."),
438 | "BRAHMI" => Some("Matches any Unicode Brahmi character."),
439 | "BRAILLE" => Some("Matches any Unicode Braille character."),
440 | "BUGINESE" => Some("Matches any Unicode Buginese character."),
441 | "BUHID" => Some("Matches any Unicode Buhid character."),
442 | "CANADIAN_ABORIGINAL" => Some("Matches any Unicode Canadian Aboriginal character."),
443 | "CARIAN" => Some("Matches any Unicode Carian character."),
444 | "CAUCASIAN_ALBANIAN" => Some("Matches any Unicode Caucasian Albanian character."),
445 | "CHAKMA" => Some("Matches any Unicode Chakma character."),
446 | "CHAM" => Some("Matches any Unicode Cham character."),
447 | "CHEROKEE" => Some("Matches any Unicode Cherokee character."),
448 | "CHORASMIAN" => Some("Matches any Unicode Chorasmian character."),
449 | "COMMON" => Some("Matches any Unicode Common character."),
450 | "COPTIC" => Some("Matches any Unicode Coptic character."),
451 | "CUNEIFORM" => Some("Matches any Unicode Cuneiform character."),
452 | "CYPRIOT" => Some("Matches any Unicode Cypriot character."),
453 | "CYPRO_MINOAN" => Some("Matches any Unicode Cypro-Minoan character."),
454 | "CYRILLIC" => Some("Matches any Unicode Cyrillic character."),
455 | "DESERET" => Some("Matches any Unicode Deseret character."),
456 | "DEVANAGARI" => Some("Matches any Unicode Devanagari character."),
457 | "DIVES_AKURU" => Some("Matches any Unicode Dives Akuru character."),
458 | "DOGRA" => Some("Matches any Unicode Dogra character."),
459 | "DUPLOYAN" => Some("Matches any Unicode Duployan character."),
460 | "EGYPTIAN_HIEROGLYPHS" => Some("Matches any Unicode Egyptian Hieroglyphs character."),
461 | "ELBASAN" => Some("Matches any Unicode Elbasan character."),
462 | "ELYMAIC" => Some("Matches any Unicode Elymaic character."),
463 | "ETHIOPIC" => Some("Matches any Unicode Ethiopic character."),
464 | "GEORGIAN" => Some("Matches any Unicode Georgian character."),
465 | "GLAGOLITIC" => Some("Matches any Unicode Glagolitic character."),
466 | "GOTHIC" => Some("Matches any Unicode Gothic character."),
467 | "GRANTHA" => Some("Matches any Unicode Grantha character."),
468 | "GREEK" => Some("Matches any Unicode Greek character."),
469 | "GUJARATI" => Some("Matches any Unicode Gujarati character."),
470 | "GUNJALA_GONDI" => Some("Matches any Unicode Gunjala Gondi character."),
471 | "GURMUKHI" => Some("Matches any Unicode Gurmukhi character."),
472 | "HAN" => Some("Matches any Unicode Han character."),
473 | "HANGUL" => Some("Matches any Unicode Hangul character."),
474 | "HANIFI_ROHINGYA" => Some("Matches any Unicode Hanifi Rohingya character."),
475 | "HANUNOO" => Some("Matches any Unicode Hanunoo character."),
476 | "HATRAN" => Some("Matches any Unicode Hatran character."),
477 | "HEBREW" => Some("Matches any Unicode Hebrew character."),
478 | "HIRAGANA" => Some("Matches any Unicode Hiragana character."),
479 | "IMPERIAL_ARAMAIC" => Some("Matches any Unicode Imperial Aramaic character."),
480 | "INHERITED" => Some("Matches any Unicode Inherited character."),
481 | "INSCRIPTIONAL_PAHLAVI" => Some("Matches any Unicode Inscriptional Pahlavi character."),
482 | "INSCRIPTIONAL_PARTHIAN" => Some("Matches any Unicode Inscriptional Parthian character."),
483 | "JAVANESE" => Some("Matches any Unicode Javanese character."),
484 | "KAITHI" => Some("Matches any Unicode Kaithi character."),
485 | "KANNADA" => Some("Matches any Unicode Kannada character."),
486 | "KATAKANA" => Some("Matches any Unicode Katakana character."),
487 | "KAWI" => Some("Matches any Unicode Kawi character."),
488 | "KAYAH_LI" => Some("Matches any Unicode Kayah Li character."),
489 | "KHAROSHTHI" => Some("Matches any Unicode Kharoshthi character."),
490 | "KHITAN_SMALL_SCRIPT" => Some("Matches any Unicode Khitan Small Script character."),
491 | "KHMER" => Some("Matches any Unicode Khmer character."),
492 | "KHOJKI" => Some("Matches any Unicode Khojki character."),
493 | "KHUDAWADI" => Some("Matches any Unicode Khudawadi character."),
494 | "LAO" => Some("Matches any Unicode Lao character."),
495 | "LATIN" => Some("Matches any Unicode Latin character."),
496 | "LEPCHA" => Some("Matches any Unicode Lepcha character."),
497 | "LIMBU" => Some("Matches any Unicode Limbu character."),
498 | "LINEAR_A" => Some("Matches any Unicode Linear A character."),
499 | "LINEAR_B" => Some("Matches any Unicode Linear B character."),
500 | "LISU" => Some("Matches any Unicode Lisu character."),
501 | "LYCIAN" => Some("Matches any Unicode Lycian character."),
502 | "LYDIAN" => Some("Matches any Unicode Lydian character."),
503 | "MAHAJANI" => Some("Matches any Unicode Mahajani character."),
504 | "MAKASAR" => Some("Matches any Unicode Makasar character."),
505 | "MALAYALAM" => Some("Matches any Unicode Malayalam character."),
506 | "MANDAIC" => Some("Matches any Unicode Mandaic character."),
507 | "MANICHAEAN" => Some("Matches any Unicode Manichaean character."),
508 | "MARCHEN" => Some("Matches any Unicode Marchen character."),
509 | "MASARAM_GONDI" => Some("Matches any Unicode Masaram Gondi character."),
510 | "MEDEFAIDRIN" => Some("Matches any Unicode Medefaidrin character."),
511 | "MEETEI_MAYEK" => Some("Matches any Unicode Meetei Mayek character."),
512 | "MENDE_KIKAKUI" => Some("Matches any Unicode Mende Kikakui character."),
513 | "MEROITIC_CURSIVE" => Some("Matches any Unicode Meroitic Cursive character."),
514 | "MEROITIC_HIEROGLYPHS" => Some("Matches any Unicode Meroitic Hieroglyphs character."),
515 | "MIAO" => Some("Matches any Unicode Miao character."),
516 | "MODI" => Some("Matches any Unicode Modi character."),
517 | "MONGOLIAN" => Some("Matches any Unicode Mongolian character."),
518 | "MRO" => Some("Matches any Unicode Mro character."),
519 | "MULTANI" => Some("Matches any Unicode Multani character."),
520 | "MYANMAR" => Some("Matches any Unicode Myanmar character."),
521 | "NABATAEAN" => Some("Matches any Unicode Nabataean character."),
522 | "NAG_MUNDARI" => Some("Matches any Unicode Nag Mundari character."),
523 | "NANDINAGARI" => Some("Matches any Unicode Nandinagari character."),
524 | "NEW_TAI_LUE" => Some("Matches any Unicode New Tai Lue character."),
525 | "NEWA" => Some("Matches any Unicode Newa character."),
526 | "NKO" => Some("Matches any Unicode Nko character."),
527 | "NUSHU" => Some("Matches any Unicode Nushu character."),
528 | "NYIAKENG_PUACHUE_HMONG" => Some("Matches any Unicode Nyiakeng Puachue Hmong character."),
529 | "OGHAM" => Some("Matches any Unicode Ogham character."),
530 | "OL_CHIKI" => Some("Matches any Unicode Ol Chiki character."),
531 | "OLD_HUNGARIAN" => Some("Matches any Unicode Old Hungarian character."),
532 | "OLD_ITALIC" => Some("Matches any Unicode Old Italic character."),
533 | "OLD_NORTH_ARABIAN" => Some("Matches any Unicode Old North Arabian character."),
534 | "OLD_PERMIC" => Some("Matches any Unicode Old Permic character."),
535 | "OLD_PERSIAN" => Some("Matches any Unicode Old Persian character."),
536 | "OLD_SOGDIAN" => Some("Matches any Unicode Old Sogdian character."),
537 | "OLD_SOUTH_ARABIAN" => Some("Matches any Unicode Old South Arabian character."),
538 | "OLD_TURKIC" => Some("Matches any Unicode Old Turkic character."),
539 | "OLD_UYGHUR" => Some("Matches any Unicode Old Uyghur character."),
540 | "ORIYA" => Some("Matches any Unicode Oriya character."),
541 | "OSAGE" => Some("Matches any Unicode Osage character."),
542 | "OSMANYA" => Some("Matches any Unicode Osmanya character."),
543 | "PAHAWH_HMONG" => Some("Matches any Unicode Pahawh Hmong character."),
544 | "PALMYRENE" => Some("Matches any Unicode Palmyrene character."),
545 | "PAU_CIN_HAU" => Some("Matches any Unicode Pau Cin Hau character."),
546 | "PHAGS_PA" => Some("Matches any Unicode Phags Pa character."),
547 | "PHOENICIAN" => Some("Matches any Unicode Phoenician character."),
548 | "PSALTER_PAHLAVI" => Some("Matches any Unicode Psalter Pahlavi character."),
549 | "REJANG" => Some("Matches any Unicode Rejang character."),
550 | "RUNIC" => Some("Matches any Unicode Runic character."),
551 | "SAMARITAN" => Some("Matches any Unicode Samaritan character."),
552 | "SAURASHTRA" => Some("Matches any Unicode Saurashtra character."),
553 | "SHARADA" => Some("Matches any Unicode Sharada character."),
554 | "SHAVIAN" => Some("Matches any Unicode Shavian character."),
555 | "SIDDHAM" => Some("Matches any Unicode Siddham character."),
556 | "SIGNWRITING" => Some("Matches any Unicode SignWriting character."),
557 | "SINHALA" => Some("Matches any Unicode Sinhala character."),
558 | "SOGDIAN" => Some("Matches any Unicode Sogdian character."),
559 | "SORA_SOMPENG" => Some("Matches any Unicode Sora Sompeng character."),
560 | "SOYOMBO" => Some("Matches any Unicode Soyombo character."),
561 | "SUNDANESE" => Some("Matches any Unicode Sundanese character."),
562 | "SYLOTI_NAGRI" => Some("Matches any Unicode Syloti Nagri character."),
563 | "SYRIAC" => Some("Matches any Unicode Syriac character."),
564 | "TAGALOG" => Some("Matches any Unicode Tagalog character."),
565 | "TAGBANWA" => Some("Matches any Unicode Tagbanwa character."),
566 | "TAI_LE" => Some("Matches any Unicode Tai Le character."),
567 | "TAI_THAM" => Some("Matches any Unicode Tai Tham character."),
568 | "TAI_VIET" => Some("Matches any Unicode Tai Viet character."),
569 | "TAKRI" => Some("Matches any Unicode Takri character."),
570 | "TAMIL" => Some("Matches any Unicode Tamil character."),
571 | "TANGSA" => Some("Matches any Unicode Tangsa character."),
572 | "TANGUT" => Some("Matches any Unicode Tangut character."),
573 | "TELUGU" => Some("Matches any Unicode Telugu character."),
574 | "THAANA" => Some("Matches any Unicode Thaana character."),
575 | "THAI" => Some("Matches any Unicode Thai character."),
576 | "TIBETAN" => Some("Matches any Unicode Tibetan character."),
577 | "TIFINAGH" => Some("Matches any Unicode Tifinagh character."),
578 | "TIRHUTA" => Some("Matches any Unicode Tirhuta character."),
579 | "TOTO" => Some("Matches any Unicode Toto character."),
580 | "UGARITIC" => Some("Matches any Unicode Ugaritic character."),
581 | "VAI" => Some("Matches any Unicode Vai character."),
582 | "VITHKUQI" => Some("Matches any Unicode Vithkuqi character."),
583 | "WANCHO" => Some("Matches any Unicode Wancho character."),
584 | "WARANG_CITI" => Some("Matches any Unicode Warang Citi character."),
585 | "YEZIDI" => Some("Matches any Unicode Yezidi character."),
586 | "YI" => Some("Matches any Unicode Yi character."),
587 | "ZANABAZAR_SQUARE" => Some("Matches any Unicode Zanabazar Square character."),
588 | _ => None,
589 | }
590 | }
591 |
--------------------------------------------------------------------------------
/language-server/src/capabilities.rs:
--------------------------------------------------------------------------------
1 | use tower_lsp::lsp_types::{
2 | CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CompletionOptions,
3 | FileOperationFilter, FileOperationPattern, FileOperationRegistrationOptions,
4 | HoverProviderCapability, InitializeResult, OneOf, ServerCapabilities, ServerInfo,
5 | TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
6 | WorkDoneProgressOptions, WorkspaceFileOperationsServerCapabilities,
7 | WorkspaceServerCapabilities,
8 | };
9 |
10 | /// Returns the capabilities of the language server.
11 | pub fn capabilities() -> InitializeResult {
12 | InitializeResult {
13 | capabilities: ServerCapabilities {
14 | text_document_sync: Some(TextDocumentSyncCapability::Options(
15 | TextDocumentSyncOptions {
16 | change: Some(TextDocumentSyncKind::FULL),
17 | open_close: Some(true),
18 | ..Default::default()
19 | },
20 | )),
21 | hover_provider: Some(HoverProviderCapability::Simple(true)),
22 | completion_provider: Some(CompletionOptions {
23 | trigger_characters: Some(vec!["{".to_string(), "~".to_string(), "|".to_string()]),
24 | ..Default::default()
25 | }),
26 | code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
27 | code_action_kinds: Some(vec![
28 | CodeActionKind::REFACTOR_EXTRACT,
29 | CodeActionKind::REFACTOR_INLINE,
30 | ]),
31 | work_done_progress_options: WorkDoneProgressOptions::default(),
32 | resolve_provider: None,
33 | //FIXME(Jamalam): Use Default here once https://github.com/gluon-lang/lsp-types/issues/260 is resolved.
34 | // ..Default::default()
35 | })),
36 | definition_provider: Some(OneOf::Left(true)),
37 | references_provider: Some(OneOf::Left(true)),
38 | document_formatting_provider: Some(OneOf::Left(true)),
39 | rename_provider: Some(OneOf::Left(true)),
40 | workspace: Some(WorkspaceServerCapabilities {
41 | file_operations: Some(WorkspaceFileOperationsServerCapabilities {
42 | did_delete: Some(FileOperationRegistrationOptions {
43 | filters: vec![FileOperationFilter {
44 | pattern: FileOperationPattern {
45 | glob: "**".to_string(),
46 | ..Default::default()
47 | },
48 | ..Default::default()
49 | }],
50 | }),
51 | ..Default::default()
52 | }),
53 | ..Default::default()
54 | }),
55 | ..Default::default()
56 | },
57 | server_info: Some(ServerInfo {
58 | name: "Pest Language Server".to_string(),
59 | version: Some(env!("CARGO_PKG_VERSION").to_string()),
60 | }),
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/language-server/src/config.rs:
--------------------------------------------------------------------------------
1 | use serde::Deserialize;
2 |
3 | #[derive(Deserialize, Default, Debug)]
4 | #[serde(rename_all = "camelCase")]
5 | pub struct Config {
6 | pub always_used_rule_names: Vec,
7 | pub check_for_updates: bool,
8 | }
9 |
--------------------------------------------------------------------------------
/language-server/src/helpers.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use pest::{
4 | error::{ErrorVariant, LineColLocation},
5 | iterators::Pairs,
6 | Span,
7 | };
8 | use tower_lsp::lsp_types::{
9 | Diagnostic, DiagnosticSeverity, Location, Position, PublishDiagnosticsParams, Range,
10 | TextDocumentItem, Url,
11 | };
12 |
13 | use pest_meta::{
14 | parser::{self, Rule},
15 | validator,
16 | };
17 | use unicode_segmentation::UnicodeSegmentation;
18 |
19 | pub type Documents = HashMap;
20 | pub type Diagnostics = HashMap;
21 |
22 | pub fn create_empty_diagnostics(
23 | (uri, doc): (&Url, &TextDocumentItem),
24 | ) -> (Url, PublishDiagnosticsParams) {
25 | let params = PublishDiagnosticsParams::new(uri.clone(), vec![], Some(doc.version));
26 | (uri.clone(), params)
27 | }
28 |
29 | pub trait IntoRange {
30 | fn into_range(self) -> Range;
31 | }
32 |
33 | impl IntoRange for LineColLocation {
34 | fn into_range(self) -> Range {
35 | match self {
36 | LineColLocation::Pos((line, col)) => {
37 | let pos = Position::new(line as u32 - 1, col as u32 - 1);
38 | Range::new(pos, pos)
39 | }
40 | LineColLocation::Span((start_line, start_col), (end_line, end_col)) => Range::new(
41 | Position::new(start_line as u32 - 1, start_col as u32 - 1),
42 | Position::new(end_line as u32 - 1, end_col as u32 - 1),
43 | ),
44 | }
45 | }
46 | }
47 |
48 | impl IntoRange for Span<'_> {
49 | fn into_range(self) -> Range {
50 | let start = self.start_pos().line_col();
51 | let end = self.end_pos().line_col();
52 | LineColLocation::Span((start.0, start.1), (end.0, end.1)).into_range()
53 | }
54 | }
55 |
56 | pub trait IntoLocation {
57 | fn into_location(self, uri: &Url) -> Location;
58 | }
59 |
60 | impl IntoLocation for Span<'_> {
61 | fn into_location(self, uri: &Url) -> Location {
62 | Location::new(uri.clone(), self.into_range())
63 | }
64 | }
65 |
66 | pub trait FindOccurrences<'a> {
67 | fn find_occurrences(&self, doc_uri: &Url, identifier: &'a str) -> Vec;
68 | }
69 |
70 | impl<'a> FindOccurrences<'a> for Pairs<'a, parser::Rule> {
71 | fn find_occurrences(&self, doc_uri: &Url, identifier: &'a str) -> Vec {
72 | let mut locs = vec![];
73 |
74 | for pair in self.clone() {
75 | if pair.as_rule() == parser::Rule::identifier && pair.as_str() == identifier {
76 | locs.push(pair.as_span().into_location(doc_uri));
77 | }
78 |
79 | let inner = pair.into_inner();
80 | locs.extend(inner.find_occurrences(doc_uri, identifier));
81 | }
82 |
83 | locs
84 | }
85 | }
86 |
87 | pub trait IntoRangeWithLine {
88 | fn into_range(self, line: u32) -> Range;
89 | }
90 |
91 | impl IntoRangeWithLine for std::ops::Range {
92 | fn into_range(self, line: u32) -> Range {
93 | let start = Position::new(line, self.start as u32);
94 | let end = Position::new(line, self.end as u32);
95 | Range::new(start, end)
96 | }
97 | }
98 |
99 | pub trait FindWordRange {
100 | fn get_word_range_at_idx(self, idx: usize) -> std::ops::Range;
101 | }
102 |
103 | impl FindWordRange for &str {
104 | fn get_word_range_at_idx(self, search_idx: usize) -> std::ops::Range {
105 | fn is_identifier(c: char) -> bool {
106 | !(c.is_whitespace()
107 | || c == '*'
108 | || c == '+'
109 | || c == '?'
110 | || c == '!'
111 | || c == '&'
112 | || c == '~'
113 | || c == '{'
114 | || c == '}'
115 | || c == '['
116 | || c == ']'
117 | || c == '('
118 | || c == ')')
119 | }
120 |
121 | let next = str_range(self, &(search_idx..self.len()))
122 | .graphemes(true)
123 | .enumerate()
124 | .find(|(_index, char)| !is_identifier(char.chars().next().unwrap_or(' ')))
125 | .map(|(index, _char)| index)
126 | .map(|index| search_idx + index)
127 | .unwrap_or(self.len());
128 |
129 | let preceding = str_range(self, &(0..search_idx))
130 | .graphemes(true)
131 | .rev()
132 | .enumerate()
133 | .find(|(_index, char)| !is_identifier(char.chars().next().unwrap_or(' ')))
134 | .map(|(index, _char)| index)
135 | .map(|index| search_idx - index)
136 | .unwrap_or(0);
137 |
138 | preceding..next
139 | }
140 | }
141 |
142 | /// Returns a string from a range of human characters (graphemes). Respects unicode.
143 | pub fn str_range(s: &str, range: &std::ops::Range) -> String {
144 | s.graphemes(true)
145 | .skip(range.start)
146 | .take(range.len())
147 | .collect()
148 | }
149 |
150 | pub trait IntoDiagnostics {
151 | fn into_diagnostics(self) -> Vec;
152 | }
153 |
154 | impl IntoDiagnostics for Vec> {
155 | fn into_diagnostics(self) -> Vec {
156 | self.iter()
157 | .map(|e| {
158 | Diagnostic::new(
159 | e.line_col.clone().into_range(),
160 | Some(DiagnosticSeverity::ERROR),
161 | None,
162 | Some("Pest Language Server".to_owned()),
163 | match &e.variant {
164 | ErrorVariant::ParsingError {
165 | positives,
166 | negatives,
167 | } => {
168 | let mut message = "Parsing error".to_owned();
169 | if !positives.is_empty() {
170 | message.push_str(" (expected ");
171 | message.push_str(
172 | positives
173 | .iter()
174 | .map(|s| format!("\"{:#?}\"", s))
175 | .collect::>()
176 | .join(", ")
177 | .as_str(),
178 | );
179 | message.push(')');
180 | }
181 |
182 | if !negatives.is_empty() {
183 | message.push_str(" (unexpected ");
184 | message.push_str(
185 | negatives
186 | .iter()
187 | .map(|s| format!("\"{:#?}\"", s))
188 | .collect::>()
189 | .join(", ")
190 | .as_str(),
191 | );
192 | message.push(')');
193 | }
194 |
195 | message
196 | }
197 | ErrorVariant::CustomError { message } => {
198 | let mut c = message.chars();
199 | match c.next() {
200 | None => String::new(),
201 | Some(f) => f.to_uppercase().collect::() + c.as_str(),
202 | }
203 | }
204 | },
205 | None,
206 | None,
207 | )
208 | })
209 | .collect()
210 | }
211 | }
212 |
213 | pub fn validate_pairs(pairs: Pairs<'_, Rule>) -> Result<(), Vec>> {
214 | validator::validate_pairs(pairs.clone())?;
215 | // This calls validator::validate_ast under the hood
216 | parser::consume_rules(pairs)?;
217 | Ok(())
218 | }
219 |
220 | pub fn range_contains(primary: &Range, secondary: &Range) -> bool {
221 | primary.start.line <= secondary.start.line
222 | && primary.start.character <= secondary.start.character
223 | && primary.end.line >= secondary.end.line
224 | && primary.end.character >= secondary.end.character
225 | }
226 |
--------------------------------------------------------------------------------
/language-server/src/lsp.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::HashMap, str::Split};
2 |
3 | use pest_meta::parser;
4 | use tower_lsp::{
5 | jsonrpc::Result,
6 | lsp_types::{
7 | request::{GotoDeclarationParams, GotoDeclarationResponse},
8 | CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse,
9 | CompletionItem, CompletionItemKind, CompletionParams, CompletionResponse,
10 | ConfigurationItem, DeleteFilesParams, Diagnostic, DiagnosticSeverity,
11 | DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
12 | DidOpenTextDocumentParams, DocumentChanges, DocumentFormattingParams, FileChangeType,
13 | FileDelete, FileEvent, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents,
14 | HoverParams, InitializedParams, Location, MarkedString, MessageType, OneOf,
15 | OptionalVersionedTextDocumentIdentifier, Position, PublishDiagnosticsParams, Range,
16 | ReferenceParams, RenameParams, TextDocumentEdit, TextDocumentItem, TextEdit, Url,
17 | VersionedTextDocumentIdentifier, WorkspaceEdit,
18 | },
19 | Client,
20 | };
21 | use unicode_segmentation::UnicodeSegmentation;
22 |
23 | use crate::{
24 | analysis::{Analysis, RuleAnalysis},
25 | builtins::BUILTINS,
26 | config::Config,
27 | helpers::{
28 | create_empty_diagnostics, range_contains, str_range, validate_pairs, Diagnostics,
29 | Documents, FindWordRange, IntoDiagnostics, IntoRangeWithLine,
30 | },
31 | };
32 | use crate::{builtins::get_builtin_description, update_checker::check_for_updates};
33 |
34 | #[derive(Debug)]
35 | pub struct PestLanguageServerImpl {
36 | pub client: Client,
37 | pub documents: Documents,
38 | pub analyses: HashMap,
39 | pub config: Config,
40 | }
41 |
42 | impl PestLanguageServerImpl {
43 | pub async fn initialized(&mut self, _: InitializedParams) {
44 | let config_items = self
45 | .client
46 | .configuration(vec![ConfigurationItem {
47 | scope_uri: None,
48 | section: Some("pestIdeTools".to_string()),
49 | }])
50 | .await;
51 |
52 | let mut updated_config = false;
53 |
54 | if let Ok(config_items) = config_items {
55 | if let Some(config) = config_items.into_iter().next() {
56 | if let Ok(config) = serde_json::from_value(config) {
57 | self.config = config;
58 | updated_config = true;
59 | }
60 | }
61 | }
62 |
63 | if !updated_config {
64 | self.client
65 | .log_message(
66 | MessageType::ERROR,
67 | "Failed to retrieve configuration from client.",
68 | )
69 | .await;
70 | }
71 |
72 | self.client
73 | .log_message(
74 | MessageType::INFO,
75 | format!("Pest Language Server v{}", env!("CARGO_PKG_VERSION")),
76 | )
77 | .await;
78 |
79 | if self.config.check_for_updates {
80 | self.client
81 | .log_message(MessageType::INFO, "Checking for updates...".to_string())
82 | .await;
83 |
84 | if let Some(new_version) = check_for_updates().await {
85 | self.client
86 | .show_message(
87 | MessageType::INFO,
88 | format!(
89 | "A new version of the Pest Language Server is available: v{}",
90 | new_version
91 | ),
92 | )
93 | .await;
94 | }
95 | }
96 | }
97 |
98 | pub async fn shutdown(&self) -> Result<()> {
99 | self.client
100 | .log_message(MessageType::INFO, "Pest Language Server shutting down :)")
101 | .await;
102 | Ok(())
103 | }
104 |
105 | pub async fn did_change_configuration(&mut self, params: DidChangeConfigurationParams) {
106 | if let Ok(config) = serde_json::from_value(params.settings) {
107 | self.config = config;
108 | self.client
109 | .log_message(
110 | MessageType::INFO,
111 | "Updated configuration from client.".to_string(),
112 | )
113 | .await;
114 |
115 | let diagnostics = self.reload().await;
116 | self.send_diagnostics(diagnostics).await;
117 | }
118 | }
119 |
120 | pub async fn did_open(&mut self, params: DidOpenTextDocumentParams) {
121 | let DidOpenTextDocumentParams { text_document } = params;
122 | self.client
123 | .log_message(MessageType::INFO, format!("Opening {}", text_document.uri))
124 | .await;
125 |
126 | if self.upsert_document(text_document).is_some() {
127 | self.client
128 | .log_message(
129 | MessageType::INFO,
130 | "\tReopened already tracked document.".to_string(),
131 | )
132 | .await;
133 | }
134 |
135 | let diagnostics = self.reload().await;
136 | self.send_diagnostics(diagnostics).await;
137 | }
138 |
139 | pub async fn did_change(&mut self, params: DidChangeTextDocumentParams) {
140 | let DidChangeTextDocumentParams {
141 | text_document,
142 | content_changes,
143 | } = params;
144 | let VersionedTextDocumentIdentifier { uri, version } = text_document;
145 |
146 | assert_eq!(content_changes.len(), 1);
147 | let change = content_changes.into_iter().next().unwrap();
148 | assert!(change.range.is_none());
149 |
150 | let updated_doc =
151 | TextDocumentItem::new(uri.clone(), "pest".to_owned(), version, change.text);
152 |
153 | if self.upsert_document(updated_doc).is_none() {
154 | self.client
155 | .log_message(
156 | MessageType::INFO,
157 | format!("Updated untracked document {}", uri),
158 | )
159 | .await;
160 | }
161 |
162 | let diagnostics = self.reload().await;
163 | self.send_diagnostics(diagnostics).await;
164 | }
165 |
166 | pub async fn did_change_watched_files(&mut self, params: DidChangeWatchedFilesParams) {
167 | let DidChangeWatchedFilesParams { changes } = params;
168 | let uris: Vec<_> = changes
169 | .into_iter()
170 | .map(|FileEvent { uri, typ }| {
171 | assert_eq!(typ, FileChangeType::DELETED);
172 | uri
173 | })
174 | .collect();
175 |
176 | let mut diagnostics = Diagnostics::new();
177 |
178 | for uri in uris {
179 | self.client
180 | .log_message(
181 | MessageType::INFO,
182 | format!("Deleting removed document {}", uri),
183 | )
184 | .await;
185 |
186 | if let Some(removed) = self.remove_document(&uri) {
187 | let (_, empty_diagnostics) = create_empty_diagnostics((&uri, &removed));
188 | if diagnostics.insert(uri, empty_diagnostics).is_some() {
189 | self.client
190 | .log_message(
191 | MessageType::WARNING,
192 | "\tDuplicate URIs in event payload".to_string(),
193 | )
194 | .await;
195 | }
196 | } else {
197 | self.client
198 | .log_message(
199 | MessageType::WARNING,
200 | "\tAttempted to delete untracked document".to_string(),
201 | )
202 | .await;
203 | }
204 | }
205 |
206 | diagnostics.extend(self.reload().await);
207 | self.send_diagnostics(diagnostics).await;
208 | }
209 |
210 | pub async fn did_delete_files(&mut self, params: DeleteFilesParams) {
211 | let DeleteFilesParams { files } = params;
212 | let mut uris = vec![];
213 | for FileDelete { uri } in files {
214 | match Url::parse(&uri) {
215 | Ok(uri) => uris.push(uri),
216 | Err(e) => {
217 | self.client
218 | .log_message(MessageType::ERROR, format!("Failed to parse URI {}", e))
219 | .await
220 | }
221 | }
222 | }
223 |
224 | let mut diagnostics = Diagnostics::new();
225 |
226 | self.client
227 | .log_message(MessageType::INFO, format!("Deleting {} files", uris.len()))
228 | .await;
229 |
230 | for uri in uris {
231 | let removed = self.remove_documents_in_dir(&uri);
232 | if !removed.is_empty() {
233 | for (uri, params) in removed {
234 | self.client
235 | .log_message(MessageType::INFO, format!("\tDeleted {}", uri))
236 | .await;
237 |
238 | if diagnostics.insert(uri, params).is_some() {
239 | self.client
240 | .log_message(
241 | MessageType::INFO,
242 | "\tDuplicate URIs in event payload".to_string(),
243 | )
244 | .await;
245 | }
246 | }
247 | }
248 | }
249 |
250 | if !diagnostics.is_empty() {
251 | diagnostics.extend(self.reload().await);
252 | self.send_diagnostics(diagnostics).await;
253 | }
254 | }
255 |
256 | pub fn code_action(&self, params: CodeActionParams) -> Result