├── rust-toolchain.toml ├── src ├── utils.rs ├── types.rs ├── main.rs ├── snapshots │ ├── jj_lsp__conflict__tests__diff_two_sides.snap │ ├── jj_lsp__conflict__tests__diff_three_sides.snap │ └── jj_lsp__conflict__tests__diff_four_sides.snap ├── conflict.rs └── backend.rs ├── Cargo.toml ├── .gitignore ├── tests └── conflicts │ ├── git │ └── two_sides.md │ ├── diff │ ├── two_sides.md │ ├── three_sides.md │ └── four_sides.md │ └── snapshot │ ├── two_sides.md │ ├── three_sides.md │ └── four_sides.md ├── README.md ├── LICENSE ├── AGENT.md ├── .github └── workflows │ ├── ci.yml │ └── release.yml └── Cargo.lock /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.91.1" 3 | components = ["clippy", "rustfmt", "rust-analyzer"] 4 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn get_utf16_len(content: &str) -> u32 { 2 | content.chars().map(|c| c.len_utf16()).sum::() as u32 3 | } 4 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use tower_lsp_server::lsp_types::Range; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Conflict { 5 | pub range: Range, 6 | pub title_range: Range, 7 | pub blocks: Vec, 8 | } 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct ChangeBlock { 12 | pub title_range: Range, 13 | pub content: String, 14 | } 15 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod backend; 2 | mod conflict; 3 | mod types; 4 | mod utils; 5 | 6 | use backend::Backend; 7 | use tower_lsp_server::{LspService, Server}; 8 | 9 | #[tokio::main] 10 | async fn main() { 11 | let stdin = tokio::io::stdin(); 12 | let stdout = tokio::io::stdout(); 13 | 14 | let (service, socket) = LspService::new(Backend::new); 15 | Server::new(stdin, stdout, socket).serve(service).await; 16 | } 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jj-lsp" 3 | version = "0.1.1-dev1" 4 | edition = "2021" 5 | description = "LSP to resolve conflicts in the jj-vcs" 6 | authors = ["Nils Koch "] 7 | repository = "https://github.com/nilskch/jj-lsp" 8 | readme = "README.md" 9 | license = "MIT" 10 | 11 | [dependencies] 12 | insta = "1.43" 13 | lazy_static = "1.5" 14 | regex = "1" 15 | tokio = { version = "1.48", features = ["full"] } 16 | tower-lsp-server = "0.22" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # RustRover 13 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 14 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 15 | # and can be added to the global gitignore or merged into this file. For a more nuclear 16 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 17 | #.idea/ 18 | -------------------------------------------------------------------------------- /tests/conflicts/git/two_sides.md: -------------------------------------------------------------------------------- 1 | # My header 2 | 3 | This is some test file 4 | 5 | ## Some paragrapgh 6 | <<<<<<< Side #1 (Conflict 1 of 2) 7 | 1 pez 8 | 2 pez 9 | 3 pez 10 | 4 pez 11 | ||||||| Base 12 | 1 fish 13 | 2 fish 14 | 3 fish 15 | 4 fish 16 | ======= 17 | 1 poisson 18 | 2 poisson 19 | 3 poisson 20 | 4 poisson 21 | >>>>>>> Side #2 (Conflict 1 of 2 ends) 22 | Some unchanged line in the middle 23 | <<<<<<< Side #1 (Conflict 2 of 2) 24 | 5 pez 25 | 6 pez 26 | 7 pez 27 | 8 pez 28 | ||||||| Base 29 | 5 fish 30 | 6 fish 31 | 7 fish 32 | 8 fish 33 | ======= 34 | 5 poisson 35 | 6 poisson 36 | 7 poisson 37 | 8 poisson 38 | >>>>>>> Side #2 (Conflict 2 of 2 ends) 39 | 40 | ## Footer 41 | 42 | Lorem ipsum 43 | -------------------------------------------------------------------------------- /tests/conflicts/diff/two_sides.md: -------------------------------------------------------------------------------- 1 | # My header 2 | 3 | This is some test file 4 | 5 | ## Some paragrapgh 6 | <<<<<<< Conflict 1 of 2 7 | %%%%%%% Changes from base to side #1 8 | -1 fish 9 | -2 fish 10 | -3 fish 11 | -4 fish 12 | foo 13 | +1 pez 14 | +2 pez 15 | +3 pez 16 | +4 pez 17 | bar 18 | baz 19 | +++++++ Contents of side #2 20 | 1 poisson 21 | 2 poisson 22 | 3 poisson 23 | 4 poisson 24 | >>>>>>> Conflict 1 of 2 ends 25 | Some unchanged line in the middle 26 | <<<<<<< Conflict 2 of 2 27 | %%%%%%% Changes from base to side #1 28 | -5 fish 29 | -6 fish 30 | -7 fish 31 | -8 fish 32 | +5 pez 33 | +6 pez 34 | +7 pez 35 | +8 pez 36 | +++++++ Contents of side #2 37 | 5 poisson 38 | 6 poisson 39 | 7 poisson 40 | 8 poisson 41 | >>>>>>> Conflict 2 of 2 ends 42 | 43 | ## Footer 44 | 45 | Lorem ipsum 46 | -------------------------------------------------------------------------------- /tests/conflicts/snapshot/two_sides.md: -------------------------------------------------------------------------------- 1 | # My header 2 | 3 | This is some test file 4 | 5 | ## Some paragrapgh 6 | <<<<<<< Conflict 1 of 2 7 | +++++++ Contents of side #1 8 | 1 pez 9 | 2 pez 10 | 3 pez 11 | 4 pez 12 | ------- Contents of base 13 | 1 fish 14 | 2 fish 15 | 3 fish 16 | 4 fish 17 | +++++++ Contents of side #2 18 | 1 poisson 19 | 2 poisson 20 | 3 poisson 21 | 4 poisson 22 | >>>>>>> Conflict 1 of 2 ends 23 | Some unchanged line in the middle 24 | <<<<<<< Conflict 2 of 2 25 | +++++++ Contents of side #1 26 | 5 pez 27 | 6 pez 28 | 7 pez 29 | 8 pez 30 | ------- Contents of base 31 | 5 fish 32 | 6 fish 33 | 7 fish 34 | 8 fish 35 | +++++++ Contents of side #2 36 | 5 poisson 37 | 6 poisson 38 | 7 poisson 39 | 8 poisson 40 | >>>>>>> Conflict 2 of 2 ends 41 | 42 | ## Footer 43 | 44 | Lorem ipsum 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 2 | 3 | # jj-lsp 4 | 5 | A Language Server Protocol (LSP) implementation for resolving conflicts in the [jj-vcs](https://github.com/jj-vcs/jj). 6 | 7 | When you hit a merge conflict, `jj-lsp` highlights the problem and helps you fix it with quick actions. 8 | 9 | ## Demo 10 | 11 | This video shows all available features: 12 | 13 | https://github.com/user-attachments/assets/15ec57b6-810f-4e62-b9ad-097a2564f78a 14 | 15 | ## Installation 16 | 17 | ### Zed Extension 18 | 19 | You can install the `jj-lsp` via a Zed extension called [JJ Conflict Resolver](https://github.com/nilskch/zed-jj-lsp). 20 | There is no further configuration needed :) 21 | 22 | ### Cargo 23 | 24 | ``` 25 | cargo install --git https://github.com/nilskch/jj-lsp.git 26 | ``` 27 | 28 | ## License 29 | 30 | This project is licensed under the [MIT License](LICENSE). 31 | -------------------------------------------------------------------------------- /tests/conflicts/diff/three_sides.md: -------------------------------------------------------------------------------- 1 | # My header 2 | 3 | This is some test file 4 | 5 | ## Some paragrapgh 6 | <<<<<<< Conflict 1 of 2 7 | %%%%%%% Changes from base #1 to side #1 8 | -1 fish 9 | -2 fish 10 | -3 fish 11 | -4 fish 12 | foo 13 | +1 pez 14 | +2 pez 15 | +3 pez 16 | +4 pez 17 | bar 18 | baz 19 | %%%%%%% Changes from base #2 to side #2 20 | -1 fish 21 | -2 fish 22 | -3 fish 23 | -4 fish 24 | +1 poisson 25 | +2 poisson 26 | +3 poisson 27 | +4 poisson 28 | +++++++ Contents of side #3 29 | 1 pescare 30 | 2 pescare 31 | 3 pescare 32 | 4 pescare 33 | >>>>>>> Conflict 1 of 2 ends 34 | Some unchanged line in the middle 35 | <<<<<<< Conflict 2 of 2 36 | %%%%%%% Changes from base #1 to side #1 37 | -5 fish 38 | -6 fish 39 | -7 fish 40 | -8 fish 41 | +5 pez 42 | +6 pez 43 | +7 pez 44 | +8 pez 45 | %%%%%%% Changes from base #2 to side #2 46 | -5 fish 47 | -6 fish 48 | -7 fish 49 | -8 fish 50 | +5 poisson 51 | +6 poisson 52 | +7 poisson 53 | +8 poisson 54 | +++++++ Contents of side #3 55 | 5 pescare 56 | 6 pescare 57 | 7 pescare 58 | 8 pescare 59 | >>>>>>> Conflict 2 of 2 ends 60 | 61 | ## Footer 62 | 63 | Lorem ipsum 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nils Koch 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 | -------------------------------------------------------------------------------- /tests/conflicts/snapshot/three_sides.md: -------------------------------------------------------------------------------- 1 | # My header 2 | 3 | This is some test file 4 | 5 | ## Some paragrapgh 6 | <<<<<<< Conflict 1 of 2 7 | +++++++ Contents of side #1 8 | 1 pez 9 | 2 pez 10 | 3 pez 11 | 4 pez 12 | ------- Contents of base #1 13 | 1 fish 14 | 2 fish 15 | 3 fish 16 | 4 fish 17 | +++++++ Contents of side #2 18 | 1 poisson 19 | 2 poisson 20 | 3 poisson 21 | 4 poisson 22 | ------- Contents of base #2 23 | 1 fish 24 | 2 fish 25 | 3 fish 26 | 4 fish 27 | +++++++ Contents of side #3 28 | 1 pescare 29 | 2 pescare 30 | 3 pescare 31 | 4 pescare 32 | >>>>>>> Conflict 1 of 2 ends 33 | Some unchanged line in the middle 34 | <<<<<<< Conflict 2 of 2 35 | +++++++ Contents of side #1 36 | 5 pez 37 | 6 pez 38 | 7 pez 39 | 8 pez 40 | ------- Contents of base #1 41 | 5 fish 42 | 6 fish 43 | 7 fish 44 | 8 fish 45 | +++++++ Contents of side #2 46 | 5 poisson 47 | 6 poisson 48 | 7 poisson 49 | 8 poisson 50 | ------- Contents of base #2 51 | 5 fish 52 | 6 fish 53 | 7 fish 54 | 8 fish 55 | +++++++ Contents of side #3 56 | 5 pescare 57 | 6 pescare 58 | 7 pescare 59 | 8 pescare 60 | >>>>>>> Conflict 2 of 2 ends 61 | 62 | ## Footer 63 | 64 | Lorem ipsum 65 | -------------------------------------------------------------------------------- /AGENT.md: -------------------------------------------------------------------------------- 1 | # Agent Guidelines for jj-lsp 2 | 3 | ## Build & Test Commands 4 | - Build: `cargo build` 5 | - Run: `cargo run` 6 | - Test all: `cargo test` 7 | - Test single: `cargo test test_name` (e.g., `cargo test test_diff_two_sides`) 8 | - Test with snapshot updates: `cargo test -- --update-snapshots` 9 | - Install: `cargo install --path .` 10 | 11 | ## Code Style Guidelines 12 | - **Imports**: Group and order by std, external crates, then internal modules 13 | - **Error Handling**: Use `Option` for recoverable absences and early returns 14 | - **Naming**: Use snake_case for variables/functions, CamelCase for types/structs 15 | - **Types**: Use explicit types for function signatures, prefer ownership over borrowing when appropriate 16 | - **Formatting**: Follow Rust standard formatting (rustfmt) 17 | - **Tests**: Use snapshot testing with insta crate for complex test assertions 18 | - **Regex**: Define regex patterns with lazy_static 19 | - **Modules**: Keep related functionality in dedicated modules 20 | 21 | ## Project Structure 22 | - `src/main.rs`: Entry point for the LSP server 23 | - `src/backend.rs`: LSP backend implementation 24 | - `src/conflict.rs`: Conflict parsing and analysis 25 | - `src/types.rs`: Shared type definitions 26 | - `src/utils.rs`: Utility functions -------------------------------------------------------------------------------- /tests/conflicts/diff/four_sides.md: -------------------------------------------------------------------------------- 1 | # My header 2 | 3 | This is some test file 4 | 5 | ## Some paragrapgh 6 | <<<<<<< Conflict 1 of 2 7 | %%%%%%% Changes from base #1 to side #1 8 | -1 fish 9 | -2 fish 10 | -3 fish 11 | -4 fish 12 | foo 13 | +1 pez 14 | +2 pez 15 | +3 pez 16 | +4 pez 17 | bar 18 | baz 19 | %%%%%%% Changes from base #2 to side #2 20 | -1 fish 21 | -2 fish 22 | -3 fish 23 | -4 fish 24 | +1 poisson 25 | +2 poisson 26 | +3 poisson 27 | +4 poisson 28 | +++++++ Contents of side #3 29 | 1 pescare 30 | 2 pescare 31 | 3 pescare 32 | 4 pescare 33 | %%%%%%% Changes from base #3 to side #4 34 | -1 fish 35 | -2 fish 36 | -3 fish 37 | -4 fish 38 | +1 fisch 39 | +2 fisch 40 | +3 fisch 41 | +4 fisch 42 | >>>>>>> Conflict 1 of 2 ends 43 | Some unchanged line in the middle 44 | <<<<<<< Conflict 2 of 2 45 | %%%%%%% Changes from base #1 to side #1 46 | -5 fish 47 | -6 fish 48 | -7 fish 49 | -8 fish 50 | +5 pez 51 | +6 pez 52 | +7 pez 53 | +8 pez 54 | %%%%%%% Changes from base #2 to side #2 55 | -5 fish 56 | -6 fish 57 | -7 fish 58 | -8 fish 59 | +5 poisson 60 | +6 poisson 61 | +7 poisson 62 | +8 poisson 63 | +++++++ Contents of side #3 64 | 5 pescare 65 | 6 pescare 66 | 7 pescare 67 | 8 pescare 68 | %%%%%%% Changes from base #3 to side #4 69 | -5 fish 70 | -6 fish 71 | -7 fish 72 | -8 fish 73 | +5 fisch 74 | +6 fisch 75 | +7 fisch 76 | +8 fisch 77 | >>>>>>> Conflict 2 of 2 ends 78 | 79 | ## Footer 80 | 81 | Lorem ipsum 82 | -------------------------------------------------------------------------------- /tests/conflicts/snapshot/four_sides.md: -------------------------------------------------------------------------------- 1 | # My header 2 | 3 | This is some test file 4 | 5 | ## Some paragrapgh 6 | <<<<<<< Conflict 1 of 2 7 | +++++++ Contents of side #1 8 | 1 pez 9 | 2 pez 10 | 3 pez 11 | 4 pez 12 | ------- Contents of base #1 13 | 1 fish 14 | 2 fish 15 | 3 fish 16 | 4 fish 17 | +++++++ Contents of side #2 18 | 1 poisson 19 | 2 poisson 20 | 3 poisson 21 | 4 poisson 22 | ------- Contents of base #2 23 | 1 fish 24 | 2 fish 25 | 3 fish 26 | 4 fish 27 | +++++++ Contents of side #3 28 | 1 pescare 29 | 2 pescare 30 | 3 pescare 31 | 4 pescare 32 | ------- Contents of base #3 33 | 1 fish 34 | 2 fish 35 | 3 fish 36 | 4 fish 37 | +++++++ Contents of side #4 38 | 1 fisch 39 | 2 fisch 40 | 3 fisch 41 | 4 fisch 42 | >>>>>>> Conflict 1 of 2 ends 43 | Some unchanged line in the middle 44 | <<<<<<< Conflict 2 of 2 45 | +++++++ Contents of side #1 46 | 5 pez 47 | 6 pez 48 | 7 pez 49 | 8 pez 50 | ------- Contents of base #1 51 | 5 fish 52 | 6 fish 53 | 7 fish 54 | 8 fish 55 | +++++++ Contents of side #2 56 | 5 poisson 57 | 6 poisson 58 | 7 poisson 59 | 8 poisson 60 | ------- Contents of base #2 61 | 5 fish 62 | 6 fish 63 | 7 fish 64 | 8 fish 65 | +++++++ Contents of side #3 66 | 5 pescare 67 | 6 pescare 68 | 7 pescare 69 | 8 pescare 70 | ------- Contents of base #3 71 | 5 fish 72 | 6 fish 73 | 7 fish 74 | 8 fish 75 | +++++++ Contents of side #4 76 | 5 fisch 77 | 6 fisch 78 | 7 fisch 79 | 8 fisch 80 | >>>>>>> Conflict 2 of 2 ends 81 | 82 | ## Footer 83 | 84 | Lorem ipsum 85 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | name: cargo test 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Install Rust toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | - name: Cache dependencies 25 | uses: actions/cache@v3 26 | with: 27 | path: | 28 | ~/.cargo/registry 29 | ~/.cargo/git 30 | target 31 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 32 | restore-keys: ${{ runner.os }}-cargo- 33 | - name: Run tests 34 | uses: actions-rs/cargo@v1 35 | with: 36 | command: test 37 | args: --verbose 38 | 39 | clippy: 40 | name: cargo clippy 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v3 44 | - name: Install Rust toolchain 45 | uses: actions-rs/toolchain@v1 46 | with: 47 | profile: minimal 48 | toolchain: stable 49 | override: true 50 | components: clippy 51 | - name: Cache dependencies 52 | uses: actions/cache@v3 53 | with: 54 | path: | 55 | ~/.cargo/registry 56 | ~/.cargo/git 57 | target 58 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 59 | restore-keys: ${{ runner.os }}-cargo- 60 | - name: Run clippy 61 | run: cargo clippy -- -D warnings 62 | 63 | machete: 64 | name: cargo machete 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v3 68 | - name: Install Rust toolchain 69 | uses: actions-rs/toolchain@v1 70 | with: 71 | profile: minimal 72 | toolchain: stable 73 | override: true 74 | - name: Cache dependencies 75 | uses: actions/cache@v3 76 | with: 77 | path: | 78 | ~/.cargo/registry 79 | ~/.cargo/git 80 | target 81 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 82 | restore-keys: ${{ runner.os }}-cargo- 83 | - name: Install cargo-machete 84 | uses: actions-rs/install@v0.1 85 | with: 86 | crate: cargo-machete 87 | version: latest 88 | - name: Run cargo-machete 89 | run: cargo machete 90 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "Version to release (e.g., 0.1.0)" 8 | required: true 9 | type: string 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | build: 19 | name: Build ${{ matrix.target }} 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | matrix: 23 | include: 24 | # TODO(nilskch): remove the gnu targets after bumping the extension version 25 | - { target: x86_64-unknown-linux-gnu, os: ubuntu-latest } 26 | - { target: aarch64-unknown-linux-gnu, os: ubuntu-24.04-arm64 } 27 | - { target: x86_64-unknown-linux-musl, os: ubuntu-latest } 28 | - { target: aarch64-unknown-linux-musl, os: ubuntu-24.04-arm64 } 29 | - { target: x86_64-apple-darwin, os: macos-14 } 30 | - { target: aarch64-apple-darwin, os: macos-14 } 31 | - { target: x86_64-pc-windows-msvc, os: windows-latest } 32 | - { target: aarch64-pc-windows-msvc, os: windows-latest } 33 | 34 | steps: 35 | - uses: actions/checkout@v4 36 | 37 | - name: Install target 38 | run: rustup target add ${{ matrix.target }} 39 | 40 | - name: Install musl-tools for musl targets 41 | if: contains(matrix.target, 'musl') 42 | run: sudo apt-get update && sudo apt-get install -y musl-tools 43 | 44 | - name: Build 45 | run: cargo build --release --target ${{ matrix.target }} 46 | 47 | - name: Package binary (Unix) 48 | if: matrix.os != 'windows-latest' 49 | run: | 50 | cd target/${{ matrix.target }}/release 51 | tar czf ../../../jj-lsp-${{ matrix.target }}.tar.gz jj-lsp 52 | 53 | - name: Package binary (Windows) 54 | if: matrix.os == 'windows-latest' 55 | run: | 56 | cd target/${{ matrix.target }}/release 57 | 7z a ../../../jj-lsp-${{ matrix.target }}.zip jj-lsp.exe 58 | 59 | - name: Upload artifact 60 | uses: actions/upload-artifact@v4 61 | with: 62 | name: jj-lsp-${{ matrix.target }} 63 | path: | 64 | *.tar.gz 65 | *.zip 66 | 67 | release: 68 | name: Create Release 69 | needs: build 70 | runs-on: ubuntu-latest 71 | permissions: 72 | contents: write 73 | steps: 74 | - name: Download artifacts 75 | uses: actions/download-artifact@v4 76 | 77 | - name: Upload Assets to Release 78 | uses: softprops/action-gh-release@v2 79 | with: 80 | tag_name: ${{ github.event.inputs.version }} 81 | files: | 82 | **/*.tar.gz 83 | **/*.zip 84 | env: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /src/snapshots/jj_lsp__conflict__tests__diff_two_sides.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/conflict.rs 3 | assertion_line: 181 4 | expression: conflicts 5 | --- 6 | [ 7 | Conflict { 8 | range: Range { 9 | start: Position { 10 | line: 5, 11 | character: 0, 12 | }, 13 | end: Position { 14 | line: 23, 15 | character: 28, 16 | }, 17 | }, 18 | title_range: Range { 19 | start: Position { 20 | line: 5, 21 | character: 0, 22 | }, 23 | end: Position { 24 | line: 5, 25 | character: 23, 26 | }, 27 | }, 28 | blocks: [ 29 | ChangeBlock { 30 | title_range: Range { 31 | start: Position { 32 | line: 6, 33 | character: 0, 34 | }, 35 | end: Position { 36 | line: 6, 37 | character: 36, 38 | }, 39 | }, 40 | content: "foo\n1 pez\n2 pez\n3 pez\n4 pez\nbar\nbaz", 41 | }, 42 | ChangeBlock { 43 | title_range: Range { 44 | start: Position { 45 | line: 18, 46 | character: 0, 47 | }, 48 | end: Position { 49 | line: 18, 50 | character: 27, 51 | }, 52 | }, 53 | content: "1 poisson\n2 poisson\n3 poisson\n4 poisson", 54 | }, 55 | ], 56 | }, 57 | Conflict { 58 | range: Range { 59 | start: Position { 60 | line: 25, 61 | character: 0, 62 | }, 63 | end: Position { 64 | line: 40, 65 | character: 28, 66 | }, 67 | }, 68 | title_range: Range { 69 | start: Position { 70 | line: 25, 71 | character: 0, 72 | }, 73 | end: Position { 74 | line: 25, 75 | character: 23, 76 | }, 77 | }, 78 | blocks: [ 79 | ChangeBlock { 80 | title_range: Range { 81 | start: Position { 82 | line: 26, 83 | character: 0, 84 | }, 85 | end: Position { 86 | line: 26, 87 | character: 36, 88 | }, 89 | }, 90 | content: "5 pez\n6 pez\n7 pez\n8 pez", 91 | }, 92 | ChangeBlock { 93 | title_range: Range { 94 | start: Position { 95 | line: 35, 96 | character: 0, 97 | }, 98 | end: Position { 99 | line: 35, 100 | character: 27, 101 | }, 102 | }, 103 | content: "5 poisson\n6 poisson\n7 poisson\n8 poisson", 104 | }, 105 | ], 106 | }, 107 | ] 108 | -------------------------------------------------------------------------------- /src/snapshots/jj_lsp__conflict__tests__diff_three_sides.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/conflict.rs 3 | assertion_line: 190 4 | expression: conflicts 5 | --- 6 | [ 7 | Conflict { 8 | range: Range { 9 | start: Position { 10 | line: 5, 11 | character: 0, 12 | }, 13 | end: Position { 14 | line: 32, 15 | character: 28, 16 | }, 17 | }, 18 | title_range: Range { 19 | start: Position { 20 | line: 5, 21 | character: 0, 22 | }, 23 | end: Position { 24 | line: 5, 25 | character: 23, 26 | }, 27 | }, 28 | blocks: [ 29 | ChangeBlock { 30 | title_range: Range { 31 | start: Position { 32 | line: 6, 33 | character: 0, 34 | }, 35 | end: Position { 36 | line: 6, 37 | character: 39, 38 | }, 39 | }, 40 | content: "foo\n1 pez\n2 pez\n3 pez\n4 pez\nbar\nbaz", 41 | }, 42 | ChangeBlock { 43 | title_range: Range { 44 | start: Position { 45 | line: 18, 46 | character: 0, 47 | }, 48 | end: Position { 49 | line: 18, 50 | character: 39, 51 | }, 52 | }, 53 | content: "1 poisson\n2 poisson\n3 poisson\n4 poisson", 54 | }, 55 | ChangeBlock { 56 | title_range: Range { 57 | start: Position { 58 | line: 27, 59 | character: 0, 60 | }, 61 | end: Position { 62 | line: 27, 63 | character: 27, 64 | }, 65 | }, 66 | content: "1 pescare\n2 pescare\n3 pescare\n4 pescare", 67 | }, 68 | ], 69 | }, 70 | Conflict { 71 | range: Range { 72 | start: Position { 73 | line: 34, 74 | character: 0, 75 | }, 76 | end: Position { 77 | line: 58, 78 | character: 28, 79 | }, 80 | }, 81 | title_range: Range { 82 | start: Position { 83 | line: 34, 84 | character: 0, 85 | }, 86 | end: Position { 87 | line: 34, 88 | character: 23, 89 | }, 90 | }, 91 | blocks: [ 92 | ChangeBlock { 93 | title_range: Range { 94 | start: Position { 95 | line: 35, 96 | character: 0, 97 | }, 98 | end: Position { 99 | line: 35, 100 | character: 39, 101 | }, 102 | }, 103 | content: "5 pez\n6 pez\n7 pez\n8 pez", 104 | }, 105 | ChangeBlock { 106 | title_range: Range { 107 | start: Position { 108 | line: 44, 109 | character: 0, 110 | }, 111 | end: Position { 112 | line: 44, 113 | character: 39, 114 | }, 115 | }, 116 | content: "5 poisson\n6 poisson\n7 poisson\n8 poisson", 117 | }, 118 | ChangeBlock { 119 | title_range: Range { 120 | start: Position { 121 | line: 53, 122 | character: 0, 123 | }, 124 | end: Position { 125 | line: 53, 126 | character: 27, 127 | }, 128 | }, 129 | content: "5 pescare\n6 pescare\n7 pescare\n8 pescare", 130 | }, 131 | ], 132 | }, 133 | ] 134 | -------------------------------------------------------------------------------- /src/snapshots/jj_lsp__conflict__tests__diff_four_sides.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/conflict.rs 3 | assertion_line: 199 4 | expression: conflicts 5 | --- 6 | [ 7 | Conflict { 8 | range: Range { 9 | start: Position { 10 | line: 5, 11 | character: 0, 12 | }, 13 | end: Position { 14 | line: 41, 15 | character: 28, 16 | }, 17 | }, 18 | title_range: Range { 19 | start: Position { 20 | line: 5, 21 | character: 0, 22 | }, 23 | end: Position { 24 | line: 5, 25 | character: 23, 26 | }, 27 | }, 28 | blocks: [ 29 | ChangeBlock { 30 | title_range: Range { 31 | start: Position { 32 | line: 6, 33 | character: 0, 34 | }, 35 | end: Position { 36 | line: 6, 37 | character: 39, 38 | }, 39 | }, 40 | content: "foo\n1 pez\n2 pez\n3 pez\n4 pez\nbar\nbaz", 41 | }, 42 | ChangeBlock { 43 | title_range: Range { 44 | start: Position { 45 | line: 18, 46 | character: 0, 47 | }, 48 | end: Position { 49 | line: 18, 50 | character: 39, 51 | }, 52 | }, 53 | content: "1 poisson\n2 poisson\n3 poisson\n4 poisson", 54 | }, 55 | ChangeBlock { 56 | title_range: Range { 57 | start: Position { 58 | line: 27, 59 | character: 0, 60 | }, 61 | end: Position { 62 | line: 27, 63 | character: 27, 64 | }, 65 | }, 66 | content: "1 pescare\n2 pescare\n3 pescare\n4 pescare", 67 | }, 68 | ChangeBlock { 69 | title_range: Range { 70 | start: Position { 71 | line: 32, 72 | character: 0, 73 | }, 74 | end: Position { 75 | line: 32, 76 | character: 39, 77 | }, 78 | }, 79 | content: "1 fisch\n2 fisch\n3 fisch\n4 fisch", 80 | }, 81 | ], 82 | }, 83 | Conflict { 84 | range: Range { 85 | start: Position { 86 | line: 43, 87 | character: 0, 88 | }, 89 | end: Position { 90 | line: 76, 91 | character: 28, 92 | }, 93 | }, 94 | title_range: Range { 95 | start: Position { 96 | line: 43, 97 | character: 0, 98 | }, 99 | end: Position { 100 | line: 43, 101 | character: 23, 102 | }, 103 | }, 104 | blocks: [ 105 | ChangeBlock { 106 | title_range: Range { 107 | start: Position { 108 | line: 44, 109 | character: 0, 110 | }, 111 | end: Position { 112 | line: 44, 113 | character: 39, 114 | }, 115 | }, 116 | content: "5 pez\n6 pez\n7 pez\n8 pez", 117 | }, 118 | ChangeBlock { 119 | title_range: Range { 120 | start: Position { 121 | line: 53, 122 | character: 0, 123 | }, 124 | end: Position { 125 | line: 53, 126 | character: 39, 127 | }, 128 | }, 129 | content: "5 poisson\n6 poisson\n7 poisson\n8 poisson", 130 | }, 131 | ChangeBlock { 132 | title_range: Range { 133 | start: Position { 134 | line: 62, 135 | character: 0, 136 | }, 137 | end: Position { 138 | line: 62, 139 | character: 27, 140 | }, 141 | }, 142 | content: "5 pescare\n6 pescare\n7 pescare\n8 pescare", 143 | }, 144 | ChangeBlock { 145 | title_range: Range { 146 | start: Position { 147 | line: 67, 148 | character: 0, 149 | }, 150 | end: Position { 151 | line: 67, 152 | character: 39, 153 | }, 154 | }, 155 | content: "5 fisch\n6 fisch\n7 fisch\n8 fisch", 156 | }, 157 | ], 158 | }, 159 | ] 160 | -------------------------------------------------------------------------------- /src/conflict.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{ChangeBlock, Conflict}; 2 | use regex::Regex; 3 | use std::str::Lines; 4 | use tower_lsp_server::lsp_types::{Position, Range}; 5 | 6 | use crate::utils::get_utf16_len; 7 | 8 | lazy_static::lazy_static! { 9 | static ref DIFF_CONFLICT_START_REGEX: Regex = 10 | Regex::new(r"^<<<<<<< Conflict \d+ of \d+$").unwrap(); 11 | static ref DIFF_CONFLICT_END_REGEX: Regex = 12 | Regex::new(r"^>>>>>>> Conflict \d+ of \d+ ends$").unwrap(); 13 | static ref DIFF_CHANGE_HEADER_REGEX: Regex = 14 | Regex::new(r"^%%%%%%% Changes from (base(?: #\d+)? to )?side #\d+$").unwrap(); 15 | static ref DIFF_CONTENTS_HEADER_REGEX: Regex = 16 | Regex::new(r"^\+{7} Contents of side #\d+$").unwrap(); 17 | } 18 | 19 | pub struct Analyzer<'a> { 20 | conflicts: Vec, 21 | lines: Lines<'a>, 22 | cur_line: Option<&'a str>, 23 | next_line: Option<&'a str>, 24 | cur_line_number: u32, 25 | } 26 | 27 | impl<'a> Analyzer<'a> { 28 | pub fn new(content: &'a str) -> Self { 29 | let mut lines = content.lines(); 30 | let cur_line = lines.next(); 31 | let next_line = lines.next(); 32 | 33 | Analyzer { 34 | conflicts: vec![], 35 | lines, 36 | cur_line, 37 | next_line, 38 | cur_line_number: 0, 39 | } 40 | } 41 | 42 | pub fn find_conflicts(&'a mut self) -> &'a Vec { 43 | while let Some(line) = self.next() { 44 | if DIFF_CONFLICT_START_REGEX.is_match(line) { 45 | self.parse_diff_marker(); 46 | } 47 | } 48 | &self.conflicts 49 | } 50 | 51 | fn parse_diff_marker(&mut self) { 52 | let title_range = self.get_range_of_current_line().unwrap(); 53 | self.next(); 54 | let mut blocks = vec![]; 55 | 56 | while let Some(cur_line) = self.cur_line { 57 | if DIFF_CHANGE_HEADER_REGEX.is_match(cur_line) { 58 | match self.parse_change_block() { 59 | Some(block) => blocks.push(block), 60 | None => return, 61 | } 62 | } else if DIFF_CONTENTS_HEADER_REGEX.is_match(cur_line) { 63 | match self.parse_contents_block() { 64 | Some(block) => { 65 | blocks.push(block); 66 | } 67 | None => return, 68 | } 69 | } else { 70 | break; 71 | } 72 | } 73 | 74 | if let Some(cur_line) = self.cur_line { 75 | let end_position = Position::new(self.cur_line_number, get_utf16_len(cur_line)); 76 | 77 | let conflict = Conflict { 78 | range: Range::new(title_range.start, end_position), 79 | title_range, 80 | blocks, 81 | }; 82 | self.conflicts.push(conflict); 83 | } 84 | } 85 | 86 | fn parse_change_block(&mut self) -> Option { 87 | let title_range = self.get_range_of_current_line()?; 88 | self.next(); 89 | 90 | let mut content = String::new(); 91 | let mut next_line = self.cur_line?; 92 | 93 | while !is_known_pattern(next_line) { 94 | if next_line.starts_with("-") { 95 | next_line = self.next()?; 96 | continue; 97 | } 98 | 99 | if !content.is_empty() { 100 | content.push('\n'); 101 | } 102 | 103 | if let Some(line_content) = next_line.strip_prefix("+") { 104 | content.push_str(line_content); 105 | } else { 106 | content.push_str(next_line); 107 | } 108 | 109 | next_line = self.next()?; 110 | } 111 | 112 | let block = ChangeBlock { 113 | title_range, 114 | content, 115 | }; 116 | 117 | Some(block) 118 | } 119 | 120 | fn parse_contents_block(&mut self) -> Option { 121 | let title_range = self.get_range_of_current_line()?; 122 | 123 | self.next()?; 124 | 125 | let mut content = String::new(); 126 | let mut next_line = self.cur_line?; 127 | 128 | while !is_known_pattern(next_line) { 129 | if !content.is_empty() { 130 | content.push('\n'); 131 | } 132 | content.push_str(next_line); 133 | next_line = self.next()?; 134 | } 135 | 136 | let block = ChangeBlock { 137 | title_range, 138 | content, 139 | }; 140 | 141 | Some(block) 142 | } 143 | 144 | fn next(&mut self) -> Option<&'a str> { 145 | self.cur_line = self.next_line; 146 | self.next_line = self.lines.next(); 147 | 148 | if self.cur_line.is_some() { 149 | self.cur_line_number += 1; 150 | } 151 | 152 | self.cur_line 153 | } 154 | 155 | fn get_range_of_current_line(&self) -> Option { 156 | Some(Range { 157 | start: Position::new(self.cur_line_number, 0), 158 | end: Position::new(self.cur_line_number, get_utf16_len(self.cur_line?)), 159 | }) 160 | } 161 | } 162 | 163 | fn is_known_pattern(content: &str) -> bool { 164 | DIFF_CHANGE_HEADER_REGEX.is_match(content) 165 | || DIFF_CONTENTS_HEADER_REGEX.is_match(content) 166 | || DIFF_CONFLICT_END_REGEX.is_match(content) 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use super::*; 172 | use insta::assert_debug_snapshot; 173 | use std::fs; 174 | 175 | #[test] 176 | fn test_diff_two_sides() { 177 | let content = fs::read_to_string("tests/conflicts/diff/two_sides.md") 178 | .expect("Failed to read input file"); 179 | let mut analyzer = Analyzer::new(&content); 180 | let conflicts = analyzer.find_conflicts(); 181 | assert_debug_snapshot!(conflicts); 182 | } 183 | 184 | #[test] 185 | fn test_diff_three_sides() { 186 | let content = fs::read_to_string("tests/conflicts/diff/three_sides.md") 187 | .expect("Failed to read input file"); 188 | let mut analyzer = Analyzer::new(&content); 189 | let conflicts = analyzer.find_conflicts(); 190 | assert_debug_snapshot!(conflicts); 191 | } 192 | 193 | #[test] 194 | fn test_diff_four_sides() { 195 | let content = fs::read_to_string("tests/conflicts/diff/four_sides.md") 196 | .expect("Failed to read input file"); 197 | let mut analyzer = Analyzer::new(&content); 198 | let conflicts = analyzer.find_conflicts(); 199 | assert_debug_snapshot!(conflicts); 200 | } 201 | 202 | #[test] 203 | fn test_regex_patterns() { 204 | let tests = [ 205 | (DIFF_CONFLICT_START_REGEX.clone(), "<<<<<<< Conflict 1 of 2"), 206 | ( 207 | DIFF_CONFLICT_END_REGEX.clone(), 208 | ">>>>>>> Conflict 2 of 2 ends", 209 | ), 210 | ( 211 | DIFF_CHANGE_HEADER_REGEX.clone(), 212 | "%%%%%%% Changes from base to side #1", 213 | ), 214 | ( 215 | DIFF_CHANGE_HEADER_REGEX.clone(), 216 | "%%%%%%% Changes from base #1 to side #1", 217 | ), 218 | ( 219 | DIFF_CONTENTS_HEADER_REGEX.clone(), 220 | "+++++++ Contents of side #2", 221 | ), 222 | ]; 223 | 224 | for (regex_pattern, haystack) in tests { 225 | assert!(regex_pattern.is_match(haystack)) 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/backend.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ops::Deref; 3 | use std::sync::Arc; 4 | 5 | use tokio::sync::Mutex; 6 | use tower_lsp_server::jsonrpc::Result; 7 | use tower_lsp_server::lsp_types::*; 8 | use tower_lsp_server::{Client, LanguageServer}; 9 | 10 | use crate::conflict::Analyzer; 11 | use crate::types::Conflict; 12 | 13 | pub struct Backend { 14 | inner: Arc, 15 | } 16 | 17 | impl Deref for Backend { 18 | type Target = BackendInner; 19 | 20 | fn deref(&self) -> &Self::Target { 21 | &self.inner 22 | } 23 | } 24 | 25 | pub struct BackendInner { 26 | client: Client, 27 | diagnostics_and_code_actions: Mutex>>, 28 | client_capabilities: Mutex, 29 | } 30 | 31 | impl Backend { 32 | pub fn new(client: Client) -> Self { 33 | Self { 34 | inner: Arc::new(BackendInner { 35 | client, 36 | diagnostics_and_code_actions: Default::default(), 37 | client_capabilities: Mutex::new(ClientCapabilities::default()), 38 | }), 39 | } 40 | } 41 | 42 | pub fn get_diagnostics_and_code_actions_from_conflicts( 43 | &self, 44 | conflicts: &[Conflict], 45 | uri: &Uri, 46 | ) -> Vec<(Diagnostic, CodeActionResponse)> { 47 | conflicts 48 | .iter() 49 | .flat_map(|conflict| { 50 | let header_diagnostic = Diagnostic { 51 | message: "Found conflicting changes".to_string(), 52 | range: conflict.title_range, 53 | severity: Some(DiagnosticSeverity::ERROR), 54 | ..Default::default() 55 | }; 56 | 57 | let block_diagnostics_and_code_actions = 58 | conflict 59 | .blocks 60 | .iter() 61 | .enumerate() 62 | .map(|(idx, conflict_block)| { 63 | let diagnostic = Diagnostic { 64 | message: "Conflicting change".to_string(), 65 | range: conflict_block.title_range, 66 | severity: Some(DiagnosticSeverity::INFORMATION), 67 | ..Default::default() 68 | }; 69 | 70 | let text_edit = TextEdit { 71 | new_text: conflict_block.content.clone(), 72 | range: conflict.range, 73 | }; 74 | 75 | let workspace_edit = WorkspaceEdit { 76 | changes: Some([(uri.clone(), vec![text_edit])].into()), 77 | ..Default::default() 78 | }; 79 | 80 | let code_actions = vec![CodeActionOrCommand::CodeAction(CodeAction { 81 | title: format!("Accept change #{}", idx + 1), 82 | diagnostics: Some(vec![diagnostic.clone()]), 83 | edit: Some(workspace_edit), 84 | ..Default::default() 85 | })]; 86 | 87 | (diagnostic, code_actions) 88 | }); 89 | 90 | let block_code_actions = block_diagnostics_and_code_actions 91 | .clone() 92 | .flat_map(|(_, code_actions)| code_actions) 93 | .collect::>(); 94 | 95 | // Create "Accept all changes" action that combines all conflict blocks 96 | let combined_content = conflict 97 | .blocks 98 | .iter() 99 | .map(|block| block.content.as_str()) 100 | .collect::>() 101 | .join(""); 102 | 103 | let accept_all_edit = TextEdit { 104 | new_text: combined_content, 105 | range: conflict.range, 106 | }; 107 | 108 | let accept_all_workspace_edit = WorkspaceEdit { 109 | changes: Some([(uri.clone(), vec![accept_all_edit])].into()), 110 | ..Default::default() 111 | }; 112 | 113 | let accept_all_action = CodeActionOrCommand::CodeAction(CodeAction { 114 | title: "Accept all changes".to_string(), 115 | diagnostics: Some(vec![header_diagnostic.clone()]), 116 | edit: Some(accept_all_workspace_edit), 117 | ..Default::default() 118 | }); 119 | 120 | let mut header_code_actions = vec![accept_all_action]; 121 | header_code_actions.extend(block_code_actions); 122 | 123 | block_diagnostics_and_code_actions 124 | .chain(std::iter::once((header_diagnostic, header_code_actions))) 125 | }) 126 | .collect() 127 | } 128 | } 129 | 130 | impl LanguageServer for Backend { 131 | async fn initialize(&self, params: InitializeParams) -> Result { 132 | *self.client_capabilities.lock().await = params.capabilities; 133 | 134 | Ok(InitializeResult { 135 | capabilities: ServerCapabilities { 136 | text_document_sync: Some(TextDocumentSyncCapability::Kind( 137 | TextDocumentSyncKind::FULL, 138 | )), 139 | diagnostic_provider: Some(DiagnosticServerCapabilities::Options( 140 | DiagnosticOptions { 141 | identifier: Some("jj".to_string()), 142 | inter_file_dependencies: false, 143 | workspace_diagnostics: false, 144 | work_done_progress_options: Default::default(), 145 | }, 146 | )), 147 | code_action_provider: Some(CodeActionProviderCapability::Simple(true)), 148 | ..Default::default() 149 | }, 150 | ..InitializeResult::default() 151 | }) 152 | } 153 | 154 | async fn did_open(&self, params: DidOpenTextDocumentParams) { 155 | let mut analyzer = Analyzer::new(¶ms.text_document.text); 156 | let conflicts = analyzer.find_conflicts(); 157 | let diagnostics_and_code_actions = self 158 | .get_diagnostics_and_code_actions_from_conflicts(conflicts, ¶ms.text_document.uri); 159 | 160 | let diagnostics = diagnostics_and_code_actions 161 | .iter() 162 | .map(|(diagnostic, _)| diagnostic.clone()) 163 | .collect::>(); 164 | 165 | self.diagnostics_and_code_actions.lock().await.insert( 166 | params.text_document.uri.clone(), 167 | diagnostics_and_code_actions, 168 | ); 169 | 170 | let capabilities = self.client_capabilities.lock().await; 171 | if capabilities 172 | .text_document 173 | .as_ref() 174 | .and_then(|td| td.diagnostic.as_ref()) 175 | .is_some() 176 | { 177 | self.client 178 | .publish_diagnostics(params.text_document.uri, diagnostics, None) 179 | .await; 180 | } 181 | } 182 | 183 | async fn did_change(&self, params: DidChangeTextDocumentParams) { 184 | let Some(content) = params.content_changes.first() else { 185 | // panic here, because the LSP won't work if the LSP client implements this incorrectly 186 | panic!("LSP client is supposed to always send the complete file contents."); 187 | }; 188 | 189 | let mut analyzer = Analyzer::new(&content.text); 190 | let conflicts = analyzer.find_conflicts(); 191 | let diagnostics_and_code_actions = self 192 | .get_diagnostics_and_code_actions_from_conflicts(conflicts, ¶ms.text_document.uri); 193 | 194 | let diagnostics = diagnostics_and_code_actions 195 | .iter() 196 | .map(|(diagnostic, _)| diagnostic.clone()) 197 | .collect::>(); 198 | 199 | let mut diagnostics_map = self.diagnostics_and_code_actions.lock().await; 200 | let uri_clone = params.text_document.uri.clone(); 201 | 202 | if diagnostics_map.get(&uri_clone) != Some(&diagnostics_and_code_actions) { 203 | diagnostics_map.insert(uri_clone, diagnostics_and_code_actions); 204 | 205 | let capabilities = self.client_capabilities.lock().await; 206 | if capabilities 207 | .text_document 208 | .as_ref() 209 | .and_then(|td| td.diagnostic.as_ref()) 210 | .is_some() 211 | { 212 | self.client 213 | .publish_diagnostics(params.text_document.uri, diagnostics, None) 214 | .await; 215 | } 216 | } 217 | } 218 | 219 | async fn code_action(&self, params: CodeActionParams) -> Result> { 220 | let capabilities = self.client_capabilities.lock().await; 221 | if capabilities 222 | .text_document 223 | .as_ref() 224 | .and_then(|td| td.code_action.as_ref()) 225 | .is_none() 226 | { 227 | return Ok(None); 228 | } 229 | 230 | let Some(diagnostics_and_code_actions) = self 231 | .diagnostics_and_code_actions 232 | .lock() 233 | .await 234 | .get(¶ms.text_document.uri) 235 | .cloned() 236 | else { 237 | return Ok(None); 238 | }; 239 | 240 | let code_actions = diagnostics_and_code_actions 241 | .into_iter() 242 | .filter_map(|(diagnostic, code_actions)| { 243 | // NOTE: there are only very few diagnostics per file, so it's ok to iterate here 244 | // instead of using a hash map; hashing will be more expensive than iterating in 245 | // most cases 246 | if params.context.diagnostics.iter().any(|x| x == &diagnostic) { 247 | Some(code_actions) 248 | } else { 249 | None 250 | } 251 | }) 252 | .flatten() 253 | .collect::>(); 254 | 255 | Ok(Some(code_actions)) 256 | } 257 | 258 | async fn shutdown(&self) -> Result<()> { 259 | Ok(()) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "1.4.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "1.3.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 25 | 26 | [[package]] 27 | name = "bitflags" 28 | version = "2.9.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 31 | 32 | [[package]] 33 | name = "bytes" 34 | version = "1.10.1" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 37 | 38 | [[package]] 39 | name = "cfg-if" 40 | version = "1.0.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 43 | 44 | [[package]] 45 | name = "console" 46 | version = "0.15.11" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 49 | dependencies = [ 50 | "encode_unicode", 51 | "libc", 52 | "once_cell", 53 | "windows-sys 0.59.0", 54 | ] 55 | 56 | [[package]] 57 | name = "crossbeam-utils" 58 | version = "0.8.21" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 61 | 62 | [[package]] 63 | name = "dashmap" 64 | version = "6.1.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 67 | dependencies = [ 68 | "cfg-if", 69 | "crossbeam-utils", 70 | "hashbrown", 71 | "lock_api", 72 | "once_cell", 73 | "parking_lot_core", 74 | ] 75 | 76 | [[package]] 77 | name = "encode_unicode" 78 | version = "1.0.0" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 81 | 82 | [[package]] 83 | name = "fluent-uri" 84 | version = "0.1.4" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" 87 | dependencies = [ 88 | "bitflags 1.3.2", 89 | ] 90 | 91 | [[package]] 92 | name = "futures" 93 | version = "0.3.31" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 96 | dependencies = [ 97 | "futures-channel", 98 | "futures-core", 99 | "futures-io", 100 | "futures-sink", 101 | "futures-task", 102 | "futures-util", 103 | ] 104 | 105 | [[package]] 106 | name = "futures-channel" 107 | version = "0.3.31" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 110 | dependencies = [ 111 | "futures-core", 112 | "futures-sink", 113 | ] 114 | 115 | [[package]] 116 | name = "futures-core" 117 | version = "0.3.31" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 120 | 121 | [[package]] 122 | name = "futures-io" 123 | version = "0.3.31" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 126 | 127 | [[package]] 128 | name = "futures-macro" 129 | version = "0.3.31" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 132 | dependencies = [ 133 | "proc-macro2", 134 | "quote", 135 | "syn", 136 | ] 137 | 138 | [[package]] 139 | name = "futures-sink" 140 | version = "0.3.31" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 143 | 144 | [[package]] 145 | name = "futures-task" 146 | version = "0.3.31" 147 | source = "registry+https://github.com/rust-lang/crates.io-index" 148 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 149 | 150 | [[package]] 151 | name = "futures-util" 152 | version = "0.3.31" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 155 | dependencies = [ 156 | "futures-channel", 157 | "futures-core", 158 | "futures-io", 159 | "futures-macro", 160 | "futures-sink", 161 | "futures-task", 162 | "memchr", 163 | "pin-project-lite", 164 | "pin-utils", 165 | "slab", 166 | ] 167 | 168 | [[package]] 169 | name = "hashbrown" 170 | version = "0.14.5" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 173 | 174 | [[package]] 175 | name = "httparse" 176 | version = "1.10.1" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 179 | 180 | [[package]] 181 | name = "insta" 182 | version = "1.43.1" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" 185 | dependencies = [ 186 | "console", 187 | "once_cell", 188 | "similar", 189 | ] 190 | 191 | [[package]] 192 | name = "itoa" 193 | version = "1.0.15" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 196 | 197 | [[package]] 198 | name = "jj-lsp" 199 | version = "0.1.1-dev1" 200 | dependencies = [ 201 | "insta", 202 | "lazy_static", 203 | "regex", 204 | "tokio", 205 | "tower-lsp-server", 206 | ] 207 | 208 | [[package]] 209 | name = "lazy_static" 210 | version = "1.5.0" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 213 | 214 | [[package]] 215 | name = "libc" 216 | version = "0.2.177" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 219 | 220 | [[package]] 221 | name = "lock_api" 222 | version = "0.4.12" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 225 | dependencies = [ 226 | "autocfg", 227 | "scopeguard", 228 | ] 229 | 230 | [[package]] 231 | name = "lsp-types" 232 | version = "0.97.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "53353550a17c04ac46c585feb189c2db82154fc84b79c7a66c96c2c644f66071" 235 | dependencies = [ 236 | "bitflags 1.3.2", 237 | "fluent-uri", 238 | "serde", 239 | "serde_json", 240 | "serde_repr", 241 | ] 242 | 243 | [[package]] 244 | name = "memchr" 245 | version = "2.7.4" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 248 | 249 | [[package]] 250 | name = "mio" 251 | version = "1.0.3" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 254 | dependencies = [ 255 | "libc", 256 | "wasi", 257 | "windows-sys 0.52.0", 258 | ] 259 | 260 | [[package]] 261 | name = "once_cell" 262 | version = "1.21.3" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 265 | 266 | [[package]] 267 | name = "parking_lot" 268 | version = "0.12.3" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 271 | dependencies = [ 272 | "lock_api", 273 | "parking_lot_core", 274 | ] 275 | 276 | [[package]] 277 | name = "parking_lot_core" 278 | version = "0.9.10" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 281 | dependencies = [ 282 | "cfg-if", 283 | "libc", 284 | "redox_syscall", 285 | "smallvec", 286 | "windows-targets 0.52.6", 287 | ] 288 | 289 | [[package]] 290 | name = "percent-encoding" 291 | version = "2.3.1" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 294 | 295 | [[package]] 296 | name = "pin-project-lite" 297 | version = "0.2.16" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 300 | 301 | [[package]] 302 | name = "pin-utils" 303 | version = "0.1.0" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 306 | 307 | [[package]] 308 | name = "proc-macro2" 309 | version = "1.0.94" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 312 | dependencies = [ 313 | "unicode-ident", 314 | ] 315 | 316 | [[package]] 317 | name = "quote" 318 | version = "1.0.40" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 321 | dependencies = [ 322 | "proc-macro2", 323 | ] 324 | 325 | [[package]] 326 | name = "redox_syscall" 327 | version = "0.5.10" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" 330 | dependencies = [ 331 | "bitflags 2.9.0", 332 | ] 333 | 334 | [[package]] 335 | name = "regex" 336 | version = "1.11.1" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 339 | dependencies = [ 340 | "aho-corasick", 341 | "memchr", 342 | "regex-automata", 343 | "regex-syntax", 344 | ] 345 | 346 | [[package]] 347 | name = "regex-automata" 348 | version = "0.4.9" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 351 | dependencies = [ 352 | "aho-corasick", 353 | "memchr", 354 | "regex-syntax", 355 | ] 356 | 357 | [[package]] 358 | name = "regex-syntax" 359 | version = "0.8.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 362 | 363 | [[package]] 364 | name = "ryu" 365 | version = "1.0.20" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 368 | 369 | [[package]] 370 | name = "scopeguard" 371 | version = "1.2.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 374 | 375 | [[package]] 376 | name = "serde" 377 | version = "1.0.219" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 380 | dependencies = [ 381 | "serde_derive", 382 | ] 383 | 384 | [[package]] 385 | name = "serde_derive" 386 | version = "1.0.219" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 389 | dependencies = [ 390 | "proc-macro2", 391 | "quote", 392 | "syn", 393 | ] 394 | 395 | [[package]] 396 | name = "serde_json" 397 | version = "1.0.140" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 400 | dependencies = [ 401 | "itoa", 402 | "memchr", 403 | "ryu", 404 | "serde", 405 | ] 406 | 407 | [[package]] 408 | name = "serde_repr" 409 | version = "0.1.20" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 412 | dependencies = [ 413 | "proc-macro2", 414 | "quote", 415 | "syn", 416 | ] 417 | 418 | [[package]] 419 | name = "signal-hook-registry" 420 | version = "1.4.2" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 423 | dependencies = [ 424 | "libc", 425 | ] 426 | 427 | [[package]] 428 | name = "similar" 429 | version = "2.7.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" 432 | 433 | [[package]] 434 | name = "slab" 435 | version = "0.4.9" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 438 | dependencies = [ 439 | "autocfg", 440 | ] 441 | 442 | [[package]] 443 | name = "smallvec" 444 | version = "1.14.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 447 | 448 | [[package]] 449 | name = "socket2" 450 | version = "0.6.1" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 453 | dependencies = [ 454 | "libc", 455 | "windows-sys 0.60.2", 456 | ] 457 | 458 | [[package]] 459 | name = "syn" 460 | version = "2.0.100" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 463 | dependencies = [ 464 | "proc-macro2", 465 | "quote", 466 | "unicode-ident", 467 | ] 468 | 469 | [[package]] 470 | name = "sync_wrapper" 471 | version = "1.0.2" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 474 | 475 | [[package]] 476 | name = "tokio" 477 | version = "1.48.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 480 | dependencies = [ 481 | "bytes", 482 | "libc", 483 | "mio", 484 | "parking_lot", 485 | "pin-project-lite", 486 | "signal-hook-registry", 487 | "socket2", 488 | "tokio-macros", 489 | "windows-sys 0.61.2", 490 | ] 491 | 492 | [[package]] 493 | name = "tokio-macros" 494 | version = "2.6.0" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 497 | dependencies = [ 498 | "proc-macro2", 499 | "quote", 500 | "syn", 501 | ] 502 | 503 | [[package]] 504 | name = "tokio-util" 505 | version = "0.7.14" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" 508 | dependencies = [ 509 | "bytes", 510 | "futures-core", 511 | "futures-sink", 512 | "pin-project-lite", 513 | "tokio", 514 | ] 515 | 516 | [[package]] 517 | name = "tower" 518 | version = "0.5.2" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 521 | dependencies = [ 522 | "futures-core", 523 | "futures-util", 524 | "pin-project-lite", 525 | "sync_wrapper", 526 | "tower-layer", 527 | "tower-service", 528 | ] 529 | 530 | [[package]] 531 | name = "tower-layer" 532 | version = "0.3.3" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 535 | 536 | [[package]] 537 | name = "tower-lsp-server" 538 | version = "0.22.0" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "76cd168c085174eafa7492a519715f2d59436dc28cdfd9d13a5b864246899db9" 541 | dependencies = [ 542 | "bytes", 543 | "dashmap", 544 | "futures", 545 | "httparse", 546 | "lsp-types", 547 | "memchr", 548 | "percent-encoding", 549 | "serde", 550 | "serde_json", 551 | "tokio", 552 | "tokio-util", 553 | "tower", 554 | "tracing", 555 | ] 556 | 557 | [[package]] 558 | name = "tower-service" 559 | version = "0.3.3" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 562 | 563 | [[package]] 564 | name = "tracing" 565 | version = "0.1.41" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 568 | dependencies = [ 569 | "pin-project-lite", 570 | "tracing-attributes", 571 | "tracing-core", 572 | ] 573 | 574 | [[package]] 575 | name = "tracing-attributes" 576 | version = "0.1.28" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 579 | dependencies = [ 580 | "proc-macro2", 581 | "quote", 582 | "syn", 583 | ] 584 | 585 | [[package]] 586 | name = "tracing-core" 587 | version = "0.1.33" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 590 | dependencies = [ 591 | "once_cell", 592 | ] 593 | 594 | [[package]] 595 | name = "unicode-ident" 596 | version = "1.0.18" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 599 | 600 | [[package]] 601 | name = "wasi" 602 | version = "0.11.0+wasi-snapshot-preview1" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 605 | 606 | [[package]] 607 | name = "windows-link" 608 | version = "0.2.1" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 611 | 612 | [[package]] 613 | name = "windows-sys" 614 | version = "0.52.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 617 | dependencies = [ 618 | "windows-targets 0.52.6", 619 | ] 620 | 621 | [[package]] 622 | name = "windows-sys" 623 | version = "0.59.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 626 | dependencies = [ 627 | "windows-targets 0.52.6", 628 | ] 629 | 630 | [[package]] 631 | name = "windows-sys" 632 | version = "0.60.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 635 | dependencies = [ 636 | "windows-targets 0.53.5", 637 | ] 638 | 639 | [[package]] 640 | name = "windows-sys" 641 | version = "0.61.2" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 644 | dependencies = [ 645 | "windows-link", 646 | ] 647 | 648 | [[package]] 649 | name = "windows-targets" 650 | version = "0.52.6" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 653 | dependencies = [ 654 | "windows_aarch64_gnullvm 0.52.6", 655 | "windows_aarch64_msvc 0.52.6", 656 | "windows_i686_gnu 0.52.6", 657 | "windows_i686_gnullvm 0.52.6", 658 | "windows_i686_msvc 0.52.6", 659 | "windows_x86_64_gnu 0.52.6", 660 | "windows_x86_64_gnullvm 0.52.6", 661 | "windows_x86_64_msvc 0.52.6", 662 | ] 663 | 664 | [[package]] 665 | name = "windows-targets" 666 | version = "0.53.5" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 669 | dependencies = [ 670 | "windows-link", 671 | "windows_aarch64_gnullvm 0.53.1", 672 | "windows_aarch64_msvc 0.53.1", 673 | "windows_i686_gnu 0.53.1", 674 | "windows_i686_gnullvm 0.53.1", 675 | "windows_i686_msvc 0.53.1", 676 | "windows_x86_64_gnu 0.53.1", 677 | "windows_x86_64_gnullvm 0.53.1", 678 | "windows_x86_64_msvc 0.53.1", 679 | ] 680 | 681 | [[package]] 682 | name = "windows_aarch64_gnullvm" 683 | version = "0.52.6" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 686 | 687 | [[package]] 688 | name = "windows_aarch64_gnullvm" 689 | version = "0.53.1" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 692 | 693 | [[package]] 694 | name = "windows_aarch64_msvc" 695 | version = "0.52.6" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 698 | 699 | [[package]] 700 | name = "windows_aarch64_msvc" 701 | version = "0.53.1" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 704 | 705 | [[package]] 706 | name = "windows_i686_gnu" 707 | version = "0.52.6" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 710 | 711 | [[package]] 712 | name = "windows_i686_gnu" 713 | version = "0.53.1" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 716 | 717 | [[package]] 718 | name = "windows_i686_gnullvm" 719 | version = "0.52.6" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 722 | 723 | [[package]] 724 | name = "windows_i686_gnullvm" 725 | version = "0.53.1" 726 | source = "registry+https://github.com/rust-lang/crates.io-index" 727 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 728 | 729 | [[package]] 730 | name = "windows_i686_msvc" 731 | version = "0.52.6" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 734 | 735 | [[package]] 736 | name = "windows_i686_msvc" 737 | version = "0.53.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 740 | 741 | [[package]] 742 | name = "windows_x86_64_gnu" 743 | version = "0.52.6" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 746 | 747 | [[package]] 748 | name = "windows_x86_64_gnu" 749 | version = "0.53.1" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 752 | 753 | [[package]] 754 | name = "windows_x86_64_gnullvm" 755 | version = "0.52.6" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 758 | 759 | [[package]] 760 | name = "windows_x86_64_gnullvm" 761 | version = "0.53.1" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 764 | 765 | [[package]] 766 | name = "windows_x86_64_msvc" 767 | version = "0.52.6" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 770 | 771 | [[package]] 772 | name = "windows_x86_64_msvc" 773 | version = "0.53.1" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 776 | --------------------------------------------------------------------------------