# Conflicts: # crates/gpui/src/window.rs
Unified · Split
| @@ -1,15 +0,0 @@ | |||
| 1 | name: 'Check formatting' | ||
| 2 | description: 'Checks code formatting use cargo fmt' | ||
| 3 | |||
| 4 | runs: | ||
| 5 | using: "composite" | ||
| 6 | steps: | ||
| 7 | - name: Install Rust | ||
| 8 | shell: bash -euxo pipefail {0} | ||
| 9 | run: | | ||
| 10 | rustup set profile minimal | ||
| 11 | rustup update stable | ||
| 12 | |||
| 13 | - name: cargo fmt | ||
| 14 | shell: bash -euxo pipefail {0} | ||
| 15 | run: cargo fmt --all -- --check | ||
| @@ -0,0 +1,17 @@ | |||
| 1 | name: "Check formatting" | ||
| 2 | description: "Checks code formatting use cargo fmt" | ||
| 3 | |||
| 4 | runs: | ||
| 5 | using: "composite" | ||
| 6 | steps: | ||
| 7 | - name: cargo fmt | ||
| 8 | shell: bash -euxo pipefail {0} | ||
| 9 | run: cargo fmt --all -- --check | ||
| 10 | |||
| 11 | - name: cargo clippy | ||
| 12 | shell: bash -euxo pipefail {0} | ||
| 13 | # clippy.toml is not currently supporting specifying allowed lints | ||
| 14 | # so specify those here, and disable the rest until Zed's workspace | ||
| 15 | # will have more fixes & suppression for the standard lint set | ||
| 16 | run: | | ||
| 17 | cargo clippy --workspace --all-features --all-targets -- -A clippy::all -D clippy::dbg_macro -D clippy::todo | ||
| @@ -2,29 +2,26 @@ name: "Run tests" · 1 unmodified line | |||
| 2 | description: "Runs the tests" | 2 | description: "Runs the tests" |
| 3 | 3 | ||
| 4 | runs: | 4 | runs: |
| 5 | using: "composite" | 5 | using: "composite" |
| 6 | steps: | 6 | steps: |
| 7 | - name: Install Rust | 7 | - name: Install Rust |
| 8 | shell: bash -euxo pipefail {0} | 8 | shell: bash -euxo pipefail {0} |
| 9 | run: | | 9 | run: | |
| 10 | rustup set profile minimal | 10 | cargo install cargo-nextest |
| 11 | rustup update stable | ||
| 12 | rustup target add wasm32-wasi | ||
| 13 | cargo install cargo-nextest | ||
| 14 | 11 | ||
| 15 | - name: Install Node | 12 | - name: Install Node |
| 16 | uses: actions/setup-node@v3 | 13 | uses: actions/setup-node@v3 |
| 17 | with: | 14 | with: |
| 18 | node-version: "18" | 15 | node-version: "18" |
| 19 | 16 | ||
| 20 | - name: Limit target directory size | 17 | - name: Limit target directory size |
| 21 | shell: bash -euxo pipefail {0} | 18 | shell: bash -euxo pipefail {0} |
| 22 | run: script/clear-target-dir-if-larger-than 100 | 19 | run: script/clear-target-dir-if-larger-than 100 |
| 23 | 20 | ||
| 24 | - name: Run check | 21 | - name: Run check |
| 25 | shell: bash -euxo pipefail {0} | 22 | shell: bash -euxo pipefail {0} |
| 26 | run: cargo check --tests --workspace | 23 | run: cargo check --tests --workspace |
| 27 | 24 | ||
| 28 | - name: Run tests | 25 | - name: Run tests |
| 29 | shell: bash -euxo pipefail {0} | 26 | shell: bash -euxo pipefail {0} |
| 30 | run: cargo nextest run --workspace --no-fail-fast | 27 | run: cargo nextest run --workspace --no-fail-fast |
| @@ -22,8 +22,8 @@ env: · 21 unmodified lines | |||
| 22 | RUST_BACKTRACE: 1 | 22 | RUST_BACKTRACE: 1 |
| 23 | 23 | ||
| 24 | jobs: | 24 | jobs: |
| 25 | rustfmt: | 25 | style: |
| 26 | name: Check formatting | 26 | name: Check formatting and Clippy lints |
| 27 | runs-on: | 27 | runs-on: |
| 28 | - self-hosted | 28 | - self-hosted |
| 29 | - test | 29 | - test |
| @@ -33,19 +33,20 @@ jobs: · 3 unmodified lines | |||
| 33 | with: | 33 | with: |
| 34 | clean: false | 34 | clean: false |
| 35 | submodules: "recursive" | 35 | submodules: "recursive" |
| 36 | fetch-depth: 0 | ||
| 36 | 37 | ||
| 37 | - name: Set up default .cargo/config.toml | 38 | - name: Set up default .cargo/config.toml |
| 38 | run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml | 39 | run: cp ./.cargo/ci-config.toml ~/.cargo/config.toml |
| 39 | 40 | ||
| 40 | - name: Run rustfmt | 41 | - name: Run style checks |
| 41 | uses: ./.github/actions/check_formatting | 42 | uses: ./.github/actions/check_style |
| 42 | 43 | ||
| 43 | tests: | 44 | tests: |
| 44 | name: Run tests | 45 | name: Run tests |
| 45 | runs-on: | 46 | runs-on: |
| 46 | - self-hosted | 47 | - self-hosted |
| 47 | - test | 48 | - test |
| 48 | needs: rustfmt | 49 | needs: style |
| 49 | steps: | 50 | steps: |
| 50 | - name: Checkout repo | 51 | - name: Checkout repo |
| 51 | uses: actions/checkout@v3 | 52 | uses: actions/checkout@v3 |
| @@ -75,14 +76,6 @@ jobs: · 23 unmodified lines | |||
| 75 | APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} | 76 | APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} |
| 76 | APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} | 77 | APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} |
| 77 | steps: | 78 | steps: |
| 78 | - name: Install Rust | ||
| 79 | run: | | ||
| 80 | rustup set profile minimal | ||
| 81 | rustup update stable | ||
| 82 | rustup target add aarch64-apple-darwin | ||
| 83 | rustup target add x86_64-apple-darwin | ||
| 84 | rustup target add wasm32-wasi | ||
| 85 | |||
| 86 | - name: Install Node | 79 | - name: Install Node |
| 87 | uses: actions/setup-node@v3 | 80 | uses: actions/setup-node@v3 |
| 88 | with: | 81 | with: |
| @@ -3,41 +3,36 @@ name: Randomized Tests · 2 unmodified lines | |||
| 3 | concurrency: randomized-tests | 3 | concurrency: randomized-tests |
| 4 | 4 | ||
| 5 | on: | 5 | on: |
| 6 | push: | 6 | push: |
| 7 | branches: | 7 | branches: |
| 8 | - randomized-tests-runner | 8 | - randomized-tests-runner |
| 9 | # schedule: | 9 | # schedule: |
| 10 | # - cron: '0 * * * *' | 10 | # - cron: '0 * * * *' |
| 11 | 11 | ||
| 12 | env: | 12 | env: |
| 13 | CARGO_TERM_COLOR: always | 13 | CARGO_TERM_COLOR: always |
| 14 | CARGO_INCREMENTAL: 0 | 14 | CARGO_INCREMENTAL: 0 |
| 15 | RUST_BACKTRACE: 1 | 15 | RUST_BACKTRACE: 1 |
| 16 | ZED_SERVER_URL: https://zed.dev | 16 | ZED_SERVER_URL: https://zed.dev |
| 17 | ZED_CLIENT_SECRET_TOKEN: ${{ secrets.ZED_CLIENT_SECRET_TOKEN }} | 17 | ZED_CLIENT_SECRET_TOKEN: ${{ secrets.ZED_CLIENT_SECRET_TOKEN }} |
| 18 | 18 | ||
| 19 | jobs: | 19 | jobs: |
| 20 | tests: | 20 | tests: |
| 21 | name: Run randomized tests | 21 | name: Run randomized tests |
| 22 | runs-on: | 22 | runs-on: |
| 23 | - self-hosted | 23 | - self-hosted |
| 24 | - randomized-tests | 24 | - randomized-tests |
| 25 | steps: | 25 | steps: |
| 26 | - name: Install Rust | 26 | - name: Install Node |
| 27 | run: | | 27 | uses: actions/setup-node@v3 |
| 28 | rustup set profile minimal | 28 | with: |
| 29 | rustup update stable | 29 | node-version: "18" |
| 30 | 30 | ||
| 31 | - name: Install Node | 31 | - name: Checkout repo |
| 32 | uses: actions/setup-node@v3 | 32 | uses: actions/checkout@v3 |
| 33 | with: | 33 | with: |
| 34 | node-version: '18' | 34 | clean: false |
| 35 | submodules: "recursive" | ||
| 35 | 36 | ||
| 36 | - name: Checkout repo | 37 | - name: Run randomized tests |
| 37 | uses: actions/checkout@v3 | 38 | run: script/randomized-test-ci |
| 38 | with: | ||
| 39 | clean: false | ||
| 40 | submodules: 'recursive' | ||
| 41 | |||
| 42 | - name: Run randomized tests | ||
| 43 | run: script/randomized-test-ci | ||
| @@ -14,8 +14,8 @@ env: · 13 unmodified lines | |||
| 14 | RUST_BACKTRACE: 1 | 14 | RUST_BACKTRACE: 1 |
| 15 | 15 | ||
| 16 | jobs: | 16 | jobs: |
| 17 | rustfmt: | 17 | style: |
| 18 | name: Check formatting | 18 | name: Check formatting and Clippy lints |
| 19 | runs-on: | 19 | runs-on: |
| 20 | - self-hosted | 20 | - self-hosted |
| 21 | - test | 21 | - test |
| @@ -25,16 +25,17 @@ jobs: · 3 unmodified lines | |||
| 25 | with: | 25 | with: |
| 26 | clean: false | 26 | clean: false |
| 27 | submodules: "recursive" | 27 | submodules: "recursive" |
| 28 | fetch-depth: 0 | ||
| 28 | 29 | ||
| 29 | - name: Run rustfmt | 30 | - name: Run style checks |
| 30 | uses: ./.github/actions/check_formatting | 31 | uses: ./.github/actions/check_style |
| 31 | 32 | ||
| 32 | tests: | 33 | tests: |
| 33 | name: Run tests | 34 | name: Run tests |
| 34 | runs-on: | 35 | runs-on: |
| 35 | - self-hosted | 36 | - self-hosted |
| 36 | - test | 37 | - test |
| 37 | needs: rustfmt | 38 | needs: style |
| 38 | steps: | 39 | steps: |
| 39 | - name: Checkout repo | 40 | - name: Checkout repo |
| 40 | uses: actions/checkout@v3 | 41 | uses: actions/checkout@v3 |
| @@ -59,14 +60,6 @@ jobs: · 18 unmodified lines | |||
| 59 | DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} | 60 | DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }} |
| 60 | DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} | 61 | DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }} |
| 61 | steps: | 62 | steps: |
| 62 | - name: Install Rust | ||
| 63 | run: | | ||
| 64 | rustup set profile minimal | ||
| 65 | rustup update stable | ||
| 66 | rustup target add aarch64-apple-darwin | ||
| 67 | rustup target add x86_64-apple-darwin | ||
| 68 | rustup target add wasm32-wasi | ||
| 69 | |||
| 70 | - name: Install Node | 63 | - name: Install Node |
| 71 | uses: actions/setup-node@v3 | 64 | uses: actions/setup-node@v3 |
| 72 | with: | 65 | with: |
| @@ -1,5 +1,6 @@ | |||
| 1 | { | 1 | { |
| 2 | "JSON": { | 2 | "JSON": { |
| 3 | "tab_size": 4 | 3 | "tab_size": 4 |
| 4 | } | 4 | }, |
| 5 | "formatter": "auto" | ||
| 5 | } | 6 | } |
| @@ -1452,7 +1452,7 @@ dependencies = [ · 1451 unmodified lines | |||
| 1452 | 1452 | ||
| 1453 | [[package]] | 1453 | [[package]] |
| 1454 | name = "collab" | 1454 | name = "collab" |
| 1455 | version = "0.36.0" | 1455 | version = "0.36.1" |
| 1456 | dependencies = [ | 1456 | dependencies = [ |
| 1457 | "anyhow", | 1457 | "anyhow", |
| 1458 | "async-trait", | 1458 | "async-trait", |
| @@ -2522,6 +2522,7 @@ dependencies = [ · 1063 unmodified lines | |||
| 2522 | name = "file_finder" | 2522 | name = "file_finder" |
| 2523 | version = "0.1.0" | 2523 | version = "0.1.0" |
| 2524 | dependencies = [ | 2524 | dependencies = [ |
| 2525 | "anyhow", | ||
| 2525 | "collections", | 2526 | "collections", |
| 2526 | "ctor", | 2527 | "ctor", |
| 2527 | "editor", | 2528 | "editor", |
| @@ -3440,40 +3441,6 @@ dependencies = [ · 912 unmodified lines | |||
| 3440 | "tiff", | 3441 | "tiff", |
| 3441 | ] | 3442 | ] |
| 3442 | 3443 | ||
| 3443 | [[package]] | ||
| 3444 | name = "include-flate" | ||
| 3445 | version = "0.2.0" | ||
| 3446 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3447 | checksum = "c2e11569346406931d20276cc460215ee2826e7cad43aa986999cb244dd7adb0" | ||
| 3448 | dependencies = [ | ||
| 3449 | "include-flate-codegen-exports", | ||
| 3450 | "lazy_static", | ||
| 3451 | "libflate", | ||
| 3452 | ] | ||
| 3453 | |||
| 3454 | [[package]] | ||
| 3455 | name = "include-flate-codegen" | ||
| 3456 | version = "0.1.4" | ||
| 3457 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3458 | checksum = "3a7d6e1419fa3129eb0802b4c99603c0d425c79fb5d76191d5a20d0ab0d664e8" | ||
| 3459 | dependencies = [ | ||
| 3460 | "libflate", | ||
| 3461 | "proc-macro-hack", | ||
| 3462 | "proc-macro2", | ||
| 3463 | "quote", | ||
| 3464 | "syn 1.0.109", | ||
| 3465 | ] | ||
| 3466 | |||
| 3467 | [[package]] | ||
| 3468 | name = "include-flate-codegen-exports" | ||
| 3469 | version = "0.1.4" | ||
| 3470 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3471 | checksum = "75657043ffe3d8280f1cb8aef0f505532b392ed7758e0baeac22edadcee31a03" | ||
| 3472 | dependencies = [ | ||
| 3473 | "include-flate-codegen", | ||
| 3474 | "proc-macro-hack", | ||
| 3475 | ] | ||
| 3476 | |||
| 3477 | [[package]] | 3444 | [[package]] |
| 3478 | name = "indexmap" | 3445 | name = "indexmap" |
| 3479 | version = "1.9.3" | 3446 | version = "1.9.3" |
| @@ -3865,26 +3832,6 @@ version = "0.2.148" · 385 unmodified lines | |||
| 3865 | source = "registry+https://github.com/rust-lang/crates.io-index" | 3832 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 3866 | checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" | 3833 | checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" |
| 3867 | 3834 | ||
| 3868 | [[package]] | ||
| 3869 | name = "libflate" | ||
| 3870 | version = "1.4.0" | ||
| 3871 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3872 | checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18" | ||
| 3873 | dependencies = [ | ||
| 3874 | "adler32", | ||
| 3875 | "crc32fast", | ||
| 3876 | "libflate_lz77", | ||
| 3877 | ] | ||
| 3878 | |||
| 3879 | [[package]] | ||
| 3880 | name = "libflate_lz77" | ||
| 3881 | version = "1.2.0" | ||
| 3882 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3883 | checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf" | ||
| 3884 | dependencies = [ | ||
| 3885 | "rle-decode-fast", | ||
| 3886 | ] | ||
| 3887 | |||
| 3888 | [[package]] | 3835 | [[package]] |
| 3889 | name = "libgit2-sys" | 3836 | name = "libgit2-sys" |
| 3890 | version = "0.14.2+1.5.1" | 3837 | version = "0.14.2+1.5.1" |
| @@ -5462,12 +5409,6 @@ dependencies = [ · 1571 unmodified lines | |||
| 5462 | "version_check", | 5409 | "version_check", |
| 5463 | ] | 5410 | ] |
| 5464 | 5411 | ||
| 5465 | [[package]] | ||
| 5466 | name = "proc-macro-hack" | ||
| 5467 | version = "0.5.20+deprecated" | ||
| 5468 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 5469 | checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" | ||
| 5470 | |||
| 5471 | [[package]] | 5412 | [[package]] |
| 5472 | name = "proc-macro2" | 5413 | name = "proc-macro2" |
| 5473 | version = "1.0.67" | 5414 | version = "1.0.67" |
| @@ -6162,12 +6103,6 @@ dependencies = [ · 688 unmodified lines | |||
| 6162 | "syn 1.0.109", | 6103 | "syn 1.0.109", |
| 6163 | ] | 6104 | ] |
| 6164 | 6105 | ||
| 6165 | [[package]] | ||
| 6166 | name = "rle-decode-fast" | ||
| 6167 | version = "1.0.3" | ||
| 6168 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 6169 | checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" | ||
| 6170 | |||
| 6171 | [[package]] | 6106 | [[package]] |
| 6172 | name = "rmp" | 6107 | name = "rmp" |
| 6173 | version = "0.8.12" | 6108 | version = "0.8.12" |
| @@ -6315,7 +6250,6 @@ version = "8.0.0" · 141 unmodified lines | |||
| 6315 | source = "registry+https://github.com/rust-lang/crates.io-index" | 6250 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 6316 | checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" | 6251 | checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" |
| 6317 | dependencies = [ | 6252 | dependencies = [ |
| 6318 | "include-flate", | ||
| 6319 | "rust-embed-impl", | 6253 | "rust-embed-impl", |
| 6320 | "rust-embed-utils", | 6254 | "rust-embed-utils", |
| 6321 | "walkdir", | 6255 | "walkdir", |
| @@ -110,7 +110,7 @@ prost = { version = "0.8" } · 109 unmodified lines | |||
| 110 | rand = { version = "0.8.5" } | 110 | rand = { version = "0.8.5" } |
| 111 | refineable = { path = "./crates/refineable" } | 111 | refineable = { path = "./crates/refineable" } |
| 112 | regex = { version = "1.5" } | 112 | regex = { version = "1.5" } |
| 113 | rust-embed = { version = "8.0", features = ["include-exclude", "compression"] } | 113 | rust-embed = { version = "8.0", features = ["include-exclude"] } |
| 114 | rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } | 114 | rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] } |
| 115 | schemars = { version = "0.8" } | 115 | schemars = { version = "0.8" } |
| 116 | serde = { version = "1.0", features = ["derive", "rc"] } | 116 | serde = { version = "1.0", features = ["derive", "rc"] } |
| @@ -412,7 +412,8 @@ · 411 unmodified lines | |||
| 412 | "cmd-shift-e": "project_panel::ToggleFocus", | 412 | "cmd-shift-e": "project_panel::ToggleFocus", |
| 413 | "cmd-?": "assistant::ToggleFocus", | 413 | "cmd-?": "assistant::ToggleFocus", |
| 414 | "cmd-alt-s": "workspace::SaveAll", | 414 | "cmd-alt-s": "workspace::SaveAll", |
| 415 | "cmd-k m": "language_selector::Toggle" | 415 | "cmd-k m": "language_selector::Toggle", |
| 416 | "escape": "workspace::Unfollow" | ||
| 416 | } | 417 | } |
| 417 | }, | 418 | }, |
| 418 | // Bindings from Sublime Text | 419 | // Bindings from Sublime Text |
| @@ -239,7 +239,8 @@ impl ActiveCall { · 238 unmodified lines | |||
| 239 | if result.is_ok() { | 239 | if result.is_ok() { |
| 240 | this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?; | 240 | this.update(&mut cx, |this, cx| this.report_call_event("invite", cx))?; |
| 241 | } else { | 241 | } else { |
| 242 | // TODO: Resport collaboration error | 242 | //TODO: report collaboration error |
| 243 | log::error!("invite failed: {:?}", result); | ||
| 243 | } | 244 | } |
| 244 | 245 | ||
| 245 | this.update(&mut cx, |this, cx| { | 246 | this.update(&mut cx, |this, cx| { |
| @@ -282,7 +283,7 @@ impl ActiveCall { · 36 unmodified lines | |||
| 282 | return Task::ready(Err(anyhow!("cannot join while on another call"))); | 283 | return Task::ready(Err(anyhow!("cannot join while on another call"))); |
| 283 | } | 284 | } |
| 284 | 285 | ||
| 285 | let call = if let Some(call) = self.incoming_call.1.borrow().clone() { | 286 | let call = if let Some(call) = self.incoming_call.0.borrow_mut().take() { |
| 286 | call | 287 | call |
| 287 | } else { | 288 | } else { |
| 288 | return Task::ready(Err(anyhow!("no incoming call"))); | 289 | return Task::ready(Err(anyhow!("no incoming call"))); |
| @@ -15,10 +15,7 @@ use gpui::{ · 14 unmodified lines | |||
| 15 | AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, | 15 | AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, |
| 16 | }; | 16 | }; |
| 17 | use language::LanguageRegistry; | 17 | use language::LanguageRegistry; |
| 18 | use live_kit_client::{ | 18 | use live_kit_client::{LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RoomUpdate}; |
| 19 | LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, | ||
| 20 | RemoteVideoTrackUpdate, | ||
| 21 | }; | ||
| 22 | use postage::{sink::Sink, stream::Stream, watch}; | 19 | use postage::{sink::Sink, stream::Stream, watch}; |
| 23 | use project::Project; | 20 | use project::Project; |
| 24 | use settings::Settings as _; | 21 | use settings::Settings as _; |
| @@ -131,30 +128,11 @@ impl Room { · 106 unmodified lines | |||
| 131 | } | 128 | } |
| 132 | }); | 129 | }); |
| 133 | 130 | ||
| 134 | let _maintain_video_tracks = cx.spawn({ | 131 | let _handle_updates = cx.spawn({ |
| 135 | let room = room.clone(); | 132 | let room = room.clone(); |
| 136 | move |this, mut cx| async move { | 133 | move |this, mut cx| async move { |
| 137 | let mut track_video_changes = room.remote_video_track_updates(); | 134 | let mut updates = room.updates(); |
| 138 | while let Some(track_change) = track_video_changes.next().await { | 135 | while let Some(update) = updates.next().await { |
| 139 | let this = if let Some(this) = this.upgrade() { | ||
| 140 | this | ||
| 141 | } else { | ||
| 142 | break; | ||
| 143 | }; | ||
| 144 | |||
| 145 | this.update(&mut cx, |this, cx| { | ||
| 146 | this.remote_video_track_updated(track_change, cx).log_err() | ||
| 147 | }) | ||
| 148 | .ok(); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | }); | ||
| 152 | |||
| 153 | let _maintain_audio_tracks = cx.spawn({ | ||
| 154 | let room = room.clone(); | ||
| 155 | |this, mut cx| async move { | ||
| 156 | let mut track_audio_changes = room.remote_audio_track_updates(); | ||
| 157 | while let Some(track_change) = track_audio_changes.next().await { | ||
| 158 | let this = if let Some(this) = this.upgrade() { | 136 | let this = if let Some(this) = this.upgrade() { |
| 159 | this | 137 | this |
| 160 | } else { | 138 | } else { |
| @@ -162,7 +140,7 @@ impl Room { · 1 unmodified line | |||
| 162 | }; | 140 | }; |
| 163 | 141 | ||
| 164 | this.update(&mut cx, |this, cx| { | 142 | this.update(&mut cx, |this, cx| { |
| 165 | this.remote_audio_track_updated(track_change, cx).log_err() | 143 | this.live_kit_room_updated(update, cx).log_err() |
| 166 | }) | 144 | }) |
| 167 | .ok(); | 145 | .ok(); |
| 168 | } | 146 | } |
| @@ -195,7 +173,7 @@ impl Room { · 26 unmodified lines | |||
| 195 | deafened: false, | 173 | deafened: false, |
| 196 | speaking: false, | 174 | speaking: false, |
| 197 | _maintain_room, | 175 | _maintain_room, |
| 198 | _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks], | 176 | _handle_updates, |
| 199 | }) | 177 | }) |
| 200 | } else { | 178 | } else { |
| 201 | None | 179 | None |
| @@ -877,8 +855,8 @@ impl Room { · 675 unmodified lines | |||
| 877 | .remote_audio_track_publications(&user.id.to_string()); | 855 | .remote_audio_track_publications(&user.id.to_string()); |
| 878 | 856 | ||
| 879 | for track in video_tracks { | 857 | for track in video_tracks { |
| 880 | this.remote_video_track_updated( | 858 | this.live_kit_room_updated( |
| 881 | RemoteVideoTrackUpdate::Subscribed(track), | 859 | RoomUpdate::SubscribedToRemoteVideoTrack(track), |
| 882 | cx, | 860 | cx, |
| 883 | ) | 861 | ) |
| 884 | .log_err(); | 862 | .log_err(); |
| @@ -887,8 +865,8 @@ impl Room { · 2 unmodified lines | |||
| 887 | for (track, publication) in | 865 | for (track, publication) in |
| 888 | audio_tracks.iter().zip(publications.iter()) | 866 | audio_tracks.iter().zip(publications.iter()) |
| 889 | { | 867 | { |
| 890 | this.remote_audio_track_updated( | 868 | this.live_kit_room_updated( |
| 891 | RemoteAudioTrackUpdate::Subscribed( | 869 | RoomUpdate::SubscribedToRemoteAudioTrack( |
| 892 | track.clone(), | 870 | track.clone(), |
| 893 | publication.clone(), | 871 | publication.clone(), |
| 894 | ), | 872 | ), |
| @@ -979,13 +957,13 @@ impl Room { · 84 unmodified lines | |||
| 979 | } | 957 | } |
| 980 | } | 958 | } |
| 981 | 959 | ||
| 982 | fn remote_video_track_updated( | 960 | fn live_kit_room_updated( |
| 983 | &mut self, | 961 | &mut self, |
| 984 | change: RemoteVideoTrackUpdate, | 962 | update: RoomUpdate, |
| 985 | cx: &mut ModelContext<Self>, | 963 | cx: &mut ModelContext<Self>, |
| 986 | ) -> Result<()> { | 964 | ) -> Result<()> { |
| 987 | match change { | 965 | match update { |
| 988 | RemoteVideoTrackUpdate::Subscribed(track) => { | 966 | RoomUpdate::SubscribedToRemoteVideoTrack(track) => { |
| 989 | let user_id = track.publisher_id().parse()?; | 967 | let user_id = track.publisher_id().parse()?; |
| 990 | let track_id = track.sid().to_string(); | 968 | let track_id = track.sid().to_string(); |
| 991 | let participant = self | 969 | let participant = self |
| @@ -997,7 +975,8 @@ impl Room { · 5 unmodified lines | |||
| 997 | participant_id: participant.peer_id, | 975 | participant_id: participant.peer_id, |
| 998 | }); | 976 | }); |
| 999 | } | 977 | } |
| 1000 | RemoteVideoTrackUpdate::Unsubscribed { | 978 | |
| 979 | RoomUpdate::UnsubscribedFromRemoteVideoTrack { | ||
| 1001 | publisher_id, | 980 | publisher_id, |
| 1002 | track_id, | 981 | track_id, |
| 1003 | } => { | 982 | } => { |
| @@ -1011,19 +990,8 @@ impl Room { · 7 unmodified lines | |||
| 1011 | participant_id: participant.peer_id, | 990 | participant_id: participant.peer_id, |
| 1012 | }); | 991 | }); |
| 1013 | } | 992 | } |
| 1014 | } | ||
| 1015 | 993 | ||
| 1016 | cx.notify(); | 994 | RoomUpdate::ActiveSpeakersChanged { speakers } => { |
| 1017 | Ok(()) | ||
| 1018 | } | ||
| 1019 | |||
| 1020 | fn remote_audio_track_updated( | ||
| 1021 | &mut self, | ||
| 1022 | change: RemoteAudioTrackUpdate, | ||
| 1023 | cx: &mut ModelContext<Self>, | ||
| 1024 | ) -> Result<()> { | ||
| 1025 | match change { | ||
| 1026 | RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } => { | ||
| 1027 | let mut speaker_ids = speakers | 995 | let mut speaker_ids = speakers |
| 1028 | .into_iter() | 996 | .into_iter() |
| 1029 | .filter_map(|speaker_sid| speaker_sid.parse().ok()) | 997 | .filter_map(|speaker_sid| speaker_sid.parse().ok()) |
| @@ -1045,9 +1013,9 @@ impl Room { · 15 unmodified lines | |||
| 1045 | } | 1013 | } |
| 1046 | } | 1014 | } |
| 1047 | } | 1015 | } |
| 1048 | cx.notify(); | ||
| 1049 | } | 1016 | } |
| 1050 | RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { | 1017 | |
| 1018 | RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } => { | ||
| 1051 | let mut found = false; | 1019 | let mut found = false; |
| 1052 | for participant in &mut self.remote_participants.values_mut() { | 1020 | for participant in &mut self.remote_participants.values_mut() { |
| 1053 | for track in participant.audio_tracks.values() { | 1021 | for track in participant.audio_tracks.values() { |
| @@ -1061,10 +1029,9 @@ impl Room { · 7 unmodified lines | |||
| 1061 | break; | 1029 | break; |
| 1062 | } | 1030 | } |
| 1063 | } | 1031 | } |
| 1064 | |||
| 1065 | cx.notify(); | ||
| 1066 | } | 1032 | } |
| 1067 | RemoteAudioTrackUpdate::Subscribed(track, publication) => { | 1033 | |
| 1034 | RoomUpdate::SubscribedToRemoteAudioTrack(track, publication) => { | ||
| 1068 | let user_id = track.publisher_id().parse()?; | 1035 | let user_id = track.publisher_id().parse()?; |
| 1069 | let track_id = track.sid().to_string(); | 1036 | let track_id = track.sid().to_string(); |
| 1070 | let participant = self | 1037 | let participant = self |
| @@ -1078,7 +1045,8 @@ impl Room { · 7 unmodified lines | |||
| 1078 | participant_id: participant.peer_id, | 1045 | participant_id: participant.peer_id, |
| 1079 | }); | 1046 | }); |
| 1080 | } | 1047 | } |
| 1081 | RemoteAudioTrackUpdate::Unsubscribed { | 1048 | |
| 1049 | RoomUpdate::UnsubscribedFromRemoteAudioTrack { | ||
| 1082 | publisher_id, | 1050 | publisher_id, |
| 1083 | track_id, | 1051 | track_id, |
| 1084 | } => { | 1052 | } => { |
| @@ -1092,6 +1060,28 @@ impl Room { · 7 unmodified lines | |||
| 1092 | participant_id: participant.peer_id, | 1060 | participant_id: participant.peer_id, |
| 1093 | }); | 1061 | }); |
| 1094 | } | 1062 | } |
| 1063 | |||
| 1064 | RoomUpdate::LocalAudioTrackUnpublished { publication } => { | ||
| 1065 | log::info!("unpublished audio track {}", publication.sid()); | ||
| 1066 | if let Some(room) = &mut self.live_kit { | ||
| 1067 | room.microphone_track = LocalTrack::None; | ||
| 1068 | } | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | RoomUpdate::LocalVideoTrackUnpublished { publication } => { | ||
| 1072 | log::info!("unpublished video track {}", publication.sid()); | ||
| 1073 | if let Some(room) = &mut self.live_kit { | ||
| 1074 | room.screen_track = LocalTrack::None; | ||
| 1075 | } | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | RoomUpdate::LocalAudioTrackPublished { publication } => { | ||
| 1079 | log::info!("published audio track {}", publication.sid()); | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | RoomUpdate::LocalVideoTrackPublished { publication } => { | ||
| 1083 | log::info!("published video track {}", publication.sid()); | ||
| 1084 | } | ||
| 1095 | } | 1085 | } |
| 1096 | 1086 | ||
| 1097 | cx.notify(); | 1087 | cx.notify(); |
| @@ -1235,7 +1225,12 @@ impl Room { · 137 unmodified lines | |||
| 1235 | }; | 1225 | }; |
| 1236 | 1226 | ||
| 1237 | self.client.send(proto::UnshareProject { project_id })?; | 1227 | self.client.send(proto::UnshareProject { project_id })?; |
| 1238 | project.update(cx, |this, cx| this.unshare(cx)) | 1228 | project.update(cx, |this, cx| this.unshare(cx))?; |
| 1229 | |||
| 1230 | if self.local_participant.active_project == Some(project.downgrade()) { | ||
| 1231 | self.set_location(Some(&project), cx).detach_and_log_err(cx); | ||
| 1232 | } | ||
| 1233 | Ok(()) | ||
| 1239 | } | 1234 | } |
| 1240 | 1235 | ||
| 1241 | pub(crate) fn set_location( | 1236 | pub(crate) fn set_location( |
| @@ -1597,7 +1592,7 @@ struct LiveKitRoom { · 355 unmodified lines | |||
| 1597 | speaking: bool, | 1592 | speaking: bool, |
| 1598 | next_publish_id: usize, | 1593 | next_publish_id: usize, |
| 1599 | _maintain_room: Task<()>, | 1594 | _maintain_room: Task<()>, |
| 1600 | _maintain_tracks: [Task<()>; 2], | 1595 | _handle_updates: Task<()>, |
| 1601 | } | 1596 | } |
| 1602 | 1597 | ||
| 1603 | impl LiveKitRoom { | 1598 | impl LiveKitRoom { |
| @@ -1371,10 +1371,7 @@ fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> { · 1370 unmodified lines | |||
| 1371 | }) | 1371 | }) |
| 1372 | } | 1372 | } |
| 1373 | 1373 | ||
| 1374 | async fn write_credentials_to_keychain( | 1374 | fn write_credentials_to_keychain(credentials: Credentials, cx: &AsyncAppContext) -> Result<()> { |
| 1375 | credentials: Credentials, | ||
| 1376 | cx: &AsyncAppContext, | ||
| 1377 | ) -> Result<()> { | ||
| 1378 | cx.update(move |cx| { | 1375 | cx.update(move |cx| { |
| 1379 | cx.write_credentials( | 1376 | cx.write_credentials( |
| 1380 | &ZED_SERVER_URL, | 1377 | &ZED_SERVER_URL, |
| @@ -1384,7 +1381,7 @@ async fn write_credentials_to_keychain( · 3 unmodified lines | |||
| 1384 | })? | 1381 | })? |
| 1385 | } | 1382 | } |
| 1386 | 1383 | ||
| 1387 | async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { | 1384 | fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { |
| 1388 | cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? | 1385 | cx.update(move |cx| cx.delete_credentials(&ZED_SERVER_URL))? |
| 1389 | } | 1386 | } |
| 1390 | 1387 | ||
| @@ -1,3 +1,5 @@ | |||
| 1 | mod event_coalescer; | ||
| 2 | |||
| 1 | use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; | 3 | use crate::{TelemetrySettings, ZED_SECRET_CLIENT_TOKEN, ZED_SERVER_URL}; |
| 2 | use chrono::{DateTime, Utc}; | 4 | use chrono::{DateTime, Utc}; |
| 3 | use futures::Future; | 5 | use futures::Future; |
| @@ -5,7 +7,6 @@ use gpui::{AppContext, AppMetadata, BackgroundExecutor, Task}; · 1 unmodified line | |||
| 5 | use lazy_static::lazy_static; | 7 | use lazy_static::lazy_static; |
| 6 | use parking_lot::Mutex; | 8 | use parking_lot::Mutex; |
| 7 | use serde::Serialize; | 9 | use serde::Serialize; |
| 8 | use serde_json; | ||
| 9 | use settings::{Settings, SettingsStore}; | 10 | use settings::{Settings, SettingsStore}; |
| 10 | use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration}; | 11 | use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration}; |
| 11 | use sysinfo::{ | 12 | use sysinfo::{ |
| @@ -15,6 +16,8 @@ use tempfile::NamedTempFile; · 3 unmodified lines | |||
| 15 | use util::http::HttpClient; | 16 | use util::http::HttpClient; |
| 16 | use util::{channel::ReleaseChannel, TryFutureExt}; | 17 | use util::{channel::ReleaseChannel, TryFutureExt}; |
| 17 | 18 | ||
| 19 | use self::event_coalescer::EventCoalescer; | ||
| 20 | |||
| 18 | pub struct Telemetry { | 21 | pub struct Telemetry { |
| 19 | http_client: Arc<dyn HttpClient>, | 22 | http_client: Arc<dyn HttpClient>, |
| 20 | executor: BackgroundExecutor, | 23 | executor: BackgroundExecutor, |
| @@ -34,6 +37,7 @@ struct TelemetryState { · 13 unmodified lines | |||
| 34 | log_file: Option<NamedTempFile>, | 37 | log_file: Option<NamedTempFile>, |
| 35 | is_staff: Option<bool>, | 38 | is_staff: Option<bool>, |
| 36 | first_event_datetime: Option<DateTime<Utc>>, | 39 | first_event_datetime: Option<DateTime<Utc>>, |
| 40 | event_coalescer: EventCoalescer, | ||
| 37 | } | 41 | } |
| 38 | 42 | ||
| 39 | const EVENTS_URL_PATH: &'static str = "/api/events"; | 43 | const EVENTS_URL_PATH: &'static str = "/api/events"; |
| @@ -118,19 +122,24 @@ pub enum Event { · 78 unmodified lines | |||
| 118 | value: String, | 122 | value: String, |
| 119 | milliseconds_since_first_event: i64, | 123 | milliseconds_since_first_event: i64, |
| 120 | }, | 124 | }, |
| 125 | Edit { | ||
| 126 | duration: i64, | ||
| 127 | environment: &'static str, | ||
| 128 | milliseconds_since_first_event: i64, | ||
| 129 | }, | ||
| 121 | } | 130 | } |
| 122 | 131 | ||
| 123 | #[cfg(debug_assertions)] | 132 | #[cfg(debug_assertions)] |
| 124 | const MAX_QUEUE_LEN: usize = 1; | 133 | const MAX_QUEUE_LEN: usize = 5; |
| 125 | 134 | ||
| 126 | #[cfg(not(debug_assertions))] | 135 | #[cfg(not(debug_assertions))] |
| 127 | const MAX_QUEUE_LEN: usize = 50; | 136 | const MAX_QUEUE_LEN: usize = 50; |
| 128 | 137 | ||
| 129 | #[cfg(debug_assertions)] | 138 | #[cfg(debug_assertions)] |
| 130 | const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); | 139 | const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(1); |
| 131 | 140 | ||
| 132 | #[cfg(not(debug_assertions))] | 141 | #[cfg(not(debug_assertions))] |
| 133 | const DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5); | 142 | const FLUSH_DEBOUNCE_INTERVAL: Duration = Duration::from_secs(60 * 5); |
| 134 | 143 | ||
| 135 | impl Telemetry { | 144 | impl Telemetry { |
| 136 | pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> { | 145 | pub fn new(client: Arc<dyn HttpClient>, cx: &mut AppContext) -> Arc<Self> { |
| @@ -150,11 +159,12 @@ impl Telemetry { · 13 unmodified lines | |||
| 150 | installation_id: None, | 159 | installation_id: None, |
| 151 | metrics_id: None, | 160 | metrics_id: None, |
| 152 | session_id: None, | 161 | session_id: None, |
| 153 | events_queue: Default::default(), | 162 | events_queue: Vec::new(), |
| 154 | flush_events_task: Default::default(), | 163 | flush_events_task: None, |
| 155 | log_file: None, | 164 | log_file: None, |
| 156 | is_staff: None, | 165 | is_staff: None, |
| 157 | first_event_datetime: None, | 166 | first_event_datetime: None, |
| 167 | event_coalescer: EventCoalescer::new(), | ||
| 158 | })); | 168 | })); |
| 159 | 169 | ||
| 160 | cx.observe_global::<SettingsStore>({ | 170 | cx.observe_global::<SettingsStore>({ |
| @@ -194,7 +204,7 @@ impl Telemetry { · 33 unmodified lines | |||
| 194 | #[cfg(not(any(test, feature = "test-support")))] | 204 | #[cfg(not(any(test, feature = "test-support")))] |
| 195 | fn shutdown_telemetry(self: &Arc<Self>) -> impl Future<Output = ()> { | 205 | fn shutdown_telemetry(self: &Arc<Self>) -> impl Future<Output = ()> { |
| 196 | self.report_app_event("close"); | 206 | self.report_app_event("close"); |
| 197 | self.flush_events(); | 207 | // TODO: close final edit period and make sure it's sent |
| 198 | Task::ready(()) | 208 | Task::ready(()) |
| 199 | } | 209 | } |
| 200 | 210 | ||
| @@ -392,6 +402,22 @@ impl Telemetry { · 191 unmodified lines | |||
| 392 | } | 402 | } |
| 393 | } | 403 | } |
| 394 | 404 | ||
| 405 | pub fn log_edit_event(self: &Arc<Self>, environment: &'static str) { | ||
| 406 | let mut state = self.state.lock(); | ||
| 407 | let period_data = state.event_coalescer.log_event(environment); | ||
| 408 | drop(state); | ||
| 409 | |||
| 410 | if let Some((start, end, environment)) = period_data { | ||
| 411 | let event = Event::Edit { | ||
| 412 | duration: end.timestamp_millis() - start.timestamp_millis(), | ||
| 413 | environment, | ||
| 414 | milliseconds_since_first_event: self.milliseconds_since_first_event(), | ||
| 415 | }; | ||
| 416 | |||
| 417 | self.report_event(event); | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 395 | fn report_event(self: &Arc<Self>, event: Event) { | 421 | fn report_event(self: &Arc<Self>, event: Event) { |
| 396 | let mut state = self.state.lock(); | 422 | let mut state = self.state.lock(); |
| 397 | 423 | ||
| @@ -410,7 +436,7 @@ impl Telemetry { · 12 unmodified lines | |||
| 410 | let this = self.clone(); | 436 | let this = self.clone(); |
| 411 | let executor = self.executor.clone(); | 437 | let executor = self.executor.clone(); |
| 412 | state.flush_events_task = Some(self.executor.spawn(async move { | 438 | state.flush_events_task = Some(self.executor.spawn(async move { |
| 413 | executor.timer(DEBOUNCE_INTERVAL).await; | 439 | executor.timer(FLUSH_DEBOUNCE_INTERVAL).await; |
| 414 | this.flush_events(); | 440 | this.flush_events(); |
| 415 | })); | 441 | })); |
| 416 | } | 442 | } |
| @@ -435,6 +461,9 @@ impl Telemetry { · 18 unmodified lines | |||
| 435 | let mut events = mem::take(&mut state.events_queue); | 461 | let mut events = mem::take(&mut state.events_queue); |
| 436 | state.flush_events_task.take(); | 462 | state.flush_events_task.take(); |
| 437 | drop(state); | 463 | drop(state); |
| 464 | if events.is_empty() { | ||
| 465 | return; | ||
| 466 | } | ||
| 438 | 467 | ||
| 439 | let this = self.clone(); | 468 | let this = self.clone(); |
| 440 | self.executor | 469 | self.executor |
| @@ -0,0 +1,279 @@ | |||
| 1 | use chrono::{DateTime, Duration, Utc}; | ||
| 2 | use std::time; | ||
| 3 | |||
| 4 | const COALESCE_TIMEOUT: time::Duration = time::Duration::from_secs(20); | ||
| 5 | const SIMULATED_DURATION_FOR_SINGLE_EVENT: time::Duration = time::Duration::from_millis(1); | ||
| 6 | |||
| 7 | #[derive(Debug, PartialEq)] | ||
| 8 | struct PeriodData { | ||
| 9 | environment: &'static str, | ||
| 10 | start: DateTime<Utc>, | ||
| 11 | end: Option<DateTime<Utc>>, | ||
| 12 | } | ||
| 13 | |||
| 14 | pub struct EventCoalescer { | ||
| 15 | state: Option<PeriodData>, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl EventCoalescer { | ||
| 19 | pub fn new() -> Self { | ||
| 20 | Self { state: None } | ||
| 21 | } | ||
| 22 | |||
| 23 | pub fn log_event( | ||
| 24 | &mut self, | ||
| 25 | environment: &'static str, | ||
| 26 | ) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> { | ||
| 27 | self.log_event_with_time(Utc::now(), environment) | ||
| 28 | } | ||
| 29 | |||
| 30 | // pub fn close_current_period(&mut self) -> Option<(DateTime<Utc>, DateTime<Utc>)> { | ||
| 31 | // self.environment.map(|env| self.log_event(env)).flatten() | ||
| 32 | // } | ||
| 33 | |||
| 34 | fn log_event_with_time( | ||
| 35 | &mut self, | ||
| 36 | log_time: DateTime<Utc>, | ||
| 37 | environment: &'static str, | ||
| 38 | ) -> Option<(DateTime<Utc>, DateTime<Utc>, &'static str)> { | ||
| 39 | let coalesce_timeout = Duration::from_std(COALESCE_TIMEOUT).unwrap(); | ||
| 40 | |||
| 41 | let Some(state) = &mut self.state else { | ||
| 42 | self.state = Some(PeriodData { | ||
| 43 | start: log_time, | ||
| 44 | end: None, | ||
| 45 | environment, | ||
| 46 | }); | ||
| 47 | return None; | ||
| 48 | }; | ||
| 49 | |||
| 50 | let period_end = state | ||
| 51 | .end | ||
| 52 | .unwrap_or(state.start + SIMULATED_DURATION_FOR_SINGLE_EVENT); | ||
| 53 | let within_timeout = log_time - period_end < coalesce_timeout; | ||
| 54 | let environment_is_same = state.environment == environment; | ||
| 55 | let should_coaelesce = !within_timeout || !environment_is_same; | ||
| 56 | |||
| 57 | if should_coaelesce { | ||
| 58 | let previous_environment = state.environment; | ||
| 59 | let original_start = state.start; | ||
| 60 | |||
| 61 | state.start = log_time; | ||
| 62 | state.end = None; | ||
| 63 | state.environment = environment; | ||
| 64 | |||
| 65 | return Some(( | ||
| 66 | original_start, | ||
| 67 | if within_timeout { log_time } else { period_end }, | ||
| 68 | previous_environment, | ||
| 69 | )); | ||
| 70 | } | ||
| 71 | |||
| 72 | state.end = Some(log_time); | ||
| 73 | |||
| 74 | None | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | #[cfg(test)] | ||
| 79 | mod tests { | ||
| 80 | use chrono::TimeZone; | ||
| 81 | |||
| 82 | use super::*; | ||
| 83 | |||
| 84 | #[test] | ||
| 85 | fn test_same_context_exceeding_timeout() { | ||
| 86 | let environment_1 = "environment_1"; | ||
| 87 | let mut event_coalescer = EventCoalescer::new(); | ||
| 88 | |||
| 89 | assert_eq!(event_coalescer.state, None); | ||
| 90 | |||
| 91 | let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); | ||
| 92 | let period_data = event_coalescer.log_event_with_time(period_start, environment_1); | ||
| 93 | |||
| 94 | assert_eq!(period_data, None); | ||
| 95 | assert_eq!( | ||
| 96 | event_coalescer.state, | ||
| 97 | Some(PeriodData { | ||
| 98 | start: period_start, | ||
| 99 | end: None, | ||
| 100 | environment: environment_1, | ||
| 101 | }) | ||
| 102 | ); | ||
| 103 | |||
| 104 | let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); | ||
| 105 | let mut period_end = period_start; | ||
| 106 | |||
| 107 | // Ensure that many calls within the timeout don't start a new period | ||
| 108 | for _ in 0..100 { | ||
| 109 | period_end += within_timeout_adjustment; | ||
| 110 | let period_data = event_coalescer.log_event_with_time(period_end, environment_1); | ||
| 111 | |||
| 112 | assert_eq!(period_data, None); | ||
| 113 | assert_eq!( | ||
| 114 | event_coalescer.state, | ||
| 115 | Some(PeriodData { | ||
| 116 | start: period_start, | ||
| 117 | end: Some(period_end), | ||
| 118 | environment: environment_1, | ||
| 119 | }) | ||
| 120 | ); | ||
| 121 | } | ||
| 122 | |||
| 123 | let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); | ||
| 124 | // Logging an event exceeding the timeout should start a new period | ||
| 125 | let new_period_start = period_end + exceed_timeout_adjustment; | ||
| 126 | let period_data = event_coalescer.log_event_with_time(new_period_start, environment_1); | ||
| 127 | |||
| 128 | assert_eq!(period_data, Some((period_start, period_end, environment_1))); | ||
| 129 | assert_eq!( | ||
| 130 | event_coalescer.state, | ||
| 131 | Some(PeriodData { | ||
| 132 | start: new_period_start, | ||
| 133 | end: None, | ||
| 134 | environment: environment_1, | ||
| 135 | }) | ||
| 136 | ); | ||
| 137 | } | ||
| 138 | |||
| 139 | #[test] | ||
| 140 | fn test_different_environment_under_timeout() { | ||
| 141 | let environment_1 = "environment_1"; | ||
| 142 | let mut event_coalescer = EventCoalescer::new(); | ||
| 143 | |||
| 144 | assert_eq!(event_coalescer.state, None); | ||
| 145 | |||
| 146 | let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); | ||
| 147 | let period_data = event_coalescer.log_event_with_time(period_start, environment_1); | ||
| 148 | |||
| 149 | assert_eq!(period_data, None); | ||
| 150 | assert_eq!( | ||
| 151 | event_coalescer.state, | ||
| 152 | Some(PeriodData { | ||
| 153 | start: period_start, | ||
| 154 | end: None, | ||
| 155 | environment: environment_1, | ||
| 156 | }) | ||
| 157 | ); | ||
| 158 | |||
| 159 | let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); | ||
| 160 | let period_end = period_start + within_timeout_adjustment; | ||
| 161 | let period_data = event_coalescer.log_event_with_time(period_end, environment_1); | ||
| 162 | |||
| 163 | assert_eq!(period_data, None); | ||
| 164 | assert_eq!( | ||
| 165 | event_coalescer.state, | ||
| 166 | Some(PeriodData { | ||
| 167 | start: period_start, | ||
| 168 | end: Some(period_end), | ||
| 169 | environment: environment_1, | ||
| 170 | }) | ||
| 171 | ); | ||
| 172 | |||
| 173 | // Logging an event within the timeout but with a different environment should start a new period | ||
| 174 | let period_end = period_end + within_timeout_adjustment; | ||
| 175 | let environment_2 = "environment_2"; | ||
| 176 | let period_data = event_coalescer.log_event_with_time(period_end, environment_2); | ||
| 177 | |||
| 178 | assert_eq!(period_data, Some((period_start, period_end, environment_1))); | ||
| 179 | assert_eq!( | ||
| 180 | event_coalescer.state, | ||
| 181 | Some(PeriodData { | ||
| 182 | start: period_end, | ||
| 183 | end: None, | ||
| 184 | environment: environment_2, | ||
| 185 | }) | ||
| 186 | ); | ||
| 187 | } | ||
| 188 | |||
| 189 | #[test] | ||
| 190 | fn test_switching_environment_while_within_timeout() { | ||
| 191 | let environment_1 = "environment_1"; | ||
| 192 | let mut event_coalescer = EventCoalescer::new(); | ||
| 193 | |||
| 194 | assert_eq!(event_coalescer.state, None); | ||
| 195 | |||
| 196 | let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); | ||
| 197 | let period_data = event_coalescer.log_event_with_time(period_start, environment_1); | ||
| 198 | |||
| 199 | assert_eq!(period_data, None); | ||
| 200 | assert_eq!( | ||
| 201 | event_coalescer.state, | ||
| 202 | Some(PeriodData { | ||
| 203 | start: period_start, | ||
| 204 | end: None, | ||
| 205 | environment: environment_1, | ||
| 206 | }) | ||
| 207 | ); | ||
| 208 | |||
| 209 | let within_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT / 2).unwrap(); | ||
| 210 | let period_end = period_start + within_timeout_adjustment; | ||
| 211 | let environment_2 = "environment_2"; | ||
| 212 | let period_data = event_coalescer.log_event_with_time(period_end, environment_2); | ||
| 213 | |||
| 214 | assert_eq!(period_data, Some((period_start, period_end, environment_1))); | ||
| 215 | assert_eq!( | ||
| 216 | event_coalescer.state, | ||
| 217 | Some(PeriodData { | ||
| 218 | start: period_end, | ||
| 219 | end: None, | ||
| 220 | environment: environment_2, | ||
| 221 | }) | ||
| 222 | ); | ||
| 223 | } | ||
| 224 | // // 0 20 40 60 | ||
| 225 | // // |-------------------|-------------------|-------------------|------------------- | ||
| 226 | // // |--------|----------env change | ||
| 227 | // // |------------------- | ||
| 228 | // // |period_start |period_end | ||
| 229 | // // |new_period_start | ||
| 230 | |||
| 231 | #[test] | ||
| 232 | fn test_switching_environment_while_exceeding_timeout() { | ||
| 233 | let environment_1 = "environment_1"; | ||
| 234 | let mut event_coalescer = EventCoalescer::new(); | ||
| 235 | |||
| 236 | assert_eq!(event_coalescer.state, None); | ||
| 237 | |||
| 238 | let period_start = Utc.with_ymd_and_hms(1990, 4, 12, 0, 0, 0).unwrap(); | ||
| 239 | let period_data = event_coalescer.log_event_with_time(period_start, environment_1); | ||
| 240 | |||
| 241 | assert_eq!(period_data, None); | ||
| 242 | assert_eq!( | ||
| 243 | event_coalescer.state, | ||
| 244 | Some(PeriodData { | ||
| 245 | start: period_start, | ||
| 246 | end: None, | ||
| 247 | environment: environment_1, | ||
| 248 | }) | ||
| 249 | ); | ||
| 250 | |||
| 251 | let exceed_timeout_adjustment = Duration::from_std(COALESCE_TIMEOUT * 2).unwrap(); | ||
| 252 | let period_end = period_start + exceed_timeout_adjustment; | ||
| 253 | let environment_2 = "environment_2"; | ||
| 254 | let period_data = event_coalescer.log_event_with_time(period_end, environment_2); | ||
| 255 | |||
| 256 | assert_eq!( | ||
| 257 | period_data, | ||
| 258 | Some(( | ||
| 259 | period_start, | ||
| 260 | period_start + SIMULATED_DURATION_FOR_SINGLE_EVENT, | ||
| 261 | environment_1 | ||
| 262 | )) | ||
| 263 | ); | ||
| 264 | assert_eq!( | ||
| 265 | event_coalescer.state, | ||
| 266 | Some(PeriodData { | ||
| 267 | start: period_end, | ||
| 268 | end: None, | ||
| 269 | environment: environment_2, | ||
| 270 | }) | ||
| 271 | ); | ||
| 272 | } | ||
| 273 | // 0 20 40 60 | ||
| 274 | // |-------------------|-------------------|-------------------|------------------- | ||
| 275 | // |--------|----------------------------------------env change | ||
| 276 | // |-------------------| | ||
| 277 | // |period_start |period_end | ||
| 278 | // |new_period_start | ||
| 279 | } | ||
| @@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"] · 2 unmodified lines | |||
| 3 | default-run = "collab" | 3 | default-run = "collab" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | name = "collab" | 5 | name = "collab" |
| 6 | version = "0.36.0" | 6 | version = "0.36.1" |
| 7 | publish = false | 7 | publish = false |
| 8 | 8 | ||
| 9 | [[bin]] | 9 | [[bin]] |
| @@ -37,7 +37,7 @@ CREATE INDEX "index_contacts_user_id_b" ON "contacts" ("user_id_b"); · 36 unmodified lines | |||
| 37 | CREATE TABLE "rooms" ( | 37 | CREATE TABLE "rooms" ( |
| 38 | "id" INTEGER PRIMARY KEY AUTOINCREMENT, | 38 | "id" INTEGER PRIMARY KEY AUTOINCREMENT, |
| 39 | "live_kit_room" VARCHAR NOT NULL, | 39 | "live_kit_room" VARCHAR NOT NULL, |
| 40 | "enviroment" VARCHAR, | 40 | "environment" VARCHAR, |
| 41 | "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE | 41 | "channel_id" INTEGER REFERENCES channels (id) ON DELETE CASCADE |
| 42 | ); | 42 | ); |
| 43 | CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); | 43 | CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); |
| @@ -0,0 +1 @@ | |||
| 1 | ALTER TABLE rooms ADD COLUMN environment TEXT; | ||
| @@ -1180,7 +1180,7 @@ impl Database { · 1179 unmodified lines | |||
| 1180 | .await?; | 1180 | .await?; |
| 1181 | 1181 | ||
| 1182 | let room_id = if let Some(room) = room { | 1182 | let room_id = if let Some(room) = room { |
| 1183 | if let Some(env) = room.enviroment { | 1183 | if let Some(env) = room.environment { |
| 1184 | if &env != environment { | 1184 | if &env != environment { |
| 1185 | Err(anyhow!("must join using the {} release", env))?; | 1185 | Err(anyhow!("must join using the {} release", env))?; |
| 1186 | } | 1186 | } |
| @@ -1190,7 +1190,7 @@ impl Database { · 3 unmodified lines | |||
| 1190 | let result = room::Entity::insert(room::ActiveModel { | 1190 | let result = room::Entity::insert(room::ActiveModel { |
| 1191 | channel_id: ActiveValue::Set(Some(channel_id)), | 1191 | channel_id: ActiveValue::Set(Some(channel_id)), |
| 1192 | live_kit_room: ActiveValue::Set(live_kit_room.to_string()), | 1192 | live_kit_room: ActiveValue::Set(live_kit_room.to_string()), |
| 1193 | enviroment: ActiveValue::Set(Some(environment.to_string())), | 1193 | environment: ActiveValue::Set(Some(environment.to_string())), |
| 1194 | ..Default::default() | 1194 | ..Default::default() |
| 1195 | }) | 1195 | }) |
| 1196 | .exec(&*tx) | 1196 | .exec(&*tx) |
| @@ -112,7 +112,7 @@ impl Database { · 111 unmodified lines | |||
| 112 | self.transaction(|tx| async move { | 112 | self.transaction(|tx| async move { |
| 113 | let room = room::ActiveModel { | 113 | let room = room::ActiveModel { |
| 114 | live_kit_room: ActiveValue::set(live_kit_room.into()), | 114 | live_kit_room: ActiveValue::set(live_kit_room.into()), |
| 115 | enviroment: ActiveValue::set(Some(release_channel.to_string())), | 115 | environment: ActiveValue::set(Some(release_channel.to_string())), |
| 116 | ..Default::default() | 116 | ..Default::default() |
| 117 | } | 117 | } |
| 118 | .insert(&*tx) | 118 | .insert(&*tx) |
| @@ -299,28 +299,28 @@ impl Database { · 180 unmodified lines | |||
| 299 | room_id: RoomId, | 299 | room_id: RoomId, |
| 300 | user_id: UserId, | 300 | user_id: UserId, |
| 301 | connection: ConnectionId, | 301 | connection: ConnectionId, |
| 302 | enviroment: &str, | 302 | environment: &str, |
| 303 | ) -> Result<RoomGuard<JoinRoom>> { | 303 | ) -> Result<RoomGuard<JoinRoom>> { |
| 304 | self.room_transaction(room_id, |tx| async move { | 304 | self.room_transaction(room_id, |tx| async move { |
| 305 | #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] | 305 | #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] |
| 306 | enum QueryChannelIdAndEnviroment { | 306 | enum QueryChannelIdAndEnvironment { |
| 307 | ChannelId, | 307 | ChannelId, |
| 308 | Enviroment, | 308 | Environment, |
| 309 | } | 309 | } |
| 310 | 310 | ||
| 311 | let (channel_id, release_channel): (Option<ChannelId>, Option<String>) = | 311 | let (channel_id, release_channel): (Option<ChannelId>, Option<String>) = |
| 312 | room::Entity::find() | 312 | room::Entity::find() |
| 313 | .select_only() | 313 | .select_only() |
| 314 | .column(room::Column::ChannelId) | 314 | .column(room::Column::ChannelId) |
| 315 | .column(room::Column::Enviroment) | 315 | .column(room::Column::Environment) |
| 316 | .filter(room::Column::Id.eq(room_id)) | 316 | .filter(room::Column::Id.eq(room_id)) |
| 317 | .into_values::<_, QueryChannelIdAndEnviroment>() | 317 | .into_values::<_, QueryChannelIdAndEnvironment>() |
| 318 | .one(&*tx) | 318 | .one(&*tx) |
| 319 | .await? | 319 | .await? |
| 320 | .ok_or_else(|| anyhow!("no such room"))?; | 320 | .ok_or_else(|| anyhow!("no such room"))?; |
| 321 | 321 | ||
| 322 | if let Some(release_channel) = release_channel { | 322 | if let Some(release_channel) = release_channel { |
| 323 | if &release_channel != enviroment { | 323 | if &release_channel != environment { |
| 324 | Err(anyhow!("must join using the {} release", release_channel))?; | 324 | Err(anyhow!("must join using the {} release", release_channel))?; |
| 325 | } | 325 | } |
| 326 | } | 326 | } |
| @@ -8,7 +8,7 @@ pub struct Model { · 7 unmodified lines | |||
| 8 | pub id: RoomId, | 8 | pub id: RoomId, |
| 9 | pub live_kit_room: String, | 9 | pub live_kit_room: String, |
| 10 | pub channel_id: Option<ChannelId>, | 10 | pub channel_id: Option<ChannelId>, |
| 11 | pub enviroment: Option<String>, | 11 | pub environment: Option<String>, |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] | 14 | #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] |
| @@ -599,152 +599,6 @@ async fn test_channel_buffers_and_server_restarts( · 598 unmodified lines | |||
| 599 | }); | 599 | }); |
| 600 | } | 600 | } |
| 601 | 601 | ||
| 602 | #[gpui::test(iterations = 10)] | ||
| 603 | async fn test_following_to_channel_notes_without_a_shared_project( | ||
| 604 | deterministic: BackgroundExecutor, | ||
| 605 | mut cx_a: &mut TestAppContext, | ||
| 606 | mut cx_b: &mut TestAppContext, | ||
| 607 | mut cx_c: &mut TestAppContext, | ||
| 608 | ) { | ||
| 609 | let mut server = TestServer::start(deterministic.clone()).await; | ||
| 610 | let client_a = server.create_client(cx_a, "user_a").await; | ||
| 611 | let client_b = server.create_client(cx_b, "user_b").await; | ||
| 612 | |||
| 613 | let client_c = server.create_client(cx_c, "user_c").await; | ||
| 614 | |||
| 615 | cx_a.update(editor::init); | ||
| 616 | cx_b.update(editor::init); | ||
| 617 | cx_c.update(editor::init); | ||
| 618 | cx_a.update(collab_ui::channel_view::init); | ||
| 619 | cx_b.update(collab_ui::channel_view::init); | ||
| 620 | cx_c.update(collab_ui::channel_view::init); | ||
| 621 | |||
| 622 | let channel_1_id = server | ||
| 623 | .make_channel( | ||
| 624 | "channel-1", | ||
| 625 | None, | ||
| 626 | (&client_a, cx_a), | ||
| 627 | &mut [(&client_b, cx_b), (&client_c, cx_c)], | ||
| 628 | ) | ||
| 629 | .await; | ||
| 630 | let channel_2_id = server | ||
| 631 | .make_channel( | ||
| 632 | "channel-2", | ||
| 633 | None, | ||
| 634 | (&client_a, cx_a), | ||
| 635 | &mut [(&client_b, cx_b), (&client_c, cx_c)], | ||
| 636 | ) | ||
| 637 | .await; | ||
| 638 | |||
| 639 | // Clients A, B, and C join a channel. | ||
| 640 | let active_call_a = cx_a.read(ActiveCall::global); | ||
| 641 | let active_call_b = cx_b.read(ActiveCall::global); | ||
| 642 | let active_call_c = cx_c.read(ActiveCall::global); | ||
| 643 | for (call, cx) in [ | ||
| 644 | (&active_call_a, &mut cx_a), | ||
| 645 | (&active_call_b, &mut cx_b), | ||
| 646 | (&active_call_c, &mut cx_c), | ||
| 647 | ] { | ||
| 648 | call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) | ||
| 649 | .await | ||
| 650 | .unwrap(); | ||
| 651 | } | ||
| 652 | deterministic.run_until_parked(); | ||
| 653 | |||
| 654 | // Clients A, B, and C all open their own unshared projects. | ||
| 655 | client_a.fs().insert_tree("/a", json!({})).await; | ||
| 656 | client_b.fs().insert_tree("/b", json!({})).await; | ||
| 657 | client_c.fs().insert_tree("/c", json!({})).await; | ||
| 658 | let (project_a, _) = client_a.build_local_project("/a", cx_a).await; | ||
| 659 | let (project_b, _) = client_b.build_local_project("/b", cx_b).await; | ||
| 660 | let (project_c, _) = client_b.build_local_project("/c", cx_c).await; | ||
| 661 | let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); | ||
| 662 | let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); | ||
| 663 | let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); | ||
| 664 | |||
| 665 | active_call_a | ||
| 666 | .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) | ||
| 667 | .await | ||
| 668 | .unwrap(); | ||
| 669 | |||
| 670 | // Client A opens the notes for channel 1. | ||
| 671 | let channel_view_1_a = cx_a | ||
| 672 | .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) | ||
| 673 | .await | ||
| 674 | .unwrap(); | ||
| 675 | channel_view_1_a.update(cx_a, |notes, cx| { | ||
| 676 | assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); | ||
| 677 | notes.editor.update(cx, |editor, cx| { | ||
| 678 | editor.insert("Hello from A.", cx); | ||
| 679 | editor.change_selections(None, cx, |selections| { | ||
| 680 | selections.select_ranges(vec![3..4]); | ||
| 681 | }); | ||
| 682 | }); | ||
| 683 | }); | ||
| 684 | |||
| 685 | // Client B follows client A. | ||
| 686 | workspace_b | ||
| 687 | .update(cx_b, |workspace, cx| { | ||
| 688 | workspace | ||
| 689 | .start_following(client_a.peer_id().unwrap(), cx) | ||
| 690 | .unwrap() | ||
| 691 | }) | ||
| 692 | .await | ||
| 693 | .unwrap(); | ||
| 694 | |||
| 695 | // Client B is taken to the notes for channel 1, with the same | ||
| 696 | // text selected as client A. | ||
| 697 | deterministic.run_until_parked(); | ||
| 698 | let channel_view_1_b = workspace_b.update(cx_b, |workspace, cx| { | ||
| 699 | assert_eq!( | ||
| 700 | workspace.leader_for_pane(workspace.active_pane()), | ||
| 701 | Some(client_a.peer_id().unwrap()) | ||
| 702 | ); | ||
| 703 | workspace | ||
| 704 | .active_item(cx) | ||
| 705 | .expect("no active item") | ||
| 706 | .downcast::<ChannelView>() | ||
| 707 | .expect("active item is not a channel view") | ||
| 708 | }); | ||
| 709 | channel_view_1_b.update(cx_b, |notes, cx| { | ||
| 710 | assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); | ||
| 711 | let editor = notes.editor.read(cx); | ||
| 712 | assert_eq!(editor.text(cx), "Hello from A."); | ||
| 713 | assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]); | ||
| 714 | }); | ||
| 715 | |||
| 716 | // Client A opens the notes for channel 2. | ||
| 717 | eprintln!("opening -------------------->"); | ||
| 718 | |||
| 719 | let channel_view_2_a = cx_a | ||
| 720 | .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) | ||
| 721 | .await | ||
| 722 | .unwrap(); | ||
| 723 | channel_view_2_a.update(cx_a, |notes, cx| { | ||
| 724 | assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); | ||
| 725 | }); | ||
| 726 | |||
| 727 | // Client B is taken to the notes for channel 2. | ||
| 728 | deterministic.run_until_parked(); | ||
| 729 | |||
| 730 | eprintln!("opening <--------------------"); | ||
| 731 | |||
| 732 | let channel_view_2_b = workspace_b.update(cx_b, |workspace, cx| { | ||
| 733 | assert_eq!( | ||
| 734 | workspace.leader_for_pane(workspace.active_pane()), | ||
| 735 | Some(client_a.peer_id().unwrap()) | ||
| 736 | ); | ||
| 737 | workspace | ||
| 738 | .active_item(cx) | ||
| 739 | .expect("no active item") | ||
| 740 | .downcast::<ChannelView>() | ||
| 741 | .expect("active item is not a channel view") | ||
| 742 | }); | ||
| 743 | channel_view_2_b.update(cx_b, |notes, cx| { | ||
| 744 | assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); | ||
| 745 | }); | ||
| 746 | } | ||
| 747 | |||
| 748 | #[gpui::test] | 602 | #[gpui::test] |
| 749 | async fn test_channel_buffer_changes( | 603 | async fn test_channel_buffer_changes( |
| 750 | deterministic: BackgroundExecutor, | 604 | deterministic: BackgroundExecutor, |
| @@ -104,12 +104,10 @@ async fn test_channel_guest_promotion(cx_a: &mut TestAppContext, cx_b: &mut Test · 103 unmodified lines | |||
| 104 | }); | 104 | }); |
| 105 | assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); | 105 | assert!(project_b.read_with(cx_b, |project, _| project.is_read_only())); |
| 106 | assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); | 106 | assert!(editor_b.update(cx_b, |e, cx| e.read_only(cx))); |
| 107 | assert!(dbg!( | 107 | assert!(room_b |
| 108 | room_b | 108 | .update(cx_b, |room, cx| room.share_microphone(cx)) |
| 109 | .update(cx_b, |room, cx| room.share_microphone(cx)) | 109 | .await |
| 110 | .await | 110 | .is_err()); |
| 111 | ) | ||
| 112 | .is_err()); | ||
| 113 | 111 | ||
| 114 | // B is promoted | 112 | // B is promoted |
| 115 | active_call_a | 113 | active_call_a |
| @@ -1,7 +1,9 @@ | |||
| 1 | use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; | 1 | use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; |
| 2 | use channel::{ChannelChat, ChannelMessageId, MessageParams}; | 2 | use channel::{ChannelChat, ChannelMessageId, MessageParams}; |
| 3 | use collab_ui::chat_panel::ChatPanel; | ||
| 3 | use gpui::{BackgroundExecutor, Model, TestAppContext}; | 4 | use gpui::{BackgroundExecutor, Model, TestAppContext}; |
| 4 | use rpc::Notification; | 5 | use rpc::Notification; |
| 6 | use workspace::dock::Panel; | ||
| 5 | 7 | ||
| 6 | #[gpui::test] | 8 | #[gpui::test] |
| 7 | async fn test_basic_channel_messages( | 9 | async fn test_basic_channel_messages( |
| @@ -273,135 +275,135 @@ fn assert_messages(chat: &Model<ChannelChat>, messages: &[&str], cx: &mut TestAp · 265 unmodified lines | |||
| 273 | ); | 275 | ); |
| 274 | } | 276 | } |
| 275 | 277 | ||
| 276 | //todo!(collab_ui) | 278 | #[gpui::test] |
| 277 | // #[gpui::test] | 279 | async fn test_channel_message_changes( |
| 278 | // async fn test_channel_message_changes( | 280 | executor: BackgroundExecutor, |
| 279 | // executor: BackgroundExecutor, | 281 | cx_a: &mut TestAppContext, |
| 280 | // cx_a: &mut TestAppContext, | 282 | cx_b: &mut TestAppContext, |
| 281 | // cx_b: &mut TestAppContext, | 283 | ) { |
| 282 | // ) { | 284 | let mut server = TestServer::start(executor.clone()).await; |
| 283 | // let mut server = TestServer::start(&executor).await; | 285 | let client_a = server.create_client(cx_a, "user_a").await; |
| 284 | // let client_a = server.create_client(cx_a, "user_a").await; | 286 | let client_b = server.create_client(cx_b, "user_b").await; |
| 285 | // let client_b = server.create_client(cx_b, "user_b").await; | 287 | |
| 286 | 288 | let channel_id = server | |
| 287 | // let channel_id = server | 289 | .make_channel( |
| 288 | // .make_channel( | 290 | "the-channel", |
| 289 | // "the-channel", | 291 | None, |
| 290 | // None, | 292 | (&client_a, cx_a), |
| 291 | // (&client_a, cx_a), | 293 | &mut [(&client_b, cx_b)], |
| 292 | // &mut [(&client_b, cx_b)], | 294 | ) |
| 293 | // ) | 295 | .await; |
| 294 | // .await; | 296 | |
| 295 | 297 | // Client A sends a message, client B should see that there is a new message. | |
| 296 | // // Client A sends a message, client B should see that there is a new message. | 298 | let channel_chat_a = client_a |
| 297 | // let channel_chat_a = client_a | 299 | .channel_store() |
| 298 | // .channel_store() | 300 | .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx)) |
| 299 | // .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx)) | 301 | .await |
| 300 | // .await | 302 | .unwrap(); |
| 301 | // .unwrap(); | 303 | |
| 302 | 304 | channel_chat_a | |
| 303 | // channel_chat_a | 305 | .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap()) |
| 304 | // .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap()) | 306 | .await |
| 305 | // .await | 307 | .unwrap(); |
| 306 | // .unwrap(); | 308 | |
| 307 | 309 | executor.run_until_parked(); | |
| 308 | // executor.run_until_parked(); | 310 | |
| 309 | 311 | let b_has_messages = cx_b.update(|cx| { | |
| 310 | // let b_has_messages = cx_b.read_with(|cx| { | 312 | client_b |
| 311 | // client_b | 313 | .channel_store() |
| 312 | // .channel_store() | 314 | .read(cx) |
| 313 | // .read(cx) | 315 | .has_new_messages(channel_id) |
| 314 | // .has_new_messages(channel_id) | 316 | .unwrap() |
| 315 | // .unwrap() | 317 | }); |
| 316 | // }); | 318 | |
| 317 | 319 | assert!(b_has_messages); | |
| 318 | // assert!(b_has_messages); | 320 | |
| 319 | 321 | // Opening the chat should clear the changed flag. | |
| 320 | // // Opening the chat should clear the changed flag. | 322 | cx_b.update(|cx| { |
| 321 | // cx_b.update(|cx| { | 323 | collab_ui::init(&client_b.app_state, cx); |
| 322 | // collab_ui::init(&client_b.app_state, cx); | 324 | }); |
| 323 | // }); | 325 | let project_b = client_b.build_empty_local_project(cx_b); |
| 324 | // let project_b = client_b.build_empty_local_project(cx_b); | 326 | let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); |
| 325 | // let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b); | 327 | |
| 326 | // let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx)); | 328 | let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx)); |
| 327 | // chat_panel_b | 329 | chat_panel_b |
| 328 | // .update(cx_b, |chat_panel, cx| { | 330 | .update(cx_b, |chat_panel, cx| { |
| 329 | // chat_panel.set_active(true, cx); | 331 | chat_panel.set_active(true, cx); |
| 330 | // chat_panel.select_channel(channel_id, None, cx) | 332 | chat_panel.select_channel(channel_id, None, cx) |
| 331 | // }) | 333 | }) |
| 332 | // .await | 334 | .await |
| 333 | // .unwrap(); | 335 | .unwrap(); |
| 334 | 336 | ||
| 335 | // executor.run_until_parked(); | 337 | executor.run_until_parked(); |
| 336 | 338 | ||
| 337 | // let b_has_messages = cx_b.read_with(|cx| { | 339 | let b_has_messages = cx_b.update(|cx| { |
| 338 | // client_b | 340 | client_b |
| 339 | // .channel_store() | 341 | .channel_store() |
| 340 | // .read(cx) | 342 | .read(cx) |
| 341 | // .has_new_messages(channel_id) | 343 | .has_new_messages(channel_id) |
| 342 | // .unwrap() | 344 | .unwrap() |
| 343 | // }); | 345 | }); |
| 344 | 346 | ||
| 345 | // assert!(!b_has_messages); | 347 | assert!(!b_has_messages); |
| 346 | 348 | ||
| 347 | // // Sending a message while the chat is open should not change the flag. | 349 | // Sending a message while the chat is open should not change the flag. |
| 348 | // channel_chat_a | 350 | channel_chat_a |
| 349 | // .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap()) | 351 | .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap()) |
| 350 | // .await | 352 | .await |
| 351 | // .unwrap(); | 353 | .unwrap(); |
| 352 | 354 | ||
| 353 | // executor.run_until_parked(); | 355 | executor.run_until_parked(); |
| 354 | 356 | ||
| 355 | // let b_has_messages = cx_b.read_with(|cx| { | 357 | let b_has_messages = cx_b.update(|cx| { |
| 356 | // client_b | 358 | client_b |
| 357 | // .channel_store() | 359 | .channel_store() |
| 358 | // .read(cx) | 360 | .read(cx) |
| 359 | // .has_new_messages(channel_id) | 361 | .has_new_messages(channel_id) |
| 360 | // .unwrap() | 362 | .unwrap() |
| 361 | // }); | 363 | }); |
| 362 | 364 | ||
| 363 | // assert!(!b_has_messages); | 365 | assert!(!b_has_messages); |
| 364 | 366 | ||
| 365 | // // Sending a message while the chat is closed should change the flag. | 367 | // Sending a message while the chat is closed should change the flag. |
| 366 | // chat_panel_b.update(cx_b, |chat_panel, cx| { | 368 | chat_panel_b.update(cx_b, |chat_panel, cx| { |
| 367 | // chat_panel.set_active(false, cx); | 369 | chat_panel.set_active(false, cx); |
| 368 | // }); | 370 | }); |
| 369 | 371 | ||
| 370 | // // Sending a message while the chat is open should not change the flag. | 372 | // Sending a message while the chat is open should not change the flag. |
| 371 | // channel_chat_a | 373 | channel_chat_a |
| 372 | // .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap()) | 374 | .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap()) |
| 373 | // .await | 375 | .await |
| 374 | // .unwrap(); | 376 | .unwrap(); |
| 375 | 377 | ||
| 376 | // executor.run_until_parked(); | 378 | executor.run_until_parked(); |
| 377 | 379 | ||
| 378 | // let b_has_messages = cx_b.read_with(|cx| { | 380 | let b_has_messages = cx_b.update(|cx| { |
| 379 | // client_b | 381 | client_b |
| 380 | // .channel_store() | 382 | .channel_store() |
| 381 | // .read(cx) | 383 | .read(cx) |
| 382 | // .has_new_messages(channel_id) | 384 | .has_new_messages(channel_id) |
| 383 | // .unwrap() | 385 | .unwrap() |
| 384 | // }); | 386 | }); |
| 385 | 387 | ||
| 386 | // assert!(b_has_messages); | 388 | assert!(b_has_messages); |
| 387 | 389 | ||
| 388 | // // Closing the chat should re-enable change tracking | 390 | // Closing the chat should re-enable change tracking |
| 389 | // cx_b.update(|_| drop(chat_panel_b)); | 391 | cx_b.update(|_| drop(chat_panel_b)); |
| 390 | 392 | ||
| 391 | // channel_chat_a | 393 | channel_chat_a |
| 392 | // .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap()) | 394 | .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap()) |
| 393 | // .await | 395 | .await |
| 394 | // .unwrap(); | 396 | .unwrap(); |
| 395 | 397 | ||
| 396 | // executor.run_until_parked(); | 398 | executor.run_until_parked(); |
| 397 | 399 | ||
| 398 | // let b_has_messages = cx_b.read_with(|cx| { | 400 | let b_has_messages = cx_b.update(|cx| { |
| 399 | // client_b | 401 | client_b |
| 400 | // .channel_store() | 402 | .channel_store() |
| 401 | // .read(cx) | 403 | .read(cx) |
| 402 | // .has_new_messages(channel_id) | 404 | .has_new_messages(channel_id) |
| 403 | // .unwrap() | 405 | .unwrap() |
| 404 | // }); | 406 | }); |
| 405 | 407 | ||
| 406 | // assert!(b_has_messages); | 408 | assert!(b_has_messages); |
| 407 | // } | 409 | } |
| @@ -1,9 +1,12 @@ | |||
| 1 | use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; | 1 | use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; |
| 2 | use call::ActiveCall; | 2 | use call::{ActiveCall, ParticipantLocation}; |
| 3 | use collab_ui::notifications::project_shared_notification::ProjectSharedNotification; | 3 | use collab_ui::{ |
| 4 | channel_view::ChannelView, | ||
| 5 | notifications::project_shared_notification::ProjectSharedNotification, | ||
| 6 | }; | ||
| 4 | use editor::{Editor, ExcerptRange, MultiBuffer}; | 7 | use editor::{Editor, ExcerptRange, MultiBuffer}; |
| 5 | use gpui::{ | 8 | use gpui::{ |
| 6 | point, BackgroundExecutor, Context, SharedString, TestAppContext, View, VisualContext, | 9 | point, BackgroundExecutor, Context, Entity, SharedString, TestAppContext, View, VisualContext, |
| 7 | VisualTestContext, | 10 | VisualTestContext, |
| 8 | }; | 11 | }; |
| 9 | use language::Capability; | 12 | use language::Capability; |
| @@ -1568,6 +1571,59 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut · 1558 unmodified lines | |||
| 1568 | }); | 1571 | }); |
| 1569 | } | 1572 | } |
| 1570 | 1573 | ||
| 1574 | #[gpui::test] | ||
| 1575 | async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) { | ||
| 1576 | let (client_a, client_b, channel_id) = TestServer::start2(cx_a, cx_b).await; | ||
| 1577 | |||
| 1578 | let (workspace_a, cx_a) = client_a.build_test_workspace(cx_a).await; | ||
| 1579 | client_a | ||
| 1580 | .host_workspace(&workspace_a, channel_id, cx_a) | ||
| 1581 | .await; | ||
| 1582 | let (workspace_b, cx_b) = client_b.join_workspace(channel_id, cx_b).await; | ||
| 1583 | |||
| 1584 | cx_a.simulate_keystrokes("cmd-p 2 enter"); | ||
| 1585 | cx_a.run_until_parked(); | ||
| 1586 | |||
| 1587 | let editor_a = workspace_a.update(cx_a, |workspace, cx| { | ||
| 1588 | workspace.active_item_as::<Editor>(cx).unwrap() | ||
| 1589 | }); | ||
| 1590 | let editor_b = workspace_b.update(cx_b, |workspace, cx| { | ||
| 1591 | workspace.active_item_as::<Editor>(cx).unwrap() | ||
| 1592 | }); | ||
| 1593 | |||
| 1594 | // b should follow a to position 1 | ||
| 1595 | editor_a.update(cx_a, |editor, cx| { | ||
| 1596 | editor.change_selections(None, cx, |s| s.select_ranges([1..1])) | ||
| 1597 | }); | ||
| 1598 | cx_a.run_until_parked(); | ||
| 1599 | editor_b.update(cx_b, |editor, cx| { | ||
| 1600 | assert_eq!(editor.selections.ranges(cx), vec![1..1]) | ||
| 1601 | }); | ||
| 1602 | |||
| 1603 | // a unshares the project | ||
| 1604 | cx_a.update(|cx| { | ||
| 1605 | let project = workspace_a.read(cx).project().clone(); | ||
| 1606 | ActiveCall::global(cx).update(cx, |call, cx| { | ||
| 1607 | call.unshare_project(project, cx).unwrap(); | ||
| 1608 | }) | ||
| 1609 | }); | ||
| 1610 | cx_a.run_until_parked(); | ||
| 1611 | |||
| 1612 | // b should not follow a to position 2 | ||
| 1613 | editor_a.update(cx_a, |editor, cx| { | ||
| 1614 | editor.change_selections(None, cx, |s| s.select_ranges([2..2])) | ||
| 1615 | }); | ||
| 1616 | cx_a.run_until_parked(); | ||
| 1617 | editor_b.update(cx_b, |editor, cx| { | ||
| 1618 | assert_eq!(editor.selections.ranges(cx), vec![1..1]) | ||
| 1619 | }); | ||
| 1620 | cx_b.update(|cx| { | ||
| 1621 | let room = ActiveCall::global(cx).read(cx).room().unwrap().read(cx); | ||
| 1622 | let participant = room.remote_participants().get(&client_a.id()).unwrap(); | ||
| 1623 | assert_eq!(participant.location, ParticipantLocation::UnsharedProject) | ||
| 1624 | }) | ||
| 1625 | } | ||
| 1626 | |||
| 1571 | #[gpui::test] | 1627 | #[gpui::test] |
| 1572 | async fn test_following_into_excluded_file( | 1628 | async fn test_following_into_excluded_file( |
| 1573 | mut cx_a: &mut TestAppContext, | 1629 | mut cx_a: &mut TestAppContext, |
| @@ -1593,9 +1649,6 @@ async fn test_following_into_excluded_file( · 19 unmodified lines | |||
| 1593 | let active_call_b = cx_b.read(ActiveCall::global); | 1649 | let active_call_b = cx_b.read(ActiveCall::global); |
| 1594 | let peer_id_a = client_a.peer_id().unwrap(); | 1650 | let peer_id_a = client_a.peer_id().unwrap(); |
| 1595 | 1651 | ||
| 1596 | cx_a.update(editor::init); | ||
| 1597 | cx_b.update(editor::init); | ||
| 1598 | |||
| 1599 | client_a | 1652 | client_a |
| 1600 | .fs() | 1653 | .fs() |
| 1601 | .insert_tree( | 1654 | .insert_tree( |
| @@ -1772,3 +1825,167 @@ fn pane_summaries(workspace: &View<Workspace>, cx: &mut VisualTestContext) -> Ve · 170 unmodified lines | |||
| 1772 | .collect() | 1825 | .collect() |
| 1773 | }) | 1826 | }) |
| 1774 | } | 1827 | } |
| 1828 | |||
| 1829 | #[gpui::test(iterations = 10)] | ||
| 1830 | async fn test_following_to_channel_notes_without_a_shared_project( | ||
| 1831 | deterministic: BackgroundExecutor, | ||
| 1832 | mut cx_a: &mut TestAppContext, | ||
| 1833 | mut cx_b: &mut TestAppContext, | ||
| 1834 | mut cx_c: &mut TestAppContext, | ||
| 1835 | ) { | ||
| 1836 | let mut server = TestServer::start(deterministic.clone()).await; | ||
| 1837 | let client_a = server.create_client(cx_a, "user_a").await; | ||
| 1838 | let client_b = server.create_client(cx_b, "user_b").await; | ||
| 1839 | let client_c = server.create_client(cx_c, "user_c").await; | ||
| 1840 | |||
| 1841 | cx_a.update(editor::init); | ||
| 1842 | cx_b.update(editor::init); | ||
| 1843 | cx_c.update(editor::init); | ||
| 1844 | cx_a.update(collab_ui::channel_view::init); | ||
| 1845 | cx_b.update(collab_ui::channel_view::init); | ||
| 1846 | cx_c.update(collab_ui::channel_view::init); | ||
| 1847 | |||
| 1848 | let channel_1_id = server | ||
| 1849 | .make_channel( | ||
| 1850 | "channel-1", | ||
| 1851 | None, | ||
| 1852 | (&client_a, cx_a), | ||
| 1853 | &mut [(&client_b, cx_b), (&client_c, cx_c)], | ||
| 1854 | ) | ||
| 1855 | .await; | ||
| 1856 | let channel_2_id = server | ||
| 1857 | .make_channel( | ||
| 1858 | "channel-2", | ||
| 1859 | None, | ||
| 1860 | (&client_a, cx_a), | ||
| 1861 | &mut [(&client_b, cx_b), (&client_c, cx_c)], | ||
| 1862 | ) | ||
| 1863 | .await; | ||
| 1864 | |||
| 1865 | // Clients A, B, and C join a channel. | ||
| 1866 | let active_call_a = cx_a.read(ActiveCall::global); | ||
| 1867 | let active_call_b = cx_b.read(ActiveCall::global); | ||
| 1868 | let active_call_c = cx_c.read(ActiveCall::global); | ||
| 1869 | for (call, cx) in [ | ||
| 1870 | (&active_call_a, &mut cx_a), | ||
| 1871 | (&active_call_b, &mut cx_b), | ||
| 1872 | (&active_call_c, &mut cx_c), | ||
| 1873 | ] { | ||
| 1874 | call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx)) | ||
| 1875 | .await | ||
| 1876 | .unwrap(); | ||
| 1877 | } | ||
| 1878 | deterministic.run_until_parked(); | ||
| 1879 | |||
| 1880 | // Clients A, B, and C all open their own unshared projects. | ||
| 1881 | client_a | ||
| 1882 | .fs() | ||
| 1883 | .insert_tree("/a", json!({ "1.txt": "" })) | ||
| 1884 | .await; | ||
| 1885 | client_b.fs().insert_tree("/b", json!({})).await; | ||
| 1886 | client_c.fs().insert_tree("/c", json!({})).await; | ||
| 1887 | let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; | ||
| 1888 | let (project_b, _) = client_b.build_local_project("/b", cx_b).await; | ||
| 1889 | let (project_c, _) = client_b.build_local_project("/c", cx_c).await; | ||
| 1890 | let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); | ||
| 1891 | let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); | ||
| 1892 | let (_workspace_c, _cx_c) = client_c.build_workspace(&project_c, cx_c); | ||
| 1893 | |||
| 1894 | active_call_a | ||
| 1895 | .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) | ||
| 1896 | .await | ||
| 1897 | .unwrap(); | ||
| 1898 | |||
| 1899 | // Client A opens the notes for channel 1. | ||
| 1900 | let channel_notes_1_a = cx_a | ||
| 1901 | .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx)) | ||
| 1902 | .await | ||
| 1903 | .unwrap(); | ||
| 1904 | channel_notes_1_a.update(cx_a, |notes, cx| { | ||
| 1905 | assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); | ||
| 1906 | notes.editor.update(cx, |editor, cx| { | ||
| 1907 | editor.insert("Hello from A.", cx); | ||
| 1908 | editor.change_selections(None, cx, |selections| { | ||
| 1909 | selections.select_ranges(vec![3..4]); | ||
| 1910 | }); | ||
| 1911 | }); | ||
| 1912 | }); | ||
| 1913 | |||
| 1914 | // Client B follows client A. | ||
| 1915 | workspace_b | ||
| 1916 | .update(cx_b, |workspace, cx| { | ||
| 1917 | workspace | ||
| 1918 | .start_following(client_a.peer_id().unwrap(), cx) | ||
| 1919 | .unwrap() | ||
| 1920 | }) | ||
| 1921 | .await | ||
| 1922 | .unwrap(); | ||
| 1923 | |||
| 1924 | // Client B is taken to the notes for channel 1, with the same | ||
| 1925 | // text selected as client A. | ||
| 1926 | deterministic.run_until_parked(); | ||
| 1927 | let channel_notes_1_b = workspace_b.update(cx_b, |workspace, cx| { | ||
| 1928 | assert_eq!( | ||
| 1929 | workspace.leader_for_pane(workspace.active_pane()), | ||
| 1930 | Some(client_a.peer_id().unwrap()) | ||
| 1931 | ); | ||
| 1932 | workspace | ||
| 1933 | .active_item(cx) | ||
| 1934 | .expect("no active item") | ||
| 1935 | .downcast::<ChannelView>() | ||
| 1936 | .expect("active item is not a channel view") | ||
| 1937 | }); | ||
| 1938 | channel_notes_1_b.update(cx_b, |notes, cx| { | ||
| 1939 | assert_eq!(notes.channel(cx).unwrap().name, "channel-1"); | ||
| 1940 | let editor = notes.editor.read(cx); | ||
| 1941 | assert_eq!(editor.text(cx), "Hello from A."); | ||
| 1942 | assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]); | ||
| 1943 | }); | ||
| 1944 | |||
| 1945 | // Client A opens the notes for channel 2. | ||
| 1946 | let channel_notes_2_a = cx_a | ||
| 1947 | .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx)) | ||
| 1948 | .await | ||
| 1949 | .unwrap(); | ||
| 1950 | channel_notes_2_a.update(cx_a, |notes, cx| { | ||
| 1951 | assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); | ||
| 1952 | }); | ||
| 1953 | |||
| 1954 | // Client B is taken to the notes for channel 2. | ||
| 1955 | deterministic.run_until_parked(); | ||
| 1956 | let channel_notes_2_b = workspace_b.update(cx_b, |workspace, cx| { | ||
| 1957 | assert_eq!( | ||
| 1958 | workspace.leader_for_pane(workspace.active_pane()), | ||
| 1959 | Some(client_a.peer_id().unwrap()) | ||
| 1960 | ); | ||
| 1961 | workspace | ||
| 1962 | .active_item(cx) | ||
| 1963 | .expect("no active item") | ||
| 1964 | .downcast::<ChannelView>() | ||
| 1965 | .expect("active item is not a channel view") | ||
| 1966 | }); | ||
| 1967 | channel_notes_2_b.update(cx_b, |notes, cx| { | ||
| 1968 | assert_eq!(notes.channel(cx).unwrap().name, "channel-2"); | ||
| 1969 | }); | ||
| 1970 | |||
| 1971 | // Client A opens a local buffer in their unshared project. | ||
| 1972 | let _unshared_editor_a1 = workspace_a | ||
| 1973 | .update(cx_a, |workspace, cx| { | ||
| 1974 | workspace.open_path((worktree_id, "1.txt"), None, true, cx) | ||
| 1975 | }) | ||
| 1976 | .await | ||
| 1977 | .unwrap() | ||
| 1978 | .downcast::<Editor>() | ||
| 1979 | .unwrap(); | ||
| 1980 | |||
| 1981 | // This does not send any leader update message to client B. | ||
| 1982 | // If it did, an error would occur on client B, since this buffer | ||
| 1983 | // is not shared with them. | ||
| 1984 | deterministic.run_until_parked(); | ||
| 1985 | workspace_b.update(cx_b, |workspace, cx| { | ||
| 1986 | assert_eq!( | ||
| 1987 | workspace.active_item(cx).expect("no active item").item_id(), | ||
| 1988 | channel_notes_2_b.entity_id() | ||
| 1989 | ); | ||
| 1990 | }); | ||
| 1991 | } | ||
| @@ -113,6 +113,20 @@ impl TestServer { · 112 unmodified lines | |||
| 113 | } | 113 | } |
| 114 | } | 114 | } |
| 115 | 115 | ||
| 116 | pub async fn start2( | ||
| 117 | cx_a: &mut TestAppContext, | ||
| 118 | cx_b: &mut TestAppContext, | ||
| 119 | ) -> (TestClient, TestClient, u64) { | ||
| 120 | let mut server = Self::start(cx_a.executor()).await; | ||
| 121 | let client_a = server.create_client(cx_a, "user_a").await; | ||
| 122 | let client_b = server.create_client(cx_b, "user_b").await; | ||
| 123 | let channel_id = server | ||
| 124 | .make_channel("a", None, (&client_a, cx_a), &mut [(&client_b, cx_b)]) | ||
| 125 | .await; | ||
| 126 | |||
| 127 | (client_a, client_b, channel_id) | ||
| 128 | } | ||
| 129 | |||
| 116 | pub async fn reset(&self) { | 130 | pub async fn reset(&self) { |
| 117 | self.app_state.db.reset(); | 131 | self.app_state.db.reset(); |
| 118 | let epoch = self | 132 | let epoch = self |
| @@ -619,14 +633,49 @@ impl TestClient { · 500 unmodified lines | |||
| 619 | "/a", | 633 | "/a", |
| 620 | json!({ | 634 | json!({ |
| 621 | "1.txt": "one\none\none", | 635 | "1.txt": "one\none\none", |
| 622 | "2.txt": "two\ntwo\ntwo", | 636 | "2.js": "function two() { return 2; }", |
| 623 | "3.txt": "three\nthree\nthree", | 637 | "3.rs": "mod test", |
| 624 | }), | 638 | }), |
| 625 | ) | 639 | ) |
| 626 | .await; | 640 | .await; |
| 627 | self.build_local_project("/a", cx).await.0 | 641 | self.build_local_project("/a", cx).await.0 |
| 628 | } | 642 | } |
| 629 | 643 | ||
| 644 | pub async fn host_workspace( | ||
| 645 | &self, | ||
| 646 | workspace: &View<Workspace>, | ||
| 647 | channel_id: u64, | ||
| 648 | cx: &mut VisualTestContext, | ||
| 649 | ) { | ||
| 650 | cx.update(|cx| { | ||
| 651 | let active_call = ActiveCall::global(cx); | ||
| 652 | active_call.update(cx, |call, cx| call.join_channel(channel_id, cx)) | ||
| 653 | }) | ||
| 654 | .await | ||
| 655 | .unwrap(); | ||
| 656 | cx.update(|cx| { | ||
| 657 | let active_call = ActiveCall::global(cx); | ||
| 658 | let project = workspace.read(cx).project().clone(); | ||
| 659 | active_call.update(cx, |call, cx| call.share_project(project, cx)) | ||
| 660 | }) | ||
| 661 | .await | ||
| 662 | .unwrap(); | ||
| 663 | cx.executor().run_until_parked(); | ||
| 664 | } | ||
| 665 | |||
| 666 | pub async fn join_workspace<'a>( | ||
| 667 | &'a self, | ||
| 668 | channel_id: u64, | ||
| 669 | cx: &'a mut TestAppContext, | ||
| 670 | ) -> (View<Workspace>, &'a mut VisualTestContext) { | ||
| 671 | cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx)) | ||
| 672 | .await | ||
| 673 | .unwrap(); | ||
| 674 | cx.run_until_parked(); | ||
| 675 | |||
| 676 | self.active_workspace(cx) | ||
| 677 | } | ||
| 678 | |||
| 630 | pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> { | 679 | pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> Model<Project> { |
| 631 | cx.update(|cx| { | 680 | cx.update(|cx| { |
| 632 | Project::local( | 681 | Project::local( |
| @@ -670,6 +719,17 @@ impl TestClient { · 37 unmodified lines | |||
| 670 | }) | 719 | }) |
| 671 | } | 720 | } |
| 672 | 721 | ||
| 722 | pub async fn build_test_workspace<'a>( | ||
| 723 | &'a self, | ||
| 724 | cx: &'a mut TestAppContext, | ||
| 725 | ) -> (View<Workspace>, &'a mut VisualTestContext) { | ||
| 726 | let project = self.build_test_project(cx).await; | ||
| 727 | cx.add_window_view(|cx| { | ||
| 728 | cx.activate_window(); | ||
| 729 | Workspace::new(0, project.clone(), self.app_state.clone(), cx) | ||
| 730 | }) | ||
| 731 | } | ||
| 732 | |||
| 673 | pub fn active_workspace<'a>( | 733 | pub fn active_workspace<'a>( |
| 674 | &'a self, | 734 | &'a self, |
| 675 | cx: &'a mut TestAppContext, | 735 | cx: &'a mut TestAppContext, |
| @@ -111,7 +111,6 @@ fn notification_window_options( · 110 unmodified lines | |||
| 111 | let screen_bounds = screen.bounds(); | 111 | let screen_bounds = screen.bounds(); |
| 112 | let size: Size<GlobalPixels> = window_size.into(); | 112 | let size: Size<GlobalPixels> = window_size.into(); |
| 113 | 113 | ||
| 114 | // todo!() use content bounds instead of screen.bounds and get rid of magics in point's 2nd argument. | ||
| 115 | let bounds = gpui::Bounds::<GlobalPixels> { | 114 | let bounds = gpui::Bounds::<GlobalPixels> { |
| 116 | origin: screen_bounds.upper_right() | 115 | origin: screen_bounds.upper_right() |
| 117 | - point( | 116 | - point( |
| @@ -11,7 +11,6 @@ use smol::future::yield_now; · 10 unmodified lines | |||
| 11 | use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration}; | 11 | use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration}; |
| 12 | use sum_tree::{Bias, Cursor, SumTree}; | 12 | use sum_tree::{Bias, Cursor, SumTree}; |
| 13 | use text::Patch; | 13 | use text::Patch; |
| 14 | use util::ResultExt; | ||
| 15 | 14 | ||
| 16 | pub use super::tab_map::TextSummary; | 15 | pub use super::tab_map::TextSummary; |
| 17 | pub type WrapEdit = text::Edit<u32>; | 16 | pub type WrapEdit = text::Edit<u32>; |
| @@ -154,26 +153,24 @@ impl WrapMap { · 136 unmodified lines | |||
| 154 | 153 | ||
| 155 | if let Some(wrap_width) = self.wrap_width { | 154 | if let Some(wrap_width) = self.wrap_width { |
| 156 | let mut new_snapshot = self.snapshot.clone(); | 155 | let mut new_snapshot = self.snapshot.clone(); |
| 157 | let mut edits = Patch::default(); | 156 | |
| 158 | let text_system = cx.text_system().clone(); | 157 | let text_system = cx.text_system().clone(); |
| 159 | let (font, font_size) = self.font_with_size.clone(); | 158 | let (font, font_size) = self.font_with_size.clone(); |
| 160 | let task = cx.background_executor().spawn(async move { | 159 | let task = cx.background_executor().spawn(async move { |
| 161 | if let Some(mut line_wrapper) = text_system.line_wrapper(font, font_size).log_err() | 160 | let mut line_wrapper = text_system.line_wrapper(font, font_size); |
| 162 | { | 161 | let tab_snapshot = new_snapshot.tab_snapshot.clone(); |
| 163 | let tab_snapshot = new_snapshot.tab_snapshot.clone(); | 162 | let range = TabPoint::zero()..tab_snapshot.max_point(); |
| 164 | let range = TabPoint::zero()..tab_snapshot.max_point(); | 163 | let edits = new_snapshot |
| 165 | edits = new_snapshot | 164 | .update( |
| 166 | .update( | 165 | tab_snapshot, |
| 167 | tab_snapshot, | 166 | &[TabEdit { |
| 168 | &[TabEdit { | 167 | old: range.clone(), |
| 169 | old: range.clone(), | 168 | new: range.clone(), |
| 170 | new: range.clone(), | 169 | }], |
| 171 | }], | 170 | wrap_width, |
| 172 | wrap_width, | 171 | &mut line_wrapper, |
| 173 | &mut line_wrapper, | 172 | ) |
| 174 | ) | 173 | .await; |
| 175 | .await; | ||
| 176 | } | ||
| 177 | (new_snapshot, edits) | 174 | (new_snapshot, edits) |
| 178 | }); | 175 | }); |
| 179 | 176 | ||
| @@ -245,15 +242,12 @@ impl WrapMap { · 65 unmodified lines | |||
| 245 | let (font, font_size) = self.font_with_size.clone(); | 242 | let (font, font_size) = self.font_with_size.clone(); |
| 246 | let update_task = cx.background_executor().spawn(async move { | 243 | let update_task = cx.background_executor().spawn(async move { |
| 247 | let mut edits = Patch::default(); | 244 | let mut edits = Patch::default(); |
| 248 | if let Some(mut line_wrapper) = | 245 | let mut line_wrapper = text_system.line_wrapper(font, font_size); |
| 249 | text_system.line_wrapper(font, font_size).log_err() | 246 | for (tab_snapshot, tab_edits) in pending_edits { |
| 250 | { | 247 | let wrap_edits = snapshot |
| 251 | for (tab_snapshot, tab_edits) in pending_edits { | 248 | .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) |
| 252 | let wrap_edits = snapshot | 249 | .await; |
| 253 | .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) | 250 | edits = edits.compose(&wrap_edits); |
| 254 | .await; | ||
| 255 | edits = edits.compose(&wrap_edits); | ||
| 256 | } | ||
| 257 | } | 251 | } |
| 258 | (snapshot, edits) | 252 | (snapshot, edits) |
| 259 | }); | 253 | }); |
| @@ -1043,7 +1037,7 @@ mod tests { · 783 unmodified lines | |||
| 1043 | 1037 | ||
| 1044 | #[gpui::test(iterations = 100)] | 1038 | #[gpui::test(iterations = 100)] |
| 1045 | async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { | 1039 | async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) { |
| 1046 | // todo!() this test is flaky | 1040 | // todo this test is flaky |
| 1047 | init_test(cx); | 1041 | init_test(cx); |
| 1048 | 1042 | ||
| 1049 | cx.background_executor.set_block_on_ticks(0..=50); | 1043 | cx.background_executor.set_block_on_ticks(0..=50); |
| @@ -1059,7 +1053,7 @@ mod tests { · 9 unmodified lines | |||
| 1059 | }; | 1053 | }; |
| 1060 | let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); | 1054 | let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap(); |
| 1061 | let font = font("Helvetica"); | 1055 | let font = font("Helvetica"); |
| 1062 | let _font_id = text_system.font_id(&font).unwrap(); | 1056 | let _font_id = text_system.font_id(&font); |
| 1063 | let font_size = px(14.0); | 1057 | let font_size = px(14.0); |
| 1064 | 1058 | ||
| 1065 | log::info!("Tab size: {}", tab_size); | 1059 | log::info!("Tab size: {}", tab_size); |
| @@ -1086,7 +1080,7 @@ mod tests { · 20 unmodified lines | |||
| 1086 | let tabs_snapshot = tab_map.set_max_expansion_column(32); | 1080 | let tabs_snapshot = tab_map.set_max_expansion_column(32); |
| 1087 | log::info!("TabMap text: {:?}", tabs_snapshot.text()); | 1081 | log::info!("TabMap text: {:?}", tabs_snapshot.text()); |
| 1088 | 1082 | ||
| 1089 | let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size).unwrap(); | 1083 | let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size); |
| 1090 | let unwrapped_text = tabs_snapshot.text(); | 1084 | let unwrapped_text = tabs_snapshot.text(); |
| 1091 | let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); | 1085 | let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); |
| 1092 | 1086 | ||
| @@ -7677,7 +7677,6 @@ impl Editor { · 7676 unmodified lines | |||
| 7677 | scrollbar_width: cx.editor_style.scrollbar_width, | 7677 | scrollbar_width: cx.editor_style.scrollbar_width, |
| 7678 | syntax: cx.editor_style.syntax.clone(), | 7678 | syntax: cx.editor_style.syntax.clone(), |
| 7679 | status: cx.editor_style.status.clone(), | 7679 | status: cx.editor_style.status.clone(), |
| 7680 | // todo!("what about the rest of the highlight style parts for inlays and suggestions?") | ||
| 7681 | inlays_style: HighlightStyle { | 7680 | inlays_style: HighlightStyle { |
| 7682 | color: Some(cx.theme().status().hint), | 7681 | color: Some(cx.theme().status().hint), |
| 7683 | font_weight: Some(FontWeight::BOLD), | 7682 | font_weight: Some(FontWeight::BOLD), |
| @@ -8678,6 +8677,10 @@ impl Editor { · 994 unmodified lines | |||
| 8678 | } | 8677 | } |
| 8679 | } | 8678 | } |
| 8680 | } | 8679 | } |
| 8680 | |||
| 8681 | let Some(project) = &self.project else { return }; | ||
| 8682 | let telemetry = project.read(cx).client().telemetry().clone(); | ||
| 8683 | telemetry.log_edit_event("editor"); | ||
| 8681 | } | 8684 | } |
| 8682 | multi_buffer::Event::ExcerptsAdded { | 8685 | multi_buffer::Event::ExcerptsAdded { |
| 8683 | buffer, | 8686 | buffer, |
| @@ -9346,7 +9349,6 @@ impl Render for Editor { · 662 unmodified lines | |||
| 9346 | scrollbar_width: px(12.), | 9349 | scrollbar_width: px(12.), |
| 9347 | syntax: cx.theme().syntax().clone(), | 9350 | syntax: cx.theme().syntax().clone(), |
| 9348 | status: cx.theme().status().clone(), | 9351 | status: cx.theme().status().clone(), |
| 9349 | // todo!("what about the rest of the highlight style parts?") | ||
| 9350 | inlays_style: HighlightStyle { | 9352 | inlays_style: HighlightStyle { |
| 9351 | color: Some(cx.theme().status().hint), | 9353 | color: Some(cx.theme().status().hint), |
| 9352 | font_weight: Some(FontWeight::BOLD), | 9354 | font_weight: Some(FontWeight::BOLD), |
| @@ -809,13 +809,18 @@ impl EditorElement { · 808 unmodified lines | |||
| 809 | // the hunk might include the rows of that header. | 809 | // the hunk might include the rows of that header. |
| 810 | // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap. | 810 | // Making the range inclusive doesn't quite cut it, as we rely on the exclusivity for the soft wrap. |
| 811 | // Instead, we simply check whether the range we're dealing with includes | 811 | // Instead, we simply check whether the range we're dealing with includes |
| 812 | // any custom elements and if so, we stop painting the diff hunk on the first row of that custom element. | 812 | // any excerpt headers and if so, we stop painting the diff hunk on the first row of that header. |
| 813 | let end_row_in_current_excerpt = layout | 813 | let end_row_in_current_excerpt = layout |
| 814 | .position_map | 814 | .position_map |
| 815 | .snapshot | 815 | .snapshot |
| 816 | .blocks_in_range(start_row..end_row) | 816 | .blocks_in_range(start_row..end_row) |
| 817 | .next() | 817 | .find_map(|(start_row, block)| { |
| 818 | .map(|(start_row, _)| start_row) | 818 | if matches!(block, TransformBlock::ExcerptHeader { .. }) { |
| 819 | Some(start_row) | ||
| 820 | } else { | ||
| 821 | None | ||
| 822 | } | ||
| 823 | }) | ||
| 819 | .unwrap_or(end_row); | 824 | .unwrap_or(end_row); |
| 820 | 825 | ||
| 821 | let start_y = start_row as f32 * line_height - scroll_top; | 826 | let start_y = start_row as f32 * line_height - scroll_top; |
| @@ -878,16 +883,23 @@ impl EditorElement { · 56 unmodified lines | |||
| 878 | let fold_corner_radius = 0.15 * layout.position_map.line_height; | 883 | let fold_corner_radius = 0.15 * layout.position_map.line_height; |
| 879 | cx.with_element_id(Some("folds"), |cx| { | 884 | cx.with_element_id(Some("folds"), |cx| { |
| 880 | let snapshot = &layout.position_map.snapshot; | 885 | let snapshot = &layout.position_map.snapshot; |
| 886 | |||
| 881 | for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) { | 887 | for fold in snapshot.folds_in_range(layout.visible_anchor_range.clone()) { |
| 882 | let fold_range = fold.range.clone(); | 888 | let fold_range = fold.range.clone(); |
| 883 | let display_range = fold.range.start.to_display_point(&snapshot) | 889 | let display_range = fold.range.start.to_display_point(&snapshot) |
| 884 | ..fold.range.end.to_display_point(&snapshot); | 890 | ..fold.range.end.to_display_point(&snapshot); |
| 885 | debug_assert_eq!(display_range.start.row(), display_range.end.row()); | 891 | debug_assert_eq!(display_range.start.row(), display_range.end.row()); |
| 886 | let row = display_range.start.row(); | 892 | let row = display_range.start.row(); |
| 893 | debug_assert!(row < layout.visible_display_row_range.end); | ||
| 894 | let Some(line_layout) = &layout | ||
| 895 | .position_map | ||
| 896 | .line_layouts | ||
| 897 | .get((row - layout.visible_display_row_range.start) as usize) | ||
| 898 | .map(|l| &l.line) | ||
| 899 | else { | ||
| 900 | continue; | ||
| 901 | }; | ||
| 887 | 902 | ||
| 888 | let line_layout = &layout.position_map.line_layouts | ||
| 889 | [(row - layout.visible_display_row_range.start) as usize] | ||
| 890 | .line; | ||
| 891 | let start_x = content_origin.x | 903 | let start_x = content_origin.x |
| 892 | + line_layout.x_for_index(display_range.start.column() as usize) | 904 | + line_layout.x_for_index(display_range.start.column() as usize) |
| 893 | - layout.position_map.scroll_position.x; | 905 | - layout.position_map.scroll_position.x; |
| @@ -1010,7 +1022,6 @@ impl EditorElement { · 116 unmodified lines | |||
| 1010 | .chars_at(cursor_position) | 1022 | .chars_at(cursor_position) |
| 1011 | .next() | 1023 | .next() |
| 1012 | .and_then(|(character, _)| { | 1024 | .and_then(|(character, _)| { |
| 1013 | // todo!() currently shape_line panics if text conatins newlines | ||
| 1014 | let text = if character == '\n' { | 1025 | let text = if character == '\n' { |
| 1015 | SharedString::from(" ") | 1026 | SharedString::from(" ") |
| 1016 | } else { | 1027 | } else { |
| @@ -2258,11 +2269,9 @@ impl EditorElement { · 1241 unmodified lines | |||
| 2258 | .map_or(range.context.start, |primary| primary.start); | 2269 | .map_or(range.context.start, |primary| primary.start); |
| 2259 | let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); | 2270 | let jump_position = language::ToPoint::to_point(&jump_anchor, buffer); |
| 2260 | 2271 | ||
| 2261 | let jump_handler = cx.listener_for(&self.editor, move |editor, _, cx| { | 2272 | cx.listener_for(&self.editor, move |editor, _, cx| { |
| 2262 | editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); | 2273 | editor.jump(jump_path.clone(), jump_position, jump_anchor, cx); |
| 2263 | }); | 2274 | }) |
| 2264 | |||
| 2265 | jump_handler | ||
| 2266 | }); | 2275 | }); |
| 2267 | 2276 | ||
| 2268 | let element = if *starts_new_buffer { | 2277 | let element = if *starts_new_buffer { |
| @@ -2342,34 +2351,25 @@ impl EditorElement { · 73 unmodified lines | |||
| 2342 | .text_color(cx.theme().colors().editor_line_number) | 2351 | .text_color(cx.theme().colors().editor_line_number) |
| 2343 | .child("..."), | 2352 | .child("..."), |
| 2344 | ) | 2353 | ) |
| 2345 | .map(|this| { | 2354 | .child( |
| 2346 | if let Some(jump_handler) = jump_handler { | 2355 | ButtonLike::new("jump to collapsed context") |
| 2347 | this.child( | 2356 | .style(ButtonStyle::Transparent) |
| 2348 | ButtonLike::new("jump to collapsed context") | 2357 | .full_width() |
| 2349 | .style(ButtonStyle::Transparent) | 2358 | .child( |
| 2350 | .full_width() | 2359 | div() |
| 2351 | .on_click(jump_handler) | 2360 | .h_px() |
| 2352 | .tooltip(|cx| { | 2361 | .w_full() |
| 2353 | Tooltip::for_action( | 2362 | .bg(cx.theme().colors().border_variant) |
| 2354 | "Jump to Buffer", | 2363 | .group_hover("", |style| { |
| 2355 | &OpenExcerpts, | 2364 | style.bg(cx.theme().colors().border) |
| 2356 | cx, | 2365 | }), |
| 2357 | ) | ||
| 2358 | }) | ||
| 2359 | .child( | ||
| 2360 | div() | ||
| 2361 | .h_px() | ||
| 2362 | .w_full() | ||
| 2363 | .bg(cx.theme().colors().border_variant) | ||
| 2364 | .group_hover("", |style| { | ||
| 2365 | style.bg(cx.theme().colors().border) | ||
| 2366 | }), | ||
| 2367 | ), | ||
| 2368 | ) | 2366 | ) |
| 2369 | } else { | 2367 | .when_some(jump_handler, |this, jump_handler| { |
| 2370 | this.child(div().size_full().bg(gpui::green())) | 2368 | this.on_click(jump_handler).tooltip(|cx| { |
| 2371 | } | 2369 | Tooltip::for_action("Jump to Buffer", &OpenExcerpts, cx) |
| 2372 | }) | 2370 | }) |
| 2371 | }), | ||
| 2372 | ) | ||
| 2373 | }; | 2373 | }; |
| 2374 | element.into_any() | 2374 | element.into_any() |
| 2375 | } | 2375 | } |
| @@ -2,13 +2,13 @@ use crate::{ · 1 unmodified line | |||
| 2 | display_map::{InlayOffset, ToDisplayPoint}, | 2 | display_map::{InlayOffset, ToDisplayPoint}, |
| 3 | link_go_to_definition::{InlayHighlight, RangeInEditor}, | 3 | link_go_to_definition::{InlayHighlight, RangeInEditor}, |
| 4 | Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, | 4 | Anchor, AnchorRangeExt, DisplayPoint, Editor, EditorSettings, EditorSnapshot, EditorStyle, |
| 5 | ExcerptId, RangeToAnchorExt, | 5 | ExcerptId, Hover, RangeToAnchorExt, |
| 6 | }; | 6 | }; |
| 7 | use futures::FutureExt; | 7 | use futures::FutureExt; |
| 8 | use gpui::{ | 8 | use gpui::{ |
| 9 | actions, div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model, | 9 | div, px, AnyElement, CursorStyle, Hsla, InteractiveElement, IntoElement, Model, MouseButton, |
| 10 | MouseButton, ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, | 10 | ParentElement, Pixels, SharedString, Size, StatefulInteractiveElement, Styled, Task, |
| 11 | Task, ViewContext, WeakView, | 11 | ViewContext, WeakView, |
| 12 | }; | 12 | }; |
| 13 | use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; | 13 | use language::{markdown, Bias, DiagnosticEntry, Language, LanguageRegistry, ParsedMarkdown}; |
| 14 | 14 | ||
| @@ -27,8 +27,6 @@ pub const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; · 12 unmodified lines | |||
| 27 | pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.); | 27 | pub const MIN_POPOVER_LINE_HEIGHT: Pixels = px(4.); |
| 28 | pub const HOVER_POPOVER_GAP: Pixels = px(10.); | 28 | pub const HOVER_POPOVER_GAP: Pixels = px(10.); |
| 29 | 29 | ||
| 30 | actions!(editor, [Hover]); | ||
| 31 | |||
| 32 | /// Bindable action which uses the most recent selection head to trigger a hover | 30 | /// Bindable action which uses the most recent selection head to trigger a hover |
| 33 | pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) { | 31 | pub fn hover(editor: &mut Editor, _: &Hover, cx: &mut ViewContext<Editor>) { |
| 34 | let head = editor.selections.newest_display(cx).head(); | 32 | let head = editor.selections.newest_display(cx).head(); |
| @@ -82,7 +82,9 @@ impl FollowableItem for Editor { · 81 unmodified lines | |||
| 82 | 82 | ||
| 83 | let pane = pane.downgrade(); | 83 | let pane = pane.downgrade(); |
| 84 | Some(cx.spawn(|mut cx| async move { | 84 | Some(cx.spawn(|mut cx| async move { |
| 85 | let mut buffers = futures::future::try_join_all(buffers).await?; | 85 | let mut buffers = futures::future::try_join_all(buffers) |
| 86 | .await | ||
| 87 | .debug_assert_ok("leaders don't share views for unshared buffers")?; | ||
| 86 | let editor = pane.update(&mut cx, |pane, cx| { | 88 | let editor = pane.update(&mut cx, |pane, cx| { |
| 87 | let mut editors = pane.items_of_type::<Self>(); | 89 | let mut editors = pane.items_of_type::<Self>(); |
| 88 | editors.find(|editor| { | 90 | editors.find(|editor| { |
| @@ -525,43 +525,4 @@ impl Render for FeedbackModal { · 524 unmodified lines | |||
| 525 | } | 525 | } |
| 526 | } | 526 | } |
| 527 | 527 | ||
| 528 | // TODO: Testing of various button states, dismissal prompts, etc. | 528 | // TODO: Testing of various button states, dismissal prompts, etc. :) |
| 529 | |||
| 530 | // #[cfg(test)] | ||
| 531 | // mod test { | ||
| 532 | // use super::*; | ||
| 533 | |||
| 534 | // #[test] | ||
| 535 | // fn test_invalid_email_addresses() { | ||
| 536 | // let markdown = markdown.await.log_err(); | ||
| 537 | // let buffer = project.update(&mut cx, |project, cx| { | ||
| 538 | // project.create_buffer("", markdown, cx) | ||
| 539 | // })??; | ||
| 540 | |||
| 541 | // workspace.update(&mut cx, |workspace, cx| { | ||
| 542 | // let system_specs = SystemSpecs::new(cx); | ||
| 543 | |||
| 544 | // workspace.toggle_modal(cx, move |cx| { | ||
| 545 | // let feedback_modal = FeedbackModal::new(system_specs, project, buffer, cx); | ||
| 546 | |||
| 547 | // assert!(!feedback_modal.can_submit()); | ||
| 548 | // assert!(!feedback_modal.valid_email_address(cx)); | ||
| 549 | // assert!(!feedback_modal.valid_character_count()); | ||
| 550 | |||
| 551 | // feedback_modal | ||
| 552 | // .email_address_editor | ||
| 553 | // .update(cx, |this, cx| this.set_text("a", cx)); | ||
| 554 | // feedback_modal.set_submission_state(cx); | ||
| 555 | |||
| 556 | // assert!(!feedback_modal.valid_email_address(cx)); | ||
| 557 | |||
| 558 | // feedback_modal | ||
| 559 | // .email_address_editor | ||
| 560 | // .update(cx, |this, cx| this.set_text("a&b.com", cx)); | ||
| 561 | // feedback_modal.set_submission_state(cx); | ||
| 562 | |||
| 563 | // assert!(feedback_modal.valid_email_address(cx)); | ||
| 564 | // }); | ||
| 565 | // })?; | ||
| 566 | // } | ||
| 567 | // } | ||
| @@ -23,6 +23,7 @@ theme = { path = "../theme" } · 22 unmodified lines | |||
| 23 | ui = { path = "../ui" } | 23 | ui = { path = "../ui" } |
| 24 | workspace = { path = "../workspace" } | 24 | workspace = { path = "../workspace" } |
| 25 | postage.workspace = true | 25 | postage.workspace = true |
| 26 | anyhow.workspace = true | ||
| 26 | serde.workspace = true | 27 | serde.workspace = true |
| 27 | 28 | ||
| 28 | [dev-dependencies] | 29 | [dev-dependencies] |
| @@ -312,7 +312,7 @@ impl FileFinderDelegate { · 311 unmodified lines | |||
| 312 | cx: &mut ViewContext<FileFinder>, | 312 | cx: &mut ViewContext<FileFinder>, |
| 313 | ) -> Self { | 313 | ) -> Self { |
| 314 | cx.observe(&project, |file_finder, _, cx| { | 314 | cx.observe(&project, |file_finder, _, cx| { |
| 315 | //todo!() We should probably not re-render on every project anything | 315 | //todo We should probably not re-render on every project anything |
| 316 | file_finder | 316 | file_finder |
| 317 | .picker | 317 | .picker |
| 318 | .update(cx, |picker, cx| picker.refresh(cx)) | 318 | .update(cx, |picker, cx| picker.refresh(cx)) |
| @@ -519,6 +519,62 @@ impl FileFinderDelegate { · 200 unmodified lines | |||
| 519 | 519 | ||
| 520 | (file_name, file_name_positions, full_path, path_positions) | 520 | (file_name, file_name_positions, full_path, path_positions) |
| 521 | } | 521 | } |
| 522 | |||
| 523 | fn lookup_absolute_path( | ||
| 524 | &self, | ||
| 525 | query: PathLikeWithPosition<FileSearchQuery>, | ||
| 526 | cx: &mut ViewContext<'_, Picker<Self>>, | ||
| 527 | ) -> Task<()> { | ||
| 528 | cx.spawn(|picker, mut cx| async move { | ||
| 529 | let Some((project, fs)) = picker | ||
| 530 | .update(&mut cx, |picker, cx| { | ||
| 531 | let fs = Arc::clone(&picker.delegate.project.read(cx).fs()); | ||
| 532 | (picker.delegate.project.clone(), fs) | ||
| 533 | }) | ||
| 534 | .log_err() | ||
| 535 | else { | ||
| 536 | return; | ||
| 537 | }; | ||
| 538 | |||
| 539 | let query_path = Path::new(query.path_like.path_query()); | ||
| 540 | let mut path_matches = Vec::new(); | ||
| 541 | match fs.metadata(query_path).await.log_err() { | ||
| 542 | Some(Some(_metadata)) => { | ||
| 543 | let update_result = project | ||
| 544 | .update(&mut cx, |project, cx| { | ||
| 545 | if let Some((worktree, relative_path)) = | ||
| 546 | project.find_local_worktree(query_path, cx) | ||
| 547 | { | ||
| 548 | path_matches.push(PathMatch { | ||
| 549 | score: 0.0, | ||
| 550 | positions: Vec::new(), | ||
| 551 | worktree_id: worktree.read(cx).id().to_usize(), | ||
| 552 | path: Arc::from(relative_path), | ||
| 553 | path_prefix: "".into(), | ||
| 554 | distance_to_relative_ancestor: usize::MAX, | ||
| 555 | }); | ||
| 556 | } | ||
| 557 | }) | ||
| 558 | .log_err(); | ||
| 559 | if update_result.is_none() { | ||
| 560 | return; | ||
| 561 | } | ||
| 562 | } | ||
| 563 | Some(None) => {} | ||
| 564 | None => return, | ||
| 565 | } | ||
| 566 | |||
| 567 | picker | ||
| 568 | .update(&mut cx, |picker, cx| { | ||
| 569 | let picker_delegate = &mut picker.delegate; | ||
| 570 | let search_id = util::post_inc(&mut picker_delegate.search_count); | ||
| 571 | picker_delegate.set_search_matches(search_id, false, query, path_matches, cx); | ||
| 572 | |||
| 573 | anyhow::Ok(()) | ||
| 574 | }) | ||
| 575 | .log_err(); | ||
| 576 | }) | ||
| 577 | } | ||
| 522 | } | 578 | } |
| 523 | 579 | ||
| 524 | impl PickerDelegate for FileFinderDelegate { | 580 | impl PickerDelegate for FileFinderDelegate { |
| @@ -588,7 +644,12 @@ impl PickerDelegate for FileFinderDelegate { · 63 unmodified lines | |||
| 588 | }) | 644 | }) |
| 589 | }) | 645 | }) |
| 590 | .expect("infallible"); | 646 | .expect("infallible"); |
| 591 | self.spawn_search(query, cx) | 647 | |
| 648 | if Path::new(query.path_like.path_query()).is_absolute() { | ||
| 649 | self.lookup_absolute_path(query, cx) | ||
| 650 | } else { | ||
| 651 | self.spawn_search(query, cx) | ||
| 652 | } | ||
| 592 | } | 653 | } |
| 593 | } | 654 | } |
| 594 | 655 | ||
| @@ -818,6 +879,68 @@ mod tests { · 223 unmodified lines | |||
| 818 | } | 879 | } |
| 819 | } | 880 | } |
| 820 | 881 | ||
| 882 | #[gpui::test] | ||
| 883 | async fn test_absolute_paths(cx: &mut TestAppContext) { | ||
| 884 | let app_state = init_test(cx); | ||
| 885 | app_state | ||
| 886 | .fs | ||
| 887 | .as_fake() | ||
| 888 | .insert_tree( | ||
| 889 | "/root", | ||
| 890 | json!({ | ||
| 891 | "a": { | ||
| 892 | "file1.txt": "", | ||
| 893 | "b": { | ||
| 894 | "file2.txt": "", | ||
| 895 | }, | ||
| 896 | } | ||
| 897 | }), | ||
| 898 | ) | ||
| 899 | .await; | ||
| 900 | |||
| 901 | let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; | ||
| 902 | |||
| 903 | let (picker, workspace, cx) = build_find_picker(project, cx); | ||
| 904 | |||
| 905 | let matching_abs_path = "/root/a/b/file2.txt"; | ||
| 906 | picker | ||
| 907 | .update(cx, |picker, cx| { | ||
| 908 | picker | ||
| 909 | .delegate | ||
| 910 | .update_matches(matching_abs_path.to_string(), cx) | ||
| 911 | }) | ||
| 912 | .await; | ||
| 913 | picker.update(cx, |picker, _| { | ||
| 914 | assert_eq!( | ||
| 915 | collect_search_results(picker), | ||
| 916 | vec![PathBuf::from("a/b/file2.txt")], | ||
| 917 | "Matching abs path should be the only match" | ||
| 918 | ) | ||
| 919 | }); | ||
| 920 | cx.dispatch_action(SelectNext); | ||
| 921 | cx.dispatch_action(Confirm); | ||
| 922 | cx.read(|cx| { | ||
| 923 | let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap(); | ||
| 924 | assert_eq!(active_editor.read(cx).title(cx), "file2.txt"); | ||
| 925 | }); | ||
| 926 | |||
| 927 | let mismatching_abs_path = "/root/a/b/file1.txt"; | ||
| 928 | picker | ||
| 929 | .update(cx, |picker, cx| { | ||
| 930 | picker | ||
| 931 | .delegate | ||
| 932 | .update_matches(mismatching_abs_path.to_string(), cx) | ||
| 933 | }) | ||
| 934 | .await; | ||
| 935 | picker.update(cx, |picker, _| { | ||
| 936 | assert_eq!( | ||
| 937 | collect_search_results(picker), | ||
| 938 | Vec::<PathBuf>::new(), | ||
| 939 | "Mismatching abs path should produce no matches" | ||
| 940 | ) | ||
| 941 | }); | ||
| 942 | } | ||
| 943 | |||
| 821 | #[gpui::test] | 944 | #[gpui::test] |
| 822 | async fn test_complex_path(cx: &mut TestAppContext) { | 945 | async fn test_complex_path(cx: &mut TestAppContext) { |
| 823 | let app_state = init_test(cx); | 946 | let app_state = init_test(cx); |
| @@ -170,7 +170,7 @@ impl ActionRegistry { · 169 unmodified lines | |||
| 170 | macro_rules! actions { | 170 | macro_rules! actions { |
| 171 | ($namespace:path, [ $($name:ident),* $(,)? ]) => { | 171 | ($namespace:path, [ $($name:ident),* $(,)? ]) => { |
| 172 | $( | 172 | $( |
| 173 | #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, gpui::private::serde_derive::Deserialize)] | 173 | #[derive(::std::cmp::PartialEq, ::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, gpui::private::serde_derive::Deserialize)] |
| 174 | #[serde(crate = "gpui::private::serde")] | 174 | #[serde(crate = "gpui::private::serde")] |
| 175 | pub struct $name; | 175 | pub struct $name; |
| 176 | 176 | ||
| @@ -28,11 +28,11 @@ impl KeystrokeMatcher { · 27 unmodified lines | |||
| 28 | 28 | ||
| 29 | /// Pushes a keystroke onto the matcher. | 29 | /// Pushes a keystroke onto the matcher. |
| 30 | /// The result of the new keystroke is returned: | 30 | /// The result of the new keystroke is returned: |
| 31 | /// KeyMatch::None => | 31 | /// - KeyMatch::None => |
| 32 | /// No match is valid for this key given any pending keystrokes. | 32 | /// No match is valid for this key given any pending keystrokes. |
| 33 | /// KeyMatch::Pending => | 33 | /// - KeyMatch::Pending => |
| 34 | /// There exist bindings which are still waiting for more keys. | 34 | /// There exist bindings which are still waiting for more keys. |
| 35 | /// KeyMatch::Complete(matches) => | 35 | /// - KeyMatch::Complete(matches) => |
| 36 | /// One or more bindings have received the necessary key presses. | 36 | /// One or more bindings have received the necessary key presses. |
| 37 | /// Bindings added later will take precedence over earlier bindings. | 37 | /// Bindings added later will take precedence over earlier bindings. |
| 38 | pub fn match_keystroke( | 38 | pub fn match_keystroke( |
| @@ -77,12 +77,10 @@ impl KeystrokeMatcher { · 38 unmodified lines | |||
| 77 | 77 | ||
| 78 | if let Some(pending_key) = pending_key { | 78 | if let Some(pending_key) = pending_key { |
| 79 | self.pending_keystrokes.push(pending_key); | 79 | self.pending_keystrokes.push(pending_key); |
| 80 | } | ||
| 81 | |||
| 82 | if self.pending_keystrokes.is_empty() { | ||
| 83 | KeyMatch::None | ||
| 84 | } else { | ||
| 85 | KeyMatch::Pending | 80 | KeyMatch::Pending |
| 81 | } else { | ||
| 82 | self.pending_keystrokes.clear(); | ||
| 83 | KeyMatch::None | ||
| 86 | } | 84 | } |
| 87 | } | 85 | } |
| 88 | } | 86 | } |
| @@ -98,367 +96,374 @@ impl KeyMatch { · 9 unmodified lines | |||
| 98 | pub fn is_some(&self) -> bool { | 96 | pub fn is_some(&self) -> bool { |
| 99 | matches!(self, KeyMatch::Some(_)) | 97 | matches!(self, KeyMatch::Some(_)) |
| 100 | } | 98 | } |
| 99 | |||
| 100 | pub fn matches(self) -> Option<Vec<Box<dyn Action>>> { | ||
| 101 | match self { | ||
| 102 | KeyMatch::Some(matches) => Some(matches), | ||
| 103 | _ => None, | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | impl PartialEq for KeyMatch { | ||
| 109 | fn eq(&self, other: &Self) -> bool { | ||
| 110 | match (self, other) { | ||
| 111 | (KeyMatch::None, KeyMatch::None) => true, | ||
| 112 | (KeyMatch::Pending, KeyMatch::Pending) => true, | ||
| 113 | (KeyMatch::Some(a), KeyMatch::Some(b)) => { | ||
| 114 | if a.len() != b.len() { | ||
| 115 | return false; | ||
| 116 | } | ||
| 117 | |||
| 118 | for (a, b) in a.iter().zip(b.iter()) { | ||
| 119 | if !a.partial_eq(b.as_ref()) { | ||
| 120 | return false; | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | true | ||
| 125 | } | ||
| 126 | _ => false, | ||
| 127 | } | ||
| 128 | } | ||
| 101 | } | 129 | } |
| 102 | 130 | ||
| 103 | // #[cfg(test)] | 131 | #[cfg(test)] |
| 104 | // mod tests { | 132 | mod tests { |
| 105 | // use anyhow::Result; | 133 | |
| 106 | // use serde::Deserialize; | 134 | use serde_derive::Deserialize; |
| 107 | 135 | ||
| 108 | // use crate::{actions, impl_actions, keymap_matcher::ActionContext}; | 136 | use super::*; |
| 109 | 137 | use crate::{self as gpui, KeyBindingContextPredicate, Modifiers}; | |
| 110 | // use super::*; | 138 | use crate::{actions, KeyBinding}; |
| 111 | 139 | ||
| 112 | // #[test] | 140 | #[test] |
| 113 | // fn test_keymap_and_view_ordering() -> Result<()> { | 141 | fn test_keymap_and_view_ordering() { |
| 114 | // actions!(test, [EditorAction, ProjectPanelAction]); | 142 | actions!(test, [EditorAction, ProjectPanelAction]); |
| 115 | 143 | ||
| 116 | // let mut editor = ActionContext::default(); | 144 | let mut editor = KeyContext::default(); |
| 117 | // editor.add_identifier("Editor"); | 145 | editor.add("Editor"); |
| 118 | 146 | ||
| 119 | // let mut project_panel = ActionContext::default(); | 147 | let mut project_panel = KeyContext::default(); |
| 120 | // project_panel.add_identifier("ProjectPanel"); | 148 | project_panel.add("ProjectPanel"); |
| 121 | 149 | ||
| 122 | // // Editor 'deeper' in than project panel | 150 | // Editor 'deeper' in than project panel |
| 123 | // let dispatch_path = vec![(2, editor), (1, project_panel)]; | 151 | let dispatch_path = vec![project_panel, editor]; |
| 124 | 152 | ||
| 125 | // // But editor actions 'higher' up in keymap | 153 | // But editor actions 'higher' up in keymap |
| 126 | // let keymap = Keymap::new(vec![ | 154 | let keymap = Keymap::new(vec![ |
| 127 | // Binding::new("left", EditorAction, Some("Editor")), | 155 | KeyBinding::new("left", EditorAction, Some("Editor")), |
| 128 | // Binding::new("left", ProjectPanelAction, Some("ProjectPanel")), | 156 | KeyBinding::new("left", ProjectPanelAction, Some("ProjectPanel")), |
| 129 | // ]); | 157 | ]); |
| 130 | 158 | ||
| 131 | // let mut matcher = KeymapMatcher::new(keymap); | 159 | let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); |
| 132 | 160 | ||
| 133 | // assert_eq!( | 161 | let matches = matcher |
| 134 | // matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()), | 162 | .match_keystroke(&Keystroke::parse("left").unwrap(), &dispatch_path) |
| 135 | // KeyMatch::Matches(vec![ | 163 | .matches() |
| 136 | // (2, Box::new(EditorAction)), | 164 | .unwrap(); |
| 137 | // (1, Box::new(ProjectPanelAction)), | 165 | |
| 138 | // ]), | 166 | assert!(matches[0].partial_eq(&EditorAction)); |
| 139 | // ); | 167 | assert!(matches.get(1).is_none()); |
| 140 | 168 | } | |
| 141 | // Ok(()) | 169 | |
| 142 | // } | 170 | #[test] |
| 143 | 171 | fn test_multi_keystroke_match() { | |
| 144 | // #[test] | 172 | actions!(test, [B, AB, C, D, DA, E, EF]); |
| 145 | // fn test_push_keystroke() -> Result<()> { | 173 | |
| 146 | // actions!(test, [B, AB, C, D, DA, E, EF]); | 174 | let mut context1 = KeyContext::default(); |
| 147 | 175 | context1.add("1"); | |
| 148 | // let mut context1 = ActionContext::default(); | 176 | |
| 149 | // context1.add_identifier("1"); | 177 | let mut context2 = KeyContext::default(); |
| 150 | 178 | context2.add("2"); | |
| 151 | // let mut context2 = ActionContext::default(); | 179 | |
| 152 | // context2.add_identifier("2"); | 180 | let dispatch_path = vec![context2, context1]; |
| 153 | 181 | ||
| 154 | // let dispatch_path = vec![(2, context2), (1, context1)]; | 182 | let keymap = Keymap::new(vec![ |
| 155 | 183 | KeyBinding::new("a b", AB, Some("1")), | |
| 156 | // let keymap = Keymap::new(vec![ | 184 | KeyBinding::new("b", B, Some("2")), |
| 157 | // Binding::new("a b", AB, Some("1")), | 185 | KeyBinding::new("c", C, Some("2")), |
| 158 | // Binding::new("b", B, Some("2")), | 186 | KeyBinding::new("d", D, Some("1")), |
| 159 | // Binding::new("c", C, Some("2")), | 187 | KeyBinding::new("d", D, Some("2")), |
| 160 | // Binding::new("d", D, Some("1")), | 188 | KeyBinding::new("d a", DA, Some("2")), |
| 161 | // Binding::new("d", D, Some("2")), | 189 | ]); |
| 162 | // Binding::new("d a", DA, Some("2")), | 190 | |
| 163 | // ]); | 191 | let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); |
| 164 | 192 | ||
| 165 | // let mut matcher = KeymapMatcher::new(keymap); | 193 | // Binding with pending prefix always takes precedence |
| 166 | 194 | assert_eq!( | |
| 167 | // // Binding with pending prefix always takes precedence | 195 | matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), |
| 168 | // assert_eq!( | 196 | KeyMatch::Pending, |
| 169 | // matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), | 197 | ); |
| 170 | // KeyMatch::Pending, | 198 | // B alone doesn't match because a was pending, so AB is returned instead |
| 171 | // ); | 199 | assert_eq!( |
| 172 | // // B alone doesn't match because a was pending, so AB is returned instead | 200 | matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path), |
| 173 | // assert_eq!( | 201 | KeyMatch::Some(vec![Box::new(AB)]), |
| 174 | // matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), | 202 | ); |
| 175 | // KeyMatch::Matches(vec![(1, Box::new(AB))]), | 203 | assert!(!matcher.has_pending_keystrokes()); |
| 176 | // ); | 204 | |
| 177 | // assert!(!matcher.has_pending_keystrokes()); | 205 | // Without an a prefix, B is dispatched like expected |
| 178 | 206 | assert_eq!( | |
| 179 | // // Without an a prefix, B is dispatched like expected | 207 | matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &dispatch_path[0..1]), |
| 180 | // assert_eq!( | 208 | KeyMatch::Some(vec![Box::new(B)]), |
| 181 | // matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()), | 209 | ); |
| 182 | // KeyMatch::Matches(vec![(2, Box::new(B))]), | 210 | assert!(!matcher.has_pending_keystrokes()); |
| 183 | // ); | 211 | |
| 184 | // assert!(!matcher.has_pending_keystrokes()); | 212 | eprintln!("PROBLEM AREA"); |
| 185 | 213 | // If a is prefixed, C will not be dispatched because there | |
| 186 | // // If a is prefixed, C will not be dispatched because there | 214 | // was a pending binding for it |
| 187 | // // was a pending binding for it | 215 | assert_eq!( |
| 188 | // assert_eq!( | 216 | matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &dispatch_path), |
| 189 | // matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), | 217 | KeyMatch::Pending, |
| 190 | // KeyMatch::Pending, | 218 | ); |
| 191 | // ); | 219 | assert_eq!( |
| 192 | // assert_eq!( | 220 | matcher.match_keystroke(&Keystroke::parse("c").unwrap(), &dispatch_path), |
| 193 | // matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()), | 221 | KeyMatch::None, |
| 194 | // KeyMatch::None, | 222 | ); |
| 195 | // ); | 223 | assert!(!matcher.has_pending_keystrokes()); |
| 196 | // assert!(!matcher.has_pending_keystrokes()); | 224 | |
| 197 | 225 | // If a single keystroke matches multiple bindings in the tree | |
| 198 | // // If a single keystroke matches multiple bindings in the tree | 226 | // only one of them is returned. |
| 199 | // // all of them are returned so that we can fallback if the action | 227 | assert_eq!( |
| 200 | // // handler decides to propagate the action | 228 | matcher.match_keystroke(&Keystroke::parse("d").unwrap(), &dispatch_path), |
| 201 | // assert_eq!( | 229 | KeyMatch::Some(vec![Box::new(D)]), |
| 202 | // matcher.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()), | 230 | ); |
| 203 | // KeyMatch::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]), | 231 | } |
| 204 | // ); | 232 | |
| 205 | 233 | #[test] | |
| 206 | // // If none of the d action handlers consume the binding, a pending | 234 | fn test_keystroke_parsing() { |
| 207 | // // binding may then be used | 235 | assert_eq!( |
| 208 | // assert_eq!( | 236 | Keystroke::parse("ctrl-p").unwrap(), |
| 209 | // matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()), | 237 | Keystroke { |
| 210 | // KeyMatch::Matches(vec![(2, Box::new(DA))]), | 238 | key: "p".into(), |
| 211 | // ); | 239 | modifiers: Modifiers { |
| 212 | // assert!(!matcher.has_pending_keystrokes()); | 240 | control: true, |
| 213 | 241 | alt: false, | |
| 214 | // Ok(()) | 242 | shift: false, |
| 215 | // } | 243 | command: false, |
| 216 | 244 | function: false, | |
| 217 | // #[test] | 245 | }, |
| 218 | // fn test_keystroke_parsing() -> Result<()> { | 246 | ime_key: None, |
| 219 | // assert_eq!( | 247 | } |
| 220 | // Keystroke::parse("ctrl-p")?, | 248 | ); |
| 221 | // Keystroke { | 249 | |
| 222 | // key: "p".into(), | 250 | assert_eq!( |
| 223 | // ctrl: true, | 251 | Keystroke::parse("alt-shift-down").unwrap(), |
| 224 | // alt: false, | 252 | Keystroke { |
| 225 | // shift: false, | 253 | key: "down".into(), |
| 226 | // cmd: false, | 254 | modifiers: Modifiers { |
| 227 | // function: false, | 255 | control: false, |
| 228 | // ime_key: None, | 256 | alt: true, |
| 229 | // } | 257 | shift: true, |
| 230 | // ); | 258 | command: false, |
| 231 | 259 | function: false, | |
| 232 | // assert_eq!( | 260 | }, |
| 233 | // Keystroke::parse("alt-shift-down")?, | 261 | ime_key: None, |
| 234 | // Keystroke { | 262 | } |
| 235 | // key: "down".into(), | 263 | ); |
| 236 | // ctrl: false, | 264 | |
| 237 | // alt: true, | 265 | assert_eq!( |
| 238 | // shift: true, | 266 | Keystroke::parse("shift-cmd--").unwrap(), |
| 239 | // cmd: false, | 267 | Keystroke { |
| 240 | // function: false, | 268 | key: "-".into(), |
| 241 | // ime_key: None, | 269 | modifiers: Modifiers { |
| 242 | // } | 270 | control: false, |
| 243 | // ); | 271 | alt: false, |
| 244 | 272 | shift: true, | |
| 245 | // assert_eq!( | 273 | command: true, |
| 246 | // Keystroke::parse("shift-cmd--")?, | 274 | function: false, |
| 247 | // Keystroke { | 275 | }, |
| 248 | // key: "-".into(), | 276 | ime_key: None, |
| 249 | // ctrl: false, | 277 | } |
| 250 | // alt: false, | 278 | ); |
| 251 | // shift: true, | 279 | } |
| 252 | // cmd: true, | 280 | |
| 253 | // function: false, | 281 | #[test] |
| 254 | // ime_key: None, | 282 | fn test_context_predicate_parsing() { |
| 255 | // } | 283 | use KeyBindingContextPredicate::*; |
| 256 | // ); | 284 | |
| 257 | 285 | assert_eq!( | |
| 258 | // Ok(()) | 286 | KeyBindingContextPredicate::parse("a && (b == c || d != e)").unwrap(), |
| 259 | // } | 287 | And( |
| 260 | 288 | Box::new(Identifier("a".into())), | |
| 261 | // #[test] | 289 | Box::new(Or( |
| 262 | // fn test_context_predicate_parsing() -> Result<()> { | 290 | Box::new(Equal("b".into(), "c".into())), |
| 263 | // use KeymapContextPredicate::*; | 291 | Box::new(NotEqual("d".into(), "e".into())), |
| 264 | 292 | )) | |
| 265 | // assert_eq!( | 293 | ) |
| 266 | // KeymapContextPredicate::parse("a && (b == c || d != e)")?, | 294 | ); |
| 267 | // And( | 295 | |
| 268 | // Box::new(Identifier("a".into())), | 296 | assert_eq!( |
| 269 | // Box::new(Or( | 297 | KeyBindingContextPredicate::parse("!a").unwrap(), |
| 270 | // Box::new(Equal("b".into(), "c".into())), | 298 | Not(Box::new(Identifier("a".into())),) |
| 271 | // Box::new(NotEqual("d".into(), "e".into())), | 299 | ); |
| 272 | // )) | 300 | } |
| 273 | // ) | 301 | |
| 274 | // ); | 302 | #[test] |
| 275 | 303 | fn test_context_predicate_eval() { | |
| 276 | // assert_eq!( | 304 | let predicate = KeyBindingContextPredicate::parse("a && b || c == d").unwrap(); |
| 277 | // KeymapContextPredicate::parse("!a")?, | 305 | |
| 278 | // Not(Box::new(Identifier("a".into())),) | 306 | let mut context = KeyContext::default(); |
| 279 | // ); | 307 | context.add("a"); |
| 280 | 308 | assert!(!predicate.eval(&[context])); | |
| 281 | // Ok(()) | 309 | |
| 282 | // } | 310 | let mut context = KeyContext::default(); |
| 283 | 311 | context.add("a"); | |
| 284 | // #[test] | 312 | context.add("b"); |
| 285 | // fn test_context_predicate_eval() { | 313 | assert!(predicate.eval(&[context])); |
| 286 | // let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap(); | 314 | |
| 287 | 315 | let mut context = KeyContext::default(); | |
| 288 | // let mut context = ActionContext::default(); | 316 | context.add("a"); |
| 289 | // context.add_identifier("a"); | 317 | context.set("c", "x"); |
| 290 | // assert!(!predicate.eval(&[context])); | 318 | assert!(!predicate.eval(&[context])); |
| 291 | 319 | ||
| 292 | // let mut context = ActionContext::default(); | 320 | let mut context = KeyContext::default(); |
| 293 | // context.add_identifier("a"); | 321 | context.add("a"); |
| 294 | // context.add_identifier("b"); | 322 | context.set("c", "d"); |
| 295 | // assert!(predicate.eval(&[context])); | 323 | assert!(predicate.eval(&[context])); |
| 296 | 324 | ||
| 297 | // let mut context = ActionContext::default(); | 325 | let predicate = KeyBindingContextPredicate::parse("!a").unwrap(); |
| 298 | // context.add_identifier("a"); | 326 | assert!(predicate.eval(&[KeyContext::default()])); |
| 299 | // context.add_key("c", "x"); | 327 | } |
| 300 | // assert!(!predicate.eval(&[context])); | 328 | |
| 301 | 329 | #[test] | |
| 302 | // let mut context = ActionContext::default(); | 330 | fn test_context_child_predicate_eval() { |
| 303 | // context.add_identifier("a"); | 331 | let predicate = KeyBindingContextPredicate::parse("a && b > c").unwrap(); |
| 304 | // context.add_key("c", "d"); | 332 | let contexts = [ |
| 305 | // assert!(predicate.eval(&[context])); | 333 | context_set(&["a", "b"]), |
| 306 | 334 | context_set(&["c", "d"]), // match this context | |
| 307 | // let predicate = KeymapContextPredicate::parse("!a").unwrap(); | 335 | context_set(&["e", "f"]), |
| 308 | // assert!(predicate.eval(&[ActionContext::default()])); | 336 | ]; |
| 309 | // } | 337 | |
| 310 | 338 | assert!(!predicate.eval(&contexts[..=0])); | |
| 311 | // #[test] | 339 | assert!(predicate.eval(&contexts[..=1])); |
| 312 | // fn test_context_child_predicate_eval() { | 340 | assert!(!predicate.eval(&contexts[..=2])); |
| 313 | // let predicate = KeymapContextPredicate::parse("a && b > c").unwrap(); | 341 | |
| 314 | // let contexts = [ | 342 | let predicate = KeyBindingContextPredicate::parse("a && b > c && !d > e").unwrap(); |
| 315 | // context_set(&["e", "f"]), | 343 | let contexts = [ |
| 316 | // context_set(&["c", "d"]), // match this context | 344 | context_set(&["a", "b"]), |
| 317 | // context_set(&["a", "b"]), | 345 | context_set(&["c", "d"]), |
| 318 | // ]; | 346 | context_set(&["e"]), |
| 319 | 347 | context_set(&["a", "b"]), | |
| 320 | // assert!(!predicate.eval(&contexts[0..])); | 348 | context_set(&["c"]), |
| 321 | // assert!(predicate.eval(&contexts[1..])); | 349 | context_set(&["e"]), // only match this context |
| 322 | // assert!(!predicate.eval(&contexts[2..])); | 350 | context_set(&["f"]), |
| 323 | 351 | ]; | |
| 324 | // let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap(); | 352 | |
| 325 | // let contexts = [ | 353 | assert!(!predicate.eval(&contexts[..=0])); |
| 326 | // context_set(&["f"]), | 354 | assert!(!predicate.eval(&contexts[..=1])); |
| 327 | // context_set(&["e"]), // only match this context | 355 | assert!(!predicate.eval(&contexts[..=2])); |
| 328 | // context_set(&["c"]), | 356 | assert!(!predicate.eval(&contexts[..=3])); |
| 329 | // context_set(&["a", "b"]), | 357 | assert!(!predicate.eval(&contexts[..=4])); |
| 330 | // context_set(&["e"]), | 358 | assert!(predicate.eval(&contexts[..=5])); |
| 331 | // context_set(&["c", "d"]), | 359 | assert!(!predicate.eval(&contexts[..=6])); |
| 332 | // context_set(&["a", "b"]), | 360 | |
| 333 | // ]; | 361 | fn context_set(names: &[&str]) -> KeyContext { |
| 334 | 362 | let mut keymap = KeyContext::default(); | |
| 335 | // assert!(!predicate.eval(&contexts[0..])); | 363 | names.iter().for_each(|name| keymap.add(name.to_string())); |
| 336 | // assert!(predicate.eval(&contexts[1..])); | 364 | keymap |
| 337 | // assert!(!predicate.eval(&contexts[2..])); | 365 | } |
| 338 | // assert!(!predicate.eval(&contexts[3..])); | 366 | } |
| 339 | // assert!(!predicate.eval(&contexts[4..])); | 367 | |
| 340 | // assert!(!predicate.eval(&contexts[5..])); | 368 | #[test] |
| 341 | // assert!(!predicate.eval(&contexts[6..])); | 369 | fn test_matcher() { |
| 342 | 370 | #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] | |
| 343 | // fn context_set(names: &[&str]) -> ActionContext { | 371 | pub struct A(pub String); |
| 344 | // let mut keymap = ActionContext::new(); | 372 | impl_actions!(test, [A]); |
| 345 | // names | 373 | actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); |
| 346 | // .iter() | 374 | |
| 347 | // .for_each(|name| keymap.add_identifier(name.to_string())); | 375 | #[derive(Clone, Debug, Eq, PartialEq)] |
| 348 | // keymap | 376 | struct ActionArg { |
| 349 | // } | 377 | a: &'static str, |
| 350 | // } | 378 | } |
| 351 | 379 | ||
| 352 | // #[test] | 380 | let keymap = Keymap::new(vec![ |
| 353 | // fn test_matcher() -> Result<()> { | 381 | KeyBinding::new("a", A("x".to_string()), Some("a")), |
| 354 | // #[derive(Clone, Deserialize, PartialEq, Eq, Debug)] | 382 | KeyBinding::new("b", B, Some("a")), |
| 355 | // pub struct A(pub String); | 383 | KeyBinding::new("a b", Ab, Some("a || b")), |
| 356 | // impl_actions!(test, [A]); | 384 | KeyBinding::new("$", Dollar, Some("a")), |
| 357 | // actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]); | 385 | KeyBinding::new("\"", Quote, Some("a")), |
| 358 | 386 | KeyBinding::new("alt-s", Ess, Some("a")), | |
| 359 | // #[derive(Clone, Debug, Eq, PartialEq)] | 387 | KeyBinding::new("ctrl-`", Backtick, Some("a")), |
| 360 | // struct ActionArg { | 388 | ]); |
| 361 | // a: &'static str, | 389 | |
| 362 | // } | 390 | let mut context_a = KeyContext::default(); |
| 363 | 391 | context_a.add("a"); | |
| 364 | // let keymap = Keymap::new(vec![ | 392 | |
| 365 | // Binding::new("a", A("x".to_string()), Some("a")), | 393 | let mut context_b = KeyContext::default(); |
| 366 | // Binding::new("b", B, Some("a")), | 394 | context_b.add("b"); |
| 367 | // Binding::new("a b", Ab, Some("a || b")), | 395 | |
| 368 | // Binding::new("$", Dollar, Some("a")), | 396 | let mut matcher = KeystrokeMatcher::new(Arc::new(Mutex::new(keymap))); |
| 369 | // Binding::new("\"", Quote, Some("a")), | 397 | |
| 370 | // Binding::new("alt-s", Ess, Some("a")), | 398 | // Basic match |
| 371 | // Binding::new("ctrl-`", Backtick, Some("a")), | 399 | assert_eq!( |
| 372 | // ]); | 400 | matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), |
| 373 | 401 | KeyMatch::Some(vec![Box::new(A("x".to_string()))]) | |
| 374 | // let mut context_a = ActionContext::default(); | 402 | ); |
| 375 | // context_a.add_identifier("a"); | 403 | matcher.clear_pending(); |
| 376 | 404 | ||
| 377 | // let mut context_b = ActionContext::default(); | 405 | // Multi-keystroke match |
| 378 | // context_b.add_identifier("b"); | 406 | assert_eq!( |
| 379 | 407 | matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_b.clone()]), | |
| 380 | // let mut matcher = KeymapMatcher::new(keymap); | 408 | KeyMatch::Pending |
| 381 | 409 | ); | |
| 382 | // // Basic match | 410 | assert_eq!( |
| 383 | // assert_eq!( | 411 | matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), |
| 384 | // matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), | 412 | KeyMatch::Some(vec![Box::new(Ab)]) |
| 385 | // KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) | 413 | ); |
| 386 | // ); | 414 | matcher.clear_pending(); |
| 387 | // matcher.clear_pending(); | 415 | |
| 388 | 416 | // Failed matches don't interfere with matching subsequent keys | |
| 389 | // // Multi-keystroke match | 417 | assert_eq!( |
| 390 | // assert_eq!( | 418 | matcher.match_keystroke(&Keystroke::parse("x").unwrap(), &[context_a.clone()]), |
| 391 | // matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), | 419 | KeyMatch::None |
| 392 | // KeyMatch::Pending | 420 | ); |
| 393 | // ); | 421 | assert_eq!( |
| 394 | // assert_eq!( | 422 | matcher.match_keystroke(&Keystroke::parse("a").unwrap(), &[context_a.clone()]), |
| 395 | // matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), | 423 | KeyMatch::Some(vec![Box::new(A("x".to_string()))]) |
| 396 | // KeyMatch::Matches(vec![(1, Box::new(Ab))]) | 424 | ); |
| 397 | // ); | 425 | matcher.clear_pending(); |
| 398 | // matcher.clear_pending(); | 426 | |
| 399 | 427 | let mut context_c = KeyContext::default(); | |
| 400 | // // Failed matches don't interfere with matching subsequent keys | 428 | context_c.add("c"); |
| 401 | // assert_eq!( | 429 | |
| 402 | // matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]), | 430 | assert_eq!( |
| 403 | // KeyMatch::None | 431 | matcher.match_keystroke( |
| 404 | // ); | 432 | &Keystroke::parse("a").unwrap(), |
| 405 | // assert_eq!( | 433 | &[context_c.clone(), context_b.clone()] |
| 406 | // matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]), | 434 | ), |
| 407 | // KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))]) | 435 | KeyMatch::Pending |
| 408 | // ); | 436 | ); |
| 409 | // matcher.clear_pending(); | 437 | assert_eq!( |
| 410 | 438 | matcher.match_keystroke(&Keystroke::parse("b").unwrap(), &[context_b.clone()]), | |
| 411 | // // Pending keystrokes are cleared when the context changes | 439 | KeyMatch::Some(vec![Box::new(Ab)]) |
| 412 | // assert_eq!( | 440 | ); |
| 413 | // matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]), | 441 | |
| 414 | // KeyMatch::Pending | 442 | // handle Czech $ (option + 4 key) |
| 415 | // ); | 443 | assert_eq!( |
| 416 | // assert_eq!( | 444 | matcher.match_keystroke(&Keystroke::parse("alt-ç->$").unwrap(), &[context_a.clone()]), |
| 417 | // matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]), | 445 | KeyMatch::Some(vec![Box::new(Dollar)]) |
| 418 | // KeyMatch::None | 446 | ); |
| 419 | // ); | 447 | |
| 420 | // matcher.clear_pending(); | 448 | // handle Brazillian quote (quote key then space key) |
| 421 | 449 | assert_eq!( | |
| 422 | // let mut context_c = ActionContext::default(); | 450 | matcher.match_keystroke( |
| 423 | // context_c.add_identifier("c"); | 451 | &Keystroke::parse("space->\"").unwrap(), |
| 424 | 452 | &[context_a.clone()] | |
| 425 | // // Pending keystrokes are maintained per-view | 453 | ), |
| 426 | // assert_eq!( | 454 | KeyMatch::Some(vec![Box::new(Quote)]) |
| 427 | // matcher.match_keystroke( | 455 | ); |
| 428 | // Keystroke::parse("a")?, | 456 | |
| 429 | // vec![(1, context_b.clone()), (2, context_c.clone())] | 457 | // handle ctrl+` on a brazillian keyboard |
| 430 | // ), | 458 | assert_eq!( |
| 431 | // KeyMatch::Pending | 459 | matcher.match_keystroke(&Keystroke::parse("ctrl-->`").unwrap(), &[context_a.clone()]), |
| 432 | // ); | 460 | KeyMatch::Some(vec![Box::new(Backtick)]) |
| 433 | // assert_eq!( | 461 | ); |
| 434 | // matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]), | 462 | |
| 435 | // KeyMatch::Matches(vec![(1, Box::new(Ab))]) | 463 | // handle alt-s on a US keyboard |
| 436 | // ); | 464 | assert_eq!( |
| 437 | 465 | matcher.match_keystroke(&Keystroke::parse("alt-s->ß").unwrap(), &[context_a.clone()]), | |
| 438 | // // handle Czech $ (option + 4 key) | 466 | KeyMatch::Some(vec![Box::new(Ess)]) |
| 439 | // assert_eq!( | 467 | ); |
| 440 | // matcher.match_keystroke(Keystroke::parse("alt-ç->$")?, vec![(1, context_a.clone())]), | 468 | } |
| 441 | // KeyMatch::Matches(vec![(1, Box::new(Dollar))]) | 469 | } |
| 442 | // ); | ||
| 443 | |||
| 444 | // // handle Brazillian quote (quote key then space key) | ||
| 445 | // assert_eq!( | ||
| 446 | // matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]), | ||
| 447 | // KeyMatch::Matches(vec![(1, Box::new(Quote))]) | ||
| 448 | // ); | ||
| 449 | |||
| 450 | // // handle ctrl+` on a brazillian keyboard | ||
| 451 | // assert_eq!( | ||
| 452 | // matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]), | ||
| 453 | // KeyMatch::Matches(vec![(1, Box::new(Backtick))]) | ||
| 454 | // ); | ||
| 455 | |||
| 456 | // // handle alt-s on a US keyboard | ||
| 457 | // assert_eq!( | ||
| 458 | // matcher.match_keystroke(Keystroke::parse("alt-s->ß")?, vec![(1, context_a.clone())]), | ||
| 459 | // KeyMatch::Matches(vec![(1, Box::new(Ess))]) | ||
| 460 | // ); | ||
| 461 | |||
| 462 | // Ok(()) | ||
| 463 | // } | ||
| 464 | // } | ||
| @@ -978,8 +978,12 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { · 977 unmodified lines | |||
| 978 | unsafe { | 978 | unsafe { |
| 979 | if let Some(event) = InputEvent::from_native(native_event, None) { | 979 | if let Some(event) = InputEvent::from_native(native_event, None) { |
| 980 | let platform = get_mac_platform(this); | 980 | let platform = get_mac_platform(this); |
| 981 | if let Some(callback) = platform.0.lock().event.as_mut() { | 981 | let mut lock = platform.0.lock(); |
| 982 | if !callback(event) { | 982 | if let Some(mut callback) = lock.event.take() { |
| 983 | drop(lock); | ||
| 984 | let result = callback(event); | ||
| 985 | platform.0.lock().event.get_or_insert(callback); | ||
| 986 | if !result { | ||
| 983 | return; | 987 | return; |
| 984 | } | 988 | } |
| 985 | } | 989 | } |
| @@ -1004,30 +1008,42 @@ extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) { · 18 unmodified lines | |||
| 1004 | extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { | 1008 | extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) { |
| 1005 | if !has_open_windows { | 1009 | if !has_open_windows { |
| 1006 | let platform = unsafe { get_mac_platform(this) }; | 1010 | let platform = unsafe { get_mac_platform(this) }; |
| 1007 | if let Some(callback) = platform.0.lock().reopen.as_mut() { | 1011 | let mut lock = platform.0.lock(); |
| 1012 | if let Some(mut callback) = lock.reopen.take() { | ||
| 1013 | drop(lock); | ||
| 1008 | callback(); | 1014 | callback(); |
| 1015 | platform.0.lock().reopen.get_or_insert(callback); | ||
| 1009 | } | 1016 | } |
| 1010 | } | 1017 | } |
| 1011 | } | 1018 | } |
| 1012 | 1019 | ||
| 1013 | extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { | 1020 | extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) { |
| 1014 | let platform = unsafe { get_mac_platform(this) }; | 1021 | let platform = unsafe { get_mac_platform(this) }; |
| 1015 | if let Some(callback) = platform.0.lock().become_active.as_mut() { | 1022 | let mut lock = platform.0.lock(); |
| 1023 | if let Some(mut callback) = lock.become_active.take() { | ||
| 1024 | drop(lock); | ||
| 1016 | callback(); | 1025 | callback(); |
| 1026 | platform.0.lock().become_active.get_or_insert(callback); | ||
| 1017 | } | 1027 | } |
| 1018 | } | 1028 | } |
| 1019 | 1029 | ||
| 1020 | extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { | 1030 | extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) { |
| 1021 | let platform = unsafe { get_mac_platform(this) }; | 1031 | let platform = unsafe { get_mac_platform(this) }; |
| 1022 | if let Some(callback) = platform.0.lock().resign_active.as_mut() { | 1032 | let mut lock = platform.0.lock(); |
| 1033 | if let Some(mut callback) = lock.resign_active.take() { | ||
| 1034 | drop(lock); | ||
| 1023 | callback(); | 1035 | callback(); |
| 1036 | platform.0.lock().resign_active.get_or_insert(callback); | ||
| 1024 | } | 1037 | } |
| 1025 | } | 1038 | } |
| 1026 | 1039 | ||
| 1027 | extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { | 1040 | extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) { |
| 1028 | let platform = unsafe { get_mac_platform(this) }; | 1041 | let platform = unsafe { get_mac_platform(this) }; |
| 1029 | if let Some(callback) = platform.0.lock().quit.as_mut() { | 1042 | let mut lock = platform.0.lock(); |
| 1043 | if let Some(mut callback) = lock.quit.take() { | ||
| 1044 | drop(lock); | ||
| 1030 | callback(); | 1045 | callback(); |
| 1046 | platform.0.lock().quit.get_or_insert(callback); | ||
| 1031 | } | 1047 | } |
| 1032 | } | 1048 | } |
| 1033 | 1049 | ||
| @@ -1047,22 +1063,27 @@ extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) { · 13 unmodified lines | |||
| 1047 | .collect::<Vec<_>>() | 1063 | .collect::<Vec<_>>() |
| 1048 | }; | 1064 | }; |
| 1049 | let platform = unsafe { get_mac_platform(this) }; | 1065 | let platform = unsafe { get_mac_platform(this) }; |
| 1050 | if let Some(callback) = platform.0.lock().open_urls.as_mut() { | 1066 | let mut lock = platform.0.lock(); |
| 1067 | if let Some(mut callback) = lock.open_urls.take() { | ||
| 1068 | drop(lock); | ||
| 1051 | callback(urls); | 1069 | callback(urls); |
| 1070 | platform.0.lock().open_urls.get_or_insert(callback); | ||
| 1052 | } | 1071 | } |
| 1053 | } | 1072 | } |
| 1054 | 1073 | ||
| 1055 | extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { | 1074 | extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) { |
| 1056 | unsafe { | 1075 | unsafe { |
| 1057 | let platform = get_mac_platform(this); | 1076 | let platform = get_mac_platform(this); |
| 1058 | let mut platform = platform.0.lock(); | 1077 | let mut lock = platform.0.lock(); |
| 1059 | if let Some(mut callback) = platform.menu_command.take() { | 1078 | if let Some(mut callback) = lock.menu_command.take() { |
| 1060 | let tag: NSInteger = msg_send![item, tag]; | 1079 | let tag: NSInteger = msg_send![item, tag]; |
| 1061 | let index = tag as usize; | 1080 | let index = tag as usize; |
| 1062 | if let Some(action) = platform.menu_actions.get(index) { | 1081 | if let Some(action) = lock.menu_actions.get(index) { |
| 1063 | callback(action.as_ref()); | 1082 | let action = action.boxed_clone(); |
| 1083 | drop(lock); | ||
| 1084 | callback(&*action); | ||
| 1064 | } | 1085 | } |
| 1065 | platform.menu_command = Some(callback); | 1086 | platform.0.lock().menu_command.get_or_insert(callback); |
| 1066 | } | 1087 | } |
| 1067 | } | 1088 | } |
| 1068 | } | 1089 | } |
| @@ -1071,14 +1092,20 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { · 2 unmodified lines | |||
| 1071 | unsafe { | 1092 | unsafe { |
| 1072 | let mut result = false; | 1093 | let mut result = false; |
| 1073 | let platform = get_mac_platform(this); | 1094 | let platform = get_mac_platform(this); |
| 1074 | let mut platform = platform.0.lock(); | 1095 | let mut lock = platform.0.lock(); |
| 1075 | if let Some(mut callback) = platform.validate_menu_command.take() { | 1096 | if let Some(mut callback) = lock.validate_menu_command.take() { |
| 1076 | let tag: NSInteger = msg_send![item, tag]; | 1097 | let tag: NSInteger = msg_send![item, tag]; |
| 1077 | let index = tag as usize; | 1098 | let index = tag as usize; |
| 1078 | if let Some(action) = platform.menu_actions.get(index) { | 1099 | if let Some(action) = lock.menu_actions.get(index) { |
| 1100 | let action = action.boxed_clone(); | ||
| 1101 | drop(lock); | ||
| 1079 | result = callback(action.as_ref()); | 1102 | result = callback(action.as_ref()); |
| 1080 | } | 1103 | } |
| 1081 | platform.validate_menu_command = Some(callback); | 1104 | platform |
| 1105 | .0 | ||
| 1106 | .lock() | ||
| 1107 | .validate_menu_command | ||
| 1108 | .get_or_insert(callback); | ||
| 1082 | } | 1109 | } |
| 1083 | result | 1110 | result |
| 1084 | } | 1111 | } |
| @@ -1087,10 +1114,11 @@ extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool { · 2 unmodified lines | |||
| 1087 | extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { | 1114 | extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) { |
| 1088 | unsafe { | 1115 | unsafe { |
| 1089 | let platform = get_mac_platform(this); | 1116 | let platform = get_mac_platform(this); |
| 1090 | let mut platform = platform.0.lock(); | 1117 | let mut lock = platform.0.lock(); |
| 1091 | if let Some(mut callback) = platform.will_open_menu.take() { | 1118 | if let Some(mut callback) = lock.will_open_menu.take() { |
| 1119 | drop(lock); | ||
| 1092 | callback(); | 1120 | callback(); |
| 1093 | platform.will_open_menu = Some(callback); | 1121 | platform.0.lock().will_open_menu.get_or_insert(callback); |
| 1094 | } | 1122 | } |
| 1095 | } | 1123 | } |
| 1096 | } | 1124 | } |
| @@ -190,6 +190,9 @@ impl MacTextSystemState { · 189 unmodified lines | |||
| 190 | for font in family.fonts() { | 190 | for font in family.fonts() { |
| 191 | let mut font = font.load()?; | 191 | let mut font = font.load()?; |
| 192 | open_type::apply_features(&mut font, features); | 192 | open_type::apply_features(&mut font, features); |
| 193 | let Some(_) = font.glyph_for_char('m') else { | ||
| 194 | continue; | ||
| 195 | }; | ||
| 193 | let font_id = FontId(self.fonts.len()); | 196 | let font_id = FontId(self.fonts.len()); |
| 194 | font_ids.push(font_id); | 197 | font_ids.push(font_id); |
| 195 | let postscript_name = font.postscript_name().unwrap(); | 198 | let postscript_name = font.postscript_name().unwrap(); |
| @@ -592,169 +595,49 @@ impl From<FontStyle> for FontkitStyle { · 396 unmodified lines | |||
| 592 | } | 595 | } |
| 593 | } | 596 | } |
| 594 | 597 | ||
| 595 | // #[cfg(test)] | 598 | #[cfg(test)] |
| 596 | // mod tests { | 599 | mod tests { |
| 597 | // use super::*; | 600 | use crate::{font, px, FontRun, MacTextSystem, PlatformTextSystem}; |
| 598 | // use crate::AppContext; | 601 | |
| 599 | // use font_kit::properties::{Style, Weight}; | 602 | #[test] |
| 600 | // use platform::FontSystem as _; | 603 | fn test_wrap_line() { |
| 601 | 604 | let fonts = MacTextSystem::new(); | |
| 602 | // #[crate::test(self, retries = 5)] | 605 | let font_id = fonts.font_id(&font("Helvetica")).unwrap(); |
| 603 | // fn test_layout_str(_: &mut AppContext) { | 606 | |
| 604 | // // This is failing intermittently on CI and we don't have time to figure it out | 607 | let line = "one two three four five\n"; |
| 605 | // let fonts = FontSystem::new(); | 608 | let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); |
| 606 | // let menlo = fonts.load_family("Menlo", &Default::default()).unwrap(); | 609 | assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); |
| 607 | // let menlo_regular = RunStyle { | 610 | |
| 608 | // font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(), | 611 | let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; |
| 609 | // color: Default::default(), | 612 | let wrap_boundaries = fonts.wrap_line(line, font_id, px(16.), px(64.0)); |
| 610 | // underline: Default::default(), | 613 | assert_eq!( |
| 611 | // }; | 614 | wrap_boundaries, |
| 612 | // let menlo_italic = RunStyle { | 615 | &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] |
| 613 | // font_id: fonts | 616 | ); |
| 614 | // .select_font(&menlo, Properties::new().style(Style::Italic)) | 617 | } |
| 615 | // .unwrap(), | 618 | |
| 616 | // color: Default::default(), | 619 | #[test] |
| 617 | // underline: Default::default(), | 620 | fn test_layout_line_bom_char() { |
| 618 | // }; | 621 | let fonts = MacTextSystem::new(); |
| 619 | // let menlo_bold = RunStyle { | 622 | let font_id = fonts.font_id(&font("Helvetica")).unwrap(); |
| 620 | // font_id: fonts | 623 | let line = "\u{feff}"; |
| 621 | // .select_font(&menlo, Properties::new().weight(Weight::BOLD)) | 624 | let mut style = FontRun { |
| 622 | // .unwrap(), | 625 | font_id, |
| 623 | // color: Default::default(), | 626 | len: line.len(), |
| 624 | // underline: Default::default(), | 627 | }; |
| 625 | // }; | 628 | |
| 626 | // assert_ne!(menlo_regular, menlo_italic); | 629 | let layout = fonts.layout_line(line, px(16.), &[style]); |
| 627 | // assert_ne!(menlo_regular, menlo_bold); | 630 | assert_eq!(layout.len, line.len()); |
| 628 | // assert_ne!(menlo_italic, menlo_bold); | 631 | assert!(layout.runs.is_empty()); |
| 629 | 632 | ||
| 630 | // let line = fonts.layout_line( | 633 | let line = "a\u{feff}b"; |
| 631 | // "hello world", | 634 | style.len = line.len(); |
| 632 | // 16.0, | 635 | let layout = fonts.layout_line(line, px(16.), &[style]); |
| 633 | // &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)], | 636 | assert_eq!(layout.len, line.len()); |
| 634 | // ); | 637 | assert_eq!(layout.runs.len(), 1); |
| 635 | // assert_eq!(line.runs.len(), 3); | 638 | assert_eq!(layout.runs[0].glyphs.len(), 2); |
| 636 | // assert_eq!(line.runs[0].font_id, menlo_bold.font_id); | 639 | assert_eq!(layout.runs[0].glyphs[0].id, 68u32.into()); // a |
| 637 | // assert_eq!(line.runs[0].glyphs.len(), 2); | 640 | // There's no glyph for \u{feff} |
| 638 | // assert_eq!(line.runs[1].font_id, menlo_italic.font_id); | 641 | assert_eq!(layout.runs[0].glyphs[1].id, 69u32.into()); // b |
| 639 | // assert_eq!(line.runs[1].glyphs.len(), 4); | 642 | } |
| 640 | // assert_eq!(line.runs[2].font_id, menlo_regular.font_id); | 643 | } |
| 641 | // assert_eq!(line.runs[2].glyphs.len(), 5); | ||
| 642 | // } | ||
| 643 | |||
| 644 | // #[test] | ||
| 645 | // fn test_glyph_offsets() -> crate::Result<()> { | ||
| 646 | // let fonts = FontSystem::new(); | ||
| 647 | // let zapfino = fonts.load_family("Zapfino", &Default::default())?; | ||
| 648 | // let zapfino_regular = RunStyle { | ||
| 649 | // font_id: fonts.select_font(&zapfino, &Properties::new())?, | ||
| 650 | // color: Default::default(), | ||
| 651 | // underline: Default::default(), | ||
| 652 | // }; | ||
| 653 | // let menlo = fonts.load_family("Menlo", &Default::default())?; | ||
| 654 | // let menlo_regular = RunStyle { | ||
| 655 | // font_id: fonts.select_font(&menlo, &Properties::new())?, | ||
| 656 | // color: Default::default(), | ||
| 657 | // underline: Default::default(), | ||
| 658 | // }; | ||
| 659 | |||
| 660 | // let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈"; | ||
| 661 | // let line = fonts.layout_line( | ||
| 662 | // text, | ||
| 663 | // 16.0, | ||
| 664 | // &[ | ||
| 665 | // (9, zapfino_regular), | ||
| 666 | // (13, menlo_regular), | ||
| 667 | // (text.len() - 22, zapfino_regular), | ||
| 668 | // ], | ||
| 669 | // ); | ||
| 670 | // assert_eq!( | ||
| 671 | // line.runs | ||
| 672 | // .iter() | ||
| 673 | // .flat_map(|r| r.glyphs.iter()) | ||
| 674 | // .map(|g| g.index) | ||
| 675 | // .collect::<Vec<_>>(), | ||
| 676 | // vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37], | ||
| 677 | // ); | ||
| 678 | // Ok(()) | ||
| 679 | // } | ||
| 680 | |||
| 681 | // #[test] | ||
| 682 | // #[ignore] | ||
| 683 | // fn test_rasterize_glyph() { | ||
| 684 | // use std::{fs::File, io::BufWriter, path::Path}; | ||
| 685 | |||
| 686 | // let fonts = FontSystem::new(); | ||
| 687 | // let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap(); | ||
| 688 | // let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); | ||
| 689 | // let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap(); | ||
| 690 | |||
| 691 | // const VARIANTS: usize = 1; | ||
| 692 | // for i in 0..VARIANTS { | ||
| 693 | // let variant = i as f32 / VARIANTS as f32; | ||
| 694 | // let (bounds, bytes) = fonts | ||
| 695 | // .rasterize_glyph( | ||
| 696 | // font_id, | ||
| 697 | // 16.0, | ||
| 698 | // glyph_id, | ||
| 699 | // vec2f(variant, variant), | ||
| 700 | // 2., | ||
| 701 | // RasterizationOptions::Alpha, | ||
| 702 | // ) | ||
| 703 | // .unwrap(); | ||
| 704 | |||
| 705 | // let name = format!("/Users/as-cii/Desktop/twog-{}.png", i); | ||
| 706 | // let path = Path::new(&name); | ||
| 707 | // let file = File::create(path).unwrap(); | ||
| 708 | // let w = &mut BufWriter::new(file); | ||
| 709 | |||
| 710 | // let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32); | ||
| 711 | // encoder.set_color(png::ColorType::Grayscale); | ||
| 712 | // encoder.set_depth(png::BitDepth::Eight); | ||
| 713 | // let mut writer = encoder.write_header().unwrap(); | ||
| 714 | // writer.write_image_data(&bytes).unwrap(); | ||
| 715 | // } | ||
| 716 | // } | ||
| 717 | |||
| 718 | // #[test] | ||
| 719 | // fn test_wrap_line() { | ||
| 720 | // let fonts = FontSystem::new(); | ||
| 721 | // let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); | ||
| 722 | // let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap(); | ||
| 723 | |||
| 724 | // let line = "one two three four five\n"; | ||
| 725 | // let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); | ||
| 726 | // assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]); | ||
| 727 | |||
| 728 | // let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n"; | ||
| 729 | // let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0); | ||
| 730 | // assert_eq!( | ||
| 731 | // wrap_boundaries, | ||
| 732 | // &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),] | ||
| 733 | // ); | ||
| 734 | // } | ||
| 735 | |||
| 736 | // #[test] | ||
| 737 | // fn test_layout_line_bom_char() { | ||
| 738 | // let fonts = FontSystem::new(); | ||
| 739 | // let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap(); | ||
| 740 | // let style = RunStyle { | ||
| 741 | // font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(), | ||
| 742 | // color: Default::default(), | ||
| 743 | // underline: Default::default(), | ||
| 744 | // }; | ||
| 745 | |||
| 746 | // let line = "\u{feff}"; | ||
| 747 | // let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); | ||
| 748 | // assert_eq!(layout.len, line.len()); | ||
| 749 | // assert!(layout.runs.is_empty()); | ||
| 750 | |||
| 751 | // let line = "a\u{feff}b"; | ||
| 752 | // let layout = fonts.layout_line(line, 16., &[(line.len(), style)]); | ||
| 753 | // assert_eq!(layout.len, line.len()); | ||
| 754 | // assert_eq!(layout.runs.len(), 1); | ||
| 755 | // assert_eq!(layout.runs[0].glyphs.len(), 2); | ||
| 756 | // assert_eq!(layout.runs[0].glyphs[0].id, 68); // a | ||
| 757 | // // There's no glyph for \u{feff} | ||
| 758 | // assert_eq!(layout.runs[0].glyphs[1].id, 69); // b | ||
| 759 | // } | ||
| 760 | // } | ||
| @@ -268,6 +268,7 @@ unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const C · 267 unmodified lines | |||
| 268 | sel!(windowShouldClose:), | 268 | sel!(windowShouldClose:), |
| 269 | window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, | 269 | window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL, |
| 270 | ); | 270 | ); |
| 271 | |||
| 271 | decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel)); | 272 | decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel)); |
| 272 | 273 | ||
| 273 | decl.add_method( | 274 | decl.add_method( |
| @@ -683,9 +684,6 @@ impl Drop for MacWindow { · 409 unmodified lines | |||
| 683 | this.executor | 684 | this.executor |
| 684 | .spawn(async move { | 685 | .spawn(async move { |
| 685 | unsafe { | 686 | unsafe { |
| 686 | // todo!() this panic()s when you click the red close button | ||
| 687 | // unless should_close returns false. | ||
| 688 | // (luckliy in zed it always returns false) | ||
| 689 | window.close(); | 687 | window.close(); |
| 690 | } | 688 | } |
| 691 | }) | 689 | }) |
| @@ -1104,37 +1102,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: · 412 unmodified lines | |||
| 1104 | .flatten() | 1102 | .flatten() |
| 1105 | .is_some(); | 1103 | .is_some(); |
| 1106 | if !is_composing { | 1104 | if !is_composing { |
| 1107 | // if the IME has changed the key, we'll first emit an event with the character | 1105 | handled = callback(InputEvent::KeyDown(event)); |
| 1108 | // generated by the IME system; then fallback to the keystroke if that is not | ||
| 1109 | // handled. | ||
| 1110 | // cases that we have working: | ||
| 1111 | // - " on a brazillian layout by typing <quote><space> | ||
| 1112 | // - ctrl-` on a brazillian layout by typing <ctrl-`> | ||
| 1113 | // - $ on a czech QWERTY layout by typing <alt-4> | ||
| 1114 | // - 4 on a czech QWERTY layout by typing <shift-4> | ||
| 1115 | // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>) | ||
| 1116 | if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) { | ||
| 1117 | let event_with_ime_text = KeyDownEvent { | ||
| 1118 | is_held: false, | ||
| 1119 | keystroke: Keystroke { | ||
| 1120 | // we match ctrl because some use-cases need it. | ||
| 1121 | // we don't match alt because it's often used to generate the optional character | ||
| 1122 | // we don't match shift because we're not here with letters (usually) | ||
| 1123 | // we don't match cmd/fn because they don't seem to use IME | ||
| 1124 | modifiers: Default::default(), | ||
| 1125 | key: ime_text.clone().unwrap(), | ||
| 1126 | ime_key: None, // todo!("handle IME key") | ||
| 1127 | }, | ||
| 1128 | }; | ||
| 1129 | handled = callback(InputEvent::KeyDown(event_with_ime_text)); | ||
| 1130 | } | ||
| 1131 | if !handled { | ||
| 1132 | // empty key happens when you type a deadkey in input composition. | ||
| 1133 | // (e.g. on a brazillian keyboard typing quote is a deadkey) | ||
| 1134 | if !event.keystroke.key.is_empty() { | ||
| 1135 | handled = callback(InputEvent::KeyDown(event)); | ||
| 1136 | } | ||
| 1137 | } | ||
| 1138 | } | 1106 | } |
| 1139 | 1107 | ||
| 1140 | if !handled { | 1108 | if !handled { |
| @@ -1574,6 +1542,9 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS · 433 unmodified lines | |||
| 1574 | replacement_range, | 1542 | replacement_range, |
| 1575 | text: text.to_string(), | 1543 | text: text.to_string(), |
| 1576 | }); | 1544 | }); |
| 1545 | if text.to_string().to_ascii_lowercase() != pending_key_down.0.keystroke.key { | ||
| 1546 | pending_key_down.0.keystroke.ime_key = Some(text.to_string()); | ||
| 1547 | } | ||
| 1577 | window_state.lock().pending_key_down = Some(pending_key_down); | 1548 | window_state.lock().pending_key_down = Some(pending_key_down); |
| 1578 | } | 1549 | } |
| 1579 | } | 1550 | } |
| @@ -65,6 +65,9 @@ impl TextSystem { · 64 unmodified lines | |||
| 65 | } | 65 | } |
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | pub fn all_font_families(&self) -> Vec<String> { | ||
| 69 | self.platform_text_system.all_font_families() | ||
| 70 | } | ||
| 68 | pub fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> { | 71 | pub fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> { |
| 69 | self.platform_text_system.add_fonts(fonts) | 72 | self.platform_text_system.add_fonts(fonts) |
| 70 | } | 73 | } |
| @@ -368,28 +371,20 @@ impl TextSystem { · 297 unmodified lines | |||
| 368 | self.line_layout_cache.finish_frame(reused_views) | 371 | self.line_layout_cache.finish_frame(reused_views) |
| 369 | } | 372 | } |
| 370 | 373 | ||
| 371 | pub fn line_wrapper( | 374 | pub fn line_wrapper(self: &Arc<Self>, font: Font, font_size: Pixels) -> LineWrapperHandle { |
| 372 | self: &Arc<Self>, | ||
| 373 | font: Font, | ||
| 374 | font_size: Pixels, | ||
| 375 | ) -> Result<LineWrapperHandle> { | ||
| 376 | let lock = &mut self.wrapper_pool.lock(); | 375 | let lock = &mut self.wrapper_pool.lock(); |
| 377 | let font_id = self.font_id(&font)?; | 376 | let font_id = self.resolve_font(&font); |
| 378 | let wrappers = lock | 377 | let wrappers = lock |
| 379 | .entry(FontIdWithSize { font_id, font_size }) | 378 | .entry(FontIdWithSize { font_id, font_size }) |
| 380 | .or_default(); | 379 | .or_default(); |
| 381 | let wrapper = wrappers.pop().map(anyhow::Ok).unwrap_or_else(|| { | 380 | let wrapper = wrappers.pop().unwrap_or_else(|| { |
| 382 | Ok(LineWrapper::new( | 381 | LineWrapper::new(font_id, font_size, self.platform_text_system.clone()) |
| 383 | font_id, | 382 | }); |
| 384 | font_size, | ||
| 385 | self.platform_text_system.clone(), | ||
| 386 | )) | ||
| 387 | })?; | ||
| 388 | 383 | ||
| 389 | Ok(LineWrapperHandle { | 384 | LineWrapperHandle { |
| 390 | wrapper: Some(wrapper), | 385 | wrapper: Some(wrapper), |
| 391 | text_system: self.clone(), | 386 | text_system: self.clone(), |
| 392 | }) | 387 | } |
| 393 | } | 388 | } |
| 394 | 389 | ||
| 395 | pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { | 390 | pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { |
| @@ -137,7 +137,7 @@ impl Boundary { · 136 unmodified lines | |||
| 137 | #[cfg(test)] | 137 | #[cfg(test)] |
| 138 | mod tests { | 138 | mod tests { |
| 139 | use super::*; | 139 | use super::*; |
| 140 | use crate::{font, TestAppContext, TestDispatcher}; | 140 | use crate::{font, TestAppContext, TestDispatcher, TextRun, WrapBoundary}; |
| 141 | use rand::prelude::*; | 141 | use rand::prelude::*; |
| 142 | 142 | ||
| 143 | #[test] | 143 | #[test] |
| @@ -206,75 +206,70 @@ mod tests { · 62 unmodified lines | |||
| 206 | }); | 206 | }); |
| 207 | } | 207 | } |
| 208 | 208 | ||
| 209 | // todo!("move this to a test on TextSystem::layout_text") | 209 | // For compatibility with the test macro |
| 210 | // todo! repeat this test | 210 | use crate as gpui; |
| 211 | // #[test] | ||
| 212 | // fn test_wrap_shaped_line() { | ||
| 213 | // App::test().run(|cx| { | ||
| 214 | // let text_system = cx.text_system().clone(); | ||
| 215 | 211 | ||
| 216 | // let normal = TextRun { | 212 | #[crate::test] |
| 217 | // len: 0, | 213 | fn test_wrap_shaped_line(cx: &mut TestAppContext) { |
| 218 | // font: font("Helvetica"), | 214 | cx.update(|cx| { |
| 219 | // color: Default::default(), | 215 | let text_system = cx.text_system().clone(); |
| 220 | // underline: Default::default(), | 216 | |
| 221 | // }; | 217 | let normal = TextRun { |
| 222 | // let bold = TextRun { | 218 | len: 0, |
| 223 | // len: 0, | 219 | font: font("Helvetica"), |
| 224 | // font: font("Helvetica").bold(), | 220 | color: Default::default(), |
| 225 | // color: Default::default(), | 221 | underline: Default::default(), |
| 226 | // underline: Default::default(), | 222 | background_color: None, |
| 227 | // }; | 223 | }; |
| 224 | let bold = TextRun { | ||
| 225 | len: 0, | ||
| 226 | font: font("Helvetica").bold(), | ||
| 227 | color: Default::default(), | ||
| 228 | underline: Default::default(), | ||
| 229 | background_color: None, | ||
| 230 | }; | ||
| 228 | 231 | ||
| 229 | // impl TextRun { | 232 | impl TextRun { |
| 230 | // fn with_len(&self, len: usize) -> Self { | 233 | fn with_len(&self, len: usize) -> Self { |
| 231 | // let mut this = self.clone(); | 234 | let mut this = self.clone(); |
| 232 | // this.len = len; | 235 | this.len = len; |
| 233 | // this | 236 | this |
| 234 | // } | 237 | } |
| 235 | // } | 238 | } |
| 236 | 239 | ||
| 237 | // let text = "aa bbb cccc ddddd eeee".into(); | 240 | let text = "aa bbb cccc ddddd eeee".into(); |
| 238 | // let lines = text_system | 241 | let lines = text_system |
| 239 | // .layout_text( | 242 | .shape_text( |
| 240 | // &text, | 243 | text, |
| 241 | // px(16.), | 244 | px(16.), |
| 242 | // &[ | 245 | &[ |
| 243 | // normal.with_len(4), | 246 | normal.with_len(4), |
| 244 | // bold.with_len(5), | 247 | bold.with_len(5), |
| 245 | // normal.with_len(6), | 248 | normal.with_len(6), |
| 246 | // bold.with_len(1), | 249 | bold.with_len(1), |
| 247 | // normal.with_len(7), | 250 | normal.with_len(7), |
| 248 | // ], | 251 | ], |
| 249 | // None, | 252 | Some(px(72.)), |
| 250 | // ) | 253 | ) |
| 251 | // .unwrap(); | 254 | .unwrap(); |
| 252 | // let line = &lines[0]; | ||
| 253 | 255 | ||
| 254 | // let mut wrapper = LineWrapper::new( | 256 | assert_eq!( |
| 255 | // text_system.font_id(&normal.font).unwrap(), | 257 | lines[0].layout.wrap_boundaries(), |
| 256 | // px(16.), | 258 | &[ |
| 257 | // text_system.platform_text_system.clone(), | 259 | WrapBoundary { |
| 258 | // ); | 260 | run_ix: 1, |
| 259 | // assert_eq!( | 261 | glyph_ix: 3 |
| 260 | // wrapper | 262 | }, |
| 261 | // .wrap_shaped_line(&text, &line, px(72.)) | 263 | WrapBoundary { |
| 262 | // .collect::<Vec<_>>(), | 264 | run_ix: 2, |
| 263 | // &[ | 265 | glyph_ix: 3 |
| 264 | // ShapedBoundary { | 266 | }, |
| 265 | // run_ix: 1, | 267 | WrapBoundary { |
| 266 | // glyph_ix: 3 | 268 | run_ix: 4, |
| 267 | // }, | 269 | glyph_ix: 2 |
| 268 | // ShapedBoundary { | 270 | } |
| 269 | // run_ix: 2, | 271 | ], |
| 270 | // glyph_ix: 3 | 272 | ); |
| 271 | // }, | 273 | }); |
| 272 | // ShapedBoundary { | 274 | } |
| 273 | // run_ix: 4, | ||
| 274 | // glyph_ix: 2 | ||
| 275 | // } | ||
| 276 | // ], | ||
| 277 | // ); | ||
| 278 | // }); | ||
| 279 | // } | ||
| 280 | } | 275 | } |
| @@ -1545,9 +1545,6 @@ impl<'a> WindowContext<'a> { · 1544 unmodified lines | |||
| 1545 | .finish(&mut self.window.rendered_frame); | 1545 | .finish(&mut self.window.rendered_frame); |
| 1546 | ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); | 1546 | ELEMENT_ARENA.with_borrow_mut(|element_arena| element_arena.clear()); |
| 1547 | 1547 | ||
| 1548 | self.window.refreshing = false; | ||
| 1549 | self.window.drawing = false; | ||
| 1550 | |||
| 1551 | let previous_focus_path = self.window.rendered_frame.focus_path(); | 1548 | let previous_focus_path = self.window.rendered_frame.focus_path(); |
| 1552 | let previous_window_active = self.window.rendered_frame.window_active; | 1549 | let previous_window_active = self.window.rendered_frame.window_active; |
| 1553 | mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); | 1550 | mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); |
| @@ -1586,6 +1583,8 @@ impl<'a> WindowContext<'a> { · 32 unmodified lines | |||
| 1586 | self.window | 1583 | self.window |
| 1587 | .platform_window | 1584 | .platform_window |
| 1588 | .draw(&self.window.rendered_frame.scene); | 1585 | .draw(&self.window.rendered_frame.scene); |
| 1586 | self.window.refreshing = false; | ||
| 1587 | self.window.drawing = false; | ||
| 1589 | } | 1588 | } |
| 1590 | 1589 | ||
| 1591 | /// Dispatch a mouse or keyboard event on the window. | 1590 | /// Dispatch a mouse or keyboard event on the window. |
| @@ -2158,7 +2157,17 @@ impl<'a> WindowContext<'a> { · 566 unmodified lines | |||
| 2158 | let mut this = self.to_async(); | 2157 | let mut this = self.to_async(); |
| 2159 | self.window | 2158 | self.window |
| 2160 | .platform_window | 2159 | .platform_window |
| 2161 | .on_should_close(Box::new(move || this.update(|_, cx| f(cx)).unwrap_or(true))) | 2160 | .on_should_close(Box::new(move || { |
| 2161 | this.update(|_, cx| { | ||
| 2162 | // Ensure that the window is removed from the app if it's been closed | ||
| 2163 | // by always pre-empting the system close event. | ||
| 2164 | if f(cx) { | ||
| 2165 | cx.remove_window(); | ||
| 2166 | } | ||
| 2167 | false | ||
| 2168 | }) | ||
| 2169 | .unwrap_or(true) | ||
| 2170 | })) | ||
| 2162 | } | 2171 | } |
| 2163 | } | 2172 | } |
| 2164 | 2173 | ||
| @@ -12,6 +12,8 @@ class LKRoomDelegate: RoomDelegate { · 11 unmodified lines | |||
| 12 | var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void | 12 | var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void |
| 13 | var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void | 13 | var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void |
| 14 | var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void | 14 | var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void |
| 15 | var onDidPublishOrUnpublishLocalAudioTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void | ||
| 16 | var onDidPublishOrUnpublishLocalVideoTrack: @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void | ||
| 15 | 17 | ||
| 16 | init( | 18 | init( |
| 17 | data: UnsafeRawPointer, | 19 | data: UnsafeRawPointer, |
| @@ -21,7 +23,10 @@ class LKRoomDelegate: RoomDelegate { · 3 unmodified lines | |||
| 21 | onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, | 23 | onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, |
| 22 | onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, | 24 | onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, |
| 23 | onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, | 25 | onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, |
| 24 | onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void) | 26 | onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, |
| 27 | onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, | ||
| 28 | onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void | ||
| 29 | ) | ||
| 25 | { | 30 | { |
| 26 | self.data = data | 31 | self.data = data |
| 27 | self.onDidDisconnect = onDidDisconnect | 32 | self.onDidDisconnect = onDidDisconnect |
| @@ -31,6 +36,8 @@ class LKRoomDelegate: RoomDelegate { · 3 unmodified lines | |||
| 31 | self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack | 36 | self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack |
| 32 | self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack | 37 | self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack |
| 33 | self.onActiveSpeakersChanged = onActiveSpeakersChanged | 38 | self.onActiveSpeakersChanged = onActiveSpeakersChanged |
| 39 | self.onDidPublishOrUnpublishLocalAudioTrack = onDidPublishOrUnpublishLocalAudioTrack | ||
| 40 | self.onDidPublishOrUnpublishLocalVideoTrack = onDidPublishOrUnpublishLocalVideoTrack | ||
| 34 | } | 41 | } |
| 35 | 42 | ||
| 36 | func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { | 43 | func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { |
| @@ -65,6 +72,22 @@ class LKRoomDelegate: RoomDelegate { · 28 unmodified lines | |||
| 65 | self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) | 72 | self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) |
| 66 | } | 73 | } |
| 67 | } | 74 | } |
| 75 | |||
| 76 | func room(_ room: Room, localParticipant: LocalParticipant, didPublish publication: LocalTrackPublication) { | ||
| 77 | if publication.kind == .video { | ||
| 78 | self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) | ||
| 79 | } else if publication.kind == .audio { | ||
| 80 | self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), true) | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | func room(_ room: Room, localParticipant: LocalParticipant, didUnpublish publication: LocalTrackPublication) { | ||
| 85 | if publication.kind == .video { | ||
| 86 | self.onDidPublishOrUnpublishLocalVideoTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) | ||
| 87 | } else if publication.kind == .audio { | ||
| 88 | self.onDidPublishOrUnpublishLocalAudioTrack(self.data, Unmanaged.passUnretained(publication).toOpaque(), false) | ||
| 89 | } | ||
| 90 | } | ||
| 68 | } | 91 | } |
| 69 | 92 | ||
| 70 | class LKVideoRenderer: NSObject, VideoRenderer { | 93 | class LKVideoRenderer: NSObject, VideoRenderer { |
| @@ -109,7 +132,9 @@ public func LKRoomDelegateCreate( · 38 unmodified lines | |||
| 109 | onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, | 132 | onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, |
| 110 | onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, | 133 | onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, |
| 111 | onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, | 134 | onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, |
| 112 | onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void | 135 | onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, |
| 136 | onDidPublishOrUnpublishLocalAudioTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void, | ||
| 137 | onDidPublishOrUnpublishLocalVideoTrack: @escaping @convention(c) (UnsafeRawPointer, UnsafeRawPointer, Bool) -> Void | ||
| 113 | ) -> UnsafeMutableRawPointer { | 138 | ) -> UnsafeMutableRawPointer { |
| 114 | let delegate = LKRoomDelegate( | 139 | let delegate = LKRoomDelegate( |
| 115 | data: data, | 140 | data: data, |
| @@ -119,7 +144,9 @@ public func LKRoomDelegateCreate( · 3 unmodified lines | |||
| 119 | onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, | 144 | onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, |
| 120 | onActiveSpeakersChanged: onActiveSpeakerChanged, | 145 | onActiveSpeakersChanged: onActiveSpeakerChanged, |
| 121 | onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, | 146 | onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, |
| 122 | onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack | 147 | onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack, |
| 148 | onDidPublishOrUnpublishLocalAudioTrack: onDidPublishOrUnpublishLocalAudioTrack, | ||
| 149 | onDidPublishOrUnpublishLocalVideoTrack: onDidPublishOrUnpublishLocalVideoTrack | ||
| 123 | ) | 150 | ) |
| 124 | return Unmanaged.passRetained(delegate).toOpaque() | 151 | return Unmanaged.passRetained(delegate).toOpaque() |
| 125 | } | 152 | } |
| @@ -292,6 +319,14 @@ public func LKLocalTrackPublicationSetMute( · 166 unmodified lines | |||
| 292 | } | 319 | } |
| 293 | } | 320 | } |
| 294 | 321 | ||
| 322 | @_cdecl("LKLocalTrackPublicationIsMuted") | ||
| 323 | public func LKLocalTrackPublicationIsMuted( | ||
| 324 | publication: UnsafeRawPointer | ||
| 325 | ) -> Bool { | ||
| 326 | let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue() | ||
| 327 | return publication.muted | ||
| 328 | } | ||
| 329 | |||
| 295 | @_cdecl("LKRemoteTrackPublicationSetEnabled") | 330 | @_cdecl("LKRemoteTrackPublicationSetEnabled") |
| 296 | public func LKRemoteTrackPublicationSetEnabled( | 331 | public func LKRemoteTrackPublicationSetEnabled( |
| 297 | publication: UnsafeRawPointer, | 332 | publication: UnsafeRawPointer, |
| @@ -325,3 +360,12 @@ public func LKRemoteTrackPublicationGetSid( · 27 unmodified lines | |||
| 325 | 360 | ||
| 326 | return publication.sid as CFString | 361 | return publication.sid as CFString |
| 327 | } | 362 | } |
| 363 | |||
| 364 | @_cdecl("LKLocalTrackPublicationGetSid") | ||
| 365 | public func LKLocalTrackPublicationGetSid( | ||
| 366 | publication: UnsafeRawPointer | ||
| 367 | ) -> CFString { | ||
| 368 | let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue() | ||
| 369 | |||
| 370 | return publication.sid as CFString | ||
| 371 | } | ||
| @@ -2,9 +2,7 @@ use std::{sync::Arc, time::Duration}; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | use futures::StreamExt; | 3 | use futures::StreamExt; |
| 4 | use gpui::{actions, KeyBinding, Menu, MenuItem}; | 4 | use gpui::{actions, KeyBinding, Menu, MenuItem}; |
| 5 | use live_kit_client::{ | 5 | use live_kit_client::{LocalAudioTrack, LocalVideoTrack, Room, RoomUpdate}; |
| 6 | LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, | ||
| 7 | }; | ||
| 8 | use live_kit_server::token::{self, VideoGrant}; | 6 | use live_kit_server::token::{self, VideoGrant}; |
| 9 | use log::LevelFilter; | 7 | use log::LevelFilter; |
| 10 | use simplelog::SimpleLogger; | 8 | use simplelog::SimpleLogger; |
| @@ -60,12 +58,12 @@ fn main() { · 49 unmodified lines | |||
| 60 | let room_b = Room::new(); | 58 | let room_b = Room::new(); |
| 61 | room_b.connect(&live_kit_url, &user2_token).await.unwrap(); | 59 | room_b.connect(&live_kit_url, &user2_token).await.unwrap(); |
| 62 | 60 | ||
| 63 | let mut audio_track_updates = room_b.remote_audio_track_updates(); | 61 | let mut room_updates = room_b.updates(); |
| 64 | let audio_track = LocalAudioTrack::create(); | 62 | let audio_track = LocalAudioTrack::create(); |
| 65 | let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); | 63 | let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); |
| 66 | 64 | ||
| 67 | if let RemoteAudioTrackUpdate::Subscribed(track, _) = | 65 | if let RoomUpdate::SubscribedToRemoteAudioTrack(track, _) = |
| 68 | audio_track_updates.next().await.unwrap() | 66 | room_updates.next().await.unwrap() |
| 69 | { | 67 | { |
| 70 | let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); | 68 | let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); |
| 71 | assert_eq!(remote_tracks.len(), 1); | 69 | assert_eq!(remote_tracks.len(), 1); |
| @@ -78,8 +76,8 @@ fn main() { · 6 unmodified lines | |||
| 78 | audio_track_publication.set_mute(true).await.unwrap(); | 76 | audio_track_publication.set_mute(true).await.unwrap(); |
| 79 | 77 | ||
| 80 | println!("waiting for mute changed!"); | 78 | println!("waiting for mute changed!"); |
| 81 | if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = | 79 | if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = |
| 82 | audio_track_updates.next().await.unwrap() | 80 | room_updates.next().await.unwrap() |
| 83 | { | 81 | { |
| 84 | let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); | 82 | let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); |
| 85 | assert_eq!(remote_tracks[0].sid(), track_id); | 83 | assert_eq!(remote_tracks[0].sid(), track_id); |
| @@ -90,8 +88,8 @@ fn main() { · 4 unmodified lines | |||
| 90 | 88 | ||
| 91 | audio_track_publication.set_mute(false).await.unwrap(); | 89 | audio_track_publication.set_mute(false).await.unwrap(); |
| 92 | 90 | ||
| 93 | if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = | 91 | if let RoomUpdate::RemoteAudioTrackMuteChanged { track_id, muted } = |
| 94 | audio_track_updates.next().await.unwrap() | 92 | room_updates.next().await.unwrap() |
| 95 | { | 93 | { |
| 96 | let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); | 94 | let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); |
| 97 | assert_eq!(remote_tracks[0].sid(), track_id); | 95 | assert_eq!(remote_tracks[0].sid(), track_id); |
| @@ -110,13 +108,13 @@ fn main() { · 12 unmodified lines | |||
| 110 | room_a.unpublish_track(audio_track_publication); | 108 | room_a.unpublish_track(audio_track_publication); |
| 111 | 109 | ||
| 112 | // Clear out any active speakers changed messages | 110 | // Clear out any active speakers changed messages |
| 113 | let mut next = audio_track_updates.next().await.unwrap(); | 111 | let mut next = room_updates.next().await.unwrap(); |
| 114 | while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { | 112 | while let RoomUpdate::ActiveSpeakersChanged { speakers } = next { |
| 115 | println!("Speakers changed: {:?}", speakers); | 113 | println!("Speakers changed: {:?}", speakers); |
| 116 | next = audio_track_updates.next().await.unwrap(); | 114 | next = room_updates.next().await.unwrap(); |
| 117 | } | 115 | } |
| 118 | 116 | ||
| 119 | if let RemoteAudioTrackUpdate::Unsubscribed { | 117 | if let RoomUpdate::UnsubscribedFromRemoteAudioTrack { |
| 120 | publisher_id, | 118 | publisher_id, |
| 121 | track_id, | 119 | track_id, |
| 122 | } = next | 120 | } = next |
| @@ -128,7 +126,6 @@ fn main() { · 5 unmodified lines | |||
| 128 | panic!("unexpected message"); | 126 | panic!("unexpected message"); |
| 129 | } | 127 | } |
| 130 | 128 | ||
| 131 | let mut video_track_updates = room_b.remote_video_track_updates(); | ||
| 132 | let displays = room_a.display_sources().await.unwrap(); | 129 | let displays = room_a.display_sources().await.unwrap(); |
| 133 | let display = displays.into_iter().next().unwrap(); | 130 | let display = displays.into_iter().next().unwrap(); |
| 134 | 131 | ||
| @@ -136,8 +133,8 @@ fn main() { · 1 unmodified line | |||
| 136 | let local_video_track_publication = | 133 | let local_video_track_publication = |
| 137 | room_a.publish_video_track(local_video_track).await.unwrap(); | 134 | room_a.publish_video_track(local_video_track).await.unwrap(); |
| 138 | 135 | ||
| 139 | if let RemoteVideoTrackUpdate::Subscribed(track) = | 136 | if let RoomUpdate::SubscribedToRemoteVideoTrack(track) = |
| 140 | video_track_updates.next().await.unwrap() | 137 | room_updates.next().await.unwrap() |
| 141 | { | 138 | { |
| 142 | let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); | 139 | let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); |
| 143 | assert_eq!(remote_video_tracks.len(), 1); | 140 | assert_eq!(remote_video_tracks.len(), 1); |
| @@ -152,10 +149,10 @@ fn main() { · 8 unmodified lines | |||
| 152 | .pop() | 149 | .pop() |
| 153 | .unwrap(); | 150 | .unwrap(); |
| 154 | room_a.unpublish_track(local_video_track_publication); | 151 | room_a.unpublish_track(local_video_track_publication); |
| 155 | if let RemoteVideoTrackUpdate::Unsubscribed { | 152 | if let RoomUpdate::UnsubscribedFromRemoteVideoTrack { |
| 156 | publisher_id, | 153 | publisher_id, |
| 157 | track_id, | 154 | track_id, |
| 158 | } = video_track_updates.next().await.unwrap() | 155 | } = room_updates.next().await.unwrap() |
| 159 | { | 156 | { |
| 160 | assert_eq!(publisher_id, "test-participant-1"); | 157 | assert_eq!(publisher_id, "test-participant-1"); |
| 161 | assert_eq!(remote_video_track.sid(), track_id); | 158 | assert_eq!(remote_video_track.sid(), track_id); |
| @@ -1,3 +1,5 @@ | |||
| 1 | use std::sync::Arc; | ||
| 2 | |||
| 1 | #[cfg(not(any(test, feature = "test-support")))] | 3 | #[cfg(not(any(test, feature = "test-support")))] |
| 2 | pub mod prod; | 4 | pub mod prod; |
| 3 | 5 | ||
| @@ -9,3 +11,25 @@ pub mod test; · 5 unmodified lines | |||
| 9 | 11 | ||
| 10 | #[cfg(any(test, feature = "test-support"))] | 12 | #[cfg(any(test, feature = "test-support"))] |
| 11 | pub use test::*; | 13 | pub use test::*; |
| 14 | |||
| 15 | pub type Sid = String; | ||
| 16 | |||
| 17 | #[derive(Clone, Eq, PartialEq)] | ||
| 18 | pub enum ConnectionState { | ||
| 19 | Disconnected, | ||
| 20 | Connected { url: String, token: String }, | ||
| 21 | } | ||
| 22 | |||
| 23 | #[derive(Clone)] | ||
| 24 | pub enum RoomUpdate { | ||
| 25 | ActiveSpeakersChanged { speakers: Vec<Sid> }, | ||
| 26 | RemoteAudioTrackMuteChanged { track_id: Sid, muted: bool }, | ||
| 27 | SubscribedToRemoteVideoTrack(Arc<RemoteVideoTrack>), | ||
| 28 | SubscribedToRemoteAudioTrack(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>), | ||
| 29 | UnsubscribedFromRemoteVideoTrack { publisher_id: Sid, track_id: Sid }, | ||
| 30 | UnsubscribedFromRemoteAudioTrack { publisher_id: Sid, track_id: Sid }, | ||
| 31 | LocalAudioTrackPublished { publication: LocalTrackPublication }, | ||
| 32 | LocalAudioTrackUnpublished { publication: LocalTrackPublication }, | ||
| 33 | LocalVideoTrackPublished { publication: LocalTrackPublication }, | ||
| 34 | LocalVideoTrackUnpublished { publication: LocalTrackPublication }, | ||
| 35 | } | ||
| @@ -1,3 +1,4 @@ | |||
| 1 | use crate::{ConnectionState, RoomUpdate, Sid}; | ||
| 1 | use anyhow::{anyhow, Context, Result}; | 2 | use anyhow::{anyhow, Context, Result}; |
| 2 | use core_foundation::{ | 3 | use core_foundation::{ |
| 3 | array::{CFArray, CFArrayRef}, | 4 | array::{CFArray, CFArrayRef}, |
| @@ -76,6 +77,16 @@ extern "C" { · 72 unmodified lines | |||
| 76 | publisher_id: CFStringRef, | 77 | publisher_id: CFStringRef, |
| 77 | track_id: CFStringRef, | 78 | track_id: CFStringRef, |
| 78 | ), | 79 | ), |
| 80 | on_did_publish_or_unpublish_local_audio_track: extern "C" fn( | ||
| 81 | callback_data: *mut c_void, | ||
| 82 | publication: swift::LocalTrackPublication, | ||
| 83 | is_published: bool, | ||
| 84 | ), | ||
| 85 | on_did_publish_or_unpublish_local_video_track: extern "C" fn( | ||
| 86 | callback_data: *mut c_void, | ||
| 87 | publication: swift::LocalTrackPublication, | ||
| 88 | is_published: bool, | ||
| 89 | ), | ||
| 79 | ) -> swift::RoomDelegate; | 90 | ) -> swift::RoomDelegate; |
| 80 | 91 | ||
| 81 | fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; | 92 | fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; |
| @@ -151,26 +162,19 @@ extern "C" { · 69 unmodified lines | |||
| 151 | callback_data: *mut c_void, | 162 | callback_data: *mut c_void, |
| 152 | ); | 163 | ); |
| 153 | 164 | ||
| 165 | fn LKLocalTrackPublicationIsMuted(publication: swift::LocalTrackPublication) -> bool; | ||
| 154 | fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; | 166 | fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; |
| 167 | fn LKLocalTrackPublicationGetSid(publication: swift::LocalTrackPublication) -> CFStringRef; | ||
| 155 | fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; | 168 | fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; |
| 156 | } | 169 | } |
| 157 | 170 | ||
| 158 | pub type Sid = String; | ||
| 159 | |||
| 160 | #[derive(Clone, Eq, PartialEq)] | ||
| 161 | pub enum ConnectionState { | ||
| 162 | Disconnected, | ||
| 163 | Connected { url: String, token: String }, | ||
| 164 | } | ||
| 165 | |||
| 166 | pub struct Room { | 171 | pub struct Room { |
| 167 | native_room: swift::Room, | 172 | native_room: swift::Room, |
| 168 | connection: Mutex<( | 173 | connection: Mutex<( |
| 169 | watch::Sender<ConnectionState>, | 174 | watch::Sender<ConnectionState>, |
| 170 | watch::Receiver<ConnectionState>, | 175 | watch::Receiver<ConnectionState>, |
| 171 | )>, | 176 | )>, |
| 172 | remote_audio_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteAudioTrackUpdate>>>, | 177 | update_subscribers: Mutex<Vec<mpsc::UnboundedSender<RoomUpdate>>>, |
| 173 | remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackUpdate>>>, | ||
| 174 | _delegate: RoomDelegate, | 178 | _delegate: RoomDelegate, |
| 175 | } | 179 | } |
| 176 | 180 | ||
| @@ -181,8 +185,7 @@ impl Room { · 4 unmodified lines | |||
| 181 | Self { | 185 | Self { |
| 182 | native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, | 186 | native_room: unsafe { LKRoomCreate(delegate.native_delegate) }, |
| 183 | connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), | 187 | connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), |
| 184 | remote_audio_track_subscribers: Default::default(), | 188 | update_subscribers: Default::default(), |
| 185 | remote_video_track_subscribers: Default::default(), | ||
| 186 | _delegate: delegate, | 189 | _delegate: delegate, |
| 187 | } | 190 | } |
| 188 | }) | 191 | }) |
| @@ -397,15 +400,9 @@ impl Room { · 208 unmodified lines | |||
| 397 | } | 400 | } |
| 398 | } | 401 | } |
| 399 | 402 | ||
| 400 | pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteAudioTrackUpdate> { | 403 | pub fn updates(&self) -> mpsc::UnboundedReceiver<RoomUpdate> { |
| 401 | let (tx, rx) = mpsc::unbounded(); | 404 | let (tx, rx) = mpsc::unbounded(); |
| 402 | self.remote_audio_track_subscribers.lock().push(tx); | 405 | self.update_subscribers.lock().push(tx); |
| 403 | rx | ||
| 404 | } | ||
| 405 | |||
| 406 | pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> { | ||
| 407 | let (tx, rx) = mpsc::unbounded(); | ||
| 408 | self.remote_video_track_subscribers.lock().push(tx); | ||
| 409 | rx | 406 | rx |
| 410 | } | 407 | } |
| 411 | 408 | ||
| @@ -416,8 +413,8 @@ impl Room { · 4 unmodified lines | |||
| 416 | ) { | 413 | ) { |
| 417 | let track = Arc::new(track); | 414 | let track = Arc::new(track); |
| 418 | let publication = Arc::new(publication); | 415 | let publication = Arc::new(publication); |
| 419 | self.remote_audio_track_subscribers.lock().retain(|tx| { | 416 | self.update_subscribers.lock().retain(|tx| { |
| 420 | tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed( | 417 | tx.unbounded_send(RoomUpdate::SubscribedToRemoteAudioTrack( |
| 421 | track.clone(), | 418 | track.clone(), |
| 422 | publication.clone(), | 419 | publication.clone(), |
| 423 | )) | 420 | )) |
| @@ -426,8 +423,8 @@ impl Room { · 2 unmodified lines | |||
| 426 | } | 423 | } |
| 427 | 424 | ||
| 428 | fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { | 425 | fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { |
| 429 | self.remote_audio_track_subscribers.lock().retain(|tx| { | 426 | self.update_subscribers.lock().retain(|tx| { |
| 430 | tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed { | 427 | tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteAudioTrack { |
| 431 | publisher_id: publisher_id.clone(), | 428 | publisher_id: publisher_id.clone(), |
| 432 | track_id: track_id.clone(), | 429 | track_id: track_id.clone(), |
| 433 | }) | 430 | }) |
| @@ -436,8 +433,8 @@ impl Room { · 2 unmodified lines | |||
| 436 | } | 433 | } |
| 437 | 434 | ||
| 438 | fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { | 435 | fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { |
| 439 | self.remote_audio_track_subscribers.lock().retain(|tx| { | 436 | self.update_subscribers.lock().retain(|tx| { |
| 440 | tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged { | 437 | tx.unbounded_send(RoomUpdate::RemoteAudioTrackMuteChanged { |
| 441 | track_id: track_id.clone(), | 438 | track_id: track_id.clone(), |
| 442 | muted, | 439 | muted, |
| 443 | }) | 440 | }) |
| @@ -445,29 +442,26 @@ impl Room { · 1 unmodified line | |||
| 445 | }); | 442 | }); |
| 446 | } | 443 | } |
| 447 | 444 | ||
| 448 | // A vec of publisher IDs | ||
| 449 | fn active_speakers_changed(&self, speakers: Vec<String>) { | 445 | fn active_speakers_changed(&self, speakers: Vec<String>) { |
| 450 | self.remote_audio_track_subscribers | 446 | self.update_subscribers.lock().retain(move |tx| { |
| 451 | .lock() | 447 | tx.unbounded_send(RoomUpdate::ActiveSpeakersChanged { |
| 452 | .retain(move |tx| { | 448 | speakers: speakers.clone(), |
| 453 | tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged { | 449 | }) |
| 454 | speakers: speakers.clone(), | 450 | .is_ok() |
| 455 | }) | 451 | }); |
| 456 | .is_ok() | ||
| 457 | }); | ||
| 458 | } | 452 | } |
| 459 | 453 | ||
| 460 | fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { | 454 | fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { |
| 461 | let track = Arc::new(track); | 455 | let track = Arc::new(track); |
| 462 | self.remote_video_track_subscribers.lock().retain(|tx| { | 456 | self.update_subscribers.lock().retain(|tx| { |
| 463 | tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone())) | 457 | tx.unbounded_send(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) |
| 464 | .is_ok() | 458 | .is_ok() |
| 465 | }); | 459 | }); |
| 466 | } | 460 | } |
| 467 | 461 | ||
| 468 | fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { | 462 | fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { |
| 469 | self.remote_video_track_subscribers.lock().retain(|tx| { | 463 | self.update_subscribers.lock().retain(|tx| { |
| 470 | tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed { | 464 | tx.unbounded_send(RoomUpdate::UnsubscribedFromRemoteVideoTrack { |
| 471 | publisher_id: publisher_id.clone(), | 465 | publisher_id: publisher_id.clone(), |
| 472 | track_id: track_id.clone(), | 466 | track_id: track_id.clone(), |
| 473 | }) | 467 | }) |
| @@ -529,6 +523,8 @@ impl RoomDelegate { · 55 unmodified lines | |||
| 529 | Self::on_active_speakers_changed, | 523 | Self::on_active_speakers_changed, |
| 530 | Self::on_did_subscribe_to_remote_video_track, | 524 | Self::on_did_subscribe_to_remote_video_track, |
| 531 | Self::on_did_unsubscribe_from_remote_video_track, | 525 | Self::on_did_unsubscribe_from_remote_video_track, |
| 526 | Self::on_did_publish_or_unpublish_local_audio_track, | ||
| 527 | Self::on_did_publish_or_unpublish_local_video_track, | ||
| 532 | ) | 528 | ) |
| 533 | }; | 529 | }; |
| 534 | Self { | 530 | Self { |
| @@ -642,6 +638,46 @@ impl RoomDelegate { · 107 unmodified lines | |||
| 642 | } | 638 | } |
| 643 | let _ = Weak::into_raw(room); | 639 | let _ = Weak::into_raw(room); |
| 644 | } | 640 | } |
| 641 | |||
| 642 | extern "C" fn on_did_publish_or_unpublish_local_audio_track( | ||
| 643 | room: *mut c_void, | ||
| 644 | publication: swift::LocalTrackPublication, | ||
| 645 | is_published: bool, | ||
| 646 | ) { | ||
| 647 | let room = unsafe { Weak::from_raw(room as *mut Room) }; | ||
| 648 | if let Some(room) = room.upgrade() { | ||
| 649 | let publication = LocalTrackPublication::new(publication); | ||
| 650 | let update = if is_published { | ||
| 651 | RoomUpdate::LocalAudioTrackPublished { publication } | ||
| 652 | } else { | ||
| 653 | RoomUpdate::LocalAudioTrackUnpublished { publication } | ||
| 654 | }; | ||
| 655 | room.update_subscribers | ||
| 656 | .lock() | ||
| 657 | .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); | ||
| 658 | } | ||
| 659 | let _ = Weak::into_raw(room); | ||
| 660 | } | ||
| 661 | |||
| 662 | extern "C" fn on_did_publish_or_unpublish_local_video_track( | ||
| 663 | room: *mut c_void, | ||
| 664 | publication: swift::LocalTrackPublication, | ||
| 665 | is_published: bool, | ||
| 666 | ) { | ||
| 667 | let room = unsafe { Weak::from_raw(room as *mut Room) }; | ||
| 668 | if let Some(room) = room.upgrade() { | ||
| 669 | let publication = LocalTrackPublication::new(publication); | ||
| 670 | let update = if is_published { | ||
| 671 | RoomUpdate::LocalVideoTrackPublished { publication } | ||
| 672 | } else { | ||
| 673 | RoomUpdate::LocalVideoTrackUnpublished { publication } | ||
| 674 | }; | ||
| 675 | room.update_subscribers | ||
| 676 | .lock() | ||
| 677 | .retain(|tx| tx.unbounded_send(update.clone()).is_ok()); | ||
| 678 | } | ||
| 679 | let _ = Weak::into_raw(room); | ||
| 680 | } | ||
| 645 | } | 681 | } |
| 646 | 682 | ||
| 647 | impl Drop for RoomDelegate { | 683 | impl Drop for RoomDelegate { |
| @@ -691,6 +727,10 @@ impl LocalTrackPublication { · 43 unmodified lines | |||
| 691 | Self(native_track_publication) | 727 | Self(native_track_publication) |
| 692 | } | 728 | } |
| 693 | 729 | ||
| 730 | pub fn sid(&self) -> String { | ||
| 731 | unsafe { CFString::wrap_under_get_rule(LKLocalTrackPublicationGetSid(self.0)).to_string() } | ||
| 732 | } | ||
| 733 | |||
| 694 | pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> { | 734 | pub fn set_mute(&self, muted: bool) -> impl Future<Output = Result<()>> { |
| 695 | let (tx, rx) = futures::channel::oneshot::channel(); | 735 | let (tx, rx) = futures::channel::oneshot::channel(); |
| 696 | 736 | ||
| @@ -715,6 +755,19 @@ impl LocalTrackPublication { · 18 unmodified lines | |||
| 715 | 755 | ||
| 716 | async move { rx.await.unwrap() } | 756 | async move { rx.await.unwrap() } |
| 717 | } | 757 | } |
| 758 | |||
| 759 | pub fn is_muted(&self) -> bool { | ||
| 760 | unsafe { LKLocalTrackPublicationIsMuted(self.0) } | ||
| 761 | } | ||
| 762 | } | ||
| 763 | |||
| 764 | impl Clone for LocalTrackPublication { | ||
| 765 | fn clone(&self) -> Self { | ||
| 766 | unsafe { | ||
| 767 | CFRetain(self.0 .0); | ||
| 768 | } | ||
| 769 | Self(self.0) | ||
| 770 | } | ||
| 718 | } | 771 | } |
| 719 | 772 | ||
| 720 | impl Drop for LocalTrackPublication { | 773 | impl Drop for LocalTrackPublication { |
| @@ -811,7 +864,11 @@ impl RemoteAudioTrack { · 90 unmodified lines | |||
| 811 | 864 | ||
| 812 | impl Drop for RemoteAudioTrack { | 865 | impl Drop for RemoteAudioTrack { |
| 813 | fn drop(&mut self) { | 866 | fn drop(&mut self) { |
| 814 | unsafe { CFRelease(self.native_track.0) } | 867 | // todo: uncomment this `CFRelease`, unless we find that it was causing |
| 868 | // the crash in the `livekit.multicast` thread. | ||
| 869 | // | ||
| 870 | // unsafe { CFRelease(self.native_track.0) } | ||
| 871 | let _ = self.native_track; | ||
| 815 | } | 872 | } |
| 816 | } | 873 | } |
| 817 | 874 | ||
| @@ -889,18 +946,6 @@ impl Drop for RemoteVideoTrack { · 71 unmodified lines | |||
| 889 | } | 946 | } |
| 890 | } | 947 | } |
| 891 | 948 | ||
| 892 | pub enum RemoteVideoTrackUpdate { | ||
| 893 | Subscribed(Arc<RemoteVideoTrack>), | ||
| 894 | Unsubscribed { publisher_id: Sid, track_id: Sid }, | ||
| 895 | } | ||
| 896 | |||
| 897 | pub enum RemoteAudioTrackUpdate { | ||
| 898 | ActiveSpeakersChanged { speakers: Vec<Sid> }, | ||
| 899 | MuteChanged { track_id: Sid, muted: bool }, | ||
| 900 | Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>), | ||
| 901 | Unsubscribed { publisher_id: Sid, track_id: Sid }, | ||
| 902 | } | ||
| 903 | |||
| 904 | pub struct MacOSDisplay(swift::MacOSDisplay); | 949 | pub struct MacOSDisplay(swift::MacOSDisplay); |
| 905 | 950 | ||
| 906 | impl MacOSDisplay { | 951 | impl MacOSDisplay { |
| @@ -1,3 +1,4 @@ | |||
| 1 | use crate::{ConnectionState, RoomUpdate, Sid}; | ||
| 1 | use anyhow::{anyhow, Context, Result}; | 2 | use anyhow::{anyhow, Context, Result}; |
| 2 | use async_trait::async_trait; | 3 | use async_trait::async_trait; |
| 3 | use collections::{BTreeMap, HashMap}; | 4 | use collections::{BTreeMap, HashMap}; |
| @@ -7,7 +8,14 @@ use live_kit_server::{proto, token}; · 3 unmodified lines | |||
| 7 | use media::core_video::CVImageBuffer; | 8 | use media::core_video::CVImageBuffer; |
| 8 | use parking_lot::Mutex; | 9 | use parking_lot::Mutex; |
| 9 | use postage::watch; | 10 | use postage::watch; |
| 10 | use std::{future::Future, mem, sync::Arc}; | 11 | use std::{ |
| 12 | future::Future, | ||
| 13 | mem, | ||
| 14 | sync::{ | ||
| 15 | atomic::{AtomicBool, Ordering::SeqCst}, | ||
| 16 | Arc, | ||
| 17 | }, | ||
| 18 | }; | ||
| 11 | 19 | ||
| 12 | static SERVERS: Mutex<BTreeMap<String, Arc<TestServer>>> = Mutex::new(BTreeMap::new()); | 20 | static SERVERS: Mutex<BTreeMap<String, Arc<TestServer>>> = Mutex::new(BTreeMap::new()); |
| 13 | 21 | ||
| @@ -104,9 +112,8 @@ impl TestServer { · 90 unmodified lines | |||
| 104 | client_room | 112 | client_room |
| 105 | .0 | 113 | .0 |
| 106 | .lock() | 114 | .lock() |
| 107 | .video_track_updates | 115 | .updates_tx |
| 108 | .0 | 116 | .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) |
| 109 | .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) | ||
| 110 | .unwrap(); | 117 | .unwrap(); |
| 111 | } | 118 | } |
| 112 | room.client_rooms.insert(identity, client_room); | 119 | room.client_rooms.insert(identity, client_room); |
| @@ -176,7 +183,11 @@ impl TestServer { · 63 unmodified lines | |||
| 176 | } | 183 | } |
| 177 | } | 184 | } |
| 178 | 185 | ||
| 179 | async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> { | 186 | async fn publish_video_track( |
| 187 | &self, | ||
| 188 | token: String, | ||
| 189 | local_track: LocalVideoTrack, | ||
| 190 | ) -> Result<Sid> { | ||
| 180 | self.executor.simulate_random_delay().await; | 191 | self.executor.simulate_random_delay().await; |
| 181 | let claims = live_kit_server::token::validate(&token, &self.secret_key)?; | 192 | let claims = live_kit_server::token::validate(&token, &self.secret_key)?; |
| 182 | let identity = claims.sub.unwrap().to_string(); | 193 | let identity = claims.sub.unwrap().to_string(); |
| @@ -198,8 +209,9 @@ impl TestServer { · 15 unmodified lines | |||
| 198 | return Err(anyhow!("user is not allowed to publish")); | 209 | return Err(anyhow!("user is not allowed to publish")); |
| 199 | } | 210 | } |
| 200 | 211 | ||
| 212 | let sid = nanoid::nanoid!(17); | ||
| 201 | let track = Arc::new(RemoteVideoTrack { | 213 | let track = Arc::new(RemoteVideoTrack { |
| 202 | sid: nanoid::nanoid!(17), | 214 | sid: sid.clone(), |
| 203 | publisher_id: identity.clone(), | 215 | publisher_id: identity.clone(), |
| 204 | frames_rx: local_track.frames_rx.clone(), | 216 | frames_rx: local_track.frames_rx.clone(), |
| 205 | }); | 217 | }); |
| @@ -211,21 +223,20 @@ impl TestServer { · 5 unmodified lines | |||
| 211 | let _ = client_room | 223 | let _ = client_room |
| 212 | .0 | 224 | .0 |
| 213 | .lock() | 225 | .lock() |
| 214 | .video_track_updates | 226 | .updates_tx |
| 215 | .0 | 227 | .try_broadcast(RoomUpdate::SubscribedToRemoteVideoTrack(track.clone())) |
| 216 | .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) | ||
| 217 | .unwrap(); | 228 | .unwrap(); |
| 218 | } | 229 | } |
| 219 | } | 230 | } |
| 220 | 231 | ||
| 221 | Ok(()) | 232 | Ok(sid) |
| 222 | } | 233 | } |
| 223 | 234 | ||
| 224 | async fn publish_audio_track( | 235 | async fn publish_audio_track( |
| 225 | &self, | 236 | &self, |
| 226 | token: String, | 237 | token: String, |
| 227 | _local_track: &LocalAudioTrack, | 238 | _local_track: &LocalAudioTrack, |
| 228 | ) -> Result<()> { | 239 | ) -> Result<Sid> { |
| 229 | self.executor.simulate_random_delay().await; | 240 | self.executor.simulate_random_delay().await; |
| 230 | let claims = live_kit_server::token::validate(&token, &self.secret_key)?; | 241 | let claims = live_kit_server::token::validate(&token, &self.secret_key)?; |
| 231 | let identity = claims.sub.unwrap().to_string(); | 242 | let identity = claims.sub.unwrap().to_string(); |
| @@ -247,8 +258,9 @@ impl TestServer { · 15 unmodified lines | |||
| 247 | return Err(anyhow!("user is not allowed to publish")); | 258 | return Err(anyhow!("user is not allowed to publish")); |
| 248 | } | 259 | } |
| 249 | 260 | ||
| 261 | let sid = nanoid::nanoid!(17); | ||
| 250 | let track = Arc::new(RemoteAudioTrack { | 262 | let track = Arc::new(RemoteAudioTrack { |
| 251 | sid: nanoid::nanoid!(17), | 263 | sid: sid.clone(), |
| 252 | publisher_id: identity.clone(), | 264 | publisher_id: identity.clone(), |
| 253 | }); | 265 | }); |
| 254 | 266 | ||
| @@ -261,9 +273,8 @@ impl TestServer { · 6 unmodified lines | |||
| 261 | let _ = client_room | 273 | let _ = client_room |
| 262 | .0 | 274 | .0 |
| 263 | .lock() | 275 | .lock() |
| 264 | .audio_track_updates | 276 | .updates_tx |
| 265 | .0 | 277 | .try_broadcast(RoomUpdate::SubscribedToRemoteAudioTrack( |
| 266 | .try_broadcast(RemoteAudioTrackUpdate::Subscribed( | ||
| 267 | track.clone(), | 278 | track.clone(), |
| 268 | publication.clone(), | 279 | publication.clone(), |
| 269 | )) | 280 | )) |
| @@ -271,7 +282,7 @@ impl TestServer { · 1 unmodified line | |||
| 271 | } | 282 | } |
| 272 | } | 283 | } |
| 273 | 284 | ||
| 274 | Ok(()) | 285 | Ok(sid) |
| 275 | } | 286 | } |
| 276 | 287 | ||
| 277 | fn video_tracks(&self, token: String) -> Result<Vec<Arc<RemoteVideoTrack>>> { | 288 | fn video_tracks(&self, token: String) -> Result<Vec<Arc<RemoteVideoTrack>>> { |
| @@ -369,39 +380,26 @@ impl live_kit_server::api::Client for TestApiClient { · 91 unmodified lines | |||
| 369 | } | 380 | } |
| 370 | } | 381 | } |
| 371 | 382 | ||
| 372 | pub type Sid = String; | ||
| 373 | |||
| 374 | struct RoomState { | 383 | struct RoomState { |
| 375 | connection: ( | 384 | connection: ( |
| 376 | watch::Sender<ConnectionState>, | 385 | watch::Sender<ConnectionState>, |
| 377 | watch::Receiver<ConnectionState>, | 386 | watch::Receiver<ConnectionState>, |
| 378 | ), | 387 | ), |
| 379 | display_sources: Vec<MacOSDisplay>, | 388 | display_sources: Vec<MacOSDisplay>, |
| 380 | audio_track_updates: ( | 389 | updates_tx: async_broadcast::Sender<RoomUpdate>, |
| 381 | async_broadcast::Sender<RemoteAudioTrackUpdate>, | 390 | updates_rx: async_broadcast::Receiver<RoomUpdate>, |
| 382 | async_broadcast::Receiver<RemoteAudioTrackUpdate>, | ||
| 383 | ), | ||
| 384 | video_track_updates: ( | ||
| 385 | async_broadcast::Sender<RemoteVideoTrackUpdate>, | ||
| 386 | async_broadcast::Receiver<RemoteVideoTrackUpdate>, | ||
| 387 | ), | ||
| 388 | } | ||
| 389 | |||
| 390 | #[derive(Clone, Eq, PartialEq)] | ||
| 391 | pub enum ConnectionState { | ||
| 392 | Disconnected, | ||
| 393 | Connected { url: String, token: String }, | ||
| 394 | } | 391 | } |
| 395 | 392 | ||
| 396 | pub struct Room(Mutex<RoomState>); | 393 | pub struct Room(Mutex<RoomState>); |
| 397 | 394 | ||
| 398 | impl Room { | 395 | impl Room { |
| 399 | pub fn new() -> Arc<Self> { | 396 | pub fn new() -> Arc<Self> { |
| 397 | let (updates_tx, updates_rx) = async_broadcast::broadcast(128); | ||
| 400 | Arc::new(Self(Mutex::new(RoomState { | 398 | Arc::new(Self(Mutex::new(RoomState { |
| 401 | connection: watch::channel_with(ConnectionState::Disconnected), | 399 | connection: watch::channel_with(ConnectionState::Disconnected), |
| 402 | display_sources: Default::default(), | 400 | display_sources: Default::default(), |
| 403 | video_track_updates: async_broadcast::broadcast(128), | 401 | updates_tx, |
| 404 | audio_track_updates: async_broadcast::broadcast(128), | 402 | updates_rx, |
| 405 | }))) | 403 | }))) |
| 406 | } | 404 | } |
| 407 | 405 | ||
| @@ -440,10 +438,14 @@ impl Room { · 32 unmodified lines | |||
| 440 | let this = self.clone(); | 438 | let this = self.clone(); |
| 441 | let track = track.clone(); | 439 | let track = track.clone(); |
| 442 | async move { | 440 | async move { |
| 443 | this.test_server() | 441 | let sid = this |
| 442 | .test_server() | ||
| 444 | .publish_video_track(this.token(), track) | 443 | .publish_video_track(this.token(), track) |
| 445 | .await?; | 444 | .await?; |
| 446 | Ok(LocalTrackPublication) | 445 | Ok(LocalTrackPublication { |
| 446 | muted: Default::default(), | ||
| 447 | sid, | ||
| 448 | }) | ||
| 447 | } | 449 | } |
| 448 | } | 450 | } |
| 449 | pub fn publish_audio_track( | 451 | pub fn publish_audio_track( |
| @@ -453,10 +455,14 @@ impl Room { · 3 unmodified lines | |||
| 453 | let this = self.clone(); | 455 | let this = self.clone(); |
| 454 | let track = track.clone(); | 456 | let track = track.clone(); |
| 455 | async move { | 457 | async move { |
| 456 | this.test_server() | 458 | let sid = this |
| 459 | .test_server() | ||
| 457 | .publish_audio_track(this.token(), &track) | 460 | .publish_audio_track(this.token(), &track) |
| 458 | .await?; | 461 | .await?; |
| 459 | Ok(LocalTrackPublication) | 462 | Ok(LocalTrackPublication { |
| 463 | muted: Default::default(), | ||
| 464 | sid, | ||
| 465 | }) | ||
| 460 | } | 466 | } |
| 461 | } | 467 | } |
| 462 | 468 | ||
| @@ -505,12 +511,8 @@ impl Room { · 42 unmodified lines | |||
| 505 | .collect() | 511 | .collect() |
| 506 | } | 512 | } |
| 507 | 513 | ||
| 508 | pub fn remote_audio_track_updates(&self) -> impl Stream<Item = RemoteAudioTrackUpdate> { | 514 | pub fn updates(&self) -> impl Stream<Item = RoomUpdate> { |
| 509 | self.0.lock().audio_track_updates.1.clone() | 515 | self.0.lock().updates_rx.clone() |
| 510 | } | ||
| 511 | |||
| 512 | pub fn remote_video_track_updates(&self) -> impl Stream<Item = RemoteVideoTrackUpdate> { | ||
| 513 | self.0.lock().video_track_updates.1.clone() | ||
| 514 | } | 516 | } |
| 515 | 517 | ||
| 516 | pub fn set_display_sources(&self, sources: Vec<MacOSDisplay>) { | 518 | pub fn set_display_sources(&self, sources: Vec<MacOSDisplay>) { |
| @@ -555,11 +557,27 @@ impl Drop for Room { · 38 unmodified lines | |||
| 555 | } | 557 | } |
| 556 | } | 558 | } |
| 557 | 559 | ||
| 558 | pub struct LocalTrackPublication; | 560 | #[derive(Clone)] |
| 561 | pub struct LocalTrackPublication { | ||
| 562 | sid: String, | ||
| 563 | muted: Arc<AtomicBool>, | ||
| 564 | } | ||
| 559 | 565 | ||
| 560 | impl LocalTrackPublication { | 566 | impl LocalTrackPublication { |
| 561 | pub fn set_mute(&self, _mute: bool) -> impl Future<Output = Result<()>> { | 567 | pub fn set_mute(&self, mute: bool) -> impl Future<Output = Result<()>> { |
| 562 | async { Ok(()) } | 568 | let muted = self.muted.clone(); |
| 569 | async move { | ||
| 570 | muted.store(mute, SeqCst); | ||
| 571 | Ok(()) | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 575 | pub fn is_muted(&self) -> bool { | ||
| 576 | self.muted.load(SeqCst) | ||
| 577 | } | ||
| 578 | |||
| 579 | pub fn sid(&self) -> String { | ||
| 580 | self.sid.clone() | ||
| 563 | } | 581 | } |
| 564 | } | 582 | } |
| 565 | 583 | ||
| @@ -646,20 +664,6 @@ impl RemoteAudioTrack { · 80 unmodified lines | |||
| 646 | } | 664 | } |
| 647 | } | 665 | } |
| 648 | 666 | ||
| 649 | #[derive(Clone)] | ||
| 650 | pub enum RemoteVideoTrackUpdate { | ||
| 651 | Subscribed(Arc<RemoteVideoTrack>), | ||
| 652 | Unsubscribed { publisher_id: Sid, track_id: Sid }, | ||
| 653 | } | ||
| 654 | |||
| 655 | #[derive(Clone)] | ||
| 656 | pub enum RemoteAudioTrackUpdate { | ||
| 657 | ActiveSpeakersChanged { speakers: Vec<Sid> }, | ||
| 658 | MuteChanged { track_id: Sid, muted: bool }, | ||
| 659 | Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>), | ||
| 660 | Unsubscribed { publisher_id: Sid, track_id: Sid }, | ||
| 661 | } | ||
| 662 | |||
| 663 | #[derive(Clone)] | 667 | #[derive(Clone)] |
| 664 | pub struct MacOSDisplay { | 668 | pub struct MacOSDisplay { |
| 665 | frames: ( | 669 | frames: ( |
| @@ -130,7 +130,7 @@ pub struct Project { · 129 unmodified lines | |||
| 130 | next_diagnostic_group_id: usize, | 130 | next_diagnostic_group_id: usize, |
| 131 | user_store: Model<UserStore>, | 131 | user_store: Model<UserStore>, |
| 132 | fs: Arc<dyn Fs>, | 132 | fs: Arc<dyn Fs>, |
| 133 | client_state: Option<ProjectClientState>, | 133 | client_state: ProjectClientState, |
| 134 | collaborators: HashMap<proto::PeerId, Collaborator>, | 134 | collaborators: HashMap<proto::PeerId, Collaborator>, |
| 135 | client_subscriptions: Vec<client::Subscription>, | 135 | client_subscriptions: Vec<client::Subscription>, |
| 136 | _subscriptions: Vec<gpui::Subscription>, | 136 | _subscriptions: Vec<gpui::Subscription>, |
| @@ -254,8 +254,10 @@ enum WorktreeHandle { · 117 unmodified lines | |||
| 254 | Weak(WeakModel<Worktree>), | 254 | Weak(WeakModel<Worktree>), |
| 255 | } | 255 | } |
| 256 | 256 | ||
| 257 | #[derive(Debug)] | ||
| 257 | enum ProjectClientState { | 258 | enum ProjectClientState { |
| 258 | Local { | 259 | Local, |
| 260 | Shared { | ||
| 259 | remote_id: u64, | 261 | remote_id: u64, |
| 260 | updates_tx: mpsc::UnboundedSender<LocalProjectUpdate>, | 262 | updates_tx: mpsc::UnboundedSender<LocalProjectUpdate>, |
| 261 | _send_updates: Task<Result<()>>, | 263 | _send_updates: Task<Result<()>>, |
| @@ -657,7 +659,7 @@ impl Project { · 395 unmodified lines | |||
| 657 | local_buffer_ids_by_entry_id: Default::default(), | 659 | local_buffer_ids_by_entry_id: Default::default(), |
| 658 | buffer_snapshots: Default::default(), | 660 | buffer_snapshots: Default::default(), |
| 659 | join_project_response_message_id: 0, | 661 | join_project_response_message_id: 0, |
| 660 | client_state: None, | 662 | client_state: ProjectClientState::Local, |
| 661 | opened_buffer: watch::channel(), | 663 | opened_buffer: watch::channel(), |
| 662 | client_subscriptions: Vec::new(), | 664 | client_subscriptions: Vec::new(), |
| 663 | _subscriptions: vec![ | 665 | _subscriptions: vec![ |
| @@ -756,12 +758,12 @@ impl Project { · 92 unmodified lines | |||
| 756 | cx.on_app_quit(Self::shutdown_language_servers), | 758 | cx.on_app_quit(Self::shutdown_language_servers), |
| 757 | ], | 759 | ], |
| 758 | client: client.clone(), | 760 | client: client.clone(), |
| 759 | client_state: Some(ProjectClientState::Remote { | 761 | client_state: ProjectClientState::Remote { |
| 760 | sharing_has_stopped: false, | 762 | sharing_has_stopped: false, |
| 761 | capability: Capability::ReadWrite, | 763 | capability: Capability::ReadWrite, |
| 762 | remote_id, | 764 | remote_id, |
| 763 | replica_id, | 765 | replica_id, |
| 764 | }), | 766 | }, |
| 765 | supplementary_language_servers: HashMap::default(), | 767 | supplementary_language_servers: HashMap::default(), |
| 766 | language_servers: Default::default(), | 768 | language_servers: Default::default(), |
| 767 | language_server_ids: Default::default(), | 769 | language_server_ids: Default::default(), |
| @@ -828,16 +830,16 @@ impl Project { · 60 unmodified lines | |||
| 828 | 830 | ||
| 829 | fn release(&mut self, cx: &mut AppContext) { | 831 | fn release(&mut self, cx: &mut AppContext) { |
| 830 | match &self.client_state { | 832 | match &self.client_state { |
| 831 | Some(ProjectClientState::Local { .. }) => { | 833 | ProjectClientState::Local => {} |
| 834 | ProjectClientState::Shared { .. } => { | ||
| 832 | let _ = self.unshare_internal(cx); | 835 | let _ = self.unshare_internal(cx); |
| 833 | } | 836 | } |
| 834 | Some(ProjectClientState::Remote { remote_id, .. }) => { | 837 | ProjectClientState::Remote { remote_id, .. } => { |
| 835 | let _ = self.client.send(proto::LeaveProject { | 838 | let _ = self.client.send(proto::LeaveProject { |
| 836 | project_id: *remote_id, | 839 | project_id: *remote_id, |
| 837 | }); | 840 | }); |
| 838 | self.disconnected_from_host_internal(cx); | 841 | self.disconnected_from_host_internal(cx); |
| 839 | } | 842 | } |
| 840 | _ => {} | ||
| 841 | } | 843 | } |
| 842 | } | 844 | } |
| 843 | 845 | ||
| @@ -1058,21 +1060,22 @@ impl Project { · 214 unmodified lines | |||
| 1058 | } | 1060 | } |
| 1059 | 1061 | ||
| 1060 | pub fn remote_id(&self) -> Option<u64> { | 1062 | pub fn remote_id(&self) -> Option<u64> { |
| 1061 | match self.client_state.as_ref()? { | 1063 | match self.client_state { |
| 1062 | ProjectClientState::Local { remote_id, .. } | 1064 | ProjectClientState::Local => None, |
| 1063 | | ProjectClientState::Remote { remote_id, .. } => Some(*remote_id), | 1065 | ProjectClientState::Shared { remote_id, .. } |
| 1066 | | ProjectClientState::Remote { remote_id, .. } => Some(remote_id), | ||
| 1064 | } | 1067 | } |
| 1065 | } | 1068 | } |
| 1066 | 1069 | ||
| 1067 | pub fn replica_id(&self) -> ReplicaId { | 1070 | pub fn replica_id(&self) -> ReplicaId { |
| 1068 | match &self.client_state { | 1071 | match self.client_state { |
| 1069 | Some(ProjectClientState::Remote { replica_id, .. }) => *replica_id, | 1072 | ProjectClientState::Remote { replica_id, .. } => replica_id, |
| 1070 | _ => 0, | 1073 | _ => 0, |
| 1071 | } | 1074 | } |
| 1072 | } | 1075 | } |
| 1073 | 1076 | ||
| 1074 | fn metadata_changed(&mut self, cx: &mut ModelContext<Self>) { | 1077 | fn metadata_changed(&mut self, cx: &mut ModelContext<Self>) { |
| 1075 | if let Some(ProjectClientState::Local { updates_tx, .. }) = &mut self.client_state { | 1078 | if let ProjectClientState::Shared { updates_tx, .. } = &mut self.client_state { |
| 1076 | updates_tx | 1079 | updates_tx |
| 1077 | .unbounded_send(LocalProjectUpdate::WorktreesChanged) | 1080 | .unbounded_send(LocalProjectUpdate::WorktreesChanged) |
| 1078 | .ok(); | 1081 | .ok(); |
| @@ -1362,7 +1365,7 @@ impl Project { · 283 unmodified lines | |||
| 1362 | } | 1365 | } |
| 1363 | 1366 | ||
| 1364 | pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> { | 1367 | pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> { |
| 1365 | if self.client_state.is_some() { | 1368 | if !matches!(self.client_state, ProjectClientState::Local) { |
| 1366 | return Err(anyhow!("project was already shared")); | 1369 | return Err(anyhow!("project was already shared")); |
| 1367 | } | 1370 | } |
| 1368 | self.client_subscriptions.push( | 1371 | self.client_subscriptions.push( |
| @@ -1423,7 +1426,7 @@ impl Project { · 54 unmodified lines | |||
| 1423 | 1426 | ||
| 1424 | let (updates_tx, mut updates_rx) = mpsc::unbounded(); | 1427 | let (updates_tx, mut updates_rx) = mpsc::unbounded(); |
| 1425 | let client = self.client.clone(); | 1428 | let client = self.client.clone(); |
| 1426 | self.client_state = Some(ProjectClientState::Local { | 1429 | self.client_state = ProjectClientState::Shared { |
| 1427 | remote_id: project_id, | 1430 | remote_id: project_id, |
| 1428 | updates_tx, | 1431 | updates_tx, |
| 1429 | _send_updates: cx.spawn(move |this, mut cx| async move { | 1432 | _send_updates: cx.spawn(move |this, mut cx| async move { |
| @@ -1508,7 +1511,7 @@ impl Project { · 78 unmodified lines | |||
| 1508 | } | 1511 | } |
| 1509 | Ok(()) | 1512 | Ok(()) |
| 1510 | }), | 1513 | }), |
| 1511 | }); | 1514 | }; |
| 1512 | 1515 | ||
| 1513 | self.metadata_changed(cx); | 1516 | self.metadata_changed(cx); |
| 1514 | cx.emit(Event::RemoteIdChanged(Some(project_id))); | 1517 | cx.emit(Event::RemoteIdChanged(Some(project_id))); |
| @@ -1578,7 +1581,8 @@ impl Project { · 63 unmodified lines | |||
| 1578 | return Err(anyhow!("attempted to unshare a remote project")); | 1581 | return Err(anyhow!("attempted to unshare a remote project")); |
| 1579 | } | 1582 | } |
| 1580 | 1583 | ||
| 1581 | if let Some(ProjectClientState::Local { remote_id, .. }) = self.client_state.take() { | 1584 | if let ProjectClientState::Shared { remote_id, .. } = self.client_state { |
| 1585 | self.client_state = ProjectClientState::Local; | ||
| 1582 | self.collaborators.clear(); | 1586 | self.collaborators.clear(); |
| 1583 | self.shared_buffers.clear(); | 1587 | self.shared_buffers.clear(); |
| 1584 | self.client_subscriptions.clear(); | 1588 | self.client_subscriptions.clear(); |
| @@ -1629,23 +1633,23 @@ impl Project { · 44 unmodified lines | |||
| 1629 | } else { | 1633 | } else { |
| 1630 | Capability::ReadOnly | 1634 | Capability::ReadOnly |
| 1631 | }; | 1635 | }; |
| 1632 | if let Some(ProjectClientState::Remote { capability, .. }) = &mut self.client_state { | 1636 | if let ProjectClientState::Remote { capability, .. } = &mut self.client_state { |
| 1633 | if *capability == new_capability { | 1637 | if *capability == new_capability { |
| 1634 | return; | 1638 | return; |
| 1635 | } | 1639 | } |
| 1636 | 1640 | ||
| 1637 | *capability = new_capability; | 1641 | *capability = new_capability; |
| 1638 | } | 1642 | for buffer in self.opened_buffers() { |
| 1639 | for buffer in self.opened_buffers() { | 1643 | buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); |
| 1640 | buffer.update(cx, |buffer, cx| buffer.set_capability(new_capability, cx)); | 1644 | } |
| 1641 | } | 1645 | } |
| 1642 | } | 1646 | } |
| 1643 | 1647 | ||
| 1644 | fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) { | 1648 | fn disconnected_from_host_internal(&mut self, cx: &mut AppContext) { |
| 1645 | if let Some(ProjectClientState::Remote { | 1649 | if let ProjectClientState::Remote { |
| 1646 | sharing_has_stopped, | 1650 | sharing_has_stopped, |
| 1647 | .. | 1651 | .. |
| 1648 | }) = &mut self.client_state | 1652 | } = &mut self.client_state |
| 1649 | { | 1653 | { |
| 1650 | *sharing_has_stopped = true; | 1654 | *sharing_has_stopped = true; |
| 1651 | 1655 | ||
| @@ -1684,18 +1688,18 @@ impl Project { · 32 unmodified lines | |||
| 1684 | 1688 | ||
| 1685 | pub fn is_disconnected(&self) -> bool { | 1689 | pub fn is_disconnected(&self) -> bool { |
| 1686 | match &self.client_state { | 1690 | match &self.client_state { |
| 1687 | Some(ProjectClientState::Remote { | 1691 | ProjectClientState::Remote { |
| 1688 | sharing_has_stopped, | 1692 | sharing_has_stopped, |
| 1689 | .. | 1693 | .. |
| 1690 | }) => *sharing_has_stopped, | 1694 | } => *sharing_has_stopped, |
| 1691 | _ => false, | 1695 | _ => false, |
| 1692 | } | 1696 | } |
| 1693 | } | 1697 | } |
| 1694 | 1698 | ||
| 1695 | pub fn capability(&self) -> Capability { | 1699 | pub fn capability(&self) -> Capability { |
| 1696 | match &self.client_state { | 1700 | match &self.client_state { |
| 1697 | Some(ProjectClientState::Remote { capability, .. }) => *capability, | 1701 | ProjectClientState::Remote { capability, .. } => *capability, |
| 1698 | Some(ProjectClientState::Local { .. }) | None => Capability::ReadWrite, | 1702 | ProjectClientState::Shared { .. } | ProjectClientState::Local => Capability::ReadWrite, |
| 1699 | } | 1703 | } |
| 1700 | } | 1704 | } |
| 1701 | 1705 | ||
| @@ -1705,8 +1709,8 @@ impl Project { · 3 unmodified lines | |||
| 1705 | 1709 | ||
| 1706 | pub fn is_local(&self) -> bool { | 1710 | pub fn is_local(&self) -> bool { |
| 1707 | match &self.client_state { | 1711 | match &self.client_state { |
| 1708 | Some(ProjectClientState::Remote { .. }) => false, | 1712 | ProjectClientState::Local | ProjectClientState::Shared { .. } => true, |
| 1709 | _ => true, | 1713 | ProjectClientState::Remote { .. } => false, |
| 1710 | } | 1714 | } |
| 1711 | } | 1715 | } |
| 1712 | 1716 | ||
| @@ -6165,8 +6169,8 @@ impl Project { · 4452 unmodified lines | |||
| 6165 | 6169 | ||
| 6166 | pub fn is_shared(&self) -> bool { | 6170 | pub fn is_shared(&self) -> bool { |
| 6167 | match &self.client_state { | 6171 | match &self.client_state { |
| 6168 | Some(ProjectClientState::Local { .. }) => true, | 6172 | ProjectClientState::Shared { .. } => true, |
| 6169 | _ => false, | 6173 | ProjectClientState::Local | ProjectClientState::Remote { .. } => false, |
| 6170 | } | 6174 | } |
| 6171 | } | 6175 | } |
| 6172 | 6176 | ||
| @@ -7954,7 +7958,7 @@ impl Project { · 1781 unmodified lines | |||
| 7954 | cx: &mut AppContext, | 7958 | cx: &mut AppContext, |
| 7955 | ) -> u64 { | 7959 | ) -> u64 { |
| 7956 | let buffer_id = buffer.read(cx).remote_id(); | 7960 | let buffer_id = buffer.read(cx).remote_id(); |
| 7957 | if let Some(ProjectClientState::Local { updates_tx, .. }) = &self.client_state { | 7961 | if let ProjectClientState::Shared { updates_tx, .. } = &self.client_state { |
| 7958 | updates_tx | 7962 | updates_tx |
| 7959 | .unbounded_send(LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id }) | 7963 | .unbounded_send(LocalProjectUpdate::CreateBufferForPeer { peer_id, buffer_id }) |
| 7960 | .ok(); | 7964 | .ok(); |
| @@ -8003,21 +8007,21 @@ impl Project { · 42 unmodified lines | |||
| 8003 | } | 8007 | } |
| 8004 | 8008 | ||
| 8005 | fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { | 8009 | fn synchronize_remote_buffers(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> { |
| 8006 | let project_id = match self.client_state.as_ref() { | 8010 | let project_id = match self.client_state { |
| 8007 | Some(ProjectClientState::Remote { | 8011 | ProjectClientState::Remote { |
| 8008 | sharing_has_stopped, | 8012 | sharing_has_stopped, |
| 8009 | remote_id, | 8013 | remote_id, |
| 8010 | .. | 8014 | .. |
| 8011 | }) => { | 8015 | } => { |
| 8012 | if *sharing_has_stopped { | 8016 | if sharing_has_stopped { |
| 8013 | return Task::ready(Err(anyhow!( | 8017 | return Task::ready(Err(anyhow!( |
| 8014 | "can't synchronize remote buffers on a readonly project" | 8018 | "can't synchronize remote buffers on a readonly project" |
| 8015 | ))); | 8019 | ))); |
| 8016 | } else { | 8020 | } else { |
| 8017 | *remote_id | 8021 | remote_id |
| 8018 | } | 8022 | } |
| 8019 | } | 8023 | } |
| 8020 | Some(ProjectClientState::Local { .. }) | None => { | 8024 | ProjectClientState::Shared { .. } | ProjectClientState::Local => { |
| 8021 | return Task::ready(Err(anyhow!( | 8025 | return Task::ready(Err(anyhow!( |
| 8022 | "can't synchronize remote buffers on a local project" | 8026 | "can't synchronize remote buffers on a local project" |
| 8023 | ))) | 8027 | ))) |
| @@ -1130,7 +1130,6 @@ mod tests { · 1129 unmodified lines | |||
| 1130 | #[gpui::test] | 1130 | #[gpui::test] |
| 1131 | async fn test_search_simple(cx: &mut TestAppContext) { | 1131 | async fn test_search_simple(cx: &mut TestAppContext) { |
| 1132 | let (editor, search_bar, cx) = init_test(cx); | 1132 | let (editor, search_bar, cx) = init_test(cx); |
| 1133 | // todo! osiewicz: these tests asserted on background color as well, that should be brought back. | ||
| 1134 | let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| { | 1133 | let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| { |
| 1135 | background_highlights | 1134 | background_highlights |
| 1136 | .into_iter() | 1135 | .into_iter() |
| @@ -1395,7 +1394,6 @@ mod tests { · 258 unmodified lines | |||
| 1395 | }) | 1394 | }) |
| 1396 | .await | 1395 | .await |
| 1397 | .unwrap(); | 1396 | .unwrap(); |
| 1398 | // todo! osiewicz: these tests previously asserted on background color highlights; that should be introduced back. | ||
| 1399 | let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| { | 1397 | let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| { |
| 1400 | background_highlights | 1398 | background_highlights |
| 1401 | .into_iter() | 1399 | .into_iter() |
| @@ -1015,9 +1015,6 @@ impl ProjectSearchView { · 1014 unmodified lines | |||
| 1015 | workspace.add_item(Box::new(view.clone()), cx); | 1015 | workspace.add_item(Box::new(view.clone()), cx); |
| 1016 | view | 1016 | view |
| 1017 | }; | 1017 | }; |
| 1018 | |||
| 1019 | workspace.add_item(Box::new(search.clone()), cx); | ||
| 1020 | |||
| 1021 | search.update(cx, |search, cx| { | 1018 | search.update(cx, |search, cx| { |
| 1022 | if let Some(query) = query { | 1019 | if let Some(query) = query { |
| 1023 | search.set_query(&query, cx); | 1020 | search.set_query(&query, cx); |
| @@ -1984,6 +1981,7 @@ pub mod tests { · 960 unmodified lines | |||
| 1984 | use semantic_index::semantic_index_settings::SemanticIndexSettings; | 1981 | use semantic_index::semantic_index_settings::SemanticIndexSettings; |
| 1985 | use serde_json::json; | 1982 | use serde_json::json; |
| 1986 | use settings::{Settings, SettingsStore}; | 1983 | use settings::{Settings, SettingsStore}; |
| 1984 | use workspace::DeploySearch; | ||
| 1987 | 1985 | ||
| 1988 | #[gpui::test] | 1986 | #[gpui::test] |
| 1989 | async fn test_project_search(cx: &mut TestAppContext) { | 1987 | async fn test_project_search(cx: &mut TestAppContext) { |
| @@ -3111,6 +3109,124 @@ pub mod tests { · 1121 unmodified lines | |||
| 3111 | .unwrap(); | 3109 | .unwrap(); |
| 3112 | } | 3110 | } |
| 3113 | 3111 | ||
| 3112 | #[gpui::test] | ||
| 3113 | async fn test_deploy_search_with_multiple_panes(cx: &mut TestAppContext) { | ||
| 3114 | init_test(cx); | ||
| 3115 | |||
| 3116 | let fs = FakeFs::new(cx.background_executor.clone()); | ||
| 3117 | fs.insert_tree( | ||
| 3118 | "/dir", | ||
| 3119 | json!({ | ||
| 3120 | "one.rs": "const ONE: usize = 1;", | ||
| 3121 | }), | ||
| 3122 | ) | ||
| 3123 | .await; | ||
| 3124 | let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; | ||
| 3125 | let worktree_id = project.update(cx, |this, cx| { | ||
| 3126 | this.worktrees().next().unwrap().read(cx).id() | ||
| 3127 | }); | ||
| 3128 | let window = cx.add_window(|cx| Workspace::test_new(project, cx)); | ||
| 3129 | let panes: Vec<_> = window | ||
| 3130 | .update(cx, |this, _| this.panes().to_owned()) | ||
| 3131 | .unwrap(); | ||
| 3132 | assert_eq!(panes.len(), 1); | ||
| 3133 | let first_pane = panes.get(0).cloned().unwrap(); | ||
| 3134 | assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 0); | ||
| 3135 | window | ||
| 3136 | .update(cx, |workspace, cx| { | ||
| 3137 | workspace.open_path( | ||
| 3138 | (worktree_id, "one.rs"), | ||
| 3139 | Some(first_pane.downgrade()), | ||
| 3140 | true, | ||
| 3141 | cx, | ||
| 3142 | ) | ||
| 3143 | }) | ||
| 3144 | .unwrap() | ||
| 3145 | .await | ||
| 3146 | .unwrap(); | ||
| 3147 | assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1); | ||
| 3148 | let second_pane = window | ||
| 3149 | .update(cx, |workspace, cx| { | ||
| 3150 | workspace.split_and_clone(first_pane.clone(), workspace::SplitDirection::Right, cx) | ||
| 3151 | }) | ||
| 3152 | .unwrap() | ||
| 3153 | .unwrap(); | ||
| 3154 | assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 1); | ||
| 3155 | assert!(window | ||
| 3156 | .update(cx, |_, cx| second_pane | ||
| 3157 | .focus_handle(cx) | ||
| 3158 | .contains_focused(cx)) | ||
| 3159 | .unwrap()); | ||
| 3160 | let search_bar = window.build_view(cx, |_| ProjectSearchBar::new()); | ||
| 3161 | window | ||
| 3162 | .update(cx, { | ||
| 3163 | let search_bar = search_bar.clone(); | ||
| 3164 | let pane = first_pane.clone(); | ||
| 3165 | move |workspace, cx| { | ||
| 3166 | assert_eq!(workspace.panes().len(), 2); | ||
| 3167 | pane.update(cx, move |pane, cx| { | ||
| 3168 | pane.toolbar() | ||
| 3169 | .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) | ||
| 3170 | }); | ||
| 3171 | } | ||
| 3172 | }) | ||
| 3173 | .unwrap(); | ||
| 3174 | window | ||
| 3175 | .update(cx, { | ||
| 3176 | let search_bar = search_bar.clone(); | ||
| 3177 | let pane = second_pane.clone(); | ||
| 3178 | move |workspace, cx| { | ||
| 3179 | assert_eq!(workspace.panes().len(), 2); | ||
| 3180 | pane.update(cx, move |pane, cx| { | ||
| 3181 | pane.toolbar() | ||
| 3182 | .update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx)) | ||
| 3183 | }); | ||
| 3184 | |||
| 3185 | ProjectSearchView::new_search(workspace, &workspace::NewSearch, cx) | ||
| 3186 | } | ||
| 3187 | }) | ||
| 3188 | .unwrap(); | ||
| 3189 | |||
| 3190 | cx.run_until_parked(); | ||
| 3191 | assert_eq!(cx.update(|cx| second_pane.read(cx).items_len()), 2); | ||
| 3192 | assert_eq!(cx.update(|cx| first_pane.read(cx).items_len()), 1); | ||
| 3193 | window | ||
| 3194 | .update(cx, |workspace, cx| { | ||
| 3195 | assert_eq!(workspace.active_pane(), &second_pane); | ||
| 3196 | second_pane.update(cx, |this, cx| { | ||
| 3197 | assert_eq!(this.active_item_index(), 1); | ||
| 3198 | this.activate_prev_item(false, cx); | ||
| 3199 | assert_eq!(this.active_item_index(), 0); | ||
| 3200 | }); | ||
| 3201 | workspace.activate_pane_in_direction(workspace::SplitDirection::Left, cx); | ||
| 3202 | }) | ||
| 3203 | .unwrap(); | ||
| 3204 | window | ||
| 3205 | .update(cx, |workspace, cx| { | ||
| 3206 | assert_eq!(workspace.active_pane(), &first_pane); | ||
| 3207 | assert_eq!(first_pane.read(cx).items_len(), 1); | ||
| 3208 | assert_eq!(second_pane.read(cx).items_len(), 2); | ||
| 3209 | }) | ||
| 3210 | .unwrap(); | ||
| 3211 | cx.dispatch_action(window.into(), DeploySearch); | ||
| 3212 | |||
| 3213 | // We should have same # of items in workspace, the only difference being that | ||
| 3214 | // the search we've deployed previously should now be focused. | ||
| 3215 | window | ||
| 3216 | .update(cx, |workspace, cx| { | ||
| 3217 | assert_eq!(workspace.active_pane(), &second_pane); | ||
| 3218 | second_pane.update(cx, |this, _| { | ||
| 3219 | assert_eq!(this.active_item_index(), 1); | ||
| 3220 | assert_eq!(this.items_len(), 2); | ||
| 3221 | }); | ||
| 3222 | first_pane.update(cx, |this, cx| { | ||
| 3223 | assert!(!cx.focus_handle().contains_focused(cx)); | ||
| 3224 | assert_eq!(this.items_len(), 1); | ||
| 3225 | }); | ||
| 3226 | }) | ||
| 3227 | .unwrap(); | ||
| 3228 | } | ||
| 3229 | |||
| 3114 | pub fn init_test(cx: &mut TestAppContext) { | 3230 | pub fn init_test(cx: &mut TestAppContext) { |
| 3115 | cx.update(|cx| { | 3231 | cx.update(|cx| { |
| 3116 | let settings = SettingsStore::test(cx); | 3232 | let settings = SettingsStore::test(cx); |
| @@ -6,7 +6,7 @@ use gpui::{ · 5 unmodified lines | |||
| 6 | InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext, | 6 | InteractiveElementState, Interactivity, IntoElement, LayoutId, Model, ModelContext, |
| 7 | ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point, | 7 | ModifiersChangedEvent, MouseButton, MouseMoveEvent, Pixels, PlatformInputHandler, Point, |
| 8 | ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, | 8 | ShapedLine, StatefulInteractiveElement, Styled, TextRun, TextStyle, TextSystem, UnderlineStyle, |
| 9 | WhiteSpace, WindowContext, | 9 | WeakView, WhiteSpace, WindowContext, |
| 10 | }; | 10 | }; |
| 11 | use itertools::Itertools; | 11 | use itertools::Itertools; |
| 12 | use language::CursorShape; | 12 | use language::CursorShape; |
| @@ -24,6 +24,7 @@ use terminal::{ · 11 unmodified lines | |||
| 24 | }; | 24 | }; |
| 25 | use theme::{ActiveTheme, Theme, ThemeSettings}; | 25 | use theme::{ActiveTheme, Theme, ThemeSettings}; |
| 26 | use ui::Tooltip; | 26 | use ui::Tooltip; |
| 27 | use workspace::Workspace; | ||
| 27 | 28 | ||
| 28 | use std::mem; | 29 | use std::mem; |
| 29 | use std::{fmt::Debug, ops::RangeInclusive}; | 30 | use std::{fmt::Debug, ops::RangeInclusive}; |
| @@ -142,6 +143,7 @@ impl LayoutRect { · 112 unmodified lines | |||
| 142 | ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection? | 143 | ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection? |
| 143 | pub struct TerminalElement { | 144 | pub struct TerminalElement { |
| 144 | terminal: Model<Terminal>, | 145 | terminal: Model<Terminal>, |
| 146 | workspace: WeakView<Workspace>, | ||
| 145 | focus: FocusHandle, | 147 | focus: FocusHandle, |
| 146 | focused: bool, | 148 | focused: bool, |
| 147 | cursor_visible: bool, | 149 | cursor_visible: bool, |
| @@ -160,6 +162,7 @@ impl StatefulInteractiveElement for TerminalElement {} · 12 unmodified lines | |||
| 160 | impl TerminalElement { | 162 | impl TerminalElement { |
| 161 | pub fn new( | 163 | pub fn new( |
| 162 | terminal: Model<Terminal>, | 164 | terminal: Model<Terminal>, |
| 165 | workspace: WeakView<Workspace>, | ||
| 163 | focus: FocusHandle, | 166 | focus: FocusHandle, |
| 164 | focused: bool, | 167 | focused: bool, |
| 165 | cursor_visible: bool, | 168 | cursor_visible: bool, |
| @@ -167,6 +170,7 @@ impl TerminalElement { · 1 unmodified line | |||
| 167 | ) -> TerminalElement { | 170 | ) -> TerminalElement { |
| 168 | TerminalElement { | 171 | TerminalElement { |
| 169 | terminal, | 172 | terminal, |
| 173 | workspace, | ||
| 170 | focused, | 174 | focused, |
| 171 | focus: focus.clone(), | 175 | focus: focus.clone(), |
| 172 | cursor_visible, | 176 | cursor_visible, |
| @@ -762,6 +766,7 @@ impl Element for TerminalElement { · 589 unmodified lines | |||
| 762 | .cursor | 766 | .cursor |
| 763 | .as_ref() | 767 | .as_ref() |
| 764 | .map(|cursor| cursor.bounding_rect(origin)), | 768 | .map(|cursor| cursor.bounding_rect(origin)), |
| 769 | workspace: self.workspace.clone(), | ||
| 765 | }; | 770 | }; |
| 766 | 771 | ||
| 767 | self.register_mouse_listeners(origin, layout.mode, bounds, cx); | 772 | self.register_mouse_listeners(origin, layout.mode, bounds, cx); |
| @@ -831,6 +836,7 @@ impl IntoElement for TerminalElement { · 63 unmodified lines | |||
| 831 | struct TerminalInputHandler { | 836 | struct TerminalInputHandler { |
| 832 | cx: AsyncWindowContext, | 837 | cx: AsyncWindowContext, |
| 833 | terminal: Model<Terminal>, | 838 | terminal: Model<Terminal>, |
| 839 | workspace: WeakView<Workspace>, | ||
| 834 | cursor_bounds: Option<Bounds<Pixels>>, | 840 | cursor_bounds: Option<Bounds<Pixels>>, |
| 835 | } | 841 | } |
| 836 | 842 | ||
| @@ -871,7 +877,14 @@ impl PlatformInputHandler for TerminalInputHandler { · 34 unmodified lines | |||
| 871 | .update(|_, cx| { | 877 | .update(|_, cx| { |
| 872 | self.terminal.update(cx, |terminal, _| { | 878 | self.terminal.update(cx, |terminal, _| { |
| 873 | terminal.input(text.into()); | 879 | terminal.input(text.into()); |
| 874 | }) | 880 | }); |
| 881 | |||
| 882 | self.workspace | ||
| 883 | .update(cx, |this, cx| { | ||
| 884 | let telemetry = this.project().read(cx).client().telemetry().clone(); | ||
| 885 | telemetry.log_edit_event("terminal"); | ||
| 886 | }) | ||
| 887 | .ok(); | ||
| 875 | }) | 888 | }) |
| 876 | .ok(); | 889 | .ok(); |
| 877 | } | 890 | } |
| @@ -73,6 +73,7 @@ pub fn init(cx: &mut AppContext) { · 72 unmodified lines | |||
| 73 | ///A terminal view, maintains the PTY's file handles and communicates with the terminal | 73 | ///A terminal view, maintains the PTY's file handles and communicates with the terminal |
| 74 | pub struct TerminalView { | 74 | pub struct TerminalView { |
| 75 | terminal: Model<Terminal>, | 75 | terminal: Model<Terminal>, |
| 76 | workspace: WeakView<Workspace>, | ||
| 76 | focus_handle: FocusHandle, | 77 | focus_handle: FocusHandle, |
| 77 | has_new_content: bool, | 78 | has_new_content: bool, |
| 78 | //Currently using iTerm bell, show bell emoji in tab until input is received | 79 | //Currently using iTerm bell, show bell emoji in tab until input is received |
| @@ -135,6 +136,7 @@ impl TerminalView { · 56 unmodified lines | |||
| 135 | workspace_id: WorkspaceId, | 136 | workspace_id: WorkspaceId, |
| 136 | cx: &mut ViewContext<Self>, | 137 | cx: &mut ViewContext<Self>, |
| 137 | ) -> Self { | 138 | ) -> Self { |
| 139 | let workspace_handle = workspace.clone(); | ||
| 138 | cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); | 140 | cx.observe(&terminal, |_, _, cx| cx.notify()).detach(); |
| 139 | cx.subscribe(&terminal, move |this, _, event, cx| match event { | 141 | cx.subscribe(&terminal, move |this, _, event, cx| match event { |
| 140 | Event::Wakeup => { | 142 | Event::Wakeup => { |
| @@ -279,6 +281,7 @@ impl TerminalView { · 138 unmodified lines | |||
| 279 | 281 | ||
| 280 | Self { | 282 | Self { |
| 281 | terminal, | 283 | terminal, |
| 284 | workspace: workspace_handle, | ||
| 282 | has_new_content: true, | 285 | has_new_content: true, |
| 283 | has_bell: false, | 286 | has_bell: false, |
| 284 | focus_handle: cx.focus_handle(), | 287 | focus_handle: cx.focus_handle(), |
| @@ -661,6 +664,7 @@ impl Render for TerminalView { · 376 unmodified lines | |||
| 661 | // TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu | 664 | // TODO: Oddly this wrapper div is needed for TerminalElement to not steal events from the context menu |
| 662 | div().size_full().child(TerminalElement::new( | 665 | div().size_full().child(TerminalElement::new( |
| 663 | terminal_handle, | 666 | terminal_handle, |
| 667 | self.workspace.clone(), | ||
| 664 | self.focus_handle.clone(), | 668 | self.focus_handle.clone(), |
| 665 | focused, | 669 | focused, |
| 666 | self.should_show_cursor(focused, cx), | 670 | self.should_show_cursor(focused, cx), |
| @@ -194,9 +194,21 @@ impl settings::Settings for ThemeSettings { · 193 unmodified lines | |||
| 194 | ..Default::default() | 194 | ..Default::default() |
| 195 | }; | 195 | }; |
| 196 | 196 | ||
| 197 | root_schema | 197 | let available_fonts = cx |
| 198 | .definitions | 198 | .text_system() |
| 199 | .extend([("ThemeName".into(), theme_name_schema.into())]); | 199 | .all_font_families() |
| 200 | .into_iter() | ||
| 201 | .map(Value::String) | ||
| 202 | .collect(); | ||
| 203 | let fonts_schema = SchemaObject { | ||
| 204 | instance_type: Some(InstanceType::String.into()), | ||
| 205 | enum_values: Some(available_fonts), | ||
| 206 | ..Default::default() | ||
| 207 | }; | ||
| 208 | root_schema.definitions.extend([ | ||
| 209 | ("ThemeName".into(), theme_name_schema.into()), | ||
| 210 | ("FontFamilies".into(), fonts_schema.into()), | ||
| 211 | ]); | ||
| 200 | 212 | ||
| 201 | root_schema | 213 | root_schema |
| 202 | .schema | 214 | .schema |
| @@ -204,10 +216,16 @@ impl settings::Settings for ThemeSettings { · 1 unmodified line | |||
| 204 | .as_mut() | 216 | .as_mut() |
| 205 | .unwrap() | 217 | .unwrap() |
| 206 | .properties | 218 | .properties |
| 207 | .extend([( | 219 | .extend([ |
| 208 | "theme".to_owned(), | 220 | ( |
| 209 | Schema::new_ref("#/definitions/ThemeName".into()), | 221 | "theme".to_owned(), |
| 210 | )]); | 222 | Schema::new_ref("#/definitions/ThemeName".into()), |
| 223 | ), | ||
| 224 | ( | ||
| 225 | "buffer_font_family".to_owned(), | ||
| 226 | Schema::new_ref("#/definitions/FontFamilies".into()), | ||
| 227 | ), | ||
| 228 | ]); | ||
| 211 | 229 | ||
| 212 | root_schema | 230 | root_schema |
| 213 | } | 231 | } |
| @@ -22,7 +22,7 @@ pub struct PlayerColors(pub Vec<PlayerColor>); · 21 unmodified lines | |||
| 22 | impl Default for PlayerColors { | 22 | impl Default for PlayerColors { |
| 23 | /// Don't use this! | 23 | /// Don't use this! |
| 24 | /// We have to have a default to be `[refineable::Refinable]`. | 24 | /// We have to have a default to be `[refineable::Refinable]`. |
| 25 | /// todo!("Find a way to not need this for Refinable") | 25 | /// TODO "Find a way to not need this for Refinable" |
| 26 | fn default() -> Self { | 26 | fn default() -> Self { |
| 27 | Self::dark() | 27 | Self::dark() |
| 28 | } | 28 | } |
| @@ -69,7 +69,7 @@ pub fn andromeda() -> UserThemeFamily { · 68 unmodified lines | |||
| 69 | editor_highlighted_line_background: Some(rgba(0x21242bff).into()), | 69 | editor_highlighted_line_background: Some(rgba(0x21242bff).into()), |
| 70 | editor_line_number: Some(rgba(0xf7f7f859).into()), | 70 | editor_line_number: Some(rgba(0xf7f7f859).into()), |
| 71 | editor_active_line_number: Some(rgba(0xf7f7f8ff).into()), | 71 | editor_active_line_number: Some(rgba(0xf7f7f8ff).into()), |
| 72 | editor_invisible: Some(rgba(0xaca8aeff).into()), | 72 | editor_invisible: Some(rgba(0x64646dff).into()), |
| 73 | editor_wrap_guide: Some(rgba(0xf7f7f80d).into()), | 73 | editor_wrap_guide: Some(rgba(0xf7f7f80d).into()), |
| 74 | editor_active_wrap_guide: Some(rgba(0xf7f7f81a).into()), | 74 | editor_active_wrap_guide: Some(rgba(0xf7f7f81a).into()), |
| 75 | editor_document_highlight_read_background: Some(rgba(0x11a7931a).into()), | 75 | editor_document_highlight_read_background: Some(rgba(0x11a7931a).into()), |
| @@ -70,7 +70,7 @@ pub fn atelier() -> UserThemeFamily { · 69 unmodified lines | |||
| 70 | editor_highlighted_line_background: Some(rgba(0x221f26ff).into()), | 70 | editor_highlighted_line_background: Some(rgba(0x221f26ff).into()), |
| 71 | editor_line_number: Some(rgba(0xefecf459).into()), | 71 | editor_line_number: Some(rgba(0xefecf459).into()), |
| 72 | editor_active_line_number: Some(rgba(0xefecf4ff).into()), | 72 | editor_active_line_number: Some(rgba(0xefecf4ff).into()), |
| 73 | editor_invisible: Some(rgba(0x898591ff).into()), | 73 | editor_invisible: Some(rgba(0x726c7aff).into()), |
| 74 | editor_wrap_guide: Some(rgba(0xefecf40d).into()), | 74 | editor_wrap_guide: Some(rgba(0xefecf40d).into()), |
| 75 | editor_active_wrap_guide: Some(rgba(0xefecf41a).into()), | 75 | editor_active_wrap_guide: Some(rgba(0xefecf41a).into()), |
| 76 | editor_document_highlight_read_background: Some(rgba(0x576dda1a).into()), | 76 | editor_document_highlight_read_background: Some(rgba(0x576dda1a).into()), |
| @@ -535,7 +535,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 535 | editor_highlighted_line_background: Some(rgba(0xe6e3ebff).into()), | 535 | editor_highlighted_line_background: Some(rgba(0xe6e3ebff).into()), |
| 536 | editor_line_number: Some(rgba(0x19171c59).into()), | 536 | editor_line_number: Some(rgba(0x19171c59).into()), |
| 537 | editor_active_line_number: Some(rgba(0x19171cff).into()), | 537 | editor_active_line_number: Some(rgba(0x19171cff).into()), |
| 538 | editor_invisible: Some(rgba(0x5a5462ff).into()), | 538 | editor_invisible: Some(rgba(0x726c7aff).into()), |
| 539 | editor_wrap_guide: Some(rgba(0x19171c0d).into()), | 539 | editor_wrap_guide: Some(rgba(0x19171c0d).into()), |
| 540 | editor_active_wrap_guide: Some(rgba(0x19171c1a).into()), | 540 | editor_active_wrap_guide: Some(rgba(0x19171c1a).into()), |
| 541 | editor_document_highlight_read_background: Some(rgba(0x586dda1a).into()), | 541 | editor_document_highlight_read_background: Some(rgba(0x586dda1a).into()), |
| @@ -1000,7 +1000,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 1000 | editor_highlighted_line_background: Some(rgba(0x262622ff).into()), | 1000 | editor_highlighted_line_background: Some(rgba(0x262622ff).into()), |
| 1001 | editor_line_number: Some(rgba(0xfefbec59).into()), | 1001 | editor_line_number: Some(rgba(0xfefbec59).into()), |
| 1002 | editor_active_line_number: Some(rgba(0xfefbecff).into()), | 1002 | editor_active_line_number: Some(rgba(0xfefbecff).into()), |
| 1003 | editor_invisible: Some(rgba(0xa4a08bff).into()), | 1003 | editor_invisible: Some(rgba(0x8b8874ff).into()), |
| 1004 | editor_wrap_guide: Some(rgba(0xfefbec0d).into()), | 1004 | editor_wrap_guide: Some(rgba(0xfefbec0d).into()), |
| 1005 | editor_active_wrap_guide: Some(rgba(0xfefbec1a).into()), | 1005 | editor_active_wrap_guide: Some(rgba(0xfefbec1a).into()), |
| 1006 | editor_document_highlight_read_background: Some(rgba(0x6684e01a).into()), | 1006 | editor_document_highlight_read_background: Some(rgba(0x6684e01a).into()), |
| @@ -1465,7 +1465,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 1465 | editor_highlighted_line_background: Some(rgba(0xeeebd7ff).into()), | 1465 | editor_highlighted_line_background: Some(rgba(0xeeebd7ff).into()), |
| 1466 | editor_line_number: Some(rgba(0x20201d59).into()), | 1466 | editor_line_number: Some(rgba(0x20201d59).into()), |
| 1467 | editor_active_line_number: Some(rgba(0x20201dff).into()), | 1467 | editor_active_line_number: Some(rgba(0x20201dff).into()), |
| 1468 | editor_invisible: Some(rgba(0x706d5fff).into()), | 1468 | editor_invisible: Some(rgba(0x8b8874ff).into()), |
| 1469 | editor_wrap_guide: Some(rgba(0x20201d0d).into()), | 1469 | editor_wrap_guide: Some(rgba(0x20201d0d).into()), |
| 1470 | editor_active_wrap_guide: Some(rgba(0x20201d1a).into()), | 1470 | editor_active_wrap_guide: Some(rgba(0x20201d1a).into()), |
| 1471 | editor_document_highlight_read_background: Some(rgba(0x6784e01a).into()), | 1471 | editor_document_highlight_read_background: Some(rgba(0x6784e01a).into()), |
| @@ -1930,7 +1930,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 1930 | editor_highlighted_line_background: Some(rgba(0x2c2b23ff).into()), | 1930 | editor_highlighted_line_background: Some(rgba(0x2c2b23ff).into()), |
| 1931 | editor_line_number: Some(rgba(0xf4f3ec59).into()), | 1931 | editor_line_number: Some(rgba(0xf4f3ec59).into()), |
| 1932 | editor_active_line_number: Some(rgba(0xf4f3ecff).into()), | 1932 | editor_active_line_number: Some(rgba(0xf4f3ecff).into()), |
| 1933 | editor_invisible: Some(rgba(0x91907fff).into()), | 1933 | editor_invisible: Some(rgba(0x7a7867ff).into()), |
| 1934 | editor_wrap_guide: Some(rgba(0xf4f3ec0d).into()), | 1934 | editor_wrap_guide: Some(rgba(0xf4f3ec0d).into()), |
| 1935 | editor_active_wrap_guide: Some(rgba(0xf4f3ec1a).into()), | 1935 | editor_active_wrap_guide: Some(rgba(0xf4f3ec1a).into()), |
| 1936 | editor_document_highlight_read_background: Some(rgba(0x37a1661a).into()), | 1936 | editor_document_highlight_read_background: Some(rgba(0x37a1661a).into()), |
| @@ -2395,7 +2395,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 2395 | editor_highlighted_line_background: Some(rgba(0xebeae3ff).into()), | 2395 | editor_highlighted_line_background: Some(rgba(0xebeae3ff).into()), |
| 2396 | editor_line_number: Some(rgba(0x22221b59).into()), | 2396 | editor_line_number: Some(rgba(0x22221b59).into()), |
| 2397 | editor_active_line_number: Some(rgba(0x22221bff).into()), | 2397 | editor_active_line_number: Some(rgba(0x22221bff).into()), |
| 2398 | editor_invisible: Some(rgba(0x61604fff).into()), | 2398 | editor_invisible: Some(rgba(0x7a7867ff).into()), |
| 2399 | editor_wrap_guide: Some(rgba(0x22221b0d).into()), | 2399 | editor_wrap_guide: Some(rgba(0x22221b0d).into()), |
| 2400 | editor_active_wrap_guide: Some(rgba(0x22221b1a).into()), | 2400 | editor_active_wrap_guide: Some(rgba(0x22221b1a).into()), |
| 2401 | editor_document_highlight_read_background: Some(rgba(0x38a1661a).into()), | 2401 | editor_document_highlight_read_background: Some(rgba(0x38a1661a).into()), |
| @@ -2860,7 +2860,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 2860 | editor_highlighted_line_background: Some(rgba(0x27211eff).into()), | 2860 | editor_highlighted_line_background: Some(rgba(0x27211eff).into()), |
| 2861 | editor_line_number: Some(rgba(0xf1efee59).into()), | 2861 | editor_line_number: Some(rgba(0xf1efee59).into()), |
| 2862 | editor_active_line_number: Some(rgba(0xf1efeeff).into()), | 2862 | editor_active_line_number: Some(rgba(0xf1efeeff).into()), |
| 2863 | editor_invisible: Some(rgba(0xa79f9dff).into()), | 2863 | editor_invisible: Some(rgba(0x89817eff).into()), |
| 2864 | editor_wrap_guide: Some(rgba(0xf1efee0d).into()), | 2864 | editor_wrap_guide: Some(rgba(0xf1efee0d).into()), |
| 2865 | editor_active_wrap_guide: Some(rgba(0xf1efee1a).into()), | 2865 | editor_active_wrap_guide: Some(rgba(0xf1efee1a).into()), |
| 2866 | editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), | 2866 | editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), |
| @@ -3325,7 +3325,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 3325 | editor_highlighted_line_background: Some(rgba(0xe9e6e4ff).into()), | 3325 | editor_highlighted_line_background: Some(rgba(0xe9e6e4ff).into()), |
| 3326 | editor_line_number: Some(rgba(0x1b191859).into()), | 3326 | editor_line_number: Some(rgba(0x1b191859).into()), |
| 3327 | editor_active_line_number: Some(rgba(0x1b1918ff).into()), | 3327 | editor_active_line_number: Some(rgba(0x1b1918ff).into()), |
| 3328 | editor_invisible: Some(rgba(0x6a6360ff).into()), | 3328 | editor_invisible: Some(rgba(0x89817eff).into()), |
| 3329 | editor_wrap_guide: Some(rgba(0x1b19180d).into()), | 3329 | editor_wrap_guide: Some(rgba(0x1b19180d).into()), |
| 3330 | editor_active_wrap_guide: Some(rgba(0x1b19181a).into()), | 3330 | editor_active_wrap_guide: Some(rgba(0x1b19181a).into()), |
| 3331 | editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), | 3331 | editor_document_highlight_read_background: Some(rgba(0x417ee61a).into()), |
| @@ -3790,7 +3790,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 3790 | editor_highlighted_line_background: Some(rgba(0x252025ff).into()), | 3790 | editor_highlighted_line_background: Some(rgba(0x252025ff).into()), |
| 3791 | editor_line_number: Some(rgba(0xf7f3f759).into()), | 3791 | editor_line_number: Some(rgba(0xf7f3f759).into()), |
| 3792 | editor_active_line_number: Some(rgba(0xf7f3f7ff).into()), | 3792 | editor_active_line_number: Some(rgba(0xf7f3f7ff).into()), |
| 3793 | editor_invisible: Some(rgba(0xa99aa9ff).into()), | 3793 | editor_invisible: Some(rgba(0x8b7c8bff).into()), |
| 3794 | editor_wrap_guide: Some(rgba(0xf7f3f70d).into()), | 3794 | editor_wrap_guide: Some(rgba(0xf7f3f70d).into()), |
| 3795 | editor_active_wrap_guide: Some(rgba(0xf7f3f71a).into()), | 3795 | editor_active_wrap_guide: Some(rgba(0xf7f3f71a).into()), |
| 3796 | editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), | 3796 | editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), |
| @@ -4255,7 +4255,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 4255 | editor_highlighted_line_background: Some(rgba(0xe1d6e1ff).into()), | 4255 | editor_highlighted_line_background: Some(rgba(0xe1d6e1ff).into()), |
| 4256 | editor_line_number: Some(rgba(0x1b181b59).into()), | 4256 | editor_line_number: Some(rgba(0x1b181b59).into()), |
| 4257 | editor_active_line_number: Some(rgba(0x1b181bff).into()), | 4257 | editor_active_line_number: Some(rgba(0x1b181bff).into()), |
| 4258 | editor_invisible: Some(rgba(0x6b5e6bff).into()), | 4258 | editor_invisible: Some(rgba(0x8b7c8bff).into()), |
| 4259 | editor_wrap_guide: Some(rgba(0x1b181b0d).into()), | 4259 | editor_wrap_guide: Some(rgba(0x1b181b0d).into()), |
| 4260 | editor_active_wrap_guide: Some(rgba(0x1b181b1a).into()), | 4260 | editor_active_wrap_guide: Some(rgba(0x1b181b1a).into()), |
| 4261 | editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), | 4261 | editor_document_highlight_read_background: Some(rgba(0x526aeb1a).into()), |
| @@ -4720,7 +4720,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 4720 | editor_highlighted_line_background: Some(rgba(0x1c2529ff).into()), | 4720 | editor_highlighted_line_background: Some(rgba(0x1c2529ff).into()), |
| 4721 | editor_line_number: Some(rgba(0xebf8ff59).into()), | 4721 | editor_line_number: Some(rgba(0xebf8ff59).into()), |
| 4722 | editor_active_line_number: Some(rgba(0xebf8ffff).into()), | 4722 | editor_active_line_number: Some(rgba(0xebf8ffff).into()), |
| 4723 | editor_invisible: Some(rgba(0x7ca0b3ff).into()), | 4723 | editor_invisible: Some(rgba(0x66889aff).into()), |
| 4724 | editor_wrap_guide: Some(rgba(0xebf8ff0d).into()), | 4724 | editor_wrap_guide: Some(rgba(0xebf8ff0d).into()), |
| 4725 | editor_active_wrap_guide: Some(rgba(0xebf8ff1a).into()), | 4725 | editor_active_wrap_guide: Some(rgba(0xebf8ff1a).into()), |
| 4726 | editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), | 4726 | editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), |
| @@ -5185,7 +5185,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 5185 | editor_highlighted_line_background: Some(rgba(0xcdeaf9ff).into()), | 5185 | editor_highlighted_line_background: Some(rgba(0xcdeaf9ff).into()), |
| 5186 | editor_line_number: Some(rgba(0x161b1d59).into()), | 5186 | editor_line_number: Some(rgba(0x161b1d59).into()), |
| 5187 | editor_active_line_number: Some(rgba(0x161b1dff).into()), | 5187 | editor_active_line_number: Some(rgba(0x161b1dff).into()), |
| 5188 | editor_invisible: Some(rgba(0x526f7dff).into()), | 5188 | editor_invisible: Some(rgba(0x66889aff).into()), |
| 5189 | editor_wrap_guide: Some(rgba(0x161b1d0d).into()), | 5189 | editor_wrap_guide: Some(rgba(0x161b1d0d).into()), |
| 5190 | editor_active_wrap_guide: Some(rgba(0x161b1d1a).into()), | 5190 | editor_active_wrap_guide: Some(rgba(0x161b1d1a).into()), |
| 5191 | editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), | 5191 | editor_document_highlight_read_background: Some(rgba(0x277fad1a).into()), |
| @@ -5650,7 +5650,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 5650 | editor_highlighted_line_background: Some(rgba(0x252020ff).into()), | 5650 | editor_highlighted_line_background: Some(rgba(0x252020ff).into()), |
| 5651 | editor_line_number: Some(rgba(0xf4ecec59).into()), | 5651 | editor_line_number: Some(rgba(0xf4ecec59).into()), |
| 5652 | editor_active_line_number: Some(rgba(0xf4ececff).into()), | 5652 | editor_active_line_number: Some(rgba(0xf4ececff).into()), |
| 5653 | editor_invisible: Some(rgba(0x898383ff).into()), | 5653 | editor_invisible: Some(rgba(0x726a6aff).into()), |
| 5654 | editor_wrap_guide: Some(rgba(0xf4ecec0d).into()), | 5654 | editor_wrap_guide: Some(rgba(0xf4ecec0d).into()), |
| 5655 | editor_active_wrap_guide: Some(rgba(0xf4ecec1a).into()), | 5655 | editor_active_wrap_guide: Some(rgba(0xf4ecec1a).into()), |
| 5656 | editor_document_highlight_read_background: Some(rgba(0x7272ca1a).into()), | 5656 | editor_document_highlight_read_background: Some(rgba(0x7272ca1a).into()), |
| @@ -6115,7 +6115,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 6115 | editor_highlighted_line_background: Some(rgba(0xebe3e3ff).into()), | 6115 | editor_highlighted_line_background: Some(rgba(0xebe3e3ff).into()), |
| 6116 | editor_line_number: Some(rgba(0x1b181859).into()), | 6116 | editor_line_number: Some(rgba(0x1b181859).into()), |
| 6117 | editor_active_line_number: Some(rgba(0x1b1818ff).into()), | 6117 | editor_active_line_number: Some(rgba(0x1b1818ff).into()), |
| 6118 | editor_invisible: Some(rgba(0x5a5252ff).into()), | 6118 | editor_invisible: Some(rgba(0x726a6aff).into()), |
| 6119 | editor_wrap_guide: Some(rgba(0x1b18180d).into()), | 6119 | editor_wrap_guide: Some(rgba(0x1b18180d).into()), |
| 6120 | editor_active_wrap_guide: Some(rgba(0x1b18181a).into()), | 6120 | editor_active_wrap_guide: Some(rgba(0x1b18181a).into()), |
| 6121 | editor_document_highlight_read_background: Some(rgba(0x7372ca1a).into()), | 6121 | editor_document_highlight_read_background: Some(rgba(0x7372ca1a).into()), |
| @@ -6580,7 +6580,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 6580 | editor_highlighted_line_background: Some(rgba(0x1f2621ff).into()), | 6580 | editor_highlighted_line_background: Some(rgba(0x1f2621ff).into()), |
| 6581 | editor_line_number: Some(rgba(0xecf4ee59).into()), | 6581 | editor_line_number: Some(rgba(0xecf4ee59).into()), |
| 6582 | editor_active_line_number: Some(rgba(0xecf4eeff).into()), | 6582 | editor_active_line_number: Some(rgba(0xecf4eeff).into()), |
| 6583 | editor_invisible: Some(rgba(0x859188ff).into()), | 6583 | editor_invisible: Some(rgba(0x6c7a71ff).into()), |
| 6584 | editor_wrap_guide: Some(rgba(0xecf4ee0d).into()), | 6584 | editor_wrap_guide: Some(rgba(0xecf4ee0d).into()), |
| 6585 | editor_active_wrap_guide: Some(rgba(0xecf4ee1a).into()), | 6585 | editor_active_wrap_guide: Some(rgba(0xecf4ee1a).into()), |
| 6586 | editor_document_highlight_read_background: Some(rgba(0x478c901a).into()), | 6586 | editor_document_highlight_read_background: Some(rgba(0x478c901a).into()), |
| @@ -7045,7 +7045,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 7045 | editor_highlighted_line_background: Some(rgba(0xe3ebe6ff).into()), | 7045 | editor_highlighted_line_background: Some(rgba(0xe3ebe6ff).into()), |
| 7046 | editor_line_number: Some(rgba(0x171c1959).into()), | 7046 | editor_line_number: Some(rgba(0x171c1959).into()), |
| 7047 | editor_active_line_number: Some(rgba(0x171c19ff).into()), | 7047 | editor_active_line_number: Some(rgba(0x171c19ff).into()), |
| 7048 | editor_invisible: Some(rgba(0x546259ff).into()), | 7048 | editor_invisible: Some(rgba(0x6c7a71ff).into()), |
| 7049 | editor_wrap_guide: Some(rgba(0x171c190d).into()), | 7049 | editor_wrap_guide: Some(rgba(0x171c190d).into()), |
| 7050 | editor_active_wrap_guide: Some(rgba(0x171c191a).into()), | 7050 | editor_active_wrap_guide: Some(rgba(0x171c191a).into()), |
| 7051 | editor_document_highlight_read_background: Some(rgba(0x488c901a).into()), | 7051 | editor_document_highlight_read_background: Some(rgba(0x488c901a).into()), |
| @@ -7510,7 +7510,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 7510 | editor_highlighted_line_background: Some(rgba(0x1f231fff).into()), | 7510 | editor_highlighted_line_background: Some(rgba(0x1f231fff).into()), |
| 7511 | editor_line_number: Some(rgba(0xf4fbf459).into()), | 7511 | editor_line_number: Some(rgba(0xf4fbf459).into()), |
| 7512 | editor_active_line_number: Some(rgba(0xf4fbf4ff).into()), | 7512 | editor_active_line_number: Some(rgba(0xf4fbf4ff).into()), |
| 7513 | editor_invisible: Some(rgba(0x8ba48bff).into()), | 7513 | editor_invisible: Some(rgba(0x748b74ff).into()), |
| 7514 | editor_wrap_guide: Some(rgba(0xf4fbf40d).into()), | 7514 | editor_wrap_guide: Some(rgba(0xf4fbf40d).into()), |
| 7515 | editor_active_wrap_guide: Some(rgba(0xf4fbf41a).into()), | 7515 | editor_active_wrap_guide: Some(rgba(0xf4fbf41a).into()), |
| 7516 | editor_document_highlight_read_background: Some(rgba(0x3e62f41a).into()), | 7516 | editor_document_highlight_read_background: Some(rgba(0x3e62f41a).into()), |
| @@ -7975,7 +7975,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 7975 | editor_highlighted_line_background: Some(rgba(0xdaeedaff).into()), | 7975 | editor_highlighted_line_background: Some(rgba(0xdaeedaff).into()), |
| 7976 | editor_line_number: Some(rgba(0x13151359).into()), | 7976 | editor_line_number: Some(rgba(0x13151359).into()), |
| 7977 | editor_active_line_number: Some(rgba(0x131513ff).into()), | 7977 | editor_active_line_number: Some(rgba(0x131513ff).into()), |
| 7978 | editor_invisible: Some(rgba(0x5f705fff).into()), | 7978 | editor_invisible: Some(rgba(0x748b74ff).into()), |
| 7979 | editor_wrap_guide: Some(rgba(0x1315130d).into()), | 7979 | editor_wrap_guide: Some(rgba(0x1315130d).into()), |
| 7980 | editor_active_wrap_guide: Some(rgba(0x1315131a).into()), | 7980 | editor_active_wrap_guide: Some(rgba(0x1315131a).into()), |
| 7981 | editor_document_highlight_read_background: Some(rgba(0x3f62f41a).into()), | 7981 | editor_document_highlight_read_background: Some(rgba(0x3f62f41a).into()), |
| @@ -8440,7 +8440,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 8440 | editor_highlighted_line_background: Some(rgba(0x262f51ff).into()), | 8440 | editor_highlighted_line_background: Some(rgba(0x262f51ff).into()), |
| 8441 | editor_line_number: Some(rgba(0xf5f7ff59).into()), | 8441 | editor_line_number: Some(rgba(0xf5f7ff59).into()), |
| 8442 | editor_active_line_number: Some(rgba(0xf5f7ffff).into()), | 8442 | editor_active_line_number: Some(rgba(0xf5f7ffff).into()), |
| 8443 | editor_invisible: Some(rgba(0x959bb2ff).into()), | 8443 | editor_invisible: Some(rgba(0x7a819cff).into()), |
| 8444 | editor_wrap_guide: Some(rgba(0xf5f7ff0d).into()), | 8444 | editor_wrap_guide: Some(rgba(0xf5f7ff0d).into()), |
| 8445 | editor_active_wrap_guide: Some(rgba(0xf5f7ff1a).into()), | 8445 | editor_active_wrap_guide: Some(rgba(0xf5f7ff1a).into()), |
| 8446 | editor_document_highlight_read_background: Some(rgba(0x3e8fd01a).into()), | 8446 | editor_document_highlight_read_background: Some(rgba(0x3e8fd01a).into()), |
| @@ -8905,7 +8905,7 @@ pub fn atelier() -> UserThemeFamily { · 458 unmodified lines | |||
| 8905 | editor_highlighted_line_background: Some(rgba(0xe5e8f5ff).into()), | 8905 | editor_highlighted_line_background: Some(rgba(0xe5e8f5ff).into()), |
| 8906 | editor_line_number: Some(rgba(0x20274659).into()), | 8906 | editor_line_number: Some(rgba(0x20274659).into()), |
| 8907 | editor_active_line_number: Some(rgba(0x202746ff).into()), | 8907 | editor_active_line_number: Some(rgba(0x202746ff).into()), |
| 8908 | editor_invisible: Some(rgba(0x606889ff).into()), | 8908 | editor_invisible: Some(rgba(0x7a819cff).into()), |
| 8909 | editor_wrap_guide: Some(rgba(0x2027460d).into()), | 8909 | editor_wrap_guide: Some(rgba(0x2027460d).into()), |
| 8910 | editor_active_wrap_guide: Some(rgba(0x2027461a).into()), | 8910 | editor_active_wrap_guide: Some(rgba(0x2027461a).into()), |
| 8911 | editor_document_highlight_read_background: Some(rgba(0x3f8fd01a).into()), | 8911 | editor_document_highlight_read_background: Some(rgba(0x3f8fd01a).into()), |
| @@ -70,7 +70,7 @@ pub fn ayu() -> UserThemeFamily { · 69 unmodified lines | |||
| 70 | editor_highlighted_line_background: Some(rgba(0x1f2127ff).into()), | 70 | editor_highlighted_line_background: Some(rgba(0x1f2127ff).into()), |
| 71 | editor_line_number: Some(rgba(0xbfbdb659).into()), | 71 | editor_line_number: Some(rgba(0xbfbdb659).into()), |
| 72 | editor_active_line_number: Some(rgba(0xbfbdb6ff).into()), | 72 | editor_active_line_number: Some(rgba(0xbfbdb6ff).into()), |
| 73 | editor_invisible: Some(rgba(0x8a8986ff).into()), | 73 | editor_invisible: Some(rgba(0x666767ff).into()), |
| 74 | editor_wrap_guide: Some(rgba(0xbfbdb60d).into()), | 74 | editor_wrap_guide: Some(rgba(0xbfbdb60d).into()), |
| 75 | editor_active_wrap_guide: Some(rgba(0xbfbdb61a).into()), | 75 | editor_active_wrap_guide: Some(rgba(0xbfbdb61a).into()), |
| 76 | editor_document_highlight_read_background: Some(rgba(0x5ac2fe1a).into()), | 76 | editor_document_highlight_read_background: Some(rgba(0x5ac2fe1a).into()), |
| @@ -514,7 +514,7 @@ pub fn ayu() -> UserThemeFamily { · 437 unmodified lines | |||
| 514 | editor_highlighted_line_background: Some(rgba(0xececedff).into()), | 514 | editor_highlighted_line_background: Some(rgba(0xececedff).into()), |
| 515 | editor_line_number: Some(rgba(0x5c616659).into()), | 515 | editor_line_number: Some(rgba(0x5c616659).into()), |
| 516 | editor_active_line_number: Some(rgba(0x5c6166ff).into()), | 516 | editor_active_line_number: Some(rgba(0x5c6166ff).into()), |
| 517 | editor_invisible: Some(rgba(0x8c8f93ff).into()), | 517 | editor_invisible: Some(rgba(0xacafb1ff).into()), |
| 518 | editor_wrap_guide: Some(rgba(0x5c61660d).into()), | 518 | editor_wrap_guide: Some(rgba(0x5c61660d).into()), |
| 519 | editor_active_wrap_guide: Some(rgba(0x5c61661a).into()), | 519 | editor_active_wrap_guide: Some(rgba(0x5c61661a).into()), |
| 520 | editor_document_highlight_read_background: Some(rgba(0x3b9ee51a).into()), | 520 | editor_document_highlight_read_background: Some(rgba(0x3b9ee51a).into()), |
| @@ -958,7 +958,7 @@ pub fn ayu() -> UserThemeFamily { · 437 unmodified lines | |||
| 958 | editor_highlighted_line_background: Some(rgba(0x353944ff).into()), | 958 | editor_highlighted_line_background: Some(rgba(0x353944ff).into()), |
| 959 | editor_line_number: Some(rgba(0xcccac259).into()), | 959 | editor_line_number: Some(rgba(0xcccac259).into()), |
| 960 | editor_active_line_number: Some(rgba(0xcccac2ff).into()), | 960 | editor_active_line_number: Some(rgba(0xcccac2ff).into()), |
| 961 | editor_invisible: Some(rgba(0x9a9a98ff).into()), | 961 | editor_invisible: Some(rgba(0x787a7cff).into()), |
| 962 | editor_wrap_guide: Some(rgba(0xcccac20d).into()), | 962 | editor_wrap_guide: Some(rgba(0xcccac20d).into()), |
| 963 | editor_active_wrap_guide: Some(rgba(0xcccac21a).into()), | 963 | editor_active_wrap_guide: Some(rgba(0xcccac21a).into()), |
| 964 | editor_document_highlight_read_background: Some(rgba(0x73cffe1a).into()), | 964 | editor_document_highlight_read_background: Some(rgba(0x73cffe1a).into()), |
| @@ -70,7 +70,7 @@ pub fn gruvbox() -> UserThemeFamily { · 69 unmodified lines | |||
| 70 | editor_highlighted_line_background: Some(rgba(0x3a3735ff).into()), | 70 | editor_highlighted_line_background: Some(rgba(0x3a3735ff).into()), |
| 71 | editor_line_number: Some(rgba(0xfbf1c759).into()), | 71 | editor_line_number: Some(rgba(0xfbf1c759).into()), |
| 72 | editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), | 72 | editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), |
| 73 | editor_invisible: Some(rgba(0xc5b597ff).into()), | 73 | editor_invisible: Some(rgba(0x928474ff).into()), |
| 74 | editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), | 74 | editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), |
| 75 | editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), | 75 | editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), |
| 76 | editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), | 76 | editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), |
| @@ -521,7 +521,7 @@ pub fn gruvbox() -> UserThemeFamily { · 444 unmodified lines | |||
| 521 | editor_highlighted_line_background: Some(rgba(0x393634ff).into()), | 521 | editor_highlighted_line_background: Some(rgba(0x393634ff).into()), |
| 522 | editor_line_number: Some(rgba(0xfbf1c759).into()), | 522 | editor_line_number: Some(rgba(0xfbf1c759).into()), |
| 523 | editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), | 523 | editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), |
| 524 | editor_invisible: Some(rgba(0xc5b597ff).into()), | 524 | editor_invisible: Some(rgba(0x928474ff).into()), |
| 525 | editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), | 525 | editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), |
| 526 | editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), | 526 | editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), |
| 527 | editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), | 527 | editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), |
| @@ -972,7 +972,7 @@ pub fn gruvbox() -> UserThemeFamily { · 444 unmodified lines | |||
| 972 | editor_highlighted_line_background: Some(rgba(0x3b3735ff).into()), | 972 | editor_highlighted_line_background: Some(rgba(0x3b3735ff).into()), |
| 973 | editor_line_number: Some(rgba(0xfbf1c759).into()), | 973 | editor_line_number: Some(rgba(0xfbf1c759).into()), |
| 974 | editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), | 974 | editor_active_line_number: Some(rgba(0xfbf1c7ff).into()), |
| 975 | editor_invisible: Some(rgba(0xc5b597ff).into()), | 975 | editor_invisible: Some(rgba(0x928474ff).into()), |
| 976 | editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), | 976 | editor_wrap_guide: Some(rgba(0xfbf1c70d).into()), |
| 977 | editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), | 977 | editor_active_wrap_guide: Some(rgba(0xfbf1c71a).into()), |
| 978 | editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), | 978 | editor_document_highlight_read_background: Some(rgba(0x83a5981a).into()), |
| @@ -1423,7 +1423,7 @@ pub fn gruvbox() -> UserThemeFamily { · 444 unmodified lines | |||
| 1423 | editor_highlighted_line_background: Some(rgba(0xecddb4ff).into()), | 1423 | editor_highlighted_line_background: Some(rgba(0xecddb4ff).into()), |
| 1424 | editor_line_number: Some(rgba(0x28282859).into()), | 1424 | editor_line_number: Some(rgba(0x28282859).into()), |
| 1425 | editor_active_line_number: Some(rgba(0x282828ff).into()), | 1425 | editor_active_line_number: Some(rgba(0x282828ff).into()), |
| 1426 | editor_invisible: Some(rgba(0x5f5650ff).into()), | 1426 | editor_invisible: Some(rgba(0x928474ff).into()), |
| 1427 | editor_wrap_guide: Some(rgba(0x2828280d).into()), | 1427 | editor_wrap_guide: Some(rgba(0x2828280d).into()), |
| 1428 | editor_active_wrap_guide: Some(rgba(0x2828281a).into()), | 1428 | editor_active_wrap_guide: Some(rgba(0x2828281a).into()), |
| 1429 | editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), | 1429 | editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), |
| @@ -1874,7 +1874,7 @@ pub fn gruvbox() -> UserThemeFamily { · 444 unmodified lines | |||
| 1874 | editor_highlighted_line_background: Some(rgba(0xecddb5ff).into()), | 1874 | editor_highlighted_line_background: Some(rgba(0xecddb5ff).into()), |
| 1875 | editor_line_number: Some(rgba(0x28282859).into()), | 1875 | editor_line_number: Some(rgba(0x28282859).into()), |
| 1876 | editor_active_line_number: Some(rgba(0x282828ff).into()), | 1876 | editor_active_line_number: Some(rgba(0x282828ff).into()), |
| 1877 | editor_invisible: Some(rgba(0x5f5650ff).into()), | 1877 | editor_invisible: Some(rgba(0x928474ff).into()), |
| 1878 | editor_wrap_guide: Some(rgba(0x2828280d).into()), | 1878 | editor_wrap_guide: Some(rgba(0x2828280d).into()), |
| 1879 | editor_active_wrap_guide: Some(rgba(0x2828281a).into()), | 1879 | editor_active_wrap_guide: Some(rgba(0x2828281a).into()), |
| 1880 | editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), | 1880 | editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), |
| @@ -2325,7 +2325,7 @@ pub fn gruvbox() -> UserThemeFamily { · 444 unmodified lines | |||
| 2325 | editor_highlighted_line_background: Some(rgba(0xecdcb3ff).into()), | 2325 | editor_highlighted_line_background: Some(rgba(0xecdcb3ff).into()), |
| 2326 | editor_line_number: Some(rgba(0x28282859).into()), | 2326 | editor_line_number: Some(rgba(0x28282859).into()), |
| 2327 | editor_active_line_number: Some(rgba(0x282828ff).into()), | 2327 | editor_active_line_number: Some(rgba(0x282828ff).into()), |
| 2328 | editor_invisible: Some(rgba(0x5f5650ff).into()), | 2328 | editor_invisible: Some(rgba(0x928474ff).into()), |
| 2329 | editor_wrap_guide: Some(rgba(0x2828280d).into()), | 2329 | editor_wrap_guide: Some(rgba(0x2828280d).into()), |
| 2330 | editor_active_wrap_guide: Some(rgba(0x2828281a).into()), | 2330 | editor_active_wrap_guide: Some(rgba(0x2828281a).into()), |
| 2331 | editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), | 2331 | editor_document_highlight_read_background: Some(rgba(0x0b66781a).into()), |
| @@ -70,7 +70,7 @@ pub fn one() -> UserThemeFamily { · 69 unmodified lines | |||
| 70 | editor_highlighted_line_background: Some(rgba(0x2f343eff).into()), | 70 | editor_highlighted_line_background: Some(rgba(0x2f343eff).into()), |
| 71 | editor_line_number: Some(rgba(0xc8ccd459).into()), | 71 | editor_line_number: Some(rgba(0xc8ccd459).into()), |
| 72 | editor_active_line_number: Some(rgba(0xc8ccd4ff).into()), | 72 | editor_active_line_number: Some(rgba(0xc8ccd4ff).into()), |
| 73 | editor_invisible: Some(rgba(0x838994ff).into()), | 73 | editor_invisible: Some(rgba(0x555a63ff).into()), |
| 74 | editor_wrap_guide: Some(rgba(0xc8ccd40d).into()), | 74 | editor_wrap_guide: Some(rgba(0xc8ccd40d).into()), |
| 75 | editor_active_wrap_guide: Some(rgba(0xc8ccd41a).into()), | 75 | editor_active_wrap_guide: Some(rgba(0xc8ccd41a).into()), |
| 76 | editor_document_highlight_read_background: Some(rgba(0x74ade81a).into()), | 76 | editor_document_highlight_read_background: Some(rgba(0x74ade81a).into()), |
| @@ -521,7 +521,7 @@ pub fn one() -> UserThemeFamily { · 444 unmodified lines | |||
| 521 | editor_highlighted_line_background: Some(rgba(0xebebecff).into()), | 521 | editor_highlighted_line_background: Some(rgba(0xebebecff).into()), |
| 522 | editor_line_number: Some(rgba(0x383a4159).into()), | 522 | editor_line_number: Some(rgba(0x383a4159).into()), |
| 523 | editor_active_line_number: Some(rgba(0x383a41ff).into()), | 523 | editor_active_line_number: Some(rgba(0x383a41ff).into()), |
| 524 | editor_invisible: Some(rgba(0x7f8188ff).into()), | 524 | editor_invisible: Some(rgba(0xa3a3a4ff).into()), |
| 525 | editor_wrap_guide: Some(rgba(0x383a410d).into()), | 525 | editor_wrap_guide: Some(rgba(0x383a410d).into()), |
| 526 | editor_active_wrap_guide: Some(rgba(0x383a411a).into()), | 526 | editor_active_wrap_guide: Some(rgba(0x383a411a).into()), |
| 527 | editor_document_highlight_read_background: Some(rgba(0x5c79e21a).into()), | 527 | editor_document_highlight_read_background: Some(rgba(0x5c79e21a).into()), |
| @@ -70,7 +70,7 @@ pub fn rose_pine() -> UserThemeFamily { · 69 unmodified lines | |||
| 70 | editor_highlighted_line_background: Some(rgba(0x1d1b2aff).into()), | 70 | editor_highlighted_line_background: Some(rgba(0x1d1b2aff).into()), |
| 71 | editor_line_number: Some(rgba(0xe0def459).into()), | 71 | editor_line_number: Some(rgba(0xe0def459).into()), |
| 72 | editor_active_line_number: Some(rgba(0xe0def4ff).into()), | 72 | editor_active_line_number: Some(rgba(0xe0def4ff).into()), |
| 73 | editor_invisible: Some(rgba(0x75718eff).into()), | 73 | editor_invisible: Some(rgba(0x28253cff).into()), |
| 74 | editor_wrap_guide: Some(rgba(0xe0def40d).into()), | 74 | editor_wrap_guide: Some(rgba(0xe0def40d).into()), |
| 75 | editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), | 75 | editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), |
| 76 | editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), | 76 | editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), |
| @@ -528,7 +528,7 @@ pub fn rose_pine() -> UserThemeFamily { · 451 unmodified lines | |||
| 528 | editor_highlighted_line_background: Some(rgba(0xfef9f2ff).into()), | 528 | editor_highlighted_line_background: Some(rgba(0xfef9f2ff).into()), |
| 529 | editor_line_number: Some(rgba(0x57527959).into()), | 529 | editor_line_number: Some(rgba(0x57527959).into()), |
| 530 | editor_active_line_number: Some(rgba(0x575279ff).into()), | 530 | editor_active_line_number: Some(rgba(0x575279ff).into()), |
| 531 | editor_invisible: Some(rgba(0x706c8cff).into()), | 531 | editor_invisible: Some(rgba(0x9691a4ff).into()), |
| 532 | editor_wrap_guide: Some(rgba(0x5752790d).into()), | 532 | editor_wrap_guide: Some(rgba(0x5752790d).into()), |
| 533 | editor_active_wrap_guide: Some(rgba(0x5752791a).into()), | 533 | editor_active_wrap_guide: Some(rgba(0x5752791a).into()), |
| 534 | editor_document_highlight_read_background: Some(rgba(0x57949f1a).into()), | 534 | editor_document_highlight_read_background: Some(rgba(0x57949f1a).into()), |
| @@ -986,7 +986,7 @@ pub fn rose_pine() -> UserThemeFamily { · 451 unmodified lines | |||
| 986 | editor_highlighted_line_background: Some(rgba(0x28253cff).into()), | 986 | editor_highlighted_line_background: Some(rgba(0x28253cff).into()), |
| 987 | editor_line_number: Some(rgba(0xe0def459).into()), | 987 | editor_line_number: Some(rgba(0xe0def459).into()), |
| 988 | editor_active_line_number: Some(rgba(0xe0def4ff).into()), | 988 | editor_active_line_number: Some(rgba(0xe0def4ff).into()), |
| 989 | editor_invisible: Some(rgba(0x85819eff).into()), | 989 | editor_invisible: Some(rgba(0x595571ff).into()), |
| 990 | editor_wrap_guide: Some(rgba(0xe0def40d).into()), | 990 | editor_wrap_guide: Some(rgba(0xe0def40d).into()), |
| 991 | editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), | 991 | editor_active_wrap_guide: Some(rgba(0xe0def41a).into()), |
| 992 | editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), | 992 | editor_document_highlight_read_background: Some(rgba(0x9cced71a).into()), |
| @@ -69,7 +69,7 @@ pub fn sandcastle() -> UserThemeFamily { · 68 unmodified lines | |||
| 69 | editor_highlighted_line_background: Some(rgba(0x2b3039ff).into()), | 69 | editor_highlighted_line_background: Some(rgba(0x2b3039ff).into()), |
| 70 | editor_line_number: Some(rgba(0xfdf4c159).into()), | 70 | editor_line_number: Some(rgba(0xfdf4c159).into()), |
| 71 | editor_active_line_number: Some(rgba(0xfdf4c1ff).into()), | 71 | editor_active_line_number: Some(rgba(0xfdf4c1ff).into()), |
| 72 | editor_invisible: Some(rgba(0xa69782ff).into()), | 72 | editor_invisible: Some(rgba(0x7c6f64ff).into()), |
| 73 | editor_wrap_guide: Some(rgba(0xfdf4c10d).into()), | 73 | editor_wrap_guide: Some(rgba(0xfdf4c10d).into()), |
| 74 | editor_active_wrap_guide: Some(rgba(0xfdf4c11a).into()), | 74 | editor_active_wrap_guide: Some(rgba(0xfdf4c11a).into()), |
| 75 | editor_document_highlight_read_background: Some(rgba(0x528b8b1a).into()), | 75 | editor_document_highlight_read_background: Some(rgba(0x528b8b1a).into()), |
| @@ -70,7 +70,7 @@ pub fn solarized() -> UserThemeFamily { · 69 unmodified lines | |||
| 70 | editor_highlighted_line_background: Some(rgba(0x04313cff).into()), | 70 | editor_highlighted_line_background: Some(rgba(0x04313cff).into()), |
| 71 | editor_line_number: Some(rgba(0xfdf6e359).into()), | 71 | editor_line_number: Some(rgba(0xfdf6e359).into()), |
| 72 | editor_active_line_number: Some(rgba(0xfdf6e3ff).into()), | 72 | editor_active_line_number: Some(rgba(0xfdf6e3ff).into()), |
| 73 | editor_invisible: Some(rgba(0x93a1a1ff).into()), | 73 | editor_invisible: Some(rgba(0x6d8288ff).into()), |
| 74 | editor_wrap_guide: Some(rgba(0xfdf6e30d).into()), | 74 | editor_wrap_guide: Some(rgba(0xfdf6e30d).into()), |
| 75 | editor_active_wrap_guide: Some(rgba(0xfdf6e31a).into()), | 75 | editor_active_wrap_guide: Some(rgba(0xfdf6e31a).into()), |
| 76 | editor_document_highlight_read_background: Some(rgba(0x288bd11a).into()), | 76 | editor_document_highlight_read_background: Some(rgba(0x288bd11a).into()), |
| @@ -514,7 +514,7 @@ pub fn solarized() -> UserThemeFamily { · 437 unmodified lines | |||
| 514 | editor_highlighted_line_background: Some(rgba(0xf3eddaff).into()), | 514 | editor_highlighted_line_background: Some(rgba(0xf3eddaff).into()), |
| 515 | editor_line_number: Some(rgba(0x002b3659).into()), | 515 | editor_line_number: Some(rgba(0x002b3659).into()), |
| 516 | editor_active_line_number: Some(rgba(0x002b36ff).into()), | 516 | editor_active_line_number: Some(rgba(0x002b36ff).into()), |
| 517 | editor_invisible: Some(rgba(0x34555eff).into()), | 517 | editor_invisible: Some(rgba(0x6d8288ff).into()), |
| 518 | editor_wrap_guide: Some(rgba(0x002b360d).into()), | 518 | editor_wrap_guide: Some(rgba(0x002b360d).into()), |
| 519 | editor_active_wrap_guide: Some(rgba(0x002b361a).into()), | 519 | editor_active_wrap_guide: Some(rgba(0x002b361a).into()), |
| 520 | editor_document_highlight_read_background: Some(rgba(0x298bd11a).into()), | 520 | editor_document_highlight_read_background: Some(rgba(0x298bd11a).into()), |
| @@ -69,7 +69,7 @@ pub fn summercamp() -> UserThemeFamily { · 68 unmodified lines | |||
| 69 | editor_highlighted_line_background: Some(rgba(0x231f16ff).into()), | 69 | editor_highlighted_line_background: Some(rgba(0x231f16ff).into()), |
| 70 | editor_line_number: Some(rgba(0xf8f5de59).into()), | 70 | editor_line_number: Some(rgba(0xf8f5de59).into()), |
| 71 | editor_active_line_number: Some(rgba(0xf8f5deff).into()), | 71 | editor_active_line_number: Some(rgba(0xf8f5deff).into()), |
| 72 | editor_invisible: Some(rgba(0x736e55ff).into()), | 72 | editor_invisible: Some(rgba(0x494433ff).into()), |
| 73 | editor_wrap_guide: Some(rgba(0xf8f5de0d).into()), | 73 | editor_wrap_guide: Some(rgba(0xf8f5de0d).into()), |
| 74 | editor_active_wrap_guide: Some(rgba(0xf8f5de1a).into()), | 74 | editor_active_wrap_guide: Some(rgba(0xf8f5de1a).into()), |
| 75 | editor_document_highlight_read_background: Some(rgba(0x499bef1a).into()), | 75 | editor_document_highlight_read_background: Some(rgba(0x499bef1a).into()), |
| @@ -240,7 +240,7 @@ impl Zed1ThemeConverter { · 239 unmodified lines | |||
| 240 | editor_highlighted_line_background: convert(editor.highlighted_line_background), | 240 | editor_highlighted_line_background: convert(editor.highlighted_line_background), |
| 241 | editor_line_number: convert(editor.line_number), | 241 | editor_line_number: convert(editor.line_number), |
| 242 | editor_active_line_number: convert(editor.line_number_active), | 242 | editor_active_line_number: convert(editor.line_number_active), |
| 243 | editor_invisible: convert(highest.variant.default.foreground), // TODO: Is this light enough? | 243 | editor_invisible: convert(editor.whitespace), |
| 244 | editor_wrap_guide: convert(editor.wrap_guide), | 244 | editor_wrap_guide: convert(editor.wrap_guide), |
| 245 | editor_active_wrap_guide: convert(editor.active_wrap_guide), | 245 | editor_active_wrap_guide: convert(editor.active_wrap_guide), |
| 246 | editor_document_highlight_read_background: convert( | 246 | editor_document_highlight_read_background: convert( |
| @@ -137,6 +137,8 @@ pub trait ResultExt<E> { · 136 unmodified lines | |||
| 137 | type Ok; | 137 | type Ok; |
| 138 | 138 | ||
| 139 | fn log_err(self) -> Option<Self::Ok>; | 139 | fn log_err(self) -> Option<Self::Ok>; |
| 140 | /// Assert that this result should never be an error in development or tests. | ||
| 141 | fn debug_assert_ok(self, reason: &str) -> Self; | ||
| 140 | fn warn_on_err(self) -> Option<Self::Ok>; | 142 | fn warn_on_err(self) -> Option<Self::Ok>; |
| 141 | fn inspect_error(self, func: impl FnOnce(&E)) -> Self; | 143 | fn inspect_error(self, func: impl FnOnce(&E)) -> Self; |
| 142 | } | 144 | } |
| @@ -159,6 +161,14 @@ where · 16 unmodified lines | |||
| 159 | } | 161 | } |
| 160 | } | 162 | } |
| 161 | 163 | ||
| 164 | #[track_caller] | ||
| 165 | fn debug_assert_ok(self, reason: &str) -> Self { | ||
| 166 | if let Err(error) = &self { | ||
| 167 | debug_panic!("{reason} - {error:?}"); | ||
| 168 | } | ||
| 169 | self | ||
| 170 | } | ||
| 171 | |||
| 162 | fn warn_on_err(self) -> Option<T> { | 172 | fn warn_on_err(self) -> Option<T> { |
| 163 | match self { | 173 | match self { |
| 164 | Ok(value) => Some(value), | 174 | Ok(value) => Some(value), |
| @@ -234,6 +244,7 @@ where · 69 unmodified lines | |||
| 234 | } | 244 | } |
| 235 | } | 245 | } |
| 236 | 246 | ||
| 247 | #[must_use] | ||
| 237 | pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>); | 248 | pub struct LogErrorFuture<F>(F, log::Level, core::panic::Location<'static>); |
| 238 | 249 | ||
| 239 | impl<F, T, E> Future for LogErrorFuture<F> | 250 | impl<F, T, E> Future for LogErrorFuture<F> |
| @@ -111,7 +111,6 @@ mod test { · 110 unmodified lines | |||
| 111 | 111 | ||
| 112 | let mut cx1 = VisualTestContext::from_window(cx.window, &cx); | 112 | let mut cx1 = VisualTestContext::from_window(cx.window, &cx); |
| 113 | let editor1 = cx.editor.clone(); | 113 | let editor1 = cx.editor.clone(); |
| 114 | dbg!(editor1.entity_id()); | ||
| 115 | 114 | ||
| 116 | let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); | 115 | let buffer = cx.new_model(|_| Buffer::new(0, 0, "a = 1\nb = 2\n")); |
| 117 | let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); | 116 | let (editor2, cx2) = cx.add_window_view(|cx| Editor::for_buffer(buffer, None, cx)); |
| @@ -6,6 +6,7 @@ use crate::{ · 5 unmodified lines | |||
| 6 | }; | 6 | }; |
| 7 | use anyhow::Result; | 7 | use anyhow::Result; |
| 8 | use collections::{HashMap, HashSet, VecDeque}; | 8 | use collections::{HashMap, HashSet, VecDeque}; |
| 9 | use futures::{stream::FuturesUnordered, StreamExt}; | ||
| 9 | use gpui::{ | 10 | use gpui::{ |
| 10 | actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyElement, AppContext, | 11 | actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyElement, AppContext, |
| 11 | AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, ExternalPaths, | 12 | AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, ExternalPaths, |
| @@ -255,8 +256,8 @@ impl Pane { · 243 unmodified lines | |||
| 255 | let focus_handle = cx.focus_handle(); | 256 | let focus_handle = cx.focus_handle(); |
| 256 | 257 | ||
| 257 | let subscriptions = vec![ | 258 | let subscriptions = vec![ |
| 258 | cx.on_focus_in(&focus_handle, move |this, cx| this.focus_in(cx)), | 259 | cx.on_focus_in(&focus_handle, Pane::focus_in), |
| 259 | cx.on_focus_out(&focus_handle, move |this, cx| this.focus_out(cx)), | 260 | cx.on_focus_out(&focus_handle, Pane::focus_out), |
| 260 | ]; | 261 | ]; |
| 261 | 262 | ||
| 262 | let handle = cx.view().downgrade(); | 263 | let handle = cx.view().downgrade(); |
| @@ -1796,23 +1797,46 @@ impl Pane { · 1533 unmodified lines | |||
| 1796 | } | 1797 | } |
| 1797 | } | 1798 | } |
| 1798 | let mut to_pane = cx.view().clone(); | 1799 | let mut to_pane = cx.view().clone(); |
| 1799 | let split_direction = self.drag_split_direction; | 1800 | let mut split_direction = self.drag_split_direction; |
| 1800 | let paths = paths.paths().to_vec(); | 1801 | let paths = paths.paths().to_vec(); |
| 1801 | self.workspace | 1802 | self.workspace |
| 1802 | .update(cx, |_, cx| { | 1803 | .update(cx, |workspace, cx| { |
| 1803 | cx.defer(move |workspace, cx| { | 1804 | let fs = Arc::clone(workspace.project().read(cx).fs()); |
| 1804 | if let Some(split_direction) = split_direction { | 1805 | cx.spawn(|workspace, mut cx| async move { |
| 1805 | to_pane = workspace.split_pane(to_pane, split_direction, cx); | 1806 | let mut is_file_checks = FuturesUnordered::new(); |
| 1807 | for path in &paths { | ||
| 1808 | is_file_checks.push(fs.is_file(path)) | ||
| 1806 | } | 1809 | } |
| 1807 | workspace | 1810 | let mut has_files_to_open = false; |
| 1808 | .open_paths( | 1811 | while let Some(is_file) = is_file_checks.next().await { |
| 1809 | paths, | 1812 | if is_file { |
| 1810 | OpenVisible::OnlyDirectories, | 1813 | has_files_to_open = true; |
| 1811 | Some(to_pane.downgrade()), | 1814 | break; |
| 1812 | cx, | 1815 | } |
| 1813 | ) | 1816 | } |
| 1814 | .detach(); | 1817 | drop(is_file_checks); |
| 1815 | }); | 1818 | if !has_files_to_open { |
| 1819 | split_direction = None; | ||
| 1820 | } | ||
| 1821 | |||
| 1822 | if let Some(open_task) = workspace | ||
| 1823 | .update(&mut cx, |workspace, cx| { | ||
| 1824 | if let Some(split_direction) = split_direction { | ||
| 1825 | to_pane = workspace.split_pane(to_pane, split_direction, cx); | ||
| 1826 | } | ||
| 1827 | workspace.open_paths( | ||
| 1828 | paths, | ||
| 1829 | OpenVisible::OnlyDirectories, | ||
| 1830 | Some(to_pane.downgrade()), | ||
| 1831 | cx, | ||
| 1832 | ) | ||
| 1833 | }) | ||
| 1834 | .ok() | ||
| 1835 | { | ||
| 1836 | let _opened_items: Vec<_> = open_task.await; | ||
| 1837 | } | ||
| 1838 | }) | ||
| 1839 | .detach(); | ||
| 1816 | }) | 1840 | }) |
| 1817 | .log_err(); | 1841 | .log_err(); |
| 1818 | } | 1842 | } |
| @@ -512,6 +512,11 @@ impl Workspace { · 511 unmodified lines | |||
| 512 | 512 | ||
| 513 | project::Event::DisconnectedFromHost => { | 513 | project::Event::DisconnectedFromHost => { |
| 514 | this.update_window_edited(cx); | 514 | this.update_window_edited(cx); |
| 515 | let panes_to_unfollow: Vec<View<Pane>> = | ||
| 516 | this.follower_states.keys().map(|k| k.clone()).collect(); | ||
| 517 | for pane in panes_to_unfollow { | ||
| 518 | this.unfollow(&pane, cx); | ||
| 519 | } | ||
| 515 | cx.disable_focus(); | 520 | cx.disable_focus(); |
| 516 | } | 521 | } |
| 517 | 522 | ||
| @@ -672,7 +677,7 @@ impl Workspace { · 154 unmodified lines | |||
| 672 | // ); | 677 | // ); |
| 673 | 678 | ||
| 674 | // this.show_notification(1, cx, |cx| { | 679 | // this.show_notification(1, cx, |cx| { |
| 675 | // cx.build_view(|_cx| { | 680 | // cx.new_view(|_cx| { |
| 676 | // simple_message_notification::MessageNotification::new(format!("Error:")) | 681 | // simple_message_notification::MessageNotification::new(format!("Error:")) |
| 677 | // .with_click_message("click here because!") | 682 | // .with_click_message("click here because!") |
| 678 | // }) | 683 | // }) |
| @@ -2603,11 +2608,20 @@ impl Workspace { · 1924 unmodified lines | |||
| 2603 | let cx = &cx; | 2608 | let cx = &cx; |
| 2604 | move |item| { | 2609 | move |item| { |
| 2605 | let item = item.to_followable_item_handle(cx)?; | 2610 | let item = item.to_followable_item_handle(cx)?; |
| 2606 | if (project_id.is_none() || project_id != follower_project_id) | 2611 | |
| 2607 | && item.is_project_item(cx) | 2612 | // If the item belongs to a particular project, then it should |
| 2613 | // only be included if this project is shared, and the follower | ||
| 2614 | // is in thie project. | ||
| 2615 | // | ||
| 2616 | // Some items, like channel notes, do not belong to a particular | ||
| 2617 | // project, so they should be included regardless of whether the | ||
| 2618 | // current project is shared, or what project the follower is in. | ||
| 2619 | if item.is_project_item(cx) | ||
| 2620 | && (project_id.is_none() || project_id != follower_project_id) | ||
| 2608 | { | 2621 | { |
| 2609 | return None; | 2622 | return None; |
| 2610 | } | 2623 | } |
| 2624 | |||
| 2611 | let id = item.remote_id(client, cx)?.to_proto(); | 2625 | let id = item.remote_id(client, cx)?.to_proto(); |
| 2612 | let variant = item.to_state_proto(cx)?; | 2626 | let variant = item.to_state_proto(cx)?; |
| 2613 | Some(proto::View { | 2627 | Some(proto::View { |
| @@ -2785,8 +2799,12 @@ impl Workspace { · 171 unmodified lines | |||
| 2785 | update: proto::update_followers::Variant, | 2799 | update: proto::update_followers::Variant, |
| 2786 | cx: &mut WindowContext, | 2800 | cx: &mut WindowContext, |
| 2787 | ) -> Option<()> { | 2801 | ) -> Option<()> { |
| 2802 | // If this update only applies to for followers in the current project, | ||
| 2803 | // then skip it unless this project is shared. If it applies to all | ||
| 2804 | // followers, regardless of project, then set `project_id` to none, | ||
| 2805 | // indicating that it goes to all followers. | ||
| 2788 | let project_id = if project_only { | 2806 | let project_id = if project_only { |
| 2789 | self.project.read(cx).remote_id() | 2807 | Some(self.project.read(cx).remote_id()?) |
| 2790 | } else { | 2808 | } else { |
| 2791 | None | 2809 | None |
| 2792 | }; | 2810 | }; |
| @@ -4363,12 +4381,15 @@ mod tests { · 1570 unmodified lines | |||
| 4363 | use std::{cell::RefCell, rc::Rc}; | 4381 | use std::{cell::RefCell, rc::Rc}; |
| 4364 | 4382 | ||
| 4365 | use super::*; | 4383 | use super::*; |
| 4366 | use crate::item::{ | 4384 | use crate::{ |
| 4367 | test::{TestItem, TestProjectItem}, | 4385 | dock::{test::TestPanel, PanelEvent}, |
| 4368 | ItemEvent, | 4386 | item::{ |
| 4387 | test::{TestItem, TestProjectItem}, | ||
| 4388 | ItemEvent, | ||
| 4389 | }, | ||
| 4369 | }; | 4390 | }; |
| 4370 | use fs::FakeFs; | 4391 | use fs::FakeFs; |
| 4371 | use gpui::TestAppContext; | 4392 | use gpui::{px, DismissEvent, TestAppContext, VisualTestContext}; |
| 4372 | use project::{Project, ProjectEntryId}; | 4393 | use project::{Project, ProjectEntryId}; |
| 4373 | use serde_json::json; | 4394 | use serde_json::json; |
| 4374 | use settings::SettingsStore; | 4395 | use settings::SettingsStore; |
| @@ -4935,362 +4956,405 @@ mod tests { · 560 unmodified lines | |||
| 4935 | }); | 4956 | }); |
| 4936 | } | 4957 | } |
| 4937 | 4958 | ||
| 4938 | // #[gpui::test] | 4959 | #[gpui::test] |
| 4939 | // async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { | 4960 | async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { |
| 4940 | // init_test(cx); | 4961 | init_test(cx); |
| 4941 | // let fs = FakeFs::new(cx.executor()); | 4962 | let fs = FakeFs::new(cx.executor()); |
| 4942 | |||
| 4943 | // let project = Project::test(fs, [], cx).await; | ||
| 4944 | // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); | ||
| 4945 | |||
| 4946 | // let panel = workspace.update(cx, |workspace, cx| { | ||
| 4947 | // let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); | ||
| 4948 | // workspace.add_panel(panel.clone(), cx); | ||
| 4949 | |||
| 4950 | // workspace | ||
| 4951 | // .right_dock() | ||
| 4952 | // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); | ||
| 4953 | |||
| 4954 | // panel | ||
| 4955 | // }); | ||
| 4956 | |||
| 4957 | // let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); | ||
| 4958 | // pane.update(cx, |pane, cx| { | ||
| 4959 | // let item = cx.build_view(|cx| TestItem::new(cx)); | ||
| 4960 | // pane.add_item(Box::new(item), true, true, None, cx); | ||
| 4961 | // }); | ||
| 4962 | |||
| 4963 | // // Transfer focus from center to panel | ||
| 4964 | // workspace.update(cx, |workspace, cx| { | ||
| 4965 | // workspace.toggle_panel_focus::<TestPanel>(cx); | ||
| 4966 | // }); | ||
| 4967 | |||
| 4968 | // workspace.update(cx, |workspace, cx| { | ||
| 4969 | // assert!(workspace.right_dock().read(cx).is_open()); | ||
| 4970 | // assert!(!panel.is_zoomed(cx)); | ||
| 4971 | // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 4972 | // }); | ||
| 4973 | |||
| 4974 | // // Transfer focus from panel to center | ||
| 4975 | // workspace.update(cx, |workspace, cx| { | ||
| 4976 | // workspace.toggle_panel_focus::<TestPanel>(cx); | ||
| 4977 | // }); | ||
| 4978 | |||
| 4979 | // workspace.update(cx, |workspace, cx| { | ||
| 4980 | // assert!(workspace.right_dock().read(cx).is_open()); | ||
| 4981 | // assert!(!panel.is_zoomed(cx)); | ||
| 4982 | // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 4983 | // }); | ||
| 4984 | |||
| 4985 | // // Close the dock | ||
| 4986 | // workspace.update(cx, |workspace, cx| { | ||
| 4987 | // workspace.toggle_dock(DockPosition::Right, cx); | ||
| 4988 | // }); | ||
| 4989 | |||
| 4990 | // workspace.update(cx, |workspace, cx| { | ||
| 4991 | // assert!(!workspace.right_dock().read(cx).is_open()); | ||
| 4992 | // assert!(!panel.is_zoomed(cx)); | ||
| 4993 | // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 4994 | // }); | ||
| 4995 | |||
| 4996 | // // Open the dock | ||
| 4997 | // workspace.update(cx, |workspace, cx| { | ||
| 4998 | // workspace.toggle_dock(DockPosition::Right, cx); | ||
| 4999 | // }); | ||
| 5000 | |||
| 5001 | // workspace.update(cx, |workspace, cx| { | ||
| 5002 | // assert!(workspace.right_dock().read(cx).is_open()); | ||
| 5003 | // assert!(!panel.is_zoomed(cx)); | ||
| 5004 | // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 5005 | // }); | ||
| 5006 | |||
| 5007 | // // Focus and zoom panel | ||
| 5008 | // panel.update(cx, |panel, cx| { | ||
| 5009 | // cx.focus_self(); | ||
| 5010 | // panel.set_zoomed(true, cx) | ||
| 5011 | // }); | ||
| 5012 | |||
| 5013 | // workspace.update(cx, |workspace, cx| { | ||
| 5014 | // assert!(workspace.right_dock().read(cx).is_open()); | ||
| 5015 | // assert!(panel.is_zoomed(cx)); | ||
| 5016 | // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 5017 | // }); | ||
| 5018 | |||
| 5019 | // // Transfer focus to the center closes the dock | ||
| 5020 | // workspace.update(cx, |workspace, cx| { | ||
| 5021 | // workspace.toggle_panel_focus::<TestPanel>(cx); | ||
| 5022 | // }); | ||
| 5023 | |||
| 5024 | // workspace.update(cx, |workspace, cx| { | ||
| 5025 | // assert!(!workspace.right_dock().read(cx).is_open()); | ||
| 5026 | // assert!(panel.is_zoomed(cx)); | ||
| 5027 | // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 5028 | // }); | ||
| 5029 | |||
| 5030 | // // Transferring focus back to the panel keeps it zoomed | ||
| 5031 | // workspace.update(cx, |workspace, cx| { | ||
| 5032 | // workspace.toggle_panel_focus::<TestPanel>(cx); | ||
| 5033 | // }); | ||
| 5034 | |||
| 5035 | // workspace.update(cx, |workspace, cx| { | ||
| 5036 | // assert!(workspace.right_dock().read(cx).is_open()); | ||
| 5037 | // assert!(panel.is_zoomed(cx)); | ||
| 5038 | // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 5039 | // }); | ||
| 5040 | |||
| 5041 | // // Close the dock while it is zoomed | ||
| 5042 | // workspace.update(cx, |workspace, cx| { | ||
| 5043 | // workspace.toggle_dock(DockPosition::Right, cx) | ||
| 5044 | // }); | ||
| 5045 | |||
| 5046 | // workspace.update(cx, |workspace, cx| { | ||
| 5047 | // assert!(!workspace.right_dock().read(cx).is_open()); | ||
| 5048 | // assert!(panel.is_zoomed(cx)); | ||
| 5049 | // assert!(workspace.zoomed.is_none()); | ||
| 5050 | // assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 5051 | // }); | ||
| 5052 | |||
| 5053 | // // Opening the dock, when it's zoomed, retains focus | ||
| 5054 | // workspace.update(cx, |workspace, cx| { | ||
| 5055 | // workspace.toggle_dock(DockPosition::Right, cx) | ||
| 5056 | // }); | ||
| 5057 | |||
| 5058 | // workspace.update(cx, |workspace, cx| { | ||
| 5059 | // assert!(workspace.right_dock().read(cx).is_open()); | ||
| 5060 | // assert!(panel.is_zoomed(cx)); | ||
| 5061 | // assert!(workspace.zoomed.is_some()); | ||
| 5062 | // assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); | ||
| 5063 | // }); | ||
| 5064 | |||
| 5065 | // // Unzoom and close the panel, zoom the active pane. | ||
| 5066 | // panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); | ||
| 5067 | // workspace.update(cx, |workspace, cx| { | ||
| 5068 | // workspace.toggle_dock(DockPosition::Right, cx) | ||
| 5069 | // }); | ||
| 5070 | // pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); | ||
| 5071 | |||
| 5072 | // // Opening a dock unzooms the pane. | ||
| 5073 | // workspace.update(cx, |workspace, cx| { | ||
| 5074 | // workspace.toggle_dock(DockPosition::Right, cx) | ||
| 5075 | // }); | ||
| 5076 | // workspace.update(cx, |workspace, cx| { | ||
| 5077 | // let pane = pane.read(cx); | ||
| 5078 | // assert!(!pane.is_zoomed()); | ||
| 5079 | // assert!(!pane.focus_handle(cx).is_focused(cx)); | ||
| 5080 | // assert!(workspace.right_dock().read(cx).is_open()); | ||
| 5081 | // assert!(workspace.zoomed.is_none()); | ||
| 5082 | // }); | ||
| 5083 | // } | ||
| 5084 | 4963 | ||
| 5085 | // #[gpui::test] | 4964 | let project = Project::test(fs, [], cx).await; |
| 5086 | // async fn test_panels(cx: &mut gpui::TestAppContext) { | 4965 | let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); |
| 5087 | // init_test(cx); | 4966 | |
| 5088 | // let fs = FakeFs::new(cx.executor()); | 4967 | let panel = workspace.update(cx, |workspace, cx| { |
| 5089 | 4968 | let panel = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx)); | |
| 5090 | // let project = Project::test(fs, [], cx).await; | 4969 | workspace.add_panel(panel.clone(), cx); |
| 5091 | // let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); | 4970 | |
| 5092 | 4971 | workspace | |
| 5093 | // let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { | 4972 | .right_dock() |
| 5094 | // // Add panel_1 on the left, panel_2 on the right. | 4973 | .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); |
| 5095 | // let panel_1 = cx.build_view(|cx| TestPanel::new(DockPosition::Left, cx)); | 4974 | |
| 5096 | // workspace.add_panel(panel_1.clone(), cx); | 4975 | panel |
| 5097 | // workspace | 4976 | }); |
| 5098 | // .left_dock() | 4977 | |
| 5099 | // .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); | 4978 | let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone()); |
| 5100 | // let panel_2 = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx)); | 4979 | pane.update(cx, |pane, cx| { |
| 5101 | // workspace.add_panel(panel_2.clone(), cx); | 4980 | let item = cx.new_view(|cx| TestItem::new(cx)); |
| 5102 | // workspace | 4981 | pane.add_item(Box::new(item), true, true, None, cx); |
| 5103 | // .right_dock() | 4982 | }); |
| 5104 | // .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); | 4983 | |
| 5105 | 4984 | // Transfer focus from center to panel | |
| 5106 | // let left_dock = workspace.left_dock(); | 4985 | workspace.update(cx, |workspace, cx| { |
| 5107 | // assert_eq!( | 4986 | workspace.toggle_panel_focus::<TestPanel>(cx); |
| 5108 | // left_dock.read(cx).visible_panel().unwrap().panel_id(), | 4987 | }); |
| 5109 | // panel_1.panel_id() | 4988 | |
| 5110 | // ); | 4989 | workspace.update(cx, |workspace, cx| { |
| 5111 | // assert_eq!( | 4990 | assert!(workspace.right_dock().read(cx).is_open()); |
| 5112 | // left_dock.read(cx).active_panel_size(cx).unwrap(), | 4991 | assert!(!panel.is_zoomed(cx)); |
| 5113 | // panel_1.size(cx) | 4992 | assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5114 | // ); | 4993 | }); |
| 5115 | 4994 | ||
| 5116 | // left_dock.update(cx, |left_dock, cx| { | 4995 | // Transfer focus from panel to center |
| 5117 | // left_dock.resize_active_panel(Some(1337.), cx) | 4996 | workspace.update(cx, |workspace, cx| { |
| 5118 | // }); | 4997 | workspace.toggle_panel_focus::<TestPanel>(cx); |
| 5119 | // assert_eq!( | 4998 | }); |
| 5120 | // workspace | 4999 | |
| 5121 | // .right_dock() | 5000 | workspace.update(cx, |workspace, cx| { |
| 5122 | // .read(cx) | 5001 | assert!(workspace.right_dock().read(cx).is_open()); |
| 5123 | // .visible_panel() | 5002 | assert!(!panel.is_zoomed(cx)); |
| 5124 | // .unwrap() | 5003 | assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5125 | // .panel_id(), | 5004 | }); |
| 5126 | // panel_2.panel_id(), | 5005 | |
| 5127 | // ); | 5006 | // Close the dock |
| 5128 | 5007 | workspace.update(cx, |workspace, cx| { | |
| 5129 | // (panel_1, panel_2) | 5008 | workspace.toggle_dock(DockPosition::Right, cx); |
| 5130 | // }); | 5009 | }); |
| 5131 | 5010 | ||
| 5132 | // // Move panel_1 to the right | 5011 | workspace.update(cx, |workspace, cx| { |
| 5133 | // panel_1.update(cx, |panel_1, cx| { | 5012 | assert!(!workspace.right_dock().read(cx).is_open()); |
| 5134 | // panel_1.set_position(DockPosition::Right, cx) | 5013 | assert!(!panel.is_zoomed(cx)); |
| 5135 | // }); | 5014 | assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5136 | 5015 | }); | |
| 5137 | // workspace.update(cx, |workspace, cx| { | 5016 | |
| 5138 | // // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. | 5017 | // Open the dock |
| 5139 | // // Since it was the only panel on the left, the left dock should now be closed. | 5018 | workspace.update(cx, |workspace, cx| { |
| 5140 | // assert!(!workspace.left_dock().read(cx).is_open()); | 5019 | workspace.toggle_dock(DockPosition::Right, cx); |
| 5141 | // assert!(workspace.left_dock().read(cx).visible_panel().is_none()); | 5020 | }); |
| 5142 | // let right_dock = workspace.right_dock(); | 5021 | |
| 5143 | // assert_eq!( | 5022 | workspace.update(cx, |workspace, cx| { |
| 5144 | // right_dock.read(cx).visible_panel().unwrap().panel_id(), | 5023 | assert!(workspace.right_dock().read(cx).is_open()); |
| 5145 | // panel_1.panel_id() | 5024 | assert!(!panel.is_zoomed(cx)); |
| 5146 | // ); | 5025 | assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5147 | // assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); | 5026 | }); |
| 5148 | 5027 | ||
| 5149 | // // Now we move panel_2 to the left | 5028 | // Focus and zoom panel |
| 5150 | // panel_2.set_position(DockPosition::Left, cx); | 5029 | panel.update(cx, |panel, cx| { |
| 5151 | // }); | 5030 | cx.focus_self(); |
| 5152 | 5031 | panel.set_zoomed(true, cx) | |
| 5153 | // workspace.update(cx, |workspace, cx| { | 5032 | }); |
| 5154 | // // Since panel_2 was not visible on the right, we don't open the left dock. | 5033 | |
| 5155 | // assert!(!workspace.left_dock().read(cx).is_open()); | 5034 | workspace.update(cx, |workspace, cx| { |
| 5156 | // // And the right dock is unaffected in it's displaying of panel_1 | 5035 | assert!(workspace.right_dock().read(cx).is_open()); |
| 5157 | // assert!(workspace.right_dock().read(cx).is_open()); | 5036 | assert!(panel.is_zoomed(cx)); |
| 5158 | // assert_eq!( | 5037 | assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5159 | // workspace | 5038 | }); |
| 5160 | // .right_dock() | 5039 | |
| 5161 | // .read(cx) | 5040 | // Transfer focus to the center closes the dock |
| 5162 | // .visible_panel() | 5041 | workspace.update(cx, |workspace, cx| { |
| 5163 | // .unwrap() | 5042 | workspace.toggle_panel_focus::<TestPanel>(cx); |
| 5164 | // .panel_id(), | 5043 | }); |
| 5165 | // panel_1.panel_id(), | 5044 | |
| 5166 | // ); | 5045 | workspace.update(cx, |workspace, cx| { |
| 5167 | // }); | 5046 | assert!(!workspace.right_dock().read(cx).is_open()); |
| 5168 | 5047 | assert!(panel.is_zoomed(cx)); | |
| 5169 | // // Move panel_1 back to the left | 5048 | assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5170 | // panel_1.update(cx, |panel_1, cx| { | 5049 | }); |
| 5171 | // panel_1.set_position(DockPosition::Left, cx) | 5050 | |
| 5172 | // }); | 5051 | // Transferring focus back to the panel keeps it zoomed |
| 5173 | 5052 | workspace.update(cx, |workspace, cx| { | |
| 5174 | // workspace.update(cx, |workspace, cx| { | 5053 | workspace.toggle_panel_focus::<TestPanel>(cx); |
| 5175 | // // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. | 5054 | }); |
| 5176 | // let left_dock = workspace.left_dock(); | 5055 | |
| 5177 | // assert!(left_dock.read(cx).is_open()); | 5056 | workspace.update(cx, |workspace, cx| { |
| 5178 | // assert_eq!( | 5057 | assert!(workspace.right_dock().read(cx).is_open()); |
| 5179 | // left_dock.read(cx).visible_panel().unwrap().panel_id(), | 5058 | assert!(panel.is_zoomed(cx)); |
| 5180 | // panel_1.panel_id() | 5059 | assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5181 | // ); | 5060 | }); |
| 5182 | // assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); | 5061 | |
| 5183 | // // And right the dock should be closed as it no longer has any panels. | 5062 | // Close the dock while it is zoomed |
| 5184 | // assert!(!workspace.right_dock().read(cx).is_open()); | 5063 | workspace.update(cx, |workspace, cx| { |
| 5185 | 5064 | workspace.toggle_dock(DockPosition::Right, cx) | |
| 5186 | // // Now we move panel_1 to the bottom | 5065 | }); |
| 5187 | // panel_1.set_position(DockPosition::Bottom, cx); | 5066 | |
| 5188 | // }); | 5067 | workspace.update(cx, |workspace, cx| { |
| 5189 | 5068 | assert!(!workspace.right_dock().read(cx).is_open()); | |
| 5190 | // workspace.update(cx, |workspace, cx| { | 5069 | assert!(panel.is_zoomed(cx)); |
| 5191 | // // Since panel_1 was visible on the left, we close the left dock. | 5070 | assert!(workspace.zoomed.is_none()); |
| 5192 | // assert!(!workspace.left_dock().read(cx).is_open()); | 5071 | assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx)); |
| 5193 | // // The bottom dock is sized based on the panel's default size, | 5072 | }); |
| 5194 | // // since the panel orientation changed from vertical to horizontal. | 5073 | |
| 5195 | // let bottom_dock = workspace.bottom_dock(); | 5074 | // Opening the dock, when it's zoomed, retains focus |
| 5196 | // assert_eq!( | 5075 | workspace.update(cx, |workspace, cx| { |
| 5197 | // bottom_dock.read(cx).active_panel_size(cx).unwrap(), | 5076 | workspace.toggle_dock(DockPosition::Right, cx) |
| 5198 | // panel_1.size(cx), | 5077 | }); |
| 5199 | // ); | 5078 | |
| 5200 | // // Close bottom dock and move panel_1 back to the left. | 5079 | workspace.update(cx, |workspace, cx| { |
| 5201 | // bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); | 5080 | assert!(workspace.right_dock().read(cx).is_open()); |
| 5202 | // panel_1.set_position(DockPosition::Left, cx); | 5081 | assert!(panel.is_zoomed(cx)); |
| 5203 | // }); | 5082 | assert!(workspace.zoomed.is_some()); |
| 5204 | 5083 | assert!(panel.read(cx).focus_handle(cx).contains_focused(cx)); | |
| 5205 | // // Emit activated event on panel 1 | 5084 | }); |
| 5206 | // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); | 5085 | |
| 5207 | 5086 | // Unzoom and close the panel, zoom the active pane. | |
| 5208 | // // Now the left dock is open and panel_1 is active and focused. | 5087 | panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); |
| 5209 | // workspace.update(cx, |workspace, cx| { | 5088 | workspace.update(cx, |workspace, cx| { |
| 5210 | // let left_dock = workspace.left_dock(); | 5089 | workspace.toggle_dock(DockPosition::Right, cx) |
| 5211 | // assert!(left_dock.read(cx).is_open()); | 5090 | }); |
| 5212 | // assert_eq!( | 5091 | pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); |
| 5213 | // left_dock.read(cx).visible_panel().unwrap().panel_id(), | 5092 | |
| 5214 | // panel_1.panel_id(), | 5093 | // Opening a dock unzooms the pane. |
| 5215 | // ); | 5094 | workspace.update(cx, |workspace, cx| { |
| 5216 | // assert!(panel_1.focus_handle(cx).is_focused(cx)); | 5095 | workspace.toggle_dock(DockPosition::Right, cx) |
| 5217 | // }); | 5096 | }); |
| 5218 | 5097 | workspace.update(cx, |workspace, cx| { | |
| 5219 | // // Emit closed event on panel 2, which is not active | 5098 | let pane = pane.read(cx); |
| 5220 | // panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); | 5099 | assert!(!pane.is_zoomed()); |
| 5221 | 5100 | assert!(!pane.focus_handle(cx).is_focused(cx)); | |
| 5222 | // // Wo don't close the left dock, because panel_2 wasn't the active panel | 5101 | assert!(workspace.right_dock().read(cx).is_open()); |
| 5223 | // workspace.update(cx, |workspace, cx| { | 5102 | assert!(workspace.zoomed.is_none()); |
| 5224 | // let left_dock = workspace.left_dock(); | 5103 | }); |
| 5225 | // assert!(left_dock.read(cx).is_open()); | 5104 | } |
| 5226 | // assert_eq!( | 5105 | |
| 5227 | // left_dock.read(cx).visible_panel().unwrap().panel_id(), | 5106 | struct TestModal(FocusHandle); |
| 5228 | // panel_1.panel_id(), | 5107 | |
| 5229 | // ); | 5108 | impl TestModal { |
| 5230 | // }); | 5109 | fn new(cx: &mut ViewContext<Self>) -> Self { |
| 5231 | 5110 | Self(cx.focus_handle()) | |
| 5232 | // // Emitting a ZoomIn event shows the panel as zoomed. | 5111 | } |
| 5233 | // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); | 5112 | } |
| 5234 | // workspace.update(cx, |workspace, _| { | 5113 | |
| 5235 | // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | 5114 | impl EventEmitter<DismissEvent> for TestModal {} |
| 5236 | // assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); | 5115 | |
| 5237 | // }); | 5116 | impl FocusableView for TestModal { |
| 5238 | 5117 | fn focus_handle(&self, _cx: &AppContext) -> FocusHandle { | |
| 5239 | // // Move panel to another dock while it is zoomed | 5118 | self.0.clone() |
| 5240 | // panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); | 5119 | } |
| 5241 | // workspace.update(cx, |workspace, _| { | 5120 | } |
| 5242 | // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | 5121 | |
| 5243 | 5122 | impl ModalView for TestModal {} | |
| 5244 | // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); | 5123 | |
| 5245 | // }); | 5124 | impl Render for TestModal { |
| 5246 | 5125 | fn render(&mut self, _cx: &mut ViewContext<TestModal>) -> impl IntoElement { | |
| 5247 | // // If focus is transferred to another view that's not a panel or another pane, we still show | 5126 | div().track_focus(&self.0) |
| 5248 | // // the panel as zoomed. | 5127 | } |
| 5249 | // let other_focus_handle = cx.update(|cx| cx.focus_handle()); | 5128 | } |
| 5250 | // cx.update(|cx| cx.focus(&other_focus_handle)); | 5129 | |
| 5251 | // workspace.update(cx, |workspace, _| { | 5130 | #[gpui::test] |
| 5252 | // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | 5131 | async fn test_panels(cx: &mut gpui::TestAppContext) { |
| 5253 | // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); | 5132 | init_test(cx); |
| 5254 | // }); | 5133 | let fs = FakeFs::new(cx.executor()); |
| 5255 | 5134 | ||
| 5256 | // // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. | 5135 | let project = Project::test(fs, [], cx).await; |
| 5257 | // workspace.update(cx, |_, cx| cx.focus_self()); | 5136 | let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx)); |
| 5258 | // workspace.update(cx, |workspace, _| { | 5137 | |
| 5259 | // assert_eq!(workspace.zoomed, None); | 5138 | let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| { |
| 5260 | // assert_eq!(workspace.zoomed_position, None); | 5139 | let panel_1 = cx.new_view(|cx| TestPanel::new(DockPosition::Left, cx)); |
| 5261 | // }); | 5140 | workspace.add_panel(panel_1.clone(), cx); |
| 5262 | 5141 | workspace | |
| 5263 | // // If focus is transferred again to another view that's not a panel or a pane, we won't | 5142 | .left_dock() |
| 5264 | // // show the panel as zoomed because it wasn't zoomed before. | 5143 | .update(cx, |left_dock, cx| left_dock.set_open(true, cx)); |
| 5265 | // cx.update(|cx| cx.focus(&other_focus_handle)); | 5144 | let panel_2 = cx.new_view(|cx| TestPanel::new(DockPosition::Right, cx)); |
| 5266 | // workspace.update(cx, |workspace, _| { | 5145 | workspace.add_panel(panel_2.clone(), cx); |
| 5267 | // assert_eq!(workspace.zoomed, None); | 5146 | workspace |
| 5268 | // assert_eq!(workspace.zoomed_position, None); | 5147 | .right_dock() |
| 5269 | // }); | 5148 | .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); |
| 5270 | 5149 | ||
| 5271 | // // When focus is transferred back to the panel, it is zoomed again. | 5150 | let left_dock = workspace.left_dock(); |
| 5272 | // panel_1.update(cx, |_, cx| cx.focus_self()); | 5151 | assert_eq!( |
| 5273 | // workspace.update(cx, |workspace, _| { | 5152 | left_dock.read(cx).visible_panel().unwrap().panel_id(), |
| 5274 | // assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | 5153 | panel_1.panel_id() |
| 5275 | // assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); | 5154 | ); |
| 5276 | // }); | 5155 | assert_eq!( |
| 5277 | 5156 | left_dock.read(cx).active_panel_size(cx).unwrap(), | |
| 5278 | // // Emitting a ZoomOut event unzooms the panel. | 5157 | panel_1.size(cx) |
| 5279 | // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); | 5158 | ); |
| 5280 | // workspace.update(cx, |workspace, _| { | 5159 | |
| 5281 | // assert_eq!(workspace.zoomed, None); | 5160 | left_dock.update(cx, |left_dock, cx| { |
| 5282 | // assert_eq!(workspace.zoomed_position, None); | 5161 | left_dock.resize_active_panel(Some(px(1337.)), cx) |
| 5283 | // }); | 5162 | }); |
| 5284 | 5163 | assert_eq!( | |
| 5285 | // // Emit closed event on panel 1, which is active | 5164 | workspace |
| 5286 | // panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); | 5165 | .right_dock() |
| 5287 | 5166 | .read(cx) | |
| 5288 | // // Now the left dock is closed, because panel_1 was the active panel | 5167 | .visible_panel() |
| 5289 | // workspace.update(cx, |workspace, cx| { | 5168 | .unwrap() |
| 5290 | // let right_dock = workspace.right_dock(); | 5169 | .panel_id(), |
| 5291 | // assert!(!right_dock.read(cx).is_open()); | 5170 | panel_2.panel_id(), |
| 5292 | // }); | 5171 | ); |
| 5293 | // } | 5172 | |
| 5173 | (panel_1, panel_2) | ||
| 5174 | }); | ||
| 5175 | |||
| 5176 | // Move panel_1 to the right | ||
| 5177 | panel_1.update(cx, |panel_1, cx| { | ||
| 5178 | panel_1.set_position(DockPosition::Right, cx) | ||
| 5179 | }); | ||
| 5180 | |||
| 5181 | workspace.update(cx, |workspace, cx| { | ||
| 5182 | // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. | ||
| 5183 | // Since it was the only panel on the left, the left dock should now be closed. | ||
| 5184 | assert!(!workspace.left_dock().read(cx).is_open()); | ||
| 5185 | assert!(workspace.left_dock().read(cx).visible_panel().is_none()); | ||
| 5186 | let right_dock = workspace.right_dock(); | ||
| 5187 | assert_eq!( | ||
| 5188 | right_dock.read(cx).visible_panel().unwrap().panel_id(), | ||
| 5189 | panel_1.panel_id() | ||
| 5190 | ); | ||
| 5191 | assert_eq!( | ||
| 5192 | right_dock.read(cx).active_panel_size(cx).unwrap(), | ||
| 5193 | px(1337.) | ||
| 5194 | ); | ||
| 5195 | |||
| 5196 | // Now we move panel_2 to the left | ||
| 5197 | panel_2.set_position(DockPosition::Left, cx); | ||
| 5198 | }); | ||
| 5199 | |||
| 5200 | workspace.update(cx, |workspace, cx| { | ||
| 5201 | // Since panel_2 was not visible on the right, we don't open the left dock. | ||
| 5202 | assert!(!workspace.left_dock().read(cx).is_open()); | ||
| 5203 | // And the right dock is unaffected in it's displaying of panel_1 | ||
| 5204 | assert!(workspace.right_dock().read(cx).is_open()); | ||
| 5205 | assert_eq!( | ||
| 5206 | workspace | ||
| 5207 | .right_dock() | ||
| 5208 | .read(cx) | ||
| 5209 | .visible_panel() | ||
| 5210 | .unwrap() | ||
| 5211 | .panel_id(), | ||
| 5212 | panel_1.panel_id(), | ||
| 5213 | ); | ||
| 5214 | }); | ||
| 5215 | |||
| 5216 | // Move panel_1 back to the left | ||
| 5217 | panel_1.update(cx, |panel_1, cx| { | ||
| 5218 | panel_1.set_position(DockPosition::Left, cx) | ||
| 5219 | }); | ||
| 5220 | |||
| 5221 | workspace.update(cx, |workspace, cx| { | ||
| 5222 | // Since panel_1 was visible on the right, we open the left dock and make panel_1 active. | ||
| 5223 | let left_dock = workspace.left_dock(); | ||
| 5224 | assert!(left_dock.read(cx).is_open()); | ||
| 5225 | assert_eq!( | ||
| 5226 | left_dock.read(cx).visible_panel().unwrap().panel_id(), | ||
| 5227 | panel_1.panel_id() | ||
| 5228 | ); | ||
| 5229 | assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), px(1337.)); | ||
| 5230 | // And the right dock should be closed as it no longer has any panels. | ||
| 5231 | assert!(!workspace.right_dock().read(cx).is_open()); | ||
| 5232 | |||
| 5233 | // Now we move panel_1 to the bottom | ||
| 5234 | panel_1.set_position(DockPosition::Bottom, cx); | ||
| 5235 | }); | ||
| 5236 | |||
| 5237 | workspace.update(cx, |workspace, cx| { | ||
| 5238 | // Since panel_1 was visible on the left, we close the left dock. | ||
| 5239 | assert!(!workspace.left_dock().read(cx).is_open()); | ||
| 5240 | // The bottom dock is sized based on the panel's default size, | ||
| 5241 | // since the panel orientation changed from vertical to horizontal. | ||
| 5242 | let bottom_dock = workspace.bottom_dock(); | ||
| 5243 | assert_eq!( | ||
| 5244 | bottom_dock.read(cx).active_panel_size(cx).unwrap(), | ||
| 5245 | panel_1.size(cx), | ||
| 5246 | ); | ||
| 5247 | // Close bottom dock and move panel_1 back to the left. | ||
| 5248 | bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx)); | ||
| 5249 | panel_1.set_position(DockPosition::Left, cx); | ||
| 5250 | }); | ||
| 5251 | |||
| 5252 | // Emit activated event on panel 1 | ||
| 5253 | panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate)); | ||
| 5254 | |||
| 5255 | // Now the left dock is open and panel_1 is active and focused. | ||
| 5256 | workspace.update(cx, |workspace, cx| { | ||
| 5257 | let left_dock = workspace.left_dock(); | ||
| 5258 | assert!(left_dock.read(cx).is_open()); | ||
| 5259 | assert_eq!( | ||
| 5260 | left_dock.read(cx).visible_panel().unwrap().panel_id(), | ||
| 5261 | panel_1.panel_id(), | ||
| 5262 | ); | ||
| 5263 | assert!(panel_1.focus_handle(cx).is_focused(cx)); | ||
| 5264 | }); | ||
| 5265 | |||
| 5266 | // Emit closed event on panel 2, which is not active | ||
| 5267 | panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close)); | ||
| 5268 | |||
| 5269 | // Wo don't close the left dock, because panel_2 wasn't the active panel | ||
| 5270 | workspace.update(cx, |workspace, cx| { | ||
| 5271 | let left_dock = workspace.left_dock(); | ||
| 5272 | assert!(left_dock.read(cx).is_open()); | ||
| 5273 | assert_eq!( | ||
| 5274 | left_dock.read(cx).visible_panel().unwrap().panel_id(), | ||
| 5275 | panel_1.panel_id(), | ||
| 5276 | ); | ||
| 5277 | }); | ||
| 5278 | |||
| 5279 | // Emitting a ZoomIn event shows the panel as zoomed. | ||
| 5280 | panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn)); | ||
| 5281 | workspace.update(cx, |workspace, _| { | ||
| 5282 | assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | ||
| 5283 | assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); | ||
| 5284 | }); | ||
| 5285 | |||
| 5286 | // Move panel to another dock while it is zoomed | ||
| 5287 | panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); | ||
| 5288 | workspace.update(cx, |workspace, _| { | ||
| 5289 | assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | ||
| 5290 | |||
| 5291 | assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); | ||
| 5292 | }); | ||
| 5293 | |||
| 5294 | // This is a helper for getting a: | ||
| 5295 | // - valid focus on an element, | ||
| 5296 | // - that isn't a part of the panes and panels system of the Workspace, | ||
| 5297 | // - and doesn't trigger the 'on_focus_lost' API. | ||
| 5298 | let focus_other_view = { | ||
| 5299 | let workspace = workspace.clone(); | ||
| 5300 | move |cx: &mut VisualTestContext| { | ||
| 5301 | workspace.update(cx, |workspace, cx| { | ||
| 5302 | if let Some(_) = workspace.active_modal::<TestModal>(cx) { | ||
| 5303 | workspace.toggle_modal(cx, TestModal::new); | ||
| 5304 | workspace.toggle_modal(cx, TestModal::new); | ||
| 5305 | } else { | ||
| 5306 | workspace.toggle_modal(cx, TestModal::new); | ||
| 5307 | } | ||
| 5308 | }) | ||
| 5309 | } | ||
| 5310 | }; | ||
| 5311 | |||
| 5312 | // If focus is transferred to another view that's not a panel or another pane, we still show | ||
| 5313 | // the panel as zoomed. | ||
| 5314 | focus_other_view(cx); | ||
| 5315 | workspace.update(cx, |workspace, _| { | ||
| 5316 | assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | ||
| 5317 | assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); | ||
| 5318 | }); | ||
| 5319 | |||
| 5320 | // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. | ||
| 5321 | workspace.update(cx, |_, cx| cx.focus_self()); | ||
| 5322 | workspace.update(cx, |workspace, _| { | ||
| 5323 | assert_eq!(workspace.zoomed, None); | ||
| 5324 | assert_eq!(workspace.zoomed_position, None); | ||
| 5325 | }); | ||
| 5326 | |||
| 5327 | // If focus is transferred again to another view that's not a panel or a pane, we won't | ||
| 5328 | // show the panel as zoomed because it wasn't zoomed before. | ||
| 5329 | focus_other_view(cx); | ||
| 5330 | workspace.update(cx, |workspace, _| { | ||
| 5331 | assert_eq!(workspace.zoomed, None); | ||
| 5332 | assert_eq!(workspace.zoomed_position, None); | ||
| 5333 | }); | ||
| 5334 | |||
| 5335 | // When the panel is activated, it is zoomed again. | ||
| 5336 | cx.dispatch_action(ToggleRightDock); | ||
| 5337 | workspace.update(cx, |workspace, _| { | ||
| 5338 | assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade())); | ||
| 5339 | assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); | ||
| 5340 | }); | ||
| 5341 | |||
| 5342 | // Emitting a ZoomOut event unzooms the panel. | ||
| 5343 | panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut)); | ||
| 5344 | workspace.update(cx, |workspace, _| { | ||
| 5345 | assert_eq!(workspace.zoomed, None); | ||
| 5346 | assert_eq!(workspace.zoomed_position, None); | ||
| 5347 | }); | ||
| 5348 | |||
| 5349 | // Emit closed event on panel 1, which is active | ||
| 5350 | panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Close)); | ||
| 5351 | |||
| 5352 | // Now the left dock is closed, because panel_1 was the active panel | ||
| 5353 | workspace.update(cx, |workspace, cx| { | ||
| 5354 | let right_dock = workspace.right_dock(); | ||
| 5355 | assert!(!right_dock.read(cx).is_open()); | ||
| 5356 | }); | ||
| 5357 | } | ||
| 5294 | 5358 | ||
| 5295 | pub fn init_test(cx: &mut TestAppContext) { | 5359 | pub fn init_test(cx: &mut TestAppContext) { |
| 5296 | cx.update(|cx| { | 5360 | cx.update(|cx| { |
| @@ -148,6 +148,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) { · 147 unmodified lines | |||
| 148 | cx.on_window_should_close(move |cx| { | 148 | cx.on_window_should_close(move |cx| { |
| 149 | handle | 149 | handle |
| 150 | .update(cx, |workspace, cx| { | 150 | .update(cx, |workspace, cx| { |
| 151 | // We'll handle closing asynchoronously | ||
| 151 | workspace.close_window(&Default::default(), cx); | 152 | workspace.close_window(&Default::default(), cx); |
| 152 | false | 153 | false |
| 153 | }) | 154 | }) |
| @@ -56,7 +56,7 @@ We use Vercel for all of our web deployments and some backend things. If you sig · 55 unmodified lines | |||
| 56 | 56 | ||
| 57 | ### Environment Variables | 57 | ### Environment Variables |
| 58 | 58 | ||
| 59 | You can get access to many of our shared enviroment variables through 1Password and Vercel. For 1Password search the value you are looking for, or sort by passwords or API credentials. | 59 | You can get access to many of our shared environment variables through 1Password and Vercel. For 1Password search the value you are looking for, or sort by passwords or API credentials. |
| 60 | 60 | ||
| 61 | For Vercel, go to `settings` -> `Environment Variables` (either on the entire org, or on a specific project depending on where it is shared.) For a given Vercel project if you have their CLI installed you can use `vercel pull` or `vercel env` to pull values down directly. More on those in their [CLI docs](https://vercel.com/docs/cli/env). | 61 | For Vercel, go to `settings` -> `Environment Variables` (either on the entire org, or on a specific project depending on where it is shared.) For a given Vercel project if you have their CLI installed you can use `vercel pull` or `vercel env` to pull values down directly. More on those in their [CLI docs](https://vercel.com/docs/cli/env). |
| 62 | 62 | ||
| @@ -1,4 +1,5 @@ | |||
| 1 | [toolchain] | 1 | [toolchain] |
| 2 | channel = "1.75" | 2 | channel = "1.75" |
| 3 | components = [ "rustfmt" ] | 3 | profile = "minimal" |
| 4 | components = [ "rustfmt", "clippy" ] | ||
| 4 | targets = [ "x86_64-apple-darwin", "aarch64-apple-darwin", "wasm32-wasi" ] | 5 | targets = [ "x86_64-apple-darwin", "aarch64-apple-darwin", "wasm32-wasi" ] |
| @@ -1,24 +1,45 @@ | |||
| 1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
| 2 | 2 | ||
| 3 | const HELP = ` | ||
| 4 | USAGE | ||
| 5 | zed-local [options] [zed args] | ||
| 6 | |||
| 7 | OPTIONS | ||
| 8 | --help Print this help message | ||
| 9 | --release Build Zed in release mode | ||
| 10 | -2, -3, -4 Spawn 2, 3, or 4 Zed instances, with their windows tiled. | ||
| 11 | --top Arrange the Zed windows so they take up the top half of the screen. | ||
| 12 | `.trim(); | ||
| 13 | |||
| 3 | const { spawn, execFileSync } = require("child_process"); | 14 | const { spawn, execFileSync } = require("child_process"); |
| 4 | 15 | ||
| 5 | const RESOLUTION_REGEX = /(\d+) x (\d+)/; | 16 | const RESOLUTION_REGEX = /(\d+) x (\d+)/; |
| 6 | const DIGIT_FLAG_REGEX = /^--?(\d+)$/; | 17 | const DIGIT_FLAG_REGEX = /^--?(\d+)$/; |
| 7 | const RELEASE_MODE = "--release"; | 18 | |
| 19 | let instanceCount = 1; | ||
| 20 | let isReleaseMode = false; | ||
| 21 | let isTop = false; | ||
| 8 | 22 | ||
| 9 | const args = process.argv.slice(2); | 23 | const args = process.argv.slice(2); |
| 24 | while (args.length > 0) { | ||
| 25 | const arg = args[0]; | ||
| 26 | |||
| 27 | const digitMatch = arg.match(DIGIT_FLAG_REGEX); | ||
| 28 | if (digitMatch) { | ||
| 29 | instanceCount = parseInt(digitMatch[1]); | ||
| 30 | } else if (arg === "--release") { | ||
| 31 | isReleaseMode = true; | ||
| 32 | } else if (arg === "--top") { | ||
| 33 | isTop = true; | ||
| 34 | } else if (arg === "--help") { | ||
| 35 | console.log(HELP); | ||
| 36 | process.exit(0); | ||
| 37 | } else { | ||
| 38 | break; | ||
| 39 | } | ||
| 10 | 40 | ||
| 11 | // Parse the number of Zed instances to spawn. | ||
| 12 | let instanceCount = 1; | ||
| 13 | const digitMatch = args[0]?.match(DIGIT_FLAG_REGEX); | ||
| 14 | if (digitMatch) { | ||
| 15 | instanceCount = parseInt(digitMatch[1]); | ||
| 16 | args.shift(); | 41 | args.shift(); |
| 17 | } | 42 | } |
| 18 | const isReleaseMode = args.some((arg) => arg === RELEASE_MODE); | ||
| 19 | if (instanceCount > 4) { | ||
| 20 | throw new Error("Cannot spawn more than 4 instances"); | ||
| 21 | } | ||
| 22 | 43 | ||
| 23 | // Parse the resolution of the main screen | 44 | // Parse the resolution of the main screen |
| 24 | const displayInfo = JSON.parse( | 45 | const displayInfo = JSON.parse( |
| @@ -34,7 +55,11 @@ if (!mainDisplayResolution) { · 9 unmodified lines | |||
| 34 | throw new Error("Could not parse screen resolution"); | 55 | throw new Error("Could not parse screen resolution"); |
| 35 | } | 56 | } |
| 36 | const screenWidth = parseInt(mainDisplayResolution[1]); | 57 | const screenWidth = parseInt(mainDisplayResolution[1]); |
| 37 | const screenHeight = parseInt(mainDisplayResolution[2]); | 58 | let screenHeight = parseInt(mainDisplayResolution[2]); |
| 59 | |||
| 60 | if (isTop) { | ||
| 61 | screenHeight = Math.floor(screenHeight / 2); | ||
| 62 | } | ||
| 38 | 63 | ||
| 39 | // Determine the window size for each instance | 64 | // Determine the window size for each instance |
| 40 | let instanceWidth = screenWidth; | 65 | let instanceWidth = screenWidth; |