diff --git a/.cargo/ci-config.toml b/.cargo/ci-config.toml index 6dbaf4b446..664419837b 100644 --- a/.cargo/ci-config.toml +++ b/.cargo/ci-config.toml @@ -10,3 +10,6 @@ # in one spot, that's going to trigger a rebuild of all of the artifacts. Using ci-config.toml we can define these overrides for CI in one spot and not worry about it. [build] rustflags = ["-D", "warnings"] + +[alias] +xtask = "run --package xtask --" diff --git a/.cargo/config.toml b/.cargo/config.toml index 32fdb271ad..d73dead142 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,6 @@ [build] # v0 mangling scheme provides more detailed backtraces around closures -rustflags = ["-C", "symbol-mangling-version=v0"] +rustflags = ["-C", "symbol-mangling-version=v0", "--cfg", "tokio_unstable"] + +[alias] +xtask = "run --package xtask --" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 95d4064e8f..93511150ad 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,16 +1,17 @@ +blank_issues_enabled: false contact_links: - - name: Language Request - url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E - about: Request a language in the extensions repository - - name: Theme Request - url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme - about: Request a theme in the extensions repository - - name: Top-Ranking Issues - url: https://github.com/zed-industries/zed/issues/5393 - about: See an overview of the most popular Zed issues - - name: Platform Support - url: https://github.com/zed-industries/zed/issues/5391 - about: A quick note on platform support - - name: Positive Feedback - url: https://github.com/zed-industries/zed/discussions/5397 - about: A central location for kind words about Zed + - name: Language Request + url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=language&projects=&template=1_language_request.yml&title=%3Cname_of_language%3E + about: Request a language in the extensions repository + - name: Theme Request + url: https://github.com/zed-industries/extensions/issues/new?assignees=&labels=theme&projects=&template=0_theme_request.yml&title=%3Cname_of_theme%3E+theme + about: Request a theme in the extensions repository + - name: Top-Ranking Issues + url: https://github.com/zed-industries/zed/issues/5393 + about: See an overview of the most popular Zed issues + - name: Platform Support + url: https://github.com/zed-industries/zed/issues/5391 + about: A quick note on platform support + - name: Positive Feedback + url: https://github.com/zed-industries/zed/discussions/5397 + about: A central location for kind words about Zed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f3ed4a18d..fac8924863 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,8 +93,7 @@ jobs: fi - name: cargo clippy - shell: bash -euxo pipefail {0} - run: script/clippy + run: cargo xtask clippy - name: Run tests uses: ./.github/actions/run_tests @@ -126,8 +125,7 @@ jobs: run: script/linux - name: cargo clippy - shell: bash -euxo pipefail {0} - run: script/clippy + run: cargo xtask clippy - name: Build Zed run: cargo build -p zed @@ -149,8 +147,7 @@ jobs: save-if: ${{ github.ref == 'refs/heads/main' }} - name: cargo clippy - shell: bash -euxo pipefail {0} - run: script/clippy + run: cargo xtask clippy - name: Build Zed run: cargo build -p zed diff --git a/.github/workflows/deploy_collab.yml b/.github/workflows/deploy_collab.yml index b866d9080c..e0783d0825 100644 --- a/.github/workflows/deploy_collab.yml +++ b/.github/workflows/deploy_collab.yml @@ -28,8 +28,7 @@ jobs: uses: ./.github/actions/check_style - name: Run clippy - shell: bash -euxo pipefail {0} - run: script/clippy + run: cargo xtask clippy tests: name: Run tests @@ -105,8 +104,12 @@ jobs: set -eu if [[ $GITHUB_REF_NAME = "collab-production" ]]; then export ZED_KUBE_NAMESPACE=production + export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10 + export ZED_API_LOAD_BALANCER_SIZE_UNIT=2 elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then export ZED_KUBE_NAMESPACE=staging + export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1 + export ZED_API_LOAD_BALANCER_SIZE_UNIT=1 else echo "cowardly refusing to deploy from an unknown branch" exit 1 @@ -121,11 +124,13 @@ jobs: export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}" export ZED_SERVICE_NAME=collab + export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f - kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}" export ZED_SERVICE_NAME=api + export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f - kubectl -n "$ZED_KUBE_NAMESPACE" rollout status deployment/$ZED_SERVICE_NAME --watch echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}" diff --git a/.github/workflows/release_nightly.yml b/.github/workflows/release_nightly.yml index 1a7a0f704d..6e34e859b5 100644 --- a/.github/workflows/release_nightly.yml +++ b/.github/workflows/release_nightly.yml @@ -32,8 +32,7 @@ jobs: uses: ./.github/actions/check_style - name: Run clippy - shell: bash -euxo pipefail {0} - run: script/clippy + run: cargo xtask clippy tests: name: Run tests if: github.repository_owner == 'zed-industries' diff --git a/Cargo.lock b/Cargo.lock index 976b888f6e..483b56d999 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,6 +40,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures", + "zeroize", +] + [[package]] name = "ahash" version = "0.7.6" @@ -312,7 +324,7 @@ dependencies = [ "serde", "serde_repr", "url", - "zbus", + "zbus 3.15.1", ] [[package]] @@ -384,6 +396,18 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-broadcast" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258b52a1aa741b9f09783b2d86cf0aeeb617bbf847f6933340a39644227acbdb" +dependencies = [ + "event-listener 5.1.0", + "event-listener-strategy 0.5.0", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -395,6 +419,19 @@ dependencies = [ "futures-core", ] +[[package]] +name = "async-channel" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +dependencies = [ + "concurrent-queue", + "event-listener 5.1.0", + "event-listener-strategy 0.5.0", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-compat" version = "0.2.1" @@ -455,7 +492,7 @@ checksum = "bc19683171f287921f2405677dd2ed2549c3b3bda697a563ebc3a121ace2aba1" dependencies = [ "async-lock 3.3.0", "blocking", - "futures-lite 2.0.0", + "futures-lite 2.2.0", ] [[package]] @@ -464,7 +501,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", "async-io 1.13.0", "async-lock 2.8.0", @@ -503,7 +540,7 @@ dependencies = [ "cfg-if 1.0.0", "concurrent-queue", "futures-io", - "futures-lite 2.0.0", + "futures-lite 2.2.0", "parking", "polling 3.3.2", "rustix 0.38.30", @@ -528,7 +565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" dependencies = [ "event-listener 4.0.3", - "event-listener-strategy", + "event-listener-strategy 0.4.0", "pin-project-lite", ] @@ -564,7 +601,7 @@ checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" dependencies = [ "async-io 2.3.1", "blocking", - "futures-lite 2.0.0", + "futures-lite 2.2.0", ] [[package]] @@ -594,6 +631,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "async-process" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451e3cf68011bd56771c79db04a9e333095ab6349f7e47592b788e9b98720cc8" +dependencies = [ + "async-channel 2.2.0", + "async-io 2.3.1", + "async-lock 3.3.0", + "async-signal", + "blocking", + "cfg-if 1.0.0", + "event-listener 5.1.0", + "futures-lite 2.2.0", + "rustix 0.38.30", + "windows-sys 0.52.0", +] + [[package]] name = "async-recursion" version = "0.3.2" @@ -616,17 +671,35 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "async-signal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +dependencies = [ + "async-io 2.3.1", + "async-lock 2.8.0", + "atomic-waker", + "cfg-if 1.0.0", + "futures-core", + "futures-io", + "rustix 0.38.30", + "signal-hook-registry", + "slab", + "windows-sys 0.48.0", +] + [[package]] name = "async-std" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-global-executor", "async-io 1.13.0", "async-lock 2.8.0", - "async-process", + "async-process 1.7.0", "crossbeam-utils", "futures-channel", "futures-core", @@ -1151,13 +1224,13 @@ dependencies = [ [[package]] name = "axum" -version = "0.5.17" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "base64 0.13.1", + "base64 0.21.4", "bitflags 1.3.2", "bytes 1.5.0", "futures-util", @@ -1171,24 +1244,25 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", + "rustversion", "serde", "serde_json", + "serde_path_to_error", "serde_urlencoded", - "sha-1 0.10.1", + "sha1", "sync_wrapper", "tokio", "tokio-tungstenite", "tower", - "tower-http 0.3.5", "tower-layer", "tower-service", ] [[package]] name = "axum-core" -version = "0.2.9" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", "bytes 1.5.0", @@ -1196,15 +1270,16 @@ dependencies = [ "http 0.2.9", "http-body", "mime", + "rustversion", "tower-layer", "tower-service", ] [[package]] name = "axum-extra" -version = "0.3.7" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69034b3b0fd97923eee2ce8a47540edb21e07f48f87f67d44bb4271cec622bdb" +checksum = "f9a320103719de37b7b4da4c8eb629d4573f6bcfd3dfe80d3208806895ccf81d" dependencies = [ "axum", "bytes 1.5.0", @@ -1378,7 +1453,7 @@ dependencies = [ [[package]] name = "blade-graphics" version = "0.3.0" -source = "git+https://github.com/kvark/blade?rev=e9d93a4d41f3946a03ffb76136290d6ccf7f2b80#e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" +source = "git+https://github.com/kvark/blade?rev=43721bf42d298b7cbee2195ee66f73a5f1c7b2fc#43721bf42d298b7cbee2195ee66f73a5f1c7b2fc" dependencies = [ "ash", "ash-window", @@ -1408,7 +1483,7 @@ dependencies = [ [[package]] name = "blade-macros" version = "0.2.1" -source = "git+https://github.com/kvark/blade?rev=e9d93a4d41f3946a03ffb76136290d6ccf7f2b80#e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" +source = "git+https://github.com/kvark/blade?rev=43721bf42d298b7cbee2195ee66f73a5f1c7b2fc#43721bf42d298b7cbee2195ee66f73a5f1c7b2fc" dependencies = [ "proc-macro2", "quote", @@ -1440,18 +1515,28 @@ dependencies = [ ] [[package]] -name = "blocking" -version = "1.3.1" +name = "block-padding" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "async-channel", - "async-lock 2.8.0", + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +dependencies = [ + "async-channel 2.2.0", + "async-lock 3.3.0", "async-task", - "atomic-waker", - "fastrand 1.9.0", - "futures-lite 1.13.0", - "log", + "fastrand 2.0.0", + "futures-io", + "futures-lite 2.2.0", + "piper", + "tracing", ] [[package]] @@ -1746,6 +1831,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "cbindgen" version = "0.26.0" @@ -1853,6 +1947,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -2038,6 +2143,16 @@ dependencies = [ "util", ] +[[package]] +name = "clipboard-win" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" +dependencies = [ + "lazy-bytes-cast", + "winapi 0.3.9", +] + [[package]] name = "clock" version = "0.1.0" @@ -2116,6 +2231,7 @@ dependencies = [ "clock", "collab_ui", "collections", + "console-subscriber", "ctor", "dashmap", "editor", @@ -2127,7 +2243,6 @@ dependencies = [ "git", "gpui", "hex", - "hyper", "indoc", "language", "lazy_static", @@ -2167,7 +2282,6 @@ dependencies = [ "tower", "tower-http 0.4.4", "tracing", - "tracing-log", "tracing-subscriber", "unindent", "util", @@ -2277,7 +2391,6 @@ dependencies = [ "picker", "postage", "project", - "release_channel", "serde", "serde_json", "settings", @@ -2318,6 +2431,43 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "console-api" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd326812b3fd01da5bb1af7d340d0d555fd3d4b641e7f1dfcf5962a902952787" +dependencies = [ + "futures-core", + "prost 0.12.3", + "prost-types 0.12.3", + "tonic", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7481d4c57092cd1c19dd541b92bdce883de840df30aa5d03fd48a3935c01842e" +dependencies = [ + "console-api", + "crossbeam-channel", + "crossbeam-utils", + "futures-task", + "hdrhistogram", + "humantime", + "prost-types 0.12.3", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "const-cstr" version = "0.3.0" @@ -2387,6 +2537,20 @@ dependencies = [ "zed_actions", ] +[[package]] +name = "copypasta" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb85422867ca93da58b7f95fb5c0c10f6183ed6e1ef8841568968a896d3a858" +dependencies = [ + "clipboard-win", + "objc", + "objc-foundation", + "objc_id", + "smithay-clipboard", + "x11-clipboard", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2540,18 +2704,18 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a6391a9172a93f413370fa561c6bca786e06c89cf85f23f02f6345b1c8ee34" +checksum = "9515fcc42b6cb5137f76b84c1a6f819782d0cf12473d145d3bc5cd67eedc8bc2" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "409c6cbb326604a53ec47eb6341fc85128f24c81012a014b4c728ed24f6e9350" +checksum = "1ad827c6071bfe6d22de1bc331296a29f9ddc506ff926d8415b435ec6a6efce0" dependencies = [ "bumpalo", "cranelift-bforest", @@ -2570,33 +2734,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff55e100130995b9ad9ac6b03a24ed5da3c1a1261dcdeb8a7a0292656994fb3" +checksum = "10e6b36237a9ca2ce2fb4cc7741d418a080afa1327402138412ef85d5367bef1" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1446e2eb395fc7b3019a36dccb7eccea923f6caf581b903c8e7e751b6d214a7" +checksum = "c36bf4bfb86898a94ccfa773a1f86e8a5346b1983ff72059bdd2db4600325251" [[package]] name = "cranelift-control" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24076ecf69cbf8b9e1e532ae8e7ac01d850a1c2e127058a26eb3245f9d5b89d1" +checksum = "7cbf36560e7a6bd1409ca91e7b43b2cc7ed8429f343d7605eadf9046e8fac0d0" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f40df95180ad317c60459bb90dd87803d35e538f4c54376d8b26c851f6f0a1b" +checksum = "a71e11061a75b1184c09bea97c026a88f08b59ade96a7bb1f259d4ea0df2e942" dependencies = [ "serde", "serde_derive", @@ -2604,9 +2768,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3974cc665b699b626742775dae1c1cdea5170f5028ab1f3eb61a7a9a6e2979" +checksum = "af5d4da63143ee3485c7bcedde0a818727d737d1083484a0ceedb8950c89e495" dependencies = [ "cranelift-codegen", "log", @@ -2616,15 +2780,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99543f92b9c361f3c54a29e945adb5b9ef1318feaa5944453cabbfcb3c495919" +checksum = "457a9832b089e26f5eea70dcf49bed8ec6edafed630ce7c83161f24d46ab8085" [[package]] name = "cranelift-native" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c0d84dc7d9b3f73ad565eacc4ab36525c407ef5150893b4b94d5f5f904eb48a" +checksum = "9b490d579df1ce365e1ea359e24ed86d82289fa785153327c2f6a69a59a731e4" dependencies = [ "cranelift-codegen", "libc", @@ -2633,9 +2797,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.105.1" +version = "0.105.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53781039219944d59c6d3ec57e6cae31a1a33db71573a945d84ba6d875d0a743" +checksum = "8cd747ed7f9a461dda9c388415392f6bb95d1a6ef3b7694d17e0817eb74b7798" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -2762,6 +2926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -2850,6 +3015,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "data-url" version = "0.1.1" @@ -3231,6 +3402,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + [[package]] name = "enumflags2" version = "0.7.9" @@ -3346,6 +3523,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + [[package]] name = "event-listener-strategy" version = "0.4.0" @@ -3356,6 +3544,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +dependencies = [ + "event-listener 5.1.0", + "pin-project-lite", +] + [[package]] name = "extension" version = "0.1.0" @@ -3773,7 +3971,7 @@ dependencies = [ [[package]] name = "fsevent" -version = "2.0.2" +version = "0.1.0" dependencies = [ "bitflags 2.4.2", "fsevent-sys 3.1.0", @@ -3913,17 +4111,15 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1155db57329dca6d018b61e76b1488ce9a2e5e44028cac420a5898f4fcef63" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" dependencies = [ "fastrand 2.0.0", "futures-core", "futures-io", - "memchr", "parking", "pin-project-lite", - "waker-fn", ] [[package]] @@ -4018,6 +4214,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -4196,6 +4402,7 @@ dependencies = [ "cbindgen", "cocoa", "collections", + "copypasta", "core-foundation", "core-graphics", "core-text", @@ -4218,6 +4425,7 @@ dependencies = [ "metal", "num_cpus", "objc", + "oo7", "open", "parking", "parking_lot 0.11.2", @@ -4248,7 +4456,9 @@ dependencies = [ "waker-fn", "wayland-backend", "wayland-client", + "wayland-cursor", "wayland-protocols", + "windows 0.53.0", "xcb", "xkbcommon", ] @@ -4335,6 +4545,19 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "base64 0.21.4", + "byteorder", + "flate2", + "nom", + "num-traits", +] + [[package]] name = "headers" version = "0.3.9" @@ -4561,6 +4784,18 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -4707,6 +4942,16 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + [[package]] name = "install_cli" version = "0.1.0" @@ -4812,7 +5057,7 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ - "async-channel", + "async-channel 1.9.0", "castaway", "crossbeam-utils", "curl", @@ -5191,6 +5436,12 @@ dependencies = [ "util", ] +[[package]] +name = "lazy-bytes-cast" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" + [[package]] name = "lazy_static" version = "1.4.0" @@ -5517,9 +5768,9 @@ checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matchit" -version = "0.5.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "matrixmultiply" @@ -5591,6 +5842,15 @@ dependencies = [ "libc", ] +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.7.1" @@ -5913,6 +6173,7 @@ dependencies = [ "bitflags 2.4.2", "cfg-if 1.0.0", "libc", + "memoffset 0.9.0", ] [[package]] @@ -6010,6 +6271,20 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint 0.4.4", + "num-complex 0.4.4", + "num-integer", + "num-iter", + "num-rational 0.4.1", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -6062,6 +6337,7 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.5", + "serde", "smallvec", "zeroize", ] @@ -6140,6 +6416,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint 0.4.4", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -6206,6 +6494,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -6215,6 +6514,15 @@ dependencies = [ "cc", ] +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + [[package]] name = "object" version = "0.32.1" @@ -6256,6 +6564,35 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oo7" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37558cac1af63a81fd2ff7f3469c02a4da06b163c5671791553b8dac10f07c82" +dependencies = [ + "aes", + "async-fs 2.1.1", + "async-io 2.3.1", + "async-lock 3.3.0", + "blocking", + "cbc", + "cipher 0.4.4", + "digest 0.10.7", + "futures-lite 2.2.0", + "futures-util", + "hkdf", + "hmac 0.12.1", + "num 0.4.1", + "num-bigint-dig 0.8.4", + "pbkdf2 0.12.2", + "rand 0.8.5", + "serde", + "sha2 0.10.7", + "zbus 4.0.1", + "zeroize", + "zvariant 4.0.2", +] + [[package]] name = "opaque-debug" version = "0.3.0" @@ -6454,9 +6791,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" @@ -6513,7 +6850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7037e5e93e0172a5a96874380bf73bc6ecef022e26fa25f2be26864d6b3ba95d" dependencies = [ "lazy_static", - "num", + "num 0.2.1", "regex", ] @@ -6567,6 +6904,16 @@ dependencies = [ "crypto-mac", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -6613,6 +6960,7 @@ dependencies = [ name = "picker" version = "0.1.0" dependencies = [ + "anyhow", "ctor", "editor", "env_logger", @@ -6851,6 +7199,15 @@ dependencies = [ "toml_edit 0.19.15", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -7078,6 +7435,16 @@ dependencies = [ "prost-derive 0.9.0", ] +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes 1.5.0", + "prost-derive 0.12.3", +] + [[package]] name = "prost-build" version = "0.9.0" @@ -7124,6 +7491,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "prost-types" version = "0.8.0" @@ -7144,6 +7524,15 @@ dependencies = [ "prost 0.9.0", ] +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost 0.12.3", +] + [[package]] name = "protobuf" version = "2.28.0" @@ -8034,7 +8423,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" dependencies = [ - "cipher", + "cipher 0.3.0", ] [[package]] @@ -8107,7 +8496,7 @@ dependencies = [ "base64ct", "hmac 0.11.0", "password-hash", - "pbkdf2", + "pbkdf2 0.8.0", "salsa20", "sha2 0.9.9", ] @@ -8420,6 +8809,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.16" @@ -8491,17 +8890,6 @@ dependencies = [ "opaque-debug", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha1" version = "0.10.6" @@ -8686,7 +9074,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" dependencies = [ - "async-channel", + "async-channel 1.9.0", "futures-core", "futures-io", ] @@ -8697,19 +9085,55 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "smithay-client-toolkit" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +dependencies = [ + "bitflags 2.4.2", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2 0.9.4", + "rustix 0.38.30", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c091e7354ea8059d6ad99eace06dd13ddeedbb0ac72d40a9a6e7ff790525882d" +dependencies = [ + "libc", + "smithay-client-toolkit", + "wayland-backend", +] + [[package]] name = "smol" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13f2b548cd8447f8de0fdf1c592929f70f4fc7039a05e47404b0d096ec6987a1" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", "async-fs 1.6.0", "async-io 1.13.0", "async-lock 2.8.0", "async-net 1.7.0", - "async-process", + "async-process 1.7.0", "blocking", "futures-lite 1.13.0", ] @@ -9143,6 +9567,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "subst" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca1318e5d6716d6541696727c88d9b8dfc8cfe6afd6908e186546fd4af7f5b98" +dependencies = [ + "memchr", + "unicode-width", +] + [[package]] name = "subtle" version = "2.5.0" @@ -9374,6 +9808,7 @@ dependencies = [ "schemars", "serde", "serde_json_lenient", + "subst", "util", ] @@ -9392,6 +9827,8 @@ dependencies = [ "serde", "serde_json", "task", + "tree-sitter-rust", + "tree-sitter-typescript", "ui", "util", "workspace", @@ -9461,6 +9898,7 @@ dependencies = [ "theme", "thiserror", "util", + "windows 0.53.0", ] [[package]] @@ -9740,6 +10178,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.4", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] @@ -9754,6 +10193,16 @@ dependencies = [ "log", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.1.0" @@ -9798,14 +10247,14 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.17.2" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", "tokio", - "tungstenite 0.17.3", + "tungstenite 0.20.1", ] [[package]] @@ -9864,6 +10313,17 @@ dependencies = [ "winnow 0.5.15", ] +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.0.0", + "toml_datetime", + "winnow 0.5.15", +] + [[package]] name = "toml_edit" version = "0.22.6" @@ -9877,6 +10337,33 @@ dependencies = [ "winnow 0.6.1", ] +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.4", + "bytes 1.5.0", + "h2", + "http 0.2.9", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.12.3", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.4.13" @@ -9885,9 +10372,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap 1.9.3", "pin-project", "pin-project-lite", + "rand 0.8.5", + "slab", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -9907,7 +10398,6 @@ dependencies = [ "http-body", "http-range-header", "pin-project-lite", - "tower", "tower-layer", "tower-service", ] @@ -10508,7 +10998,7 @@ dependencies = [ "log", "native-tls", "rand 0.8.5", - "sha-1 0.9.8", + "sha-1", "thiserror", "url", "utf-8", @@ -10516,18 +11006,18 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.1", "byteorder", "bytes 1.5.0", + "data-encoding", "http 0.2.9", "httparse", "log", "rand 0.8.5", - "sha-1 0.10.1", + "sha1", "thiserror", "url", "utf-8", @@ -10953,9 +11443,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi-common" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "082a661fe31df4dbb34409f4835ad3d8ba65036bf74aaec9b21fde779978aba7" +checksum = "880c1461417b2bf90262591bf8a5f04358fb86dac8a585a49b87024971296763" dependencies = [ "anyhow", "bitflags 2.4.2", @@ -11054,9 +11544,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.200.0" +version = "0.201.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e3fb0c8fbddd78aa6095b850dfeedbc7506cf5f81e633f69cf8f2333ab84b9" +checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" dependencies = [ "leb128", ] @@ -11100,9 +11590,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06f80b13fdeba0ea5267813d0f06af822309f7125fc8db6094bcd485f0a4ae7" +checksum = "4c843b8bc4dd4f3a76173ba93405c71111d570af0d90ea5f6299c705d0c2add2" dependencies = [ "addr2line", "anyhow", @@ -11144,18 +11634,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d7395b475c6f858c7edfce375f00d8282a32fbf5d1ebc93eddfac5c2458a52" +checksum = "86b9d329c718b3a18412a6a017c912b539baa8fe1210d21b651f6b4dbafed743" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "wasmtime-c-api-impl" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c09ac0c18464f8ef0b554c12defc94e3fc082b62309a3da229de60d47cf75a" +checksum = "cc93587c24d8e3cb28912eb7abf95f7e350380656faccc46cff04c0821ec58c2" dependencies = [ "anyhow", "log", @@ -11167,9 +11657,9 @@ dependencies = [ [[package]] name = "wasmtime-c-api-macros" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864c4a337294fe690f02b39f2b3f45414447d9321d0ed24d3dc7696bf291e789" +checksum = "2e571a71eba52dfe81ef653a3a336888141f00fc2208a9962722e036fe2a34be" dependencies = [ "proc-macro2", "quote", @@ -11177,9 +11667,9 @@ dependencies = [ [[package]] name = "wasmtime-cache" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a78f86b27f099bea3aaa0894464e22e84a08cadf3d8cd353378d3d15385535" +checksum = "6fb4fc2bbf9c790a57875eba65588fa97acf57a7d784dc86d057e648d9a1ed91" dependencies = [ "anyhow", "base64 0.21.4", @@ -11197,9 +11687,9 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e54483c542e304e17fa73d3f9263bf071e21915c8f048c7d42916da5b4bfd6" +checksum = "d8d55ddfd02898885c39638eae9631cd430c83a368f5996ed0f7bfb181d02157" dependencies = [ "anyhow", "proc-macro2", @@ -11212,15 +11702,15 @@ dependencies = [ [[package]] name = "wasmtime-component-util" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9f72619f484df95fc03162cdef9cb98778abc4103811849501bb34e79a3aac" +checksum = "1d6d69c430cddc70ec42159506962c66983ce0192ebde4eb125b7aabc49cff88" [[package]] name = "wasmtime-cranelift" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974d9455611e26c97d31705e19545de58fa8867416592bd93b7a54a7fc37cedb" +checksum = "31ca62f519225492bd555d0ec85a2dacb0c10315db3418c8b9aeb3824bf54a24" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -11243,9 +11733,9 @@ dependencies = [ [[package]] name = "wasmtime-cranelift-shared" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40667ba458634db703aea3bd960e80bc9352c21d5e765b69f43e3b0c964eb611" +checksum = "fd5f2071f42e61490bf7cb95b9acdbe6a29dd577a398019304a960585f28b844" dependencies = [ "anyhow", "cranelift-codegen", @@ -11259,9 +11749,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8da991421528c2767053cb0cfa70b5d28279100dbcf70ed7f74b51abe1656ef" +checksum = "82bf1a47f384610da19f58b0fd392ca6a3b720974315c08afb0392c0f3951fed" dependencies = [ "anyhow", "bincode", @@ -11285,9 +11775,9 @@ dependencies = [ [[package]] name = "wasmtime-fiber" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fdd780272515bfcdf316e2efe20231719ec40223d67fcdd7d17068a16d39384" +checksum = "0e31aecada2831e067ebfe93faa3001cc153d506f8af40bbea58aa1d20fe4820" dependencies = [ "anyhow", "cc", @@ -11300,9 +11790,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87be9ed561dbe2aca3bde30d442c292fda53748343d0220873d1df65270c8fcf" +checksum = "833dae95bc7a4f9177bf93f9497419763535b74e37eb8c37be53937d3281e287" dependencies = [ "object", "once_cell", @@ -11312,9 +11802,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3346431a41fbb0c5af0081c2322361b00289f2902e54ee7b115e9b2ad32b156b" +checksum = "33f4121cb29dda08139b2824a734dd095d83ce843f2d613a84eb580b9cfc17ac" dependencies = [ "cfg-if 1.0.0", "libc", @@ -11323,9 +11813,9 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a489353aa297b46a66cde8da48cab8e1e967e7f4b0ae3d9889a0550bf274810b" +checksum = "4e517f2b996bb3b0e34a82a2bce194f850d9bcfc25c08328ef5fb71b071066b8" dependencies = [ "anyhow", "cc", @@ -11353,9 +11843,9 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c56e31fd7fa707fbd7720b2b29ac42ccfb092fe9d85c98f1d3988f9a1d4558" +checksum = "54a327d7a0ef57bd52a507d28b4561a74126c7a8535a2fc6f2025716bc6a52e8" dependencies = [ "cranelift-entity", "serde", @@ -11366,9 +11856,9 @@ dependencies = [ [[package]] name = "wasmtime-versioned-export-macros" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0300976c36a9427d184e3ecf7c121c2cb3f030844faf9fcb767821e9d4c382" +checksum = "8ef32eea9fc7035a55159a679d1e89b43ece5ae45d24eed4808e6a92c99a0da4" dependencies = [ "proc-macro2", "quote", @@ -11377,9 +11867,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f7d9cfaf9f70e83a164f5d772e376fafa2d7b7b0ca2ef88f9bcaf8b2363a38b" +checksum = "d04d2fb2257245aa05ff799ded40520ae4d8cd31b0d14972afac89061f12fe12" dependencies = [ "anyhow", "async-trait", @@ -11410,9 +11900,9 @@ dependencies = [ [[package]] name = "wasmtime-winch" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f773a904d2bd5ecd8ad095f4c965ad56a836929d8c26368621f75328d500649" +checksum = "db3378c0e808a744b5d4df2a9a9d2746a53b151811926731f04fc401707f7d54" dependencies = [ "anyhow", "cranelift-codegen", @@ -11427,9 +11917,9 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e9754e0a526238ea66da9ba21965a54846a2b22d9de89a298fb8998389507" +checksum = "ca677c36869e45602617b25a9968ec0d895ad9a0aee3756d9dee1ddd89456f91" dependencies = [ "anyhow", "heck 0.4.1", @@ -11439,9 +11929,9 @@ dependencies = [ [[package]] name = "wasmtime-wmemcheck" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdf5b8da6ebf7549dad0cd32ca4a3a0461449ef4feec9d0d8450d8da9f51f9b" +checksum = "7f4cbfb052d66f03603a9b77f18171ea245c7805714caad370a549a6344bf86b" [[package]] name = "wast" @@ -11454,24 +11944,24 @@ dependencies = [ [[package]] name = "wast" -version = "200.0.0" +version = "201.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1810d14e6b03ebb8fb05eef4009ad5749c989b65197d83bce7de7172ed91366" +checksum = "1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.200.0", + "wasm-encoder 0.201.0", ] [[package]] name = "wat" -version = "1.200.0" +version = "1.201.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "776cbd10e217f83869beaa3f40e312bb9e91d5eee29bbf6f560db1261b6a4c3d" +checksum = "453d5b37a45b98dee4f4cb68015fc73634d7883bbef1c65e6e9c78d454cf3f32" dependencies = [ - "wast 200.0.0", + "wast 201.0.0", ] [[package]] @@ -11500,6 +11990,28 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.4.2", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" +dependencies = [ + "rustix 0.38.30", + "wayland-client", + "xcursor", +] + [[package]] name = "wayland-protocols" version = "0.31.2" @@ -11512,6 +12024,19 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.4.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-scanner" version = "0.31.1" @@ -11531,6 +12056,7 @@ checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" dependencies = [ "dlib", "log", + "once_cell", "pkg-config", ] @@ -11565,6 +12091,7 @@ version = "0.1.0" dependencies = [ "anyhow", "client", + "copilot_ui", "db", "editor", "fuzzy", @@ -11615,9 +12142,9 @@ checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" [[package]] name = "wiggle" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "454570f4fecadb881f0ba157e98b575a2850607a9eac79d8868f3ab70633f632" +checksum = "b69812e493f8a43d8551abfaaf9539e1aff0cf56a58cdd276845fc4af035d0cd" dependencies = [ "anyhow", "async-trait", @@ -11630,9 +12157,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443ac1ebb753ca22bca98d01742762de1243ff722839907c35ea683a8264c74e" +checksum = "0446357a5a7af0172848b6eca7b2aa1ab7d90065cd2ab02b633a322e1a52f636" dependencies = [ "anyhow", "heck 0.4.1", @@ -11645,9 +12172,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "18.0.1" +version = "18.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9e2f1f06ae07bac15273774782c04ab14e9adfbf414762fc84dbbfcf7fb1ac" +checksum = "9498ef53a12cf25dc6de9baef6ccd8b58d159202c412a19f4d72b218393086c5" dependencies = [ "proc-macro2", "quote", @@ -11700,9 +12227,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f7eaac56988f986181099c15860946fea93ed826322a1f92c4ff04541b7744" +checksum = "8197ed4a2ebf612f0624ddda10de71f8cd2d3a4ecf8ffac0586a264599708d63" dependencies = [ "anyhow", "cranelift-codegen", @@ -11732,6 +12259,35 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +dependencies = [ + "windows-core", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-core" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +dependencies = [ + "windows-result", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-result" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -11756,7 +12312,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -11791,17 +12347,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -11818,9 +12374,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -11836,9 +12392,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -11854,9 +12410,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -11872,9 +12428,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -11890,9 +12446,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -11908,9 +12464,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -11926,9 +12482,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" @@ -12147,6 +12703,33 @@ dependencies = [ "tap", ] +[[package]] +name = "x11-clipboard" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98785a09322d7446e28a13203d2cae1059a0dd3dfb32cb06d0a225f023d8286" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "gethostname", + "rustix 0.38.30", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + [[package]] name = "xattr" version = "0.2.3" @@ -12168,6 +12751,12 @@ dependencies = [ "quick-xml 0.30.0", ] +[[package]] +name = "xcursor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" + [[package]] name = "xdg-home" version = "1.1.0" @@ -12208,6 +12797,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "xtask" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.4.4", +] + [[package]] name = "yansi" version = "0.5.1" @@ -12234,16 +12831,16 @@ dependencies = [ [[package]] name = "zbus" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c45d06ae3b0f9ba1fb2671268b975557d8f5a84bb5ec6e43964f87e763d8bca8" +checksum = "5acecd3f8422f198b1a2f954bcc812fe89f3fa4281646f3da1da7925db80085d" dependencies = [ "async-broadcast 0.5.1", "async-executor", "async-fs 1.6.0", "async-io 1.13.0", "async-lock 2.8.0", - "async-process", + "async-process 1.7.0", "async-recursion 1.0.5", "async-task", "async-trait", @@ -12268,16 +12865,69 @@ dependencies = [ "uds_windows", "winapi 0.3.9", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 3.15.1", + "zbus_names 2.6.1", + "zvariant 3.15.1", +] + +[[package]] +name = "zbus" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b8e3d6ae3342792a6cc2340e4394334c7402f3d793b390d2c5494a4032b3030" +dependencies = [ + "async-broadcast 0.7.0", + "async-executor", + "async-fs 2.1.1", + "async-io 2.3.1", + "async-lock 3.3.0", + "async-process 2.1.0", + "async-recursion 1.0.5", + "async-task", + "async-trait", + "blocking", + "derivative", + "enumflags2", + "event-listener 5.1.0", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.27.1", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.0.1", + "zbus_names 3.0.0", + "zvariant 4.0.2", ] [[package]] name = "zbus_macros" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a1ba45ed0ad344b85a2bb5a1fe9830aed23d67812ea39a586e7d0136439c7d" +checksum = "2207eb71efebda17221a579ca78b45c4c5f116f074eb745c3a172e688ccf89f5" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zbus_macros" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3e850ff1e7217a3b7a07eba90d37fe9bb9e89a310f718afcde5885ca9b6d7" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -12289,13 +12939,24 @@ dependencies = [ [[package]] name = "zbus_names" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 3.15.1", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant 4.0.2", ] [[package]] @@ -12480,9 +13141,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +checksum = "c5b4fcf3660d30fc33ae5cd97e2017b23a96e85afd7a1dd014534cd0bf34ba67" dependencies = [ "byteorder", "enumflags2", @@ -12490,14 +13151,27 @@ dependencies = [ "serde", "static_assertions", "url", - "zvariant_derive", + "zvariant_derive 3.15.1", +] + +[[package]] +name = "zvariant" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1b3ca6db667bfada0f1ebfc94b2b1759ba25472ee5373d4551bb892616389a" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive 4.0.2", ] [[package]] name = "zvariant_derive" -version = "3.15.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +checksum = "0277758a8a0afc0e573e80ed5bfd9d9c2b48bd3108ffe09384f9f738c83f4a55" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -12507,10 +13181,23 @@ dependencies = [ ] [[package]] -name = "zvariant_utils" -version = "1.0.1" +name = "zvariant_derive" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +checksum = "b7a4b236063316163b69039f77ce3117accb41a09567fd24c168e43491e521bc" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 1.0.109", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00bedb16a193cc12451873fee2a1bc6550225acece0e36f333e68326c73c8172" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index b39dfbdad5..68ce41c644 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ members = [ "crates/zed", "crates/zed_actions", "extensions/gleam", + "tooling/xtask", ] default-members = ["crates/zed"] resolver = "2" @@ -196,10 +197,11 @@ async-compression = { version = "0.4", features = ["gzip", "futures-io"] } async-tar = "0.4.2" async-trait = "0.1" bitflags = "2.4.2" -blade-graphics = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" } -blade-macros = { git = "https://github.com/kvark/blade", rev = "e9d93a4d41f3946a03ffb76136290d6ccf7f2b80" } +blade-graphics = { git = "https://github.com/kvark/blade", rev = "43721bf42d298b7cbee2195ee66f73a5f1c7b2fc" } +blade-macros = { git = "https://github.com/kvark/blade", rev = "43721bf42d298b7cbee2195ee66f73a5f1c7b2fc" } blade-rwh = { package = "raw-window-handle", version = "0.5" } chrono = { version = "0.4", features = ["serde"] } +clap = "4.4" clickhouse = { version = "0.11.6" } ctor = "0.2.6" core-foundation = { version = "0.9.3" } @@ -307,6 +309,19 @@ wasmtime-wasi = "18.0" which = "6.0.0" sys-locale = "0.3.1" +[workspace.dependencies.windows] +version = "0.53.0" +features = [ + "Win32_Graphics_Gdi", + "Win32_UI_WindowsAndMessaging", + "Win32_UI_Input_KeyboardAndMouse", + "Win32_System_SystemServices", + "Win32_Security", + "Win32_System_Threading", + "Win32_System_DataExchange", + "Win32_System_Ole", +] + [patch.crates-io] tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "e4a23971ec3071a09c1e84816954c98f96e98e52" } # Workaround for a broken nightly build of gpui: See #7644 and revisit once 0.5.3 is released. @@ -324,6 +339,8 @@ debug = "full" [profile.dev.package] taffy = { opt-level = 3 } cranelift-codegen = { opt-level = 3 } +rustybuzz = { opt-level = 3 } +ttf-parser = { opt-level = 3 } wasmtime-cranelift = { opt-level = 3 } [profile.release] @@ -331,5 +348,42 @@ debug = "limited" lto = "thin" codegen-units = 1 +[workspace.lints.clippy] +dbg_macro = "deny" +todo = "deny" + +# These are all of the rules that currently have violations in the Zed +# codebase. +# +# We'll want to drive this list down by either: +# 1. fixing violations of the rule and begin enforcing it +# 2. deciding we want to allow the rule permanently, at which point +# we should codify that separately above. +# +# This list shouldn't be added to; it should only get shorter. +# ============================================================================= + +# There are a bunch of rules currently failing in the `style` group, so +# allow all of those, for now. +style = "allow" + +# Individual rules that have violations in the codebase: +almost_complete_range = "allow" +arc_with_non_send_sync = "allow" +await_holding_lock = "allow" +borrowed_box = "allow" +cast_abs_to_unsigned = "allow" +derive_ord_xor_partial_ord = "allow" +eq_op = "allow" +let_underscore_future = "allow" +map_entry = "allow" +never_loop = "allow" +non_canonical_clone_impl = "allow" +non_canonical_partial_ord_impl = "allow" +reversed_empty_ranges = "allow" +single_range_in_vec_init = "allow" +suspicious_to_owned = "allow" +type_complexity = "allow" + [workspace.metadata.cargo-machete] ignored = ["bindgen", "cbindgen", "prost_build", "serde"] diff --git a/assets/keymaps/default-linux.json b/assets/keymaps/default-linux.json index 865d064884..29e3d19d78 100644 --- a/assets/keymaps/default-linux.json +++ b/assets/keymaps/default-linux.json @@ -135,10 +135,21 @@ "focus": true } ], - "alt-\\": "copilot::Suggest", + "ctrl->": "assistant::QuoteSelection" + } + }, + { + "context": "Editor && mode == full && copilot_suggestion", + "bindings": { "alt-]": "copilot::NextSuggestion", "alt-[": "copilot::PreviousSuggestion", - "ctrl->": "assistant::QuoteSelection" + "alt-right": "editor::AcceptPartialCopilotSuggestion" + } + }, + { + "context": "Editor && !copilot_suggestion", + "bindings": { + "alt-\\": "copilot::Suggest" } }, { @@ -354,6 +365,7 @@ "ctrl-alt-b": "branches::OpenRecent", "ctrl-~": "workspace::NewTerminal", "ctrl-s": "workspace::Save", + "ctrl-k s": "workspace::SaveWithoutFormat", "ctrl-shift-s": "workspace::SaveAs", "ctrl-n": "workspace::NewFile", "ctrl-shift-n": "workspace::NewWindow", diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index b6008fde8c..13a411c1da 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -176,10 +176,21 @@ "focus": false } ], - "alt-\\": "copilot::Suggest", + "cmd->": "assistant::QuoteSelection" + } + }, + { + "context": "Editor && mode == full && copilot_suggestion", + "bindings": { "alt-]": "copilot::NextSuggestion", "alt-[": "copilot::PreviousSuggestion", - "cmd->": "assistant::QuoteSelection" + "alt-right": "editor::AcceptPartialCopilotSuggestion" + } + }, + { + "context": "Editor && !copilot_suggestion", + "bindings": { + "alt-\\": "copilot::Suggest" } }, { @@ -397,6 +408,7 @@ "alt-cmd-b": "branches::OpenRecent", "ctrl-~": "workspace::NewTerminal", "cmd-s": "workspace::Save", + "cmd-k s": "workspace::SaveWithoutFormat", "cmd-shift-s": "workspace::SaveAs", "cmd-n": "workspace::NewFile", "cmd-shift-n": "workspace::NewWindow", @@ -415,8 +427,8 @@ "cmd-j": "workspace::ToggleBottomDock", "alt-cmd-y": "workspace::CloseAllDocks", "cmd-shift-f": "pane::DeploySearch", - "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", + "cmd-k cmd-t": "theme_selector::Toggle", "cmd-t": "project_symbols::Toggle", "cmd-p": "file_finder::Toggle", "cmd-shift-p": "command_palette::Toggle", diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index bfaa444a68..c68f209917 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -218,6 +218,7 @@ // z commands "z t": "editor::ScrollCursorTop", "z z": "editor::ScrollCursorCenter", + "z .": ["workspace::SendKeystrokes", "z z ^"], "z b": "editor::ScrollCursorBottom", "z c": "editor::Fold", "z o": "editor::UnfoldLines", @@ -392,6 +393,7 @@ ], "t": "vim::Tag", "s": "vim::Sentence", + "p": "vim::Paragraph", "'": "vim::Quotes", "`": "vim::BackQuotes", "\"": "vim::DoubleQuotes", diff --git a/crates/activity_indicator/Cargo.toml b/crates/activity_indicator/Cargo.toml index 1513377e7d..ff0ae5c6a1 100644 --- a/crates/activity_indicator/Cargo.toml +++ b/crates/activity_indicator/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/activity_indicator.rs" doctest = false diff --git a/crates/ai/Cargo.toml b/crates/ai/Cargo.toml index 726c7329dc..69c3b88e62 100644 --- a/crates/ai/Cargo.toml +++ b/crates/ai/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/ai.rs" doctest = false diff --git a/crates/ai/src/embedding.rs b/crates/ai/src/embedding.rs index 6768b7ce7b..49611e002a 100644 --- a/crates/ai/src/embedding.rs +++ b/crates/ai/src/embedding.rs @@ -19,11 +19,9 @@ pub struct Embedding(pub Vec); impl FromSql for Embedding { fn column_result(value: ValueRef) -> FromSqlResult { let bytes = value.as_blob()?; - let embedding: Result, Box> = bincode::deserialize(bytes); - if embedding.is_err() { - return Err(rusqlite::types::FromSqlError::Other(embedding.unwrap_err())); - } - Ok(Embedding(embedding.unwrap())) + let embedding = + bincode::deserialize(bytes).map_err(|err| rusqlite::types::FromSqlError::Other(err))?; + Ok(Embedding(embedding)) } } @@ -112,7 +110,7 @@ mod tests { } fn round_to_decimals(n: OrderedFloat, decimal_places: i32) -> f32 { - let factor = (10.0 as f32).powi(decimal_places); + let factor = 10.0_f32.powi(decimal_places); (n * factor).round() / factor } diff --git a/crates/ai/src/prompts/base.rs b/crates/ai/src/prompts/base.rs index 5e624f23ac..529f775ae8 100644 --- a/crates/ai/src/prompts/base.rs +++ b/crates/ai/src/prompts/base.rs @@ -30,7 +30,7 @@ impl PromptArguments { if self .language_name .as_ref() - .and_then(|name| Some(!["Markdown", "Plain Text"].contains(&name.as_str()))) + .map(|name| !["Markdown", "Plain Text"].contains(&name.as_str())) .unwrap_or(true) { PromptFileType::Code @@ -51,8 +51,10 @@ pub trait PromptTemplate { #[repr(i8)] #[derive(PartialEq, Eq, Ord)] pub enum PromptPriority { - Mandatory, // Ignores truncation - Ordered { order: usize }, // Truncates based on priority + /// Ignores truncation. + Mandatory, + /// Truncates based on priority. + Ordered { order: usize }, } impl PartialOrd for PromptPriority { @@ -86,7 +88,6 @@ impl PromptChain { let mut sorted_indices = (0..self.templates.len()).collect::>(); sorted_indices.sort_by_key(|&i| Reverse(&self.templates[i].0)); - // If Truncate let mut tokens_outstanding = if truncate { Some(self.args.model.capacity()? - self.args.reserved_tokens) } else { diff --git a/crates/ai/src/prompts/repository_context.rs b/crates/ai/src/prompts/repository_context.rs index 89869c53a0..b31a3f63c2 100644 --- a/crates/ai/src/prompts/repository_context.rs +++ b/crates/ai/src/prompts/repository_context.rs @@ -24,11 +24,9 @@ impl PromptCodeSnippet { let language_name = buffer .language() - .and_then(|language| Some(language.name().to_string().to_lowercase())); + .map(|language| language.name().to_string().to_lowercase()); - let file_path = buffer - .file() - .and_then(|file| Some(file.path().to_path_buf())); + let file_path = buffer.file().map(|file| file.path().to_path_buf()); (content, language_name, file_path) })?; @@ -46,7 +44,7 @@ impl ToString for PromptCodeSnippet { let path = self .path .as_ref() - .and_then(|path| Some(path.to_string_lossy().to_string())) + .map(|path| path.to_string_lossy().to_string()) .unwrap_or("".to_string()); let language_name = self.language_name.clone().unwrap_or("".to_string()); let content = self.content.clone(); @@ -67,7 +65,7 @@ impl PromptTemplate for RepositoryContext { let template = "You are working inside a large repository, here are a few code snippets that may be useful."; let mut prompt = String::new(); - let mut remaining_tokens = max_token_length.clone(); + let mut remaining_tokens = max_token_length; let separator_token_length = args.model.count_tokens("\n")?; for snippet in &args.snippets { let mut snippet_prompt = template.to_string(); diff --git a/crates/ai/src/test.rs b/crates/ai/src/test.rs index 89edc71b0b..f10ca4f5fa 100644 --- a/crates/ai/src/test.rs +++ b/crates/ai/src/test.rs @@ -54,6 +54,7 @@ impl LanguageModel for FakeLanguageModel { } } +#[derive(Default)] pub struct FakeEmbeddingProvider { pub embedding_count: AtomicUsize, } @@ -66,14 +67,6 @@ impl Clone for FakeEmbeddingProvider { } } -impl Default for FakeEmbeddingProvider { - fn default() -> Self { - FakeEmbeddingProvider { - embedding_count: AtomicUsize::default(), - } - } -} - impl FakeEmbeddingProvider { pub fn embedding_count(&self) -> usize { self.embedding_count.load(atomic::Ordering::SeqCst) diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml index 19eef955dc..8fcb1f9cfe 100644 --- a/crates/assets/Cargo.toml +++ b/crates/assets/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] anyhow.workspace = true gpui.workspace = true diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 3f24babef6..d84075a632 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/assistant.rs" doctest = false diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index d5bc08b7bf..28623ea8b1 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -652,7 +652,7 @@ impl AssistantPanel { // If Markdown or No Language is Known, increase the randomness for more creative output // If Code, decrease temperature to get more deterministic outputs let temperature = if let Some(language) = language_name.clone() { - if language.to_string() != "Markdown".to_string() { + if *language != *"Markdown" { 0.5 } else { 1.0 @@ -979,7 +979,7 @@ impl AssistantPanel { font_size: rems(0.875).into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.3).into(), + line_height: relative(1.3), background_color: None, underline: None, strikethrough: None, @@ -1483,7 +1483,7 @@ impl Conversation { max_token_count: tiktoken_rs::model::get_context_size(&model.full_name()), pending_token_count: Task::ready(None), api_url: Some(api_url), - model: model.clone(), + model, _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)], pending_save: Task::ready(Ok(())), path: None, @@ -1527,7 +1527,7 @@ impl Conversation { .as_ref() .map(|summary| summary.text.clone()) .unwrap_or_default(), - model: self.model.clone(), + model: self.model, api_url: self.api_url.clone(), } } @@ -1633,26 +1633,23 @@ impl Conversation { fn count_remaining_tokens(&mut self, cx: &mut ModelContext) { let messages = self .messages(cx) - .into_iter() - .filter_map(|message| { - Some(tiktoken_rs::ChatCompletionRequestMessage { - role: match message.role { - Role::User => "user".into(), - Role::Assistant => "assistant".into(), - Role::System => "system".into(), - }, - content: Some( - self.buffer - .read(cx) - .text_for_range(message.offset_range) - .collect(), - ), - name: None, - function_call: None, - }) + .map(|message| tiktoken_rs::ChatCompletionRequestMessage { + role: match message.role { + Role::User => "user".into(), + Role::Assistant => "assistant".into(), + Role::System => "system".into(), + }, + content: Some( + self.buffer + .read(cx) + .text_for_range(message.offset_range) + .collect(), + ), + name: None, + function_call: None, }) .collect::>(); - let model = self.model.clone(); + let model = self.model; self.pending_token_count = cx.spawn(|this, mut cx| { async move { cx.background_executor() @@ -2835,6 +2832,7 @@ impl FocusableView for InlineAssistant { } impl InlineAssistant { + #[allow(clippy::too_many_arguments)] fn new( id: usize, measurements: Rc>, @@ -3200,7 +3198,7 @@ impl InlineAssistant { font_size: rems(0.875).into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.3).into(), + line_height: relative(1.3), background_color: None, underline: None, strikethrough: None, diff --git a/crates/assistant/src/assistant_settings.rs b/crates/assistant/src/assistant_settings.rs index 4b8f3feb85..007e994389 100644 --- a/crates/assistant/src/assistant_settings.rs +++ b/crates/assistant/src/assistant_settings.rs @@ -111,9 +111,23 @@ impl AssistantSettings { AiProviderSettings::OpenAi(settings) => { Ok(settings.default_model.unwrap_or(OpenAiModel::FourTurbo)) } - AiProviderSettings::AzureOpenAi(_settings) => { - // TODO: We need to use an Azure OpenAI model here. - Ok(OpenAiModel::FourTurbo) + AiProviderSettings::AzureOpenAi(settings) => { + let deployment_id = settings + .deployment_id + .as_deref() + .ok_or_else(|| anyhow!("no Azure OpenAI deployment ID"))?; + + match deployment_id { + // https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#gpt-4-and-gpt-4-turbo-preview + "gpt-4" | "gpt-4-32k" => Ok(OpenAiModel::Four), + // https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models#gpt-35 + "gpt-35-turbo" | "gpt-35-turbo-16k" | "gpt-35-turbo-instruct" => { + Ok(OpenAiModel::ThreePointFiveTurbo) + } + _ => Err(anyhow!( + "no matching OpenAI model found for deployment ID: '{deployment_id}'" + )), + } } } } diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index c1a663d7ef..04d08a3315 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -297,7 +297,7 @@ fn strip_invalid_spans_from_codeblock( } else if buffer.starts_with("<|") || buffer.starts_with("<|S") || buffer.starts_with("<|S|") - || buffer.ends_with("|") + || buffer.ends_with('|') || buffer.ends_with("|E") || buffer.ends_with("|E|") { @@ -335,7 +335,7 @@ fn strip_invalid_spans_from_codeblock( .strip_suffix("|E|>") .or_else(|| text.strip_suffix("E|>")) .or_else(|| text.strip_prefix("|>")) - .or_else(|| text.strip_prefix(">")) + .or_else(|| text.strip_prefix('>')) .unwrap_or(&text) .to_string(); }; diff --git a/crates/audio/Cargo.toml b/crates/audio/Cargo.toml index d66df11f9f..bfe22de1f0 100644 --- a/crates/audio/Cargo.toml +++ b/crates/audio/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/audio.rs" doctest = false diff --git a/crates/auto_update/Cargo.toml b/crates/auto_update/Cargo.toml index 8135d5b795..6d4de08d04 100644 --- a/crates/auto_update/Cargo.toml +++ b/crates/auto_update/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/auto_update.rs" doctest = false diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 23ca550516..f364304d59 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -343,8 +343,7 @@ impl AutoUpdater { )); cx.update(|cx| { if let Some(param) = ReleaseChannel::try_global(cx) - .map(|release_channel| release_channel.release_query_param()) - .flatten() + .and_then(|release_channel| release_channel.release_query_param()) { url_string += "&"; url_string += param; diff --git a/crates/breadcrumbs/Cargo.toml b/crates/breadcrumbs/Cargo.toml index 24f3a70fd3..45d0f09f66 100644 --- a/crates/breadcrumbs/Cargo.toml +++ b/crates/breadcrumbs/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/breadcrumbs.rs" doctest = false diff --git a/crates/call/Cargo.toml b/crates/call/Cargo.toml index a0acf544b5..569026d006 100644 --- a/crates/call/Cargo.toml +++ b/crates/call/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/call.rs" doctest = false diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 8c9ff71ba4..f0a0a22fb3 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -302,7 +302,7 @@ impl ActiveCall { return Task::ready(Ok(())); } - let room_id = call.room_id.clone(); + let room_id = call.room_id; let client = self.client.clone(); let user_store = self.user_store.clone(); let join = self diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 3e524f5b3e..5599c15b6d 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -1182,19 +1182,10 @@ impl Room { ) -> Task>> { let client = self.client.clone(); let user_store = self.user_store.clone(); - let role = self.local_participant.role; cx.emit(Event::RemoteProjectJoined { project_id: id }); cx.spawn(move |this, mut cx| async move { - let project = Project::remote( - id, - client, - user_store, - language_registry, - fs, - role, - cx.clone(), - ) - .await?; + let project = + Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?; this.update(&mut cx, |this, cx| { this.joined_projects.retain(|project| { diff --git a/crates/channel/Cargo.toml b/crates/channel/Cargo.toml index ccd690059f..49132a4b7b 100644 --- a/crates/channel/Cargo.toml +++ b/crates/channel/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/channel.rs" doctest = false diff --git a/crates/channel/src/channel.rs b/crates/channel/src/channel.rs index 1dbf502252..aee92d0f6c 100644 --- a/crates/channel/src/channel.rs +++ b/crates/channel/src/channel.rs @@ -11,7 +11,7 @@ pub use channel_chat::{ mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, MessageParams, }; -pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore, HostedProjectId}; +pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore}; #[cfg(test)] mod channel_store_tests; diff --git a/crates/channel/src/channel_buffer.rs b/crates/channel/src/channel_buffer.rs index 8e63e347f1..c2115a7cab 100644 --- a/crates/channel/src/channel_buffer.rs +++ b/crates/channel/src/channel_buffer.rs @@ -126,7 +126,7 @@ impl ChannelBuffer { for (_, old_collaborator) in &self.collaborators { if !new_collaborators.contains_key(&old_collaborator.peer_id) { self.buffer.update(cx, |buffer, cx| { - buffer.remove_peer(old_collaborator.replica_id as u16, cx) + buffer.remove_peer(old_collaborator.replica_id, cx) }); } } diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index fa86779892..fdd8a123bc 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -759,7 +759,7 @@ pub fn mentions_to_proto(mentions: &[(Range, UserId)]) -> Vec, user_store: Model, cx: &mut AppCont cx.set_global(GlobalChannelStore(channel_store)); } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] -pub struct HostedProjectId(pub u64); - #[derive(Debug, Clone, Default)] struct NotesVersion { epoch: u64, @@ -93,16 +92,17 @@ pub struct ChannelState { } impl Channel { - pub fn link(&self) -> String { - RELEASE_CHANNEL.link_prefix().to_owned() - + "channel/" - + &Self::slug(&self.name) - + "-" - + &self.id.to_string() + pub fn link(&self, cx: &AppContext) -> String { + format!( + "{}/channel/{}-{}", + ClientSettings::get_global(cx).server_url, + Self::slug(&self.name), + self.id + ) } - pub fn notes_link(&self, heading: Option) -> String { - self.link() + pub fn notes_link(&self, heading: Option, cx: &AppContext) -> String { + self.link(cx) + "/notes" + &heading .map(|h| format!("#{}", Self::slug(&h))) @@ -592,7 +592,7 @@ impl ChannelStore { cx: &mut ModelContext, ) -> Task> { let client = self.client.clone(); - let name = name.trim_start_matches("#").to_owned(); + let name = name.trim_start_matches('#').to_owned(); cx.spawn(move |this, mut cx| async move { let response = client .request(proto::CreateChannel { @@ -839,12 +839,10 @@ impl ChannelStore { Ok(users .into_iter() .zip(response.members) - .filter_map(|(user, member)| { - Some(ChannelMembership { - user, - role: member.role(), - kind: member.kind(), - }) + .map(|(user, member)| ChannelMembership { + user, + role: member.role(), + kind: member.kind(), }) .collect()) }) diff --git a/crates/channel/src/channel_store/channel_index.rs b/crates/channel/src/channel_store/channel_index.rs index fc753f7444..02a8cd333b 100644 --- a/crates/channel/src/channel_store/channel_index.rs +++ b/crates/channel/src/channel_store/channel_index.rs @@ -97,9 +97,9 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> { } } -fn channel_path_sorting_key<'a>( +fn channel_path_sorting_key( id: ChannelId, - channels_by_id: &'a BTreeMap>, + channels_by_id: &BTreeMap>, ) -> impl Iterator { let (parent_path, name) = channels_by_id .get(&id) diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index f1b2875670..6316c64bdc 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/cli.rs" doctest = false @@ -15,6 +18,7 @@ path = "src/main.rs" [dependencies] anyhow.workspace = true +# TODO: Use workspace version of `clap`. clap = { version = "3.1", features = ["derive"] } ipc-channel = "0.16" serde.workspace = true diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 4dc8c38ef4..2b45efec85 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/client.rs" doctest = false diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 57d59fa041..754a47baa4 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -27,7 +27,7 @@ use release_channel::{AppVersion, ReleaseChannel}; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_json; + use settings::{Settings, SettingsStore}; use std::{ any::TypeId, @@ -61,7 +61,7 @@ lazy_static! { pub static ref ZED_APP_PATH: Option = std::env::var("ZED_APP_PATH").ok().map(PathBuf::from); pub static ref ZED_ALWAYS_ACTIVE: bool = - std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| e.len() > 0); + std::env::var("ZED_ALWAYS_ACTIVE").map_or(false, |e| !e.is_empty()); } pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100); @@ -427,7 +427,7 @@ impl Client { http: Arc, cx: &mut AppContext, ) -> Arc { - let client = Arc::new(Self { + Arc::new(Self { id: AtomicU64::new(0), peer: Peer::new(0), telemetry: Telemetry::new(clock, http.clone(), cx), @@ -438,9 +438,7 @@ impl Client { authenticate: Default::default(), #[cfg(any(test, feature = "test-support"))] establish_connection: Default::default(), - }); - - client + }) } pub fn id(&self) -> u64 { @@ -573,17 +571,18 @@ impl Client { let mut state = self.state.write(); if state.entities_by_type_and_remote_id.contains_key(&id) { return Err(anyhow!("already subscribed to entity")); - } else { - state - .entities_by_type_and_remote_id - .insert(id, WeakSubscriber::Pending(Default::default())); - Ok(PendingEntitySubscription { - client: self.clone(), - remote_id, - consumed: false, - _entity_type: PhantomData, - }) } + + state + .entities_by_type_and_remote_id + .insert(id, WeakSubscriber::Pending(Default::default())); + + Ok(PendingEntitySubscription { + client: self.clone(), + remote_id, + consumed: false, + _entity_type: PhantomData, + }) } #[track_caller] @@ -926,7 +925,7 @@ impl Client { move |cx| async move { match handle_io.await { Ok(()) => { - if this.status().borrow().clone() + if *this.status().borrow() == (Status::Connected { connection_id, peer_id, @@ -1335,7 +1334,7 @@ impl Client { pending.push(message); return; } - Some(weak_subscriber @ _) => match weak_subscriber { + Some(weak_subscriber) => match weak_subscriber { WeakSubscriber::Entity { handle } => { subscriber = handle.upgrade(); } @@ -1438,21 +1437,29 @@ async fn delete_credentials_from_keychain(cx: &AsyncAppContext) -> Result<()> { .await } -const WORKTREE_URL_PREFIX: &str = "zed://worktrees/"; +/// prefix for the zed:// url scheme +pub static ZED_URL_SCHEME: &str = "zed"; -pub fn encode_worktree_url(id: u64, access_token: &str) -> String { - format!("{}{}/{}", WORKTREE_URL_PREFIX, id, access_token) -} - -pub fn decode_worktree_url(url: &str) -> Option<(u64, String)> { - let path = url.trim().strip_prefix(WORKTREE_URL_PREFIX)?; - let mut parts = path.split('/'); - let id = parts.next()?.parse::().ok()?; - let access_token = parts.next()?; - if access_token.is_empty() { - return None; +/// Parses the given link into a Zed link. +/// +/// Returns a [`Some`] containing the unprefixed link if the link is a Zed link. +/// Returns [`None`] otherwise. +pub fn parse_zed_link<'a>(link: &'a str, cx: &AppContext) -> Option<&'a str> { + let server_url = &ClientSettings::get_global(cx).server_url; + if let Some(stripped) = link + .strip_prefix(server_url) + .and_then(|result| result.strip_prefix('/')) + { + return Some(stripped); } - Some((id, access_token.to_string())) + if let Some(stripped) = link + .strip_prefix(ZED_URL_SCHEME) + .and_then(|result| result.strip_prefix("://")) + { + return Some(stripped); + } + + None } #[cfg(test)] @@ -1630,17 +1637,6 @@ mod tests { assert_eq!(*dropped_auth_count.lock(), 1); } - #[test] - fn test_encode_and_decode_worktree_url() { - let url = encode_worktree_url(5, "deadbeef"); - assert_eq!(decode_worktree_url(&url), Some((5, "deadbeef".to_string()))); - assert_eq!( - decode_worktree_url(&format!("\n {}\t", url)), - Some((5, "deadbeef".to_string())) - ); - assert_eq!(decode_worktree_url("not://the-right-format"), None); - } - #[gpui::test] async fn test_subscribing_to_entity(cx: &mut TestAppContext) { init_test(cx); diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 946e5da407..475da1658c 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -84,7 +84,7 @@ impl Telemetry { TelemetrySettings::register(cx); let state = Arc::new(Mutex::new(TelemetryState { - settings: TelemetrySettings::get_global(cx).clone(), + settings: *TelemetrySettings::get_global(cx), app_metadata: cx.app_metadata(), architecture: env::consts::ARCH, release_channel, @@ -119,7 +119,7 @@ impl Telemetry { move |cx| { let mut state = state.lock(); - state.settings = TelemetrySettings::get_global(cx).clone(); + state.settings = *TelemetrySettings::get_global(cx); } }) .detach(); @@ -168,7 +168,7 @@ impl Telemetry { ) { let mut state = self.state.lock(); state.installation_id = installation_id.map(|id| id.into()); - state.session_id = Some(session_id.into()); + state.session_id = Some(session_id); drop(state); let this = self.clone(); @@ -387,11 +387,9 @@ impl Telemetry { event, }); - if state.installation_id.is_some() { - if state.events_queue.len() >= state.max_queue_size { - drop(state); - self.flush_events(); - } + if state.installation_id.is_some() && state.events_queue.len() >= state.max_queue_size { + drop(state); + self.flush_events(); } } @@ -433,7 +431,7 @@ impl Telemetry { json_bytes.clear(); serde_json::to_writer(&mut json_bytes, event)?; file.write_all(&json_bytes)?; - file.write(b"\n")?; + file.write_all(b"\n")?; } } @@ -442,7 +440,7 @@ impl Telemetry { let request_body = EventRequestBody { installation_id: state.installation_id.as_deref().map(Into::into), session_id: state.session_id.clone(), - is_staff: state.is_staff.clone(), + is_staff: state.is_staff, app_version: state .app_metadata .app_version diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 7a45ddb8f9..0c10e50ac5 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -24,6 +24,9 @@ impl std::fmt::Display for ChannelId { } } +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct HostedProjectId(pub u64); + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ParticipantIndex(pub u32); @@ -653,7 +656,7 @@ impl UserStore { let users = response .users .into_iter() - .map(|user| User::new(user)) + .map(User::new) .collect::>(); this.update(&mut cx, |this, _| { diff --git a/crates/clock/Cargo.toml b/crates/clock/Cargo.toml index adef338cc7..d1fb21747b 100644 --- a/crates/clock/Cargo.toml +++ b/crates/clock/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/clock.rs" doctest = false diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 7cd499513a..c32bb53237 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -7,6 +7,9 @@ version = "0.44.0" publish = false license = "AGPL-3.0-or-later" +[lints] +workspace = true + [[bin]] name = "collab" @@ -14,12 +17,13 @@ name = "collab" name = "seed" [dependencies] +console-subscriber = "0.2" anyhow.workspace = true async-tungstenite = "0.16" aws-config = { version = "1.1.5" } aws-sdk-s3 = { version = "1.15.0" } -axum = { version = "0.5", features = ["json", "headers", "ws"] } -axum-extra = { version = "0.3", features = ["erased-json"] } +axum = { version = "0.6", features = ["json", "headers", "ws"] } +axum-extra = { version = "0.4", features = ["erased-json"] } chrono.workspace = true clock.workspace = true clickhouse.workspace = true @@ -28,7 +32,6 @@ dashmap = "5.4" envy = "0.4.2" futures.workspace = true hex.workspace = true -hyper = "0.14" live_kit_server.workspace = true log.workspace = true nanoid = "0.4" @@ -55,8 +58,7 @@ toml.workspace = true tower = "0.4" tower-http = { workspace = true, features = ["trace"] } tracing = "0.1.34" -tracing-log = "0.1.3" -tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] } +tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json", "registry", "tracing-log"] } util.workspace = true uuid.workspace = true diff --git a/crates/collab/k8s/collab.template.yml b/crates/collab/k8s/collab.template.yml index 4915c6c97c..528479105b 100644 --- a/crates/collab/k8s/collab.template.yml +++ b/crates/collab/k8s/collab.template.yml @@ -11,6 +11,8 @@ metadata: namespace: ${ZED_KUBE_NAMESPACE} name: ${ZED_SERVICE_NAME} annotations: + service.beta.kubernetes.io/do-loadbalancer-name: "${ZED_SERVICE_NAME}-${ZED_KUBE_NAMESPACE}" + service.beta.kubernetes.io/do-loadbalancer-size-unit: "${ZED_LOAD_BALANCER_SIZE_UNIT}" service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443" service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID} service.beta.kubernetes.io/do-loadbalancer-disable-lets-encrypt-dns-records: "true" @@ -33,6 +35,11 @@ metadata: spec: replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 selector: matchLabels: app: ${ZED_SERVICE_NAME} @@ -63,6 +70,8 @@ spec: ports: - containerPort: 8080 protocol: TCP + - containerPort: 6669 + protocol: TCP livenessProbe: httpGet: path: /healthz @@ -76,6 +85,13 @@ spec: port: 8080 initialDelaySeconds: 1 periodSeconds: 1 + startupProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 1 + periodSeconds: 1 + failureThreshold: 15 env: - name: HTTP_PORT value: "8080" @@ -176,3 +192,4 @@ spec: # FIXME - Switch to the more restrictive `PERFMON` capability. # This capability isn't yet available in a stable version of Debian. add: ["SYS_ADMIN"] + terminationGracePeriodSeconds: 10 diff --git a/crates/collab/k8s/postgrest.template.yml b/crates/collab/k8s/postgrest.template.yml index f03c0ce33c..ff83880a95 100644 --- a/crates/collab/k8s/postgrest.template.yml +++ b/crates/collab/k8s/postgrest.template.yml @@ -5,6 +5,7 @@ metadata: namespace: ${ZED_KUBE_NAMESPACE} name: postgrest annotations: + service.beta.kubernetes.io/do-loadbalancer-name: "postgrest-${ZED_KUBE_NAMESPACE}" service.beta.kubernetes.io/do-loadbalancer-tls-ports: "443" service.beta.kubernetes.io/do-loadbalancer-certificate-id: ${ZED_DO_CERTIFICATE_ID} service.beta.kubernetes.io/do-loadbalancer-disable-lets-encrypt-dns-records: "true" diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index ce6b55c202..338521d05e 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -46,10 +46,11 @@ CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id"); CREATE TABLE "projects" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT, "room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE NOT NULL, - "host_user_id" INTEGER REFERENCES users (id) NOT NULL, + "host_user_id" INTEGER REFERENCES users (id), "host_connection_id" INTEGER, "host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE, - "unregistered" BOOLEAN NOT NULL DEFAULT FALSE + "unregistered" BOOLEAN NOT NULL DEFAULT FALSE, + "hosted_project_id" INTEGER REFERENCES hosted_projects (id) ); CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id"); CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id"); diff --git a/crates/collab/migrations/20240227215556_hosted_projects_in_projects.sql b/crates/collab/migrations/20240227215556_hosted_projects_in_projects.sql new file mode 100644 index 0000000000..69905d12f6 --- /dev/null +++ b/crates/collab/migrations/20240227215556_hosted_projects_in_projects.sql @@ -0,0 +1,3 @@ +-- Add migration script here +ALTER TABLE projects ALTER COLUMN host_user_id DROP NOT NULL; +ALTER TABLE projects ADD COLUMN hosted_project_id INTEGER REFERENCES hosted_projects(id) UNIQUE NULL; diff --git a/crates/collab/src/api.rs b/crates/collab/src/api.rs index 2d42beccc8..15c07838ae 100644 --- a/crates/collab/src/api.rs +++ b/crates/collab/src/api.rs @@ -11,7 +11,7 @@ use crate::{ use anyhow::anyhow; use axum::{ body::Body, - extract::{Path, Query}, + extract::{self, Path, Query}, http::{self, Request, StatusCode}, middleware::{self, Next}, response::IntoResponse, @@ -26,7 +26,7 @@ use tower::ServiceBuilder; pub use extensions::fetch_extensions_from_blob_store_periodically; -pub fn routes(rpc_server: Option>, state: Arc) -> Router { +pub fn routes(rpc_server: Option>, state: Arc) -> Router<(), Body> { Router::new() .route("/user", get(get_authenticated_user)) .route("/users/:id/access_tokens", post(create_access_token)) @@ -176,17 +176,16 @@ async fn check_is_contributor( } async fn add_contributor( - Json(params): Json, Extension(app): Extension>, + extract::Json(params): extract::Json, ) -> Result<()> { - Ok(app - .db + app.db .add_contributor( ¶ms.github_login, params.github_user_id, params.github_email.as_deref(), ) - .await?) + .await } #[derive(Deserialize)] diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index f15383232a..102609f2d9 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -3,9 +3,12 @@ use std::sync::{Arc, OnceLock}; use anyhow::{anyhow, Context}; use aws_sdk_s3::primitives::ByteStream; use axum::{ - body::Bytes, headers::Header, http::HeaderName, routing::post, Extension, Router, TypedHeader, + body::Bytes, + headers::Header, + http::{HeaderMap, HeaderName, StatusCode}, + routing::post, + Extension, Router, TypedHeader, }; -use hyper::{HeaderMap, StatusCode}; use serde::{Serialize, Serializer}; use sha2::{Digest, Sha256}; use telemetry_events::{ @@ -81,8 +84,8 @@ impl Header for CloudflareIpCountryHeader { pub async fn post_crash( Extension(app): Extension>, - body: Bytes, headers: HeaderMap, + body: Bytes, ) -> Result<()> { static CRASH_REPORTS_BUCKET: &str = "zed-crash-reports"; @@ -355,40 +358,68 @@ struct ToUpload { impl ToUpload { pub async fn upload(&self, clickhouse_client: &clickhouse::Client) -> anyhow::Result<()> { - Self::upload_to_table("editor_events", &self.editor_events, clickhouse_client) + const EDITOR_EVENTS_TABLE: &str = "editor_events"; + Self::upload_to_table(EDITOR_EVENTS_TABLE, &self.editor_events, clickhouse_client) .await - .with_context(|| format!("failed to upload to table 'editor_events'"))?; - Self::upload_to_table("copilot_events", &self.copilot_events, clickhouse_client) - .await - .with_context(|| format!("failed to upload to table 'copilot_events'"))?; + .with_context(|| format!("failed to upload to table '{EDITOR_EVENTS_TABLE}'"))?; + + const COPILOT_EVENTS_TABLE: &str = "copilot_events"; Self::upload_to_table( - "assistant_events", + COPILOT_EVENTS_TABLE, + &self.copilot_events, + clickhouse_client, + ) + .await + .with_context(|| format!("failed to upload to table '{COPILOT_EVENTS_TABLE}'"))?; + + const ASSISTANT_EVENTS_TABLE: &str = "assistant_events"; + Self::upload_to_table( + ASSISTANT_EVENTS_TABLE, &self.assistant_events, clickhouse_client, ) .await - .with_context(|| format!("failed to upload to table 'assistant_events'"))?; - Self::upload_to_table("call_events", &self.call_events, clickhouse_client) + .with_context(|| format!("failed to upload to table '{ASSISTANT_EVENTS_TABLE}'"))?; + + const CALL_EVENTS_TABLE: &str = "call_events"; + Self::upload_to_table(CALL_EVENTS_TABLE, &self.call_events, clickhouse_client) .await - .with_context(|| format!("failed to upload to table 'call_events'"))?; - Self::upload_to_table("cpu_events", &self.cpu_events, clickhouse_client) + .with_context(|| format!("failed to upload to table '{CALL_EVENTS_TABLE}'"))?; + + const CPU_EVENTS_TABLE: &str = "cpu_events"; + Self::upload_to_table(CPU_EVENTS_TABLE, &self.cpu_events, clickhouse_client) .await - .with_context(|| format!("failed to upload to table 'cpu_events'"))?; - Self::upload_to_table("memory_events", &self.memory_events, clickhouse_client) + .with_context(|| format!("failed to upload to table '{CPU_EVENTS_TABLE}'"))?; + + const MEMORY_EVENTS_TABLE: &str = "memory_events"; + Self::upload_to_table(MEMORY_EVENTS_TABLE, &self.memory_events, clickhouse_client) .await - .with_context(|| format!("failed to upload to table 'memory_events'"))?; - Self::upload_to_table("app_events", &self.app_events, clickhouse_client) + .with_context(|| format!("failed to upload to table '{MEMORY_EVENTS_TABLE}'"))?; + + const APP_EVENTS_TABLE: &str = "app_events"; + Self::upload_to_table(APP_EVENTS_TABLE, &self.app_events, clickhouse_client) .await - .with_context(|| format!("failed to upload to table 'app_events'"))?; - Self::upload_to_table("setting_events", &self.setting_events, clickhouse_client) + .with_context(|| format!("failed to upload to table '{APP_EVENTS_TABLE}'"))?; + + const SETTING_EVENTS_TABLE: &str = "setting_events"; + Self::upload_to_table( + SETTING_EVENTS_TABLE, + &self.setting_events, + clickhouse_client, + ) + .await + .with_context(|| format!("failed to upload to table '{SETTING_EVENTS_TABLE}'"))?; + + const EDIT_EVENTS_TABLE: &str = "edit_events"; + Self::upload_to_table(EDIT_EVENTS_TABLE, &self.edit_events, clickhouse_client) .await - .with_context(|| format!("failed to upload to table 'setting_events'"))?; - Self::upload_to_table("edit_events", &self.edit_events, clickhouse_client) + .with_context(|| format!("failed to upload to table '{EDIT_EVENTS_TABLE}'"))?; + + const ACTION_EVENTS_TABLE: &str = "action_events"; + Self::upload_to_table(ACTION_EVENTS_TABLE, &self.action_events, clickhouse_client) .await - .with_context(|| format!("failed to upload to table 'edit_events'"))?; - Self::upload_to_table("action_events", &self.action_events, clickhouse_client) - .await - .with_context(|| format!("failed to upload to table 'action_events'"))?; + .with_context(|| format!("failed to upload to table '{ACTION_EVENTS_TABLE}'"))?; + Ok(()) } diff --git a/crates/collab/src/api/extensions.rs b/crates/collab/src/api/extensions.rs index c276e219d1..34b5ae0d9f 100644 --- a/crates/collab/src/api/extensions.rs +++ b/crates/collab/src/api/extensions.rs @@ -7,12 +7,12 @@ use anyhow::{anyhow, Context as _}; use aws_sdk_s3::presigning::PresigningConfig; use axum::{ extract::{Path, Query}, + http::StatusCode, response::Redirect, routing::get, Extension, Json, Router, }; use collections::HashMap; -use hyper::StatusCode; use serde::{Deserialize, Serialize}; use std::{sync::Arc, time::Duration}; use time::PrimitiveDateTime; diff --git a/crates/collab/src/api/ips_file.rs b/crates/collab/src/api/ips_file.rs index 6f1b4cd8f4..cbbf042abf 100644 --- a/crates/collab/src/api/ips_file.rs +++ b/crates/collab/src/api/ips_file.rs @@ -45,7 +45,7 @@ impl IpsFile { pub fn description(&self, panic: Option<&str>) -> String { let mut desc = if self.body.termination.indicator == "Abort trap: 6" { match panic { - Some(panic_message) => format!("Panic `{}`", panic_message).into(), + Some(panic_message) => format!("Panic `{}`", panic_message), None => "Crash `Abort trap: 6` (possible panic)".into(), } } else if let Some(msg) = &self.body.exception.message { diff --git a/crates/collab/src/bin/seed.rs b/crates/collab/src/bin/seed.rs index 632116a363..8a998d0bf3 100644 --- a/crates/collab/src/bin/seed.rs +++ b/crates/collab/src/bin/seed.rs @@ -88,9 +88,9 @@ async fn fetch_github(client: &reqwest::Client, url: &str) .header("user-agent", "zed") .send() .await - .expect(&format!("failed to fetch '{}'", url)); + .unwrap_or_else(|_| panic!("failed to fetch '{}'", url)); response .json() .await - .expect(&format!("failed to deserialize github user from '{}'", url)) + .unwrap_or_else(|_| panic!("failed to deserialize github user from '{}'", url)) } diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 20385b3dc8..caf8a83d50 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -359,7 +359,7 @@ impl Database { const SLEEPS: [f32; 10] = [10., 20., 40., 80., 160., 320., 640., 1280., 2560., 5120.]; if is_serialization_error(error) && prev_attempt_count < SLEEPS.len() { let base_delay = SLEEPS[prev_attempt_count]; - let randomized_delay = base_delay as f32 * self.rng.lock().await.gen_range(0.5..=2.0); + let randomized_delay = base_delay * self.rng.lock().await.gen_range(0.5..=2.0); log::info!( "retrying transaction after serialization error. delay: {} ms.", randomized_delay @@ -670,6 +670,8 @@ pub struct RefreshedChannelBuffer { } pub struct Project { + pub id: ProjectId, + pub role: ChannelRole, pub collaborators: Vec, pub worktrees: BTreeMap, pub language_servers: Vec, @@ -695,7 +697,7 @@ impl ProjectCollaborator { #[derive(Debug)] pub struct LeftProject { pub id: ProjectId, - pub host_user_id: UserId, + pub host_user_id: Option, pub host_connection_id: Option, pub connection_ids: Vec, } diff --git a/crates/collab/src/db/queries/buffers.rs b/crates/collab/src/db/queries/buffers.rs index e141ea8865..eddde98fd9 100644 --- a/crates/collab/src/db/queries/buffers.rs +++ b/crates/collab/src/db/queries/buffers.rs @@ -18,7 +18,7 @@ impl Database { connection: ConnectionId, ) -> Result { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; self.check_user_is_channel_participant(&channel, user_id, &tx) .await?; @@ -134,10 +134,10 @@ impl Database { let mut results = Vec::new(); for client_buffer in buffers { let channel = self - .get_channel_internal(ChannelId::from_proto(client_buffer.channel_id), &*tx) + .get_channel_internal(ChannelId::from_proto(client_buffer.channel_id), &tx) .await?; if self - .check_user_is_channel_participant(&channel, user_id, &*tx) + .check_user_is_channel_participant(&channel, user_id, &tx) .await .is_err() { @@ -145,7 +145,7 @@ impl Database { continue; } - let buffer = self.get_channel_buffer(channel.id, &*tx).await?; + let buffer = self.get_channel_buffer(channel.id, &tx).await?; let mut collaborators = channel_buffer_collaborator::Entity::find() .filter(channel_buffer_collaborator::Column::ChannelId.eq(channel.id)) .all(&*tx) @@ -180,7 +180,7 @@ impl Database { let client_version = version_from_wire(&client_buffer.version); let serialization_version = self - .get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx) + .get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &tx) .await?; let mut rows = buffer_operation::Entity::find() @@ -283,7 +283,7 @@ impl Database { connection: ConnectionId, ) -> Result { self.transaction(|tx| async move { - self.leave_channel_buffer_internal(channel_id, connection, &*tx) + self.leave_channel_buffer_internal(channel_id, connection, &tx) .await }) .await @@ -308,7 +308,7 @@ impl Database { connection_lost: ActiveValue::set(true), ..Default::default() }) - .exec(&*tx) + .exec(tx) .await?; Ok(()) } @@ -337,7 +337,7 @@ impl Database { let mut result = Vec::new(); for channel_id in channel_ids { let left_channel_buffer = self - .leave_channel_buffer_internal(channel_id, connection, &*tx) + .leave_channel_buffer_internal(channel_id, connection, &tx) .await?; result.push(left_channel_buffer); } @@ -363,7 +363,7 @@ impl Database { .eq(connection.owner_id as i32), ), ) - .exec(&*tx) + .exec(tx) .await?; if result.rows_affected == 0 { Err(anyhow!("not a collaborator on this project"))?; @@ -375,7 +375,7 @@ impl Database { .filter( Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)), ) - .stream(&*tx) + .stream(tx) .await?; while let Some(row) = rows.next().await { let row = row?; @@ -406,7 +406,7 @@ impl Database { channel_id: ChannelId, ) -> Result> { self.transaction(|tx| async move { - self.get_channel_buffer_collaborators_internal(channel_id, &*tx) + self.get_channel_buffer_collaborators_internal(channel_id, &tx) .await }) .await @@ -429,7 +429,7 @@ impl Database { Condition::all().add(channel_buffer_collaborator::Column::ChannelId.eq(channel_id)), ) .into_values::<_, QueryUserIds>() - .all(&*tx) + .all(tx) .await?; Ok(users) @@ -447,7 +447,7 @@ impl Database { Vec, )> { self.transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; let mut requires_write_permission = false; for op in operations.iter() { @@ -457,10 +457,10 @@ impl Database { } } if requires_write_permission { - self.check_user_is_channel_member(&channel, user, &*tx) + self.check_user_is_channel_member(&channel, user, &tx) .await?; } else { - self.check_user_is_channel_participant(&channel, user, &*tx) + self.check_user_is_channel_participant(&channel, user, &tx) .await?; } @@ -471,7 +471,7 @@ impl Database { .ok_or_else(|| anyhow!("no such buffer"))?; let serialization_version = self - .get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &*tx) + .get_buffer_operation_serialization_version(buffer.id, buffer.epoch, &tx) .await?; let operations = operations @@ -500,13 +500,13 @@ impl Database { buffer.epoch, *max_operation.replica_id.as_ref(), *max_operation.lamport_timestamp.as_ref(), - &*tx, + &tx, ) .await?; - channel_members = self.get_channel_participants(&channel, &*tx).await?; + channel_members = self.get_channel_participants(&channel, &tx).await?; let collaborators = self - .get_channel_buffer_collaborators_internal(channel_id, &*tx) + .get_channel_buffer_collaborators_internal(channel_id, &tx) .await?; channel_members.retain(|member| !collaborators.contains(member)); @@ -602,7 +602,7 @@ impl Database { .select_only() .column(buffer_snapshot::Column::OperationSerializationVersion) .into_values::<_, QueryOperationSerializationVersion>() - .one(&*tx) + .one(tx) .await? .ok_or_else(|| anyhow!("missing buffer snapshot"))?) } @@ -617,7 +617,7 @@ impl Database { ..Default::default() } .find_related(buffer::Entity) - .one(&*tx) + .one(tx) .await? .ok_or_else(|| anyhow!("no such buffer"))?) } @@ -639,7 +639,7 @@ impl Database { .eq(id) .and(buffer_snapshot::Column::Epoch.eq(buffer.epoch)), ) - .one(&*tx) + .one(tx) .await? .ok_or_else(|| anyhow!("no such snapshot"))?; @@ -657,7 +657,7 @@ impl Database { ) .order_by_asc(buffer_operation::Column::LamportTimestamp) .order_by_asc(buffer_operation::Column::ReplicaId) - .stream(&*tx) + .stream(tx) .await?; let mut operations = Vec::new(); @@ -737,7 +737,7 @@ impl Database { epoch, component.replica_id as i32, component.timestamp as i32, - &*tx, + &tx, ) .await?; Ok(()) @@ -751,7 +751,7 @@ impl Database { tx: &DatabaseTransaction, ) -> Result> { let latest_operations = self - .get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), &*tx) + .get_latest_operations_for_buffers(channel_ids_by_buffer_id.keys().copied(), tx) .await?; Ok(latest_operations @@ -781,7 +781,7 @@ impl Database { observed_buffer_edits::Column::BufferId .is_in(channel_ids_by_buffer_id.keys().copied()), ) - .all(&*tx) + .all(tx) .await?; Ok(observed_operations @@ -844,7 +844,7 @@ impl Database { let stmt = Statement::from_string(self.pool.get_database_backend(), sql); Ok(buffer_operation::Entity::find() .from_raw_sql(stmt) - .all(&*tx) + .all(tx) .await?) } } diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 7a9034e8d0..2d622a94d8 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -53,8 +53,8 @@ impl Database { let mut membership = None; if let Some(parent_channel_id) = parent_channel_id { - let parent_channel = self.get_channel_internal(parent_channel_id, &*tx).await?; - self.check_user_is_channel_admin(&parent_channel, admin_id, &*tx) + let parent_channel = self.get_channel_internal(parent_channel_id, &tx).await?; + self.check_user_is_channel_admin(&parent_channel, admin_id, &tx) .await?; parent = Some(parent_channel); } @@ -105,14 +105,14 @@ impl Database { connection: ConnectionId, ) -> Result<(JoinRoom, Option, ChannelRole)> { self.transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - let mut role = self.channel_role_for_user(&channel, user_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; + let mut role = self.channel_role_for_user(&channel, user_id, &tx).await?; let mut accept_invite_result = None; if role.is_none() { if let Some(invitation) = self - .pending_invite_for_channel(&channel, user_id, &*tx) + .pending_invite_for_channel(&channel, user_id, &tx) .await? { // note, this may be a parent channel @@ -125,12 +125,12 @@ impl Database { .await?; accept_invite_result = Some( - self.calculate_membership_updated(&channel, user_id, &*tx) + self.calculate_membership_updated(&channel, user_id, &tx) .await?, ); debug_assert!( - self.channel_role_for_user(&channel, user_id, &*tx).await? == role + self.channel_role_for_user(&channel, user_id, &tx).await? == role ); } else if channel.visibility == ChannelVisibility::Public { role = Some(ChannelRole::Guest); @@ -145,12 +145,12 @@ impl Database { .await?; accept_invite_result = Some( - self.calculate_membership_updated(&channel, user_id, &*tx) + self.calculate_membership_updated(&channel, user_id, &tx) .await?, ); debug_assert!( - self.channel_role_for_user(&channel, user_id, &*tx).await? == role + self.channel_role_for_user(&channel, user_id, &tx).await? == role ); } } @@ -162,10 +162,10 @@ impl Database { let live_kit_room = format!("channel-{}", nanoid::nanoid!(30)); let room_id = self - .get_or_create_channel_room(channel_id, &live_kit_room, &*tx) + .get_or_create_channel_room(channel_id, &live_kit_room, &tx) .await?; - self.join_channel_room_internal(room_id, user_id, connection, role, &*tx) + self.join_channel_room_internal(room_id, user_id, connection, role, &tx) .await .map(|jr| (jr, accept_invite_result, role)) }) @@ -180,13 +180,13 @@ impl Database { admin_id: UserId, ) -> Result<(Channel, Vec)> { self.transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_admin(&channel, admin_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_admin(&channel, admin_id, &tx) .await?; if visibility == ChannelVisibility::Public { if let Some(parent_id) = channel.parent_id() { - let parent = self.get_channel_internal(parent_id, &*tx).await?; + let parent = self.get_channel_internal(parent_id, &tx).await?; if parent.visibility != ChannelVisibility::Public { Err(ErrorCode::BadPublicNesting @@ -196,7 +196,7 @@ impl Database { } } else if visibility == ChannelVisibility::Members { if self - .get_channel_descendants_excluding_self([&channel], &*tx) + .get_channel_descendants_excluding_self([&channel], &tx) .await? .into_iter() .any(|channel| channel.visibility == ChannelVisibility::Public) @@ -228,7 +228,7 @@ impl Database { requires_zed_cla: bool, ) -> Result<()> { self.transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; let mut model = channel.into_active_model(); model.requires_zed_cla = ActiveValue::Set(requires_zed_cla); model.update(&*tx).await?; @@ -244,8 +244,8 @@ impl Database { user_id: UserId, ) -> Result<(Vec, Vec)> { self.transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_admin(&channel, user_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_admin(&channel, user_id, &tx) .await?; let members_to_notify: Vec = channel_member::Entity::find() @@ -258,7 +258,7 @@ impl Database { .await?; let channels_to_remove = self - .get_channel_descendants_excluding_self([&channel], &*tx) + .get_channel_descendants_excluding_self([&channel], &tx) .await? .into_iter() .map(|channel| channel.id) @@ -284,8 +284,8 @@ impl Database { role: ChannelRole, ) -> Result { self.transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_admin(&channel, inviter_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_admin(&channel, inviter_id, &tx) .await?; if !channel.is_root() { Err(ErrorCode::NotARootChannel.anyhow())? @@ -312,7 +312,7 @@ impl Database { inviter_id: inviter_id.to_proto(), }, true, - &*tx, + &tx, ) .await? .into_iter() @@ -344,8 +344,8 @@ impl Database { self.transaction(move |tx| async move { let new_name = Self::sanitize_channel_name(new_name)?.to_string(); - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_admin(&channel, admin_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_admin(&channel, admin_id, &tx) .await?; let mut model = channel.into_active_model(); @@ -370,7 +370,7 @@ impl Database { accept: bool, ) -> Result { self.transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; let membership_update = if accept { let rows_affected = channel_member::Entity::update_many() @@ -393,7 +393,7 @@ impl Database { } Some( - self.calculate_membership_updated(&channel, user_id, &*tx) + self.calculate_membership_updated(&channel, user_id, &tx) .await?, ) } else { @@ -425,7 +425,7 @@ impl Database { inviter_id: Default::default(), }, accept, - &*tx, + &tx, ) .await? .into_iter() @@ -441,9 +441,9 @@ impl Database { user_id: UserId, tx: &DatabaseTransaction, ) -> Result { - let new_channels = self.get_user_channels(user_id, Some(channel), &*tx).await?; + let new_channels = self.get_user_channels(user_id, Some(channel), tx).await?; let removed_channels = self - .get_channel_descendants_excluding_self([channel], &*tx) + .get_channel_descendants_excluding_self([channel], tx) .await? .into_iter() .map(|channel| channel.id) @@ -466,10 +466,10 @@ impl Database { admin_id: UserId, ) -> Result { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; if member_id != admin_id { - self.check_user_is_channel_admin(&channel, admin_id, &*tx) + self.check_user_is_channel_admin(&channel, admin_id, &tx) .await?; } @@ -488,7 +488,7 @@ impl Database { Ok(RemoveChannelMemberResult { membership_update: self - .calculate_membership_updated(&channel, member_id, &*tx) + .calculate_membership_updated(&channel, member_id, &tx) .await?, notification_id: self .remove_notification( @@ -498,7 +498,7 @@ impl Database { channel_name: Default::default(), inviter_id: Default::default(), }, - &*tx, + &tx, ) .await?, }) @@ -529,10 +529,7 @@ impl Database { .all(&*tx) .await?; - let channels = channels - .into_iter() - .filter_map(|channel| Some(Channel::from_model(channel))) - .collect(); + let channels = channels.into_iter().map(Channel::from_model).collect(); Ok(channels) }) @@ -567,16 +564,16 @@ impl Database { let channel_memberships = channel_member::Entity::find() .filter(filter) - .all(&*tx) + .all(tx) .await?; let channels = channel::Entity::find() .filter(channel::Column::Id.is_in(channel_memberships.iter().map(|m| m.channel_id))) - .all(&*tx) + .all(tx) .await?; let mut descendants = self - .get_channel_descendants_excluding_self(channels.iter(), &*tx) + .get_channel_descendants_excluding_self(channels.iter(), tx) .await?; for channel in channels { @@ -617,7 +614,7 @@ impl Database { .column(room::Column::ChannelId) .column(room_participant::Column::UserId) .into_values::<_, QueryUserIdsAndChannelIds>() - .stream(&*tx) + .stream(tx) .await?; while let Some(row) = rows.next().await { let row: (ChannelId, UserId) = row?; @@ -630,7 +627,7 @@ impl Database { let mut channel_ids_by_buffer_id = HashMap::default(); let mut rows = buffer::Entity::find() .filter(buffer::Column::ChannelId.is_in(channel_ids.iter().copied())) - .stream(&*tx) + .stream(tx) .await?; while let Some(row) = rows.next().await { let row = row?; @@ -639,21 +636,21 @@ impl Database { drop(rows); let latest_buffer_versions = self - .latest_channel_buffer_changes(&channel_ids_by_buffer_id, &*tx) + .latest_channel_buffer_changes(&channel_ids_by_buffer_id, tx) .await?; - let latest_channel_messages = self.latest_channel_messages(&channel_ids, &*tx).await?; + let latest_channel_messages = self.latest_channel_messages(&channel_ids, tx).await?; let observed_buffer_versions = self - .observed_channel_buffer_changes(&channel_ids_by_buffer_id, user_id, &*tx) + .observed_channel_buffer_changes(&channel_ids_by_buffer_id, user_id, tx) .await?; let observed_channel_messages = self - .observed_channel_messages(&channel_ids, user_id, &*tx) + .observed_channel_messages(&channel_ids, user_id, tx) .await?; let hosted_projects = self - .get_hosted_projects(&channel_ids, &roles_by_channel_id, &*tx) + .get_hosted_projects(&channel_ids, &roles_by_channel_id, tx) .await?; Ok(ChannelsForUser { @@ -677,8 +674,8 @@ impl Database { role: ChannelRole, ) -> Result { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_admin(&channel, admin_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_admin(&channel, admin_id, &tx) .await?; let membership = channel_member::Entity::find() @@ -700,7 +697,7 @@ impl Database { if updated.accepted { Ok(SetMemberRoleResult::MembershipUpdated( - self.calculate_membership_updated(&channel, for_user, &*tx) + self.calculate_membership_updated(&channel, for_user, &tx) .await?, )) } else { @@ -720,13 +717,13 @@ impl Database { ) -> Result> { let (role, members) = self .transaction(move |tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; let role = self - .check_user_is_channel_participant(&channel, user_id, &*tx) + .check_user_is_channel_participant(&channel, user_id, &tx) .await?; Ok(( role, - self.get_channel_participant_details_internal(&channel, &*tx) + self.get_channel_participant_details_internal(&channel, &tx) .await?, )) }) @@ -781,7 +778,7 @@ impl Database { tx: &DatabaseTransaction, ) -> Result> { let participants = self - .get_channel_participant_details_internal(channel, &*tx) + .get_channel_participant_details_internal(channel, tx) .await?; Ok(participants .into_iter() @@ -858,7 +855,7 @@ impl Database { .filter(channel_member::Column::ChannelId.eq(channel.root_id())) .filter(channel_member::Column::UserId.eq(user_id)) .filter(channel_member::Column::Accepted.eq(false)) - .one(&*tx) + .one(tx) .await?; Ok(row) @@ -878,7 +875,7 @@ impl Database { .and(channel_member::Column::UserId.eq(user_id)) .and(channel_member::Column::Accepted.eq(true)), ) - .one(&*tx) + .one(tx) .await?; let Some(membership) = membership else { @@ -918,8 +915,8 @@ impl Database { /// Returns the channel with the given ID. pub async fn get_channel(&self, channel_id: ChannelId, user_id: UserId) -> Result { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_participant(&channel, user_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_participant(&channel, user_id, &tx) .await?; Ok(Channel::from_model(channel)) @@ -933,7 +930,7 @@ impl Database { tx: &DatabaseTransaction, ) -> Result { Ok(channel::Entity::find_by_id(channel_id) - .one(&*tx) + .one(tx) .await? .ok_or_else(|| proto::ErrorCode::NoSuchChannel.anyhow())?) } @@ -946,7 +943,7 @@ impl Database { ) -> Result { let room = room::Entity::find() .filter(room::Column::ChannelId.eq(channel_id)) - .one(&*tx) + .one(tx) .await?; let room_id = if let Some(room) = room { @@ -957,7 +954,7 @@ impl Database { live_kit_room: ActiveValue::Set(live_kit_room.to_string()), ..Default::default() }) - .exec(&*tx) + .exec(tx) .await?; result.last_insert_id @@ -974,10 +971,10 @@ impl Database { admin_id: UserId, ) -> Result<(Vec, Vec)> { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_admin(&channel, admin_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_admin(&channel, admin_id, &tx) .await?; - let new_parent = self.get_channel_internal(new_parent_id, &*tx).await?; + let new_parent = self.get_channel_internal(new_parent_id, &tx).await?; if new_parent.root_id() != channel.root_id() { Err(anyhow!(ErrorCode::WrongMoveTarget))?; diff --git a/crates/collab/src/db/queries/contacts.rs b/crates/collab/src/db/queries/contacts.rs index c66c33b80d..89bb07f3d9 100644 --- a/crates/collab/src/db/queries/contacts.rs +++ b/crates/collab/src/db/queries/contacts.rs @@ -177,7 +177,7 @@ impl Database { sender_id: sender_id.to_proto(), }, true, - &*tx, + &tx, ) .await? .into_iter() @@ -227,7 +227,7 @@ impl Database { rpc::Notification::ContactRequest { sender_id: requester_id.to_proto(), }, - &*tx, + &tx, ) .await?; } @@ -335,7 +335,7 @@ impl Database { sender_id: requester_id.to_proto(), }, accept, - &*tx, + &tx, ) .await?, ); @@ -348,7 +348,7 @@ impl Database { responder_id: responder_id.to_proto(), }, true, - &*tx, + &tx, ) .await?, ); diff --git a/crates/collab/src/db/queries/contributors.rs b/crates/collab/src/db/queries/contributors.rs index 0972779ce9..49194d6861 100644 --- a/crates/collab/src/db/queries/contributors.rs +++ b/crates/collab/src/db/queries/contributors.rs @@ -72,7 +72,7 @@ impl Database { github_login, github_user_id, github_email, - &*tx, + &tx, ) .await?; diff --git a/crates/collab/src/db/queries/hosted_projects.rs b/crates/collab/src/db/queries/hosted_projects.rs index fd9a991906..ca91e2822e 100644 --- a/crates/collab/src/db/queries/hosted_projects.rs +++ b/crates/collab/src/db/queries/hosted_projects.rs @@ -1,4 +1,4 @@ -use rpc::proto; +use rpc::{proto, ErrorCode}; use super::*; @@ -11,7 +11,7 @@ impl Database { ) -> Result> { Ok(hosted_project::Entity::find() .filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0))) - .all(&*tx) + .all(tx) .await? .into_iter() .flat_map(|project| { @@ -39,4 +39,44 @@ impl Database { }) .collect()) } + + pub async fn get_hosted_project( + &self, + hosted_project_id: HostedProjectId, + user_id: UserId, + tx: &DatabaseTransaction, + ) -> Result<(hosted_project::Model, ChannelRole)> { + let project = hosted_project::Entity::find_by_id(hosted_project_id) + .one(tx) + .await? + .ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?; + let channel = channel::Entity::find_by_id(project.channel_id) + .one(tx) + .await? + .ok_or_else(|| anyhow!(ErrorCode::NoSuchChannel))?; + + let role = match project.visibility { + ChannelVisibility::Public => { + self.check_user_is_channel_participant(&channel, user_id, tx) + .await? + } + ChannelVisibility::Members => { + self.check_user_is_channel_member(&channel, user_id, tx) + .await? + } + }; + + Ok((project, role)) + } + + pub async fn is_hosted_project(&self, project_id: ProjectId) -> Result { + self.transaction(|tx| async move { + Ok(project::Entity::find_by_id(project_id) + .one(&*tx) + .await? + .map(|project| project.hosted_project_id.is_some()) + .ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?) + }) + .await + } } diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index e6aa3bf62e..7caa8ec06a 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -12,8 +12,8 @@ impl Database { user_id: UserId, ) -> Result<()> { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_participant(&channel, user_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_participant(&channel, user_id, &tx) .await?; channel_chat_participant::ActiveModel { id: ActiveValue::NotSet, @@ -87,8 +87,8 @@ impl Database { before_message_id: Option, ) -> Result> { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_participant(&channel, user_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_participant(&channel, user_id, &tx) .await?; let mut condition = @@ -105,7 +105,7 @@ impl Database { .all(&*tx) .await?; - self.load_channel_messages(rows, &*tx).await + self.load_channel_messages(rows, &tx).await }) .await } @@ -127,16 +127,16 @@ impl Database { for row in &rows { channels.insert( row.channel_id, - self.get_channel_internal(row.channel_id, &*tx).await?, + self.get_channel_internal(row.channel_id, &tx).await?, ); } for (_, channel) in channels { - self.check_user_is_channel_participant(&channel, user_id, &*tx) + self.check_user_is_channel_participant(&channel, user_id, &tx) .await?; } - let messages = self.load_channel_messages(rows, &*tx).await?; + let messages = self.load_channel_messages(rows, &tx).await?; Ok(messages) }) .await @@ -174,7 +174,7 @@ impl Database { .filter(channel_message_mention::Column::MessageId.is_in(messages.iter().map(|m| m.id))) .order_by_asc(channel_message_mention::Column::MessageId) .order_by_asc(channel_message_mention::Column::StartOffset) - .stream(&*tx) + .stream(tx) .await?; let mut message_ix = 0; @@ -228,6 +228,7 @@ impl Database { } /// Creates a new channel message. + #[allow(clippy::too_many_arguments)] pub async fn create_channel_message( &self, channel_id: ChannelId, @@ -239,8 +240,8 @@ impl Database { reply_to_message_id: Option, ) -> Result { self.transaction(|tx| async move { - let channel = self.get_channel_internal(channel_id, &*tx).await?; - self.check_user_is_channel_participant(&channel, user_id, &*tx) + let channel = self.get_channel_internal(channel_id, &tx).await?; + self.check_user_is_channel_participant(&channel, user_id, &tx) .await?; let mut rows = channel_chat_participant::Entity::find() @@ -315,13 +316,13 @@ impl Database { channel_id: channel_id.to_proto(), }, false, - &*tx, + &tx, ) .await?, ); } - self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx) + self.observe_channel_message_internal(channel_id, user_id, message_id, &tx) .await?; } _ => { @@ -334,7 +335,7 @@ impl Database { } } - let mut channel_members = self.get_channel_participants(&channel, &*tx).await?; + let mut channel_members = self.get_channel_participants(&channel, &tx).await?; channel_members.retain(|member| !participant_user_ids.contains(member)); Ok(CreatedChannelMessage { @@ -354,7 +355,7 @@ impl Database { message_id: MessageId, ) -> Result { self.transaction(|tx| async move { - self.observe_channel_message_internal(channel_id, user_id, message_id, &*tx) + self.observe_channel_message_internal(channel_id, user_id, message_id, &tx) .await?; let mut batch = NotificationBatch::default(); batch.extend( @@ -365,7 +366,7 @@ impl Database { sender_id: Default::default(), channel_id: Default::default(), }, - &*tx, + &tx, ) .await?, ); @@ -396,7 +397,7 @@ impl Database { .to_owned(), ) // TODO: Try to upgrade SeaORM so we don't have to do this hack around their bug - .exec_without_returning(&*tx) + .exec_without_returning(tx) .await?; Ok(()) } @@ -413,7 +414,7 @@ impl Database { observed_channel_messages::Column::ChannelId .is_in(channel_ids.iter().map(|id| id.0)), ) - .all(&*tx) + .all(tx) .await?; Ok(rows @@ -464,7 +465,7 @@ impl Database { let stmt = Statement::from_string(self.pool.get_database_backend(), sql); let mut last_messages = channel_message::Model::find_by_statement(stmt) - .stream(&*tx) + .stream(tx) .await?; let mut results = Vec::new(); @@ -513,9 +514,9 @@ impl Database { .await?; if result.rows_affected == 0 { - let channel = self.get_channel_internal(channel_id, &*tx).await?; + let channel = self.get_channel_internal(channel_id, &tx).await?; if self - .check_user_is_channel_admin(&channel, user_id, &*tx) + .check_user_is_channel_admin(&channel, user_id, &tx) .await .is_ok() { diff --git a/crates/collab/src/db/queries/notifications.rs b/crates/collab/src/db/queries/notifications.rs index ccdda65342..5a44f62a53 100644 --- a/crates/collab/src/db/queries/notifications.rs +++ b/crates/collab/src/db/queries/notifications.rs @@ -95,7 +95,7 @@ impl Database { content: ActiveValue::Set(proto.content.clone()), ..Default::default() } - .save(&*tx) + .save(tx) .await?; Ok(Some(( @@ -184,7 +184,7 @@ impl Database { tx: &DatabaseTransaction, ) -> Result> { if let Some(id) = self - .find_notification(recipient_id, notification, &*tx) + .find_notification(recipient_id, notification, tx) .await? { let row = notification::Entity::update(notification::ActiveModel { @@ -236,7 +236,7 @@ impl Database { }), ) .into_values::<_, QueryIds>() - .one(&*tx) + .one(tx) .await?) } } diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index 33302eece1..c051882d95 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -57,13 +57,14 @@ impl Database { } let project = project::ActiveModel { - room_id: ActiveValue::set(participant.room_id), - host_user_id: ActiveValue::set(participant.user_id), + room_id: ActiveValue::set(Some(participant.room_id)), + host_user_id: ActiveValue::set(Some(participant.user_id)), host_connection_id: ActiveValue::set(Some(connection.id as i32)), host_connection_server_id: ActiveValue::set(Some(ServerId( connection.owner_id as i32, ))), - ..Default::default() + id: ActiveValue::NotSet, + hosted_project_id: ActiveValue::Set(None), } .insert(&*tx) .await?; @@ -153,8 +154,12 @@ impl Database { self.update_project_worktrees(project.id, worktrees, &tx) .await?; + let room_id = project + .room_id + .ok_or_else(|| anyhow!("project not in a room"))?; + let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?; - let room = self.get_room(project.room_id, &tx).await?; + let room = self.get_room(room_id, &tx).await?; Ok((room, guest_connection_ids)) }) .await @@ -181,7 +186,7 @@ impl Database { .update_column(worktree::Column::RootName) .to_owned(), ) - .exec(&*tx) + .exec(tx) .await?; } @@ -189,7 +194,7 @@ impl Database { .filter(worktree::Column::ProjectId.eq(project_id).and( worktree::Column::Id.is_not_in(worktrees.iter().map(|worktree| worktree.id as i64)), )) - .exec(&*tx) + .exec(tx) .await?; Ok(()) @@ -382,7 +387,6 @@ impl Database { language_server_id: ActiveValue::set(summary.language_server_id as i64), error_count: ActiveValue::set(summary.error_count as i32), warning_count: ActiveValue::set(summary.warning_count as i32), - ..Default::default() }) .on_conflict( OnConflict::columns([ @@ -434,7 +438,6 @@ impl Database { project_id: ActiveValue::set(project_id), id: ActiveValue::set(server.id as i64), name: ActiveValue::set(server.name.clone()), - ..Default::default() }) .on_conflict( OnConflict::columns([ @@ -506,8 +509,30 @@ impl Database { .await } - /// Adds the given connection to the specified project. - pub async fn join_project( + /// Adds the given connection to the specified hosted project + pub async fn join_hosted_project( + &self, + id: HostedProjectId, + user_id: UserId, + connection: ConnectionId, + ) -> Result<(Project, ReplicaId)> { + self.transaction(|tx| async move { + let (hosted_project, role) = self.get_hosted_project(id, user_id, &tx).await?; + let project = project::Entity::find() + .filter(project::Column::HostedProjectId.eq(hosted_project.id)) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("hosted project is no longer shared"))?; + + self.join_project_internal(project, user_id, connection, role, &tx) + .await + }) + .await + } + + /// Adds the given connection to the specified project + /// in the current room. + pub async fn join_project_in_room( &self, project_id: ProjectId, connection: ConnectionId, @@ -534,180 +559,240 @@ impl Database { .one(&*tx) .await? .ok_or_else(|| anyhow!("no such project"))?; - if project.room_id != participant.room_id { + if project.room_id != Some(participant.room_id) { return Err(anyhow!("no such project"))?; } + self.join_project_internal( + project, + participant.user_id, + connection, + participant.role.unwrap_or(ChannelRole::Member), + &tx, + ) + .await + }) + .await + } - let mut collaborators = project + async fn join_project_internal( + &self, + project: project::Model, + user_id: UserId, + connection: ConnectionId, + role: ChannelRole, + tx: &DatabaseTransaction, + ) -> Result<(Project, ReplicaId)> { + let mut collaborators = project + .find_related(project_collaborator::Entity) + .all(tx) + .await?; + let replica_ids = collaborators + .iter() + .map(|c| c.replica_id) + .collect::>(); + let mut replica_id = ReplicaId(1); + while replica_ids.contains(&replica_id) { + replica_id.0 += 1; + } + let new_collaborator = project_collaborator::ActiveModel { + project_id: ActiveValue::set(project.id), + connection_id: ActiveValue::set(connection.id as i32), + connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)), + user_id: ActiveValue::set(user_id), + replica_id: ActiveValue::set(replica_id), + is_host: ActiveValue::set(false), + ..Default::default() + } + .insert(tx) + .await?; + collaborators.push(new_collaborator); + + let db_worktrees = project.find_related(worktree::Entity).all(tx).await?; + let mut worktrees = db_worktrees + .into_iter() + .map(|db_worktree| { + ( + db_worktree.id as u64, + Worktree { + id: db_worktree.id as u64, + abs_path: db_worktree.abs_path, + root_name: db_worktree.root_name, + visible: db_worktree.visible, + entries: Default::default(), + repository_entries: Default::default(), + diagnostic_summaries: Default::default(), + settings_files: Default::default(), + scan_id: db_worktree.scan_id as u64, + completed_scan_id: db_worktree.completed_scan_id as u64, + }, + ) + }) + .collect::>(); + + // Populate worktree entries. + { + let mut db_entries = worktree_entry::Entity::find() + .filter( + Condition::all() + .add(worktree_entry::Column::ProjectId.eq(project.id)) + .add(worktree_entry::Column::IsDeleted.eq(false)), + ) + .stream(tx) + .await?; + while let Some(db_entry) = db_entries.next().await { + let db_entry = db_entry?; + if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) { + worktree.entries.push(proto::Entry { + id: db_entry.id as u64, + is_dir: db_entry.is_dir, + path: db_entry.path, + inode: db_entry.inode as u64, + mtime: Some(proto::Timestamp { + seconds: db_entry.mtime_seconds as u64, + nanos: db_entry.mtime_nanos as u32, + }), + is_symlink: db_entry.is_symlink, + is_ignored: db_entry.is_ignored, + is_external: db_entry.is_external, + git_status: db_entry.git_status.map(|status| status as i32), + }); + } + } + } + + // Populate repository entries. + { + let mut db_repository_entries = worktree_repository::Entity::find() + .filter( + Condition::all() + .add(worktree_repository::Column::ProjectId.eq(project.id)) + .add(worktree_repository::Column::IsDeleted.eq(false)), + ) + .stream(tx) + .await?; + while let Some(db_repository_entry) = db_repository_entries.next().await { + let db_repository_entry = db_repository_entry?; + if let Some(worktree) = worktrees.get_mut(&(db_repository_entry.worktree_id as u64)) + { + worktree.repository_entries.insert( + db_repository_entry.work_directory_id as u64, + proto::RepositoryEntry { + work_directory_id: db_repository_entry.work_directory_id as u64, + branch: db_repository_entry.branch, + }, + ); + } + } + } + + // Populate worktree diagnostic summaries. + { + let mut db_summaries = worktree_diagnostic_summary::Entity::find() + .filter(worktree_diagnostic_summary::Column::ProjectId.eq(project.id)) + .stream(tx) + .await?; + while let Some(db_summary) = db_summaries.next().await { + let db_summary = db_summary?; + if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) { + worktree + .diagnostic_summaries + .push(proto::DiagnosticSummary { + path: db_summary.path, + language_server_id: db_summary.language_server_id as u64, + error_count: db_summary.error_count as u32, + warning_count: db_summary.warning_count as u32, + }); + } + } + } + + // Populate worktree settings files + { + let mut db_settings_files = worktree_settings_file::Entity::find() + .filter(worktree_settings_file::Column::ProjectId.eq(project.id)) + .stream(tx) + .await?; + while let Some(db_settings_file) = db_settings_files.next().await { + let db_settings_file = db_settings_file?; + if let Some(worktree) = worktrees.get_mut(&(db_settings_file.worktree_id as u64)) { + worktree.settings_files.push(WorktreeSettingsFile { + path: db_settings_file.path, + content: db_settings_file.content, + }); + } + } + } + + // Populate language servers. + let language_servers = project + .find_related(language_server::Entity) + .all(tx) + .await?; + + let project = Project { + id: project.id, + role, + collaborators: collaborators + .into_iter() + .map(|collaborator| ProjectCollaborator { + connection_id: collaborator.connection(), + user_id: collaborator.user_id, + replica_id: collaborator.replica_id, + is_host: collaborator.is_host, + }) + .collect(), + worktrees, + language_servers: language_servers + .into_iter() + .map(|language_server| proto::LanguageServer { + id: language_server.id as u64, + name: language_server.name, + }) + .collect(), + }; + Ok((project, replica_id as ReplicaId)) + } + + pub async fn leave_hosted_project( + &self, + project_id: ProjectId, + connection: ConnectionId, + ) -> Result { + self.transaction(|tx| async move { + let result = project_collaborator::Entity::delete_many() + .filter( + Condition::all() + .add(project_collaborator::Column::ProjectId.eq(project_id)) + .add(project_collaborator::Column::ConnectionId.eq(connection.id as i32)) + .add( + project_collaborator::Column::ConnectionServerId + .eq(connection.owner_id as i32), + ), + ) + .exec(&*tx) + .await?; + if result.rows_affected == 0 { + return Err(anyhow!("not in the project"))?; + } + + let project = project::Entity::find_by_id(project_id) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such project"))?; + let collaborators = project .find_related(project_collaborator::Entity) .all(&*tx) .await?; - let replica_ids = collaborators - .iter() - .map(|c| c.replica_id) - .collect::>(); - let mut replica_id = ReplicaId(1); - while replica_ids.contains(&replica_id) { - replica_id.0 += 1; - } - let new_collaborator = project_collaborator::ActiveModel { - project_id: ActiveValue::set(project_id), - connection_id: ActiveValue::set(connection.id as i32), - connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)), - user_id: ActiveValue::set(participant.user_id), - replica_id: ActiveValue::set(replica_id), - is_host: ActiveValue::set(false), - ..Default::default() - } - .insert(&*tx) - .await?; - collaborators.push(new_collaborator); - - let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?; - let mut worktrees = db_worktrees + let connection_ids = collaborators .into_iter() - .map(|db_worktree| { - ( - db_worktree.id as u64, - Worktree { - id: db_worktree.id as u64, - abs_path: db_worktree.abs_path, - root_name: db_worktree.root_name, - visible: db_worktree.visible, - entries: Default::default(), - repository_entries: Default::default(), - diagnostic_summaries: Default::default(), - settings_files: Default::default(), - scan_id: db_worktree.scan_id as u64, - completed_scan_id: db_worktree.completed_scan_id as u64, - }, - ) - }) - .collect::>(); - - // Populate worktree entries. - { - let mut db_entries = worktree_entry::Entity::find() - .filter( - Condition::all() - .add(worktree_entry::Column::ProjectId.eq(project_id)) - .add(worktree_entry::Column::IsDeleted.eq(false)), - ) - .stream(&*tx) - .await?; - while let Some(db_entry) = db_entries.next().await { - let db_entry = db_entry?; - if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) { - worktree.entries.push(proto::Entry { - id: db_entry.id as u64, - is_dir: db_entry.is_dir, - path: db_entry.path, - inode: db_entry.inode as u64, - mtime: Some(proto::Timestamp { - seconds: db_entry.mtime_seconds as u64, - nanos: db_entry.mtime_nanos as u32, - }), - is_symlink: db_entry.is_symlink, - is_ignored: db_entry.is_ignored, - is_external: db_entry.is_external, - git_status: db_entry.git_status.map(|status| status as i32), - }); - } - } - } - - // Populate repository entries. - { - let mut db_repository_entries = worktree_repository::Entity::find() - .filter( - Condition::all() - .add(worktree_repository::Column::ProjectId.eq(project_id)) - .add(worktree_repository::Column::IsDeleted.eq(false)), - ) - .stream(&*tx) - .await?; - while let Some(db_repository_entry) = db_repository_entries.next().await { - let db_repository_entry = db_repository_entry?; - if let Some(worktree) = - worktrees.get_mut(&(db_repository_entry.worktree_id as u64)) - { - worktree.repository_entries.insert( - db_repository_entry.work_directory_id as u64, - proto::RepositoryEntry { - work_directory_id: db_repository_entry.work_directory_id as u64, - branch: db_repository_entry.branch, - }, - ); - } - } - } - - // Populate worktree diagnostic summaries. - { - let mut db_summaries = worktree_diagnostic_summary::Entity::find() - .filter(worktree_diagnostic_summary::Column::ProjectId.eq(project_id)) - .stream(&*tx) - .await?; - while let Some(db_summary) = db_summaries.next().await { - let db_summary = db_summary?; - if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) { - worktree - .diagnostic_summaries - .push(proto::DiagnosticSummary { - path: db_summary.path, - language_server_id: db_summary.language_server_id as u64, - error_count: db_summary.error_count as u32, - warning_count: db_summary.warning_count as u32, - }); - } - } - } - - // Populate worktree settings files - { - let mut db_settings_files = worktree_settings_file::Entity::find() - .filter(worktree_settings_file::Column::ProjectId.eq(project_id)) - .stream(&*tx) - .await?; - while let Some(db_settings_file) = db_settings_files.next().await { - let db_settings_file = db_settings_file?; - if let Some(worktree) = - worktrees.get_mut(&(db_settings_file.worktree_id as u64)) - { - worktree.settings_files.push(WorktreeSettingsFile { - path: db_settings_file.path, - content: db_settings_file.content, - }); - } - } - } - - // Populate language servers. - let language_servers = project - .find_related(language_server::Entity) - .all(&*tx) - .await?; - - let project = Project { - collaborators: collaborators - .into_iter() - .map(|collaborator| ProjectCollaborator { - connection_id: collaborator.connection(), - user_id: collaborator.user_id, - replica_id: collaborator.replica_id, - is_host: collaborator.is_host, - }) - .collect(), - worktrees, - language_servers: language_servers - .into_iter() - .map(|language_server| proto::LanguageServer { - id: language_server.id as u64, - name: language_server.name, - }) - .collect(), - }; - Ok((project, replica_id as ReplicaId)) + .map(|collaborator| collaborator.connection()) + .collect(); + Ok(LeftProject { + id: project.id, + connection_ids, + host_user_id: None, + host_connection_id: None, + }) }) .await } @@ -774,7 +859,7 @@ impl Database { .exec(&*tx) .await?; - let room = self.get_room(project.room_id, &tx).await?; + let room = self.get_room(room_id, &tx).await?; let left_project = LeftProject { id: project_id, host_user_id: project.host_user_id, @@ -998,7 +1083,9 @@ impl Database { .one(&*tx) .await? .ok_or_else(|| anyhow!("project {} not found", project_id))?; - Ok(project.room_id) + Ok(project + .room_id + .ok_or_else(|| anyhow!("project not in room"))?) }) .await } @@ -1061,7 +1148,7 @@ impl Database { .insert(&*tx) .await?; - let room = self.get_room(room_id, &*tx).await?; + let room = self.get_room(room_id, &tx).await?; Ok(room) }) .await @@ -1095,7 +1182,7 @@ impl Database { .exec(&*tx) .await?; - let room = self.get_room(room_id, &*tx).await?; + let room = self.get_room(room_id, &tx).await?; Ok(room) }) .await diff --git a/crates/collab/src/db/queries/rooms.rs b/crates/collab/src/db/queries/rooms.rs index b5073ae7ab..dcb31266df 100644 --- a/crates/collab/src/db/queries/rooms.rs +++ b/crates/collab/src/db/queries/rooms.rs @@ -321,7 +321,7 @@ impl Database { } let participant_index = self - .get_next_participant_index_internal(room_id, &*tx) + .get_next_participant_index_internal(room_id, &tx) .await?; let result = room_participant::Entity::update_many() @@ -374,7 +374,7 @@ impl Database { .select_only() .column(room_participant::Column::ParticipantIndex) .into_values::<_, QueryParticipantIndices>() - .all(&*tx) + .all(tx) .await?; let mut participant_index = 0; @@ -407,7 +407,7 @@ impl Database { tx: &DatabaseTransaction, ) -> Result { let participant_index = self - .get_next_participant_index_internal(room_id, &*tx) + .get_next_participant_index_internal(room_id, tx) .await?; room_participant::Entity::insert_many([room_participant::ActiveModel { @@ -441,12 +441,12 @@ impl Database { ]) .to_owned(), ) - .exec(&*tx) + .exec(tx) .await?; let (channel, room) = self.get_channel_room(room_id, &tx).await?; let channel = channel.ok_or_else(|| anyhow!("no channel for room"))?; - let channel_members = self.get_channel_participants(&channel, &*tx).await?; + let channel_members = self.get_channel_participants(&channel, tx).await?; Ok(JoinRoom { room, channel_id: Some(channel.id), @@ -491,7 +491,7 @@ impl Database { .one(&*tx) .await? .ok_or_else(|| anyhow!("project does not exist"))?; - if project.host_user_id != user_id { + if project.host_user_id != Some(user_id) { return Err(anyhow!("no such project"))?; } @@ -851,7 +851,7 @@ impl Database { } if collaborator.is_host { - left_project.host_user_id = collaborator.user_id; + left_project.host_user_id = Some(collaborator.user_id); left_project.host_connection_id = Some(collaborator_connection_id); } } @@ -1010,7 +1010,7 @@ impl Database { .ok_or_else(|| anyhow!("only admins can set participant role"))?; if role.requires_cla() { - self.check_user_has_signed_cla(user_id, room_id, &*tx) + self.check_user_has_signed_cla(user_id, room_id, &tx) .await?; } @@ -1021,7 +1021,7 @@ impl Database { .add(room_participant::Column::UserId.eq(user_id)), ) .set(room_participant::ActiveModel { - role: ActiveValue::set(Some(ChannelRole::from(role))), + role: ActiveValue::set(Some(role)), ..Default::default() }) .exec(&*tx) @@ -1030,7 +1030,7 @@ impl Database { if result.rows_affected != 1 { Err(anyhow!("could not update room participant role"))?; } - Ok(self.get_room(room_id, &tx).await?) + self.get_room(room_id, &tx).await }) .await } @@ -1042,11 +1042,11 @@ impl Database { tx: &DatabaseTransaction, ) -> Result<()> { let channel = room::Entity::find_by_id(room_id) - .one(&*tx) + .one(tx) .await? .ok_or_else(|| anyhow!("could not find room"))? .find_related(channel::Entity) - .one(&*tx) + .one(tx) .await?; if let Some(channel) = channel { @@ -1057,13 +1057,13 @@ impl Database { .is_in(channel.ancestors()) .and(channel::Column::RequiresZedCla.eq(true)), ) - .count(&*tx) + .count(tx) .await? > 0; if requires_zed_cla { if contributor::Entity::find() .filter(contributor::Column::UserId.eq(user_id)) - .one(&*tx) + .one(tx) .await? .is_none() { @@ -1076,10 +1076,9 @@ impl Database { pub async fn connection_lost(&self, connection: ConnectionId) -> Result<()> { self.transaction(|tx| async move { - self.room_connection_lost(connection, &*tx).await?; - self.channel_buffer_connection_lost(connection, &*tx) - .await?; - self.channel_chat_connection_lost(connection, &*tx).await?; + self.room_connection_lost(connection, &tx).await?; + self.channel_buffer_connection_lost(connection, &tx).await?; + self.channel_chat_connection_lost(connection, &tx).await?; Ok(()) }) .await @@ -1099,7 +1098,7 @@ impl Database { .eq(connection.owner_id as i32), ), ) - .one(&*tx) + .one(tx) .await?; if let Some(participant) = participant { @@ -1107,7 +1106,7 @@ impl Database { answering_connection_lost: ActiveValue::set(true), ..participant.into_active_model() }) - .exec(&*tx) + .exec(tx) .await?; } Ok(()) @@ -1296,7 +1295,7 @@ impl Database { drop(db_followers); let channel = if let Some(channel_id) = db_room.channel_id { - Some(self.get_channel_internal(channel_id, &*tx).await?) + Some(self.get_channel_internal(channel_id, tx).await?) } else { None }; diff --git a/crates/collab/src/db/queries/servers.rs b/crates/collab/src/db/queries/servers.rs index c79b00eee8..f4e01beba1 100644 --- a/crates/collab/src/db/queries/servers.rs +++ b/crates/collab/src/db/queries/servers.rs @@ -98,7 +98,7 @@ impl Database { .add(server::Column::Environment.eq(environment)) .add(server::Column::Id.ne(new_server_id)), ) - .all(&*tx) + .all(tx) .await?; Ok(stale_servers.into_iter().map(|server| server.id).collect()) } diff --git a/crates/collab/src/db/queries/users.rs b/crates/collab/src/db/queries/users.rs index f0768a3a9c..f17b43bb98 100644 --- a/crates/collab/src/db/queries/users.rs +++ b/crates/collab/src/db/queries/users.rs @@ -80,7 +80,7 @@ impl Database { github_login, github_user_id, github_email, - &*tx, + &tx, ) .await }) @@ -122,7 +122,7 @@ impl Database { metrics_id: ActiveValue::set(Uuid::new_v4()), ..Default::default() }) - .exec_with_returning(&*tx) + .exec_with_returning(tx) .await?; Ok(user) } diff --git a/crates/collab/src/db/tables/project.rs b/crates/collab/src/db/tables/project.rs index 8c26836046..550f8415d7 100644 --- a/crates/collab/src/db/tables/project.rs +++ b/crates/collab/src/db/tables/project.rs @@ -1,4 +1,4 @@ -use crate::db::{ProjectId, Result, RoomId, ServerId, UserId}; +use crate::db::{HostedProjectId, ProjectId, Result, RoomId, ServerId, UserId}; use anyhow::anyhow; use rpc::ConnectionId; use sea_orm::entity::prelude::*; @@ -8,10 +8,11 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key)] pub id: ProjectId, - pub room_id: RoomId, - pub host_user_id: UserId, + pub room_id: Option, + pub host_user_id: Option, pub host_connection_id: Option, pub host_connection_server_id: Option, + pub hosted_project_id: Option, } impl Model { diff --git a/crates/collab/src/db/tests.rs b/crates/collab/src/db/tests.rs index 7790b951b2..35da659e54 100644 --- a/crates/collab/src/db/tests.rs +++ b/crates/collab/src/db/tests.rs @@ -23,7 +23,7 @@ pub struct TestDb { impl TestDb { pub fn sqlite(background: BackgroundExecutor) -> Self { - let url = format!("sqlite::memory:"); + let url = "sqlite::memory:"; let runtime = tokio::runtime::Builder::new_current_thread() .enable_io() .enable_time() @@ -109,13 +109,13 @@ macro_rules! test_both_dbs { ($test_name:ident, $postgres_test_name:ident, $sqlite_test_name:ident) => { #[gpui::test] async fn $postgres_test_name(cx: &mut gpui::TestAppContext) { - let test_db = crate::db::TestDb::postgres(cx.executor().clone()); + let test_db = $crate::db::TestDb::postgres(cx.executor().clone()); $test_name(test_db.db()).await; } #[gpui::test] async fn $sqlite_test_name(cx: &mut gpui::TestAppContext) { - let test_db = crate::db::TestDb::sqlite(cx.executor().clone()); + let test_db = $crate::db::TestDb::sqlite(cx.executor().clone()); $test_name(test_db.db()).await; } }; @@ -168,7 +168,7 @@ async fn new_test_user(db: &Arc, email: &str) -> UserId { email, false, NewUserParams { - github_login: email[0..email.find("@").unwrap()].to_string(), + github_login: email[0..email.find('@').unwrap()].to_string(), github_user_id: GITHUB_USER_ID.fetch_add(1, SeqCst), }, ) diff --git a/crates/collab/src/db/tests/buffer_tests.rs b/crates/collab/src/db/tests/buffer_tests.rs index c1015330bb..8f9492d648 100644 --- a/crates/collab/src/db/tests/buffer_tests.rs +++ b/crates/collab/src/db/tests/buffer_tests.rs @@ -68,11 +68,12 @@ async fn test_channel_buffers(db: &Arc) { .unwrap(); let mut buffer_a = Buffer::new(0, text::BufferId::new(1).unwrap(), "".to_string()); - let mut operations = Vec::new(); - operations.push(buffer_a.edit([(0..0, "hello world")])); - operations.push(buffer_a.edit([(5..5, ", cruel")])); - operations.push(buffer_a.edit([(0..5, "goodbye")])); - operations.push(buffer_a.undo().unwrap().1); + let operations = vec![ + buffer_a.edit([(0..0, "hello world")]), + buffer_a.edit([(5..5, ", cruel")]), + buffer_a.edit([(0..5, "goodbye")]), + buffer_a.undo().unwrap().1, + ]; assert_eq!(buffer_a.text(), "hello, cruel world"); let operations = operations @@ -222,7 +223,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { .unwrap(); buffers.push( - db.transaction(|tx| async move { db.get_channel_buffer(channel, &*tx).await }) + db.transaction(|tx| async move { db.get_channel_buffer(channel, &tx).await }) .await .unwrap(), ); @@ -238,7 +239,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { .transaction(|tx| { let buffers = &buffers; async move { - db.get_latest_operations_for_buffers([buffers[0].id, buffers[2].id], &*tx) + db.get_latest_operations_for_buffers([buffers[0].id, buffers[2].id], &tx) .await } }) @@ -302,7 +303,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { .transaction(|tx| { let buffers = &buffers; async move { - db.get_latest_operations_for_buffers([buffers[1].id, buffers[2].id], &*tx) + db.get_latest_operations_for_buffers([buffers[1].id, buffers[2].id], &tx) .await } }) @@ -320,7 +321,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { .transaction(|tx| { let buffers = &buffers; async move { - db.get_latest_operations_for_buffers([buffers[0].id, buffers[1].id], &*tx) + db.get_latest_operations_for_buffers([buffers[0].id, buffers[1].id], &tx) .await } }) @@ -342,7 +343,7 @@ async fn test_channel_buffers_last_operations(db: &Database) { hash.insert(buffers[1].id, buffers[1].channel_id); hash.insert(buffers[2].id, buffers[2].channel_id); - async move { db.latest_channel_buffer_changes(&hash, &*tx).await } + async move { db.latest_channel_buffer_changes(&hash, &tx).await } }) .await .unwrap(); diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index 7f74bf15aa..54be002c41 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -42,8 +42,8 @@ async fn test_channels(db: &Arc) { let mut members = db .transaction(|tx| async move { - let channel = db.get_channel_internal(replace_id, &*tx).await?; - Ok(db.get_channel_participants(&channel, &*tx).await?) + let channel = db.get_channel_internal(replace_id, &tx).await?; + db.get_channel_participants(&channel, &tx).await }) .await .unwrap(); @@ -464,9 +464,9 @@ async fn test_user_is_channel_participant(db: &Arc) { db.transaction(|tx| async move { db.check_user_is_channel_participant( - &db.get_channel_internal(public_channel_id, &*tx).await?, + &db.get_channel_internal(public_channel_id, &tx).await?, admin, - &*tx, + &tx, ) .await }) @@ -474,9 +474,9 @@ async fn test_user_is_channel_participant(db: &Arc) { .unwrap(); db.transaction(|tx| async move { db.check_user_is_channel_participant( - &db.get_channel_internal(public_channel_id, &*tx).await?, + &db.get_channel_internal(public_channel_id, &tx).await?, member, - &*tx, + &tx, ) .await }) @@ -517,9 +517,9 @@ async fn test_user_is_channel_participant(db: &Arc) { db.transaction(|tx| async move { db.check_user_is_channel_participant( - &db.get_channel_internal(public_channel_id, &*tx).await?, + &db.get_channel_internal(public_channel_id, &tx).await?, guest, - &*tx, + &tx, ) .await }) @@ -547,11 +547,11 @@ async fn test_user_is_channel_participant(db: &Arc) { assert!(db .transaction(|tx| async move { db.check_user_is_channel_participant( - &db.get_channel_internal(public_channel_id, &*tx) + &db.get_channel_internal(public_channel_id, &tx) .await .unwrap(), guest, - &*tx, + &tx, ) .await }) @@ -629,9 +629,9 @@ async fn test_user_is_channel_participant(db: &Arc) { db.transaction(|tx| async move { db.check_user_is_channel_participant( - &db.get_channel_internal(zed_channel, &*tx).await.unwrap(), + &db.get_channel_internal(zed_channel, &tx).await.unwrap(), guest, - &*tx, + &tx, ) .await }) @@ -640,11 +640,11 @@ async fn test_user_is_channel_participant(db: &Arc) { assert!(db .transaction(|tx| async move { db.check_user_is_channel_participant( - &db.get_channel_internal(internal_channel_id, &*tx) + &db.get_channel_internal(internal_channel_id, &tx) .await .unwrap(), guest, - &*tx, + &tx, ) .await }) @@ -653,11 +653,11 @@ async fn test_user_is_channel_participant(db: &Arc) { db.transaction(|tx| async move { db.check_user_is_channel_participant( - &db.get_channel_internal(public_channel_id, &*tx) + &db.get_channel_internal(public_channel_id, &tx) .await .unwrap(), guest, - &*tx, + &tx, ) .await }) diff --git a/crates/collab/src/db/tests/contributor_tests.rs b/crates/collab/src/db/tests/contributor_tests.rs index c826f0083a..a51817574a 100644 --- a/crates/collab/src/db/tests/contributor_tests.rs +++ b/crates/collab/src/db/tests/contributor_tests.rs @@ -10,16 +10,15 @@ test_both_dbs!( async fn test_contributors(db: &Arc) { db.create_user( - &format!("user1@example.com"), + "user1@example.com", false, NewUserParams { - github_login: format!("user1"), + github_login: "user1".to_string(), github_user_id: 1, }, ) .await - .unwrap() - .user_id; + .unwrap(); assert_eq!(db.get_contributors().await.unwrap(), Vec::::new()); diff --git a/crates/collab/src/db/tests/db_tests.rs b/crates/collab/src/db/tests/db_tests.rs index 3149c8a8b6..2edaaa4314 100644 --- a/crates/collab/src/db/tests/db_tests.rs +++ b/crates/collab/src/db/tests/db_tests.rs @@ -87,8 +87,7 @@ async fn test_get_or_create_user_by_github_account(db: &Arc) { }, ) .await - .unwrap() - .user_id; + .unwrap(); let user_id2 = db .create_user( "user2@example.com", @@ -495,7 +494,7 @@ async fn test_project_count(db: &Arc) { let user1 = db .create_user( - &format!("admin@example.com"), + "admin@example.com", true, NewUserParams { github_login: "admin".into(), @@ -506,7 +505,7 @@ async fn test_project_count(db: &Arc) { .unwrap(); let user2 = db .create_user( - &format!("user@example.com"), + "user@example.com", false, NewUserParams { github_login: "user".into(), diff --git a/crates/collab/src/db/tests/feature_flag_tests.rs b/crates/collab/src/db/tests/feature_flag_tests.rs index 32ce4c4d23..5269d5354f 100644 --- a/crates/collab/src/db/tests/feature_flag_tests.rs +++ b/crates/collab/src/db/tests/feature_flag_tests.rs @@ -13,10 +13,10 @@ test_both_dbs!( async fn test_get_user_flags(db: &Arc) { let user_1 = db .create_user( - &format!("user1@example.com"), + "user1@example.com", false, NewUserParams { - github_login: format!("user1"), + github_login: "user1".to_string(), github_user_id: 1, }, ) @@ -26,10 +26,10 @@ async fn test_get_user_flags(db: &Arc) { let user_2 = db .create_user( - &format!("user2@example.com"), + "user2@example.com", false, NewUserParams { - github_login: format!("user2"), + github_login: "user2".to_string(), github_user_id: 2, }, ) diff --git a/crates/collab/src/db/tests/message_tests.rs b/crates/collab/src/db/tests/message_tests.rs index c785e3cb73..e20473d3bd 100644 --- a/crates/collab/src/db/tests/message_tests.rs +++ b/crates/collab/src/db/tests/message_tests.rs @@ -297,7 +297,7 @@ async fn test_unseen_channel_messages(db: &Arc) { // Check that observer has new messages let latest_messages = db .transaction(|tx| async move { - db.latest_channel_messages(&[channel_1, channel_2], &*tx) + db.latest_channel_messages(&[channel_1, channel_2], &tx) .await }) .await diff --git a/crates/collab/src/lib.rs b/crates/collab/src/lib.rs index 0be28c1358..616405edad 100644 --- a/crates/collab/src/lib.rs +++ b/crates/collab/src/lib.rs @@ -43,8 +43,8 @@ impl From for Error { } } -impl From for Error { - fn from(error: hyper::Error) -> Self { +impl From for Error { + fn from(error: axum::http::Error) -> Self { Self::Internal(error.into()) } } diff --git a/crates/collab/src/main.rs b/crates/collab/src/main.rs index add6bc47f8..7f4cb8e0cb 100644 --- a/crates/collab/src/main.rs +++ b/crates/collab/src/main.rs @@ -1,11 +1,10 @@ use anyhow::anyhow; -use axum::{extract::MatchedPath, routing::get, Extension, Router}; +use axum::{extract::MatchedPath, http::Request, routing::get, Extension, Router}; use collab::{ api::fetch_extensions_from_blob_store_periodically, db, env, executor::Executor, AppState, Config, MigrateConfig, Result, }; use db::Database; -use hyper::Request; use std::{ env::args, net::{SocketAddr, TcpListener}, @@ -16,8 +15,9 @@ use std::{ use tokio::signal::unix::SignalKind; use tower_http::trace::{self, TraceLayer}; use tracing::Level; -use tracing_log::LogTracer; -use tracing_subscriber::{filter::EnvFilter, fmt::format::JsonFields, Layer}; +use tracing_subscriber::{ + filter::EnvFilter, fmt::format::JsonFields, util::SubscriberInitExt, Layer, +}; use util::ResultExt; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -111,7 +111,8 @@ async fn main() -> Result<()> { ); #[cfg(unix)] - axum::Server::from_tcp(listener)? + axum::Server::from_tcp(listener) + .map_err(|e| anyhow!(e))? .serve(app.into_make_service_with_connect_info::()) .with_graceful_shutdown(async move { let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate()) @@ -128,7 +129,8 @@ async fn main() -> Result<()> { rpc_server.teardown(); } }) - .await?; + .await + .map_err(|e| anyhow!(e))?; // todo("windows") #[cfg(windows)] @@ -178,11 +180,11 @@ async fn handle_liveness_probe(Extension(state): Extension>) -> Re pub fn init_tracing(config: &Config) -> Option<()> { use std::str::FromStr; use tracing_subscriber::layer::SubscriberExt; - let rust_log = config.rust_log.clone()?; - LogTracer::init().log_err()?; + let filter = EnvFilter::from_str(config.rust_log.as_deref()?).log_err()?; - let subscriber = tracing_subscriber::Registry::default() + tracing_subscriber::registry() + .with(console_subscriber::spawn()) .with(if config.log_json.unwrap_or(false) { Box::new( tracing_subscriber::fmt::layer() @@ -192,17 +194,17 @@ pub fn init_tracing(config: &Config) -> Option<()> { .json() .flatten_event(true) .with_span_list(true), - ), + ) + .with_filter(filter), ) as Box + Send + Sync> } else { Box::new( tracing_subscriber::fmt::layer() - .event_format(tracing_subscriber::fmt::format().pretty()), + .event_format(tracing_subscriber::fmt::format().pretty()) + .with_filter(filter), ) }) - .with(EnvFilter::from_str(rust_log.as_str()).log_err()?); - - tracing::subscriber::set_global_default(subscriber).unwrap(); + .init(); None } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 845e0ffe1d..cc931fed37 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -4,8 +4,9 @@ use crate::{ auth::{self, Impersonator}, db::{ self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database, - InviteMemberResult, MembershipUpdated, MessageId, NotificationId, ProjectId, - RemoveChannelMemberResult, RespondToChannelInvite, RoomId, ServerId, User, UserId, + HostedProjectId, InviteMemberResult, MembershipUpdated, MessageId, NotificationId, Project, + ProjectId, RemoveChannelMemberResult, ReplicaId, RespondToChannelInvite, RoomId, ServerId, + User, UserId, }, executor::Executor, AppState, Error, Result, @@ -66,7 +67,9 @@ use tracing::{field, info_span, instrument, Instrument}; use util::SemanticVersion; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); -pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); + +// kubernetes gives terminated pods 10s to shutdown gracefully. After they're gone, we can clean up old resources. +pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(15); const MESSAGE_COUNT_PER_PAGE: usize = 100; const MAX_MESSAGE_LEN: usize = 1024; @@ -197,6 +200,7 @@ impl Server { .add_request_handler(share_project) .add_message_handler(unshare_project) .add_request_handler(join_project) + .add_request_handler(join_hosted_project) .add_message_handler(leave_project) .add_request_handler(update_project) .add_request_handler(update_worktree) @@ -354,7 +358,7 @@ impl Server { &refreshed_room.room, &refreshed_room.channel_members, &peer, - &*pool.lock(), + &pool.lock(), ); } contacts_to_update @@ -463,6 +467,7 @@ impl Server { TypeId::of::(), Box::new(move |envelope, session| { let envelope = envelope.into_any().downcast::>().unwrap(); + let received_at = envelope.received_at; let span = info_span!( "handle message", payload_type = envelope.payload_type_name() @@ -477,12 +482,14 @@ impl Server { let future = (handler)(*envelope, session); async move { let result = future.await; - let duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0; + let total_duration_ms = received_at.elapsed().as_micros() as f64 / 1000.0; + let processing_duration_ms = start_time.elapsed().as_micros() as f64 / 1000.0; + let queue_duration_ms = total_duration_ms - processing_duration_ms; match result { Err(error) => { - tracing::error!(%error, ?duration_ms, "error handling message") + tracing::error!(%error, ?total_duration_ms, ?processing_duration_ms, ?queue_duration_ms, "error handling message") } - Ok(()) => tracing::info!(?duration_ms, "finished handling message"), + Ok(()) => tracing::info!(?total_duration_ms, ?processing_duration_ms, ?queue_duration_ms, "finished handling message"), } } .instrument(span) @@ -544,6 +551,7 @@ impl Server { }) } + #[allow(clippy::too_many_arguments)] pub fn handle_connection( self: &Arc, connection: Connection, @@ -754,13 +762,13 @@ impl<'a> Deref for ConnectionPoolGuard<'a> { type Target = ConnectionPool; fn deref(&self) -> &Self::Target { - &*self.guard + &self.guard } } impl<'a> DerefMut for ConnectionPoolGuard<'a> { fn deref_mut(&mut self) -> &mut Self::Target { - &mut *self.guard + &mut self.guard } } @@ -842,7 +850,7 @@ impl Header for AppVersionHeader { } } -pub fn routes(server: Arc) -> Router { +pub fn routes(server: Arc) -> Router<(), Body> { Router::new() .route("/rpc", get(handle_websocket_request)) .layer( @@ -1584,22 +1592,46 @@ async fn join_project( session: Session, ) -> Result<()> { let project_id = ProjectId::from_proto(request.project_id); - let guest_user_id = session.user_id; tracing::info!(%project_id, "join project"); let (project, replica_id) = &mut *session .db() .await - .join_project(project_id, session.connection_id) + .join_project_in_room(project_id, session.connection_id) .await?; + join_project_internal(response, session, project, replica_id) +} + +trait JoinProjectInternalResponse { + fn send(self, result: proto::JoinProjectResponse) -> Result<()>; +} +impl JoinProjectInternalResponse for Response { + fn send(self, result: proto::JoinProjectResponse) -> Result<()> { + Response::::send(self, result) + } +} +impl JoinProjectInternalResponse for Response { + fn send(self, result: proto::JoinProjectResponse) -> Result<()> { + Response::::send(self, result) + } +} + +fn join_project_internal( + response: impl JoinProjectInternalResponse, + session: Session, + project: &mut Project, + replica_id: &ReplicaId, +) -> Result<()> { let collaborators = project .collaborators .iter() .filter(|collaborator| collaborator.connection_id != session.connection_id) .map(|collaborator| collaborator.to_proto()) .collect::>(); + let project_id = project.id; + let guest_user_id = session.user_id; let worktrees = project .worktrees @@ -1631,10 +1663,12 @@ async fn join_project( // First, we send the metadata associated with each worktree. response.send(proto::JoinProjectResponse { + project_id: project.id.0 as u64, worktrees: worktrees.clone(), replica_id: replica_id.0 as u32, collaborators: collaborators.clone(), language_servers: project.language_servers.clone(), + role: project.role.into(), // todo })?; for (worktree_id, worktree) in mem::take(&mut project.worktrees) { @@ -1707,15 +1741,17 @@ async fn join_project( async fn leave_project(request: proto::LeaveProject, session: Session) -> Result<()> { let sender_id = session.connection_id; let project_id = ProjectId::from_proto(request.project_id); + let db = session.db().await; + if db.is_hosted_project(project_id).await? { + let project = db.leave_hosted_project(project_id, sender_id).await?; + project_left(&project, &session); + return Ok(()); + } - let (room, project) = &*session - .db() - .await - .leave_project(project_id, sender_id) - .await?; + let (room, project) = &*db.leave_project(project_id, sender_id).await?; tracing::info!( %project_id, - host_user_id = %project.host_user_id, + host_user_id = ?project.host_user_id, host_connection_id = ?project.host_connection_id, "leave project" ); @@ -1726,6 +1762,24 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result Ok(()) } +async fn join_hosted_project( + request: proto::JoinHostedProject, + response: Response, + session: Session, +) -> Result<()> { + let (mut project, replica_id) = session + .db() + .await + .join_hosted_project( + HostedProjectId(request.id as i32), + session.user_id, + session.connection_id, + ) + .await?; + + join_project_internal(response, session, &mut project, &replica_id) +} + /// Updates other participants with changes to the project async fn update_project( request: proto::UpdateProject, @@ -2226,7 +2280,7 @@ async fn request_contact( session.peer.send(connection_id, update.clone())?; } - send_notifications(&*connection_pool, &session.peer, notifications); + send_notifications(&connection_pool, &session.peer, notifications); response.send(proto::Ack {})?; Ok(()) @@ -2283,7 +2337,7 @@ async fn respond_to_contact_request( session.peer.send(connection_id, update.clone())?; } - send_notifications(&*pool, &session.peer, notifications); + send_notifications(&pool, &session.peer, notifications); } response.send(proto::Ack {})?; @@ -2451,7 +2505,7 @@ async fn invite_channel_member( session.peer.send(connection_id, update.clone())?; } - send_notifications(&*connection_pool, &session.peer, notifications); + send_notifications(&connection_pool, &session.peer, notifications); response.send(proto::Ack {})?; Ok(()) @@ -2713,7 +2767,7 @@ async fn respond_to_channel_invite( } }; - send_notifications(&*connection_pool, &session.peer, notifications); + send_notifications(&connection_pool, &session.peer, notifications); response.send(proto::Ack {})?; @@ -2883,7 +2937,7 @@ async fn update_channel_buffer( .flat_map(|user_id| pool.user_connection_ids(*user_id)), |peer_id| { session.peer.send( - peer_id.into(), + peer_id, proto::UpdateChannels { latest_channel_buffer_versions: vec![proto::ChannelBufferVersion { channel_id: channel_id.to_proto(), @@ -2968,8 +3022,8 @@ fn channel_buffer_updated( message: &T, peer: &Peer, ) { - broadcast(Some(sender_id), collaborators.into_iter(), |peer_id| { - peer.send(peer_id.into(), message.clone()) + broadcast(Some(sender_id), collaborators, |peer_id| { + peer.send(peer_id, message.clone()) }); } @@ -3075,7 +3129,7 @@ async fn send_channel_message( .flat_map(|user_id| pool.user_connection_ids(*user_id)), |peer_id| { session.peer.send( - peer_id.into(), + peer_id, proto::UpdateChannels { latest_channel_message_ids: vec![proto::ChannelMessageId { channel_id: channel_id.to_proto(), @@ -3401,7 +3455,6 @@ fn build_update_user_channels(channels: &ChannelsForUser) -> proto::UpdateUserCh .collect(), observed_channel_buffer_version: channels.observed_buffer_versions.clone(), observed_channel_message_id: channels.observed_channel_messages.clone(), - ..Default::default() } } @@ -3478,7 +3531,7 @@ fn room_updated(room: &proto::Room, peer: &Peer) { .filter_map(|participant| Some(participant.peer_id?.into())), |peer_id| { peer.send( - peer_id.into(), + peer_id, proto::RoomUpdated { room: Some(room.clone()), }, @@ -3507,7 +3560,7 @@ fn channel_updated( .flat_map(|user_id| pool.user_connection_ids(*user_id)), |peer_id| { peer.send( - peer_id.into(), + peer_id, proto::UpdateChannels { channel_participants: vec![proto::ChannelParticipants { channel_id: channel_id.to_proto(), @@ -3656,7 +3709,7 @@ async fn leave_channel_buffers_for_session(session: &Session) -> Result<()> { fn project_left(project: &db::LeftProject, session: &Session) { for connection_id in &project.connection_ids { - if project.host_user_id == session.user_id { + if project.host_user_id == Some(session.user_id) { session .peer .send( diff --git a/crates/collab/src/rpc/connection_pool.rs b/crates/collab/src/rpc/connection_pool.rs index e438fa2caf..2d28290373 100644 --- a/crates/collab/src/rpc/connection_pool.rs +++ b/crates/collab/src/rpc/connection_pool.rs @@ -94,21 +94,19 @@ impl ConnectionPool { self.connected_users .get(&user_id) .into_iter() - .map(|state| { + .flat_map(|state| { state .connection_ids .iter() .flat_map(|cid| self.connections.get(cid)) }) - .flatten() } pub fn user_connection_ids(&self, user_id: UserId) -> impl Iterator + '_ { self.connected_users .get(&user_id) .into_iter() - .map(|state| &state.connection_ids) - .flatten() + .flat_map(|state| &state.connection_ids) .copied() } diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index 9d2f333304..14b0a87485 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -1431,7 +1431,7 @@ fn assert_channels( .ordered_channels() .map(|(depth, channel)| ExpectedChannel { depth, - name: channel.name.clone().into(), + name: channel.name.clone(), id: channel.id, }) .collect::>() diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 9bf92e87f2..50a798b5a6 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -833,7 +833,7 @@ async fn test_language_server_statuses(cx_a: &mut TestAppContext, cx_b: &mut Tes let mut fake_language_servers = client_a.language_registry().register_fake_lsp_adapter( "Rust", FakeLspAdapter { - name: "the-language-server".into(), + name: "the-language-server", ..Default::default() }, ); diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index 8561346e97..0dff43e41b 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -373,8 +373,10 @@ async fn test_basic_following( editor_a1.update(cx_a, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2])); }); + executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); executor.run_until_parked(); cx_b.background_executor.run_until_parked(); + editor_b1.update(cx_b, |editor, cx| { assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]); }); @@ -387,6 +389,7 @@ async fn test_basic_following( editor.change_selections(None, cx, |s| s.select_ranges([3..3])); editor.set_scroll_position(point(0., 100.), cx); }); + executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); executor.run_until_parked(); editor_b1.update(cx_b, |editor, cx| { assert_eq!(editor.selections.ranges(cx), &[3..3]); @@ -1437,14 +1440,13 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut }); executor.run_until_parked(); - let window_b_project_a = cx_b + let window_b_project_a = *cx_b .windows() .iter() .max_by_key(|window| window.window_id()) - .unwrap() - .clone(); + .unwrap(); - let mut cx_b2 = VisualTestContext::from_window(window_b_project_a.clone(), cx_b); + let mut cx_b2 = VisualTestContext::from_window(window_b_project_a, cx_b); let workspace_b_project_a = window_b_project_a .downcast::() @@ -1535,7 +1537,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut executor.run_until_parked(); assert_eq!(visible_push_notifications(cx_a).len(), 1); cx_a.update(|cx| { - workspace::join_remote_project( + workspace::join_in_room_project( project_b_id, client_b.user_id().unwrap(), client_a.app_state.clone(), @@ -1548,13 +1550,12 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut executor.run_until_parked(); assert_eq!(visible_push_notifications(cx_a).len(), 0); - let window_a_project_b = cx_a + let window_a_project_b = *cx_a .windows() .iter() .max_by_key(|window| window.window_id()) - .unwrap() - .clone(); - let cx_a2 = &mut VisualTestContext::from_window(window_a_project_b.clone(), cx_a); + .unwrap(); + let cx_a2 = &mut VisualTestContext::from_window(window_a_project_b, cx_a); let workspace_a_project_b = window_a_project_b .downcast::() .unwrap() @@ -1600,6 +1601,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T editor_a.update(cx_a, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([1..1])) }); + cx_a.executor() + .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); cx_a.run_until_parked(); editor_b.update(cx_b, |editor, cx| { assert_eq!(editor.selections.ranges(cx), vec![1..1]) @@ -1618,6 +1621,8 @@ async fn test_following_stops_on_unshare(cx_a: &mut TestAppContext, cx_b: &mut T editor_a.update(cx_a, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([2..2])) }); + cx_a.executor() + .advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); cx_a.run_until_parked(); editor_b.update(cx_b, |editor, cx| { assert_eq!(editor.selections.ranges(cx), vec![1..1]) @@ -1722,6 +1727,7 @@ async fn test_following_into_excluded_file( // When client B starts following client A, currently visible file is replicated workspace_b.update(cx_b, |workspace, cx| workspace.follow(peer_id_a, cx)); + executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); executor.run_until_parked(); let editor_for_excluded_b = workspace_b.update(cx_b, |workspace, cx| { @@ -1743,6 +1749,7 @@ async fn test_following_into_excluded_file( editor_for_excluded_a.update(cx_a, |editor, cx| { editor.select_right(&Default::default(), cx); }); + executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE); executor.run_until_parked(); // Changes from B to the excluded file are replicated in A's editor diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 7dd6c7c48b..443617bbe3 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -22,7 +22,6 @@ use project::{ search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath, }; use rand::prelude::*; -use rpc::proto::ChannelRole; use serde_json::json; use settings::SettingsStore; use std::{ @@ -3742,7 +3741,6 @@ async fn test_leaving_project( client_b.user_store().clone(), client_b.language_registry().clone(), FakeFs::new(cx.background_executor().clone()), - ChannelRole::Member, cx, ) }) @@ -3885,7 +3883,6 @@ async fn test_collaborating_with_diagnostics( DiagnosticSummary { error_count: 1, warning_count: 0, - ..Default::default() }, )] ) @@ -3922,7 +3919,6 @@ async fn test_collaborating_with_diagnostics( DiagnosticSummary { error_count: 1, warning_count: 0, - ..Default::default() }, )] ); diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index 9957b785f2..c1705be8ec 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -996,7 +996,7 @@ impl RandomizedTest for ProjectCollaborationTest { let statuses = statuses .iter() - .map(|(path, val)| (path.as_path(), val.clone())) + .map(|(path, val)| (path.as_path(), *val)) .collect::>(); if client.fs().metadata(&dot_git_dir).await?.is_none() { @@ -1483,10 +1483,10 @@ fn project_for_root_name( root_name: &str, cx: &TestAppContext, ) -> Option> { - if let Some(ix) = project_ix_for_root_name(&*client.local_projects().deref(), root_name, cx) { + if let Some(ix) = project_ix_for_root_name(client.local_projects().deref(), root_name, cx) { return Some(client.local_projects()[ix].clone()); } - if let Some(ix) = project_ix_for_root_name(&*client.remote_projects().deref(), root_name, cx) { + if let Some(ix) = project_ix_for_root_name(client.remote_projects().deref(), root_name, cx) { return Some(client.remote_projects()[ix].clone()); } None diff --git a/crates/collab/src/tests/randomized_test_helpers.rs b/crates/collab/src/tests/randomized_test_helpers.rs index 7d7abe7dfe..edce9c184d 100644 --- a/crates/collab/src/tests/randomized_test_helpers.rs +++ b/crates/collab/src/tests/randomized_test_helpers.rs @@ -464,6 +464,7 @@ impl TestPlan { }) } + #[allow(clippy::too_many_arguments)] async fn apply_server_operation( plan: Arc>, deterministic: BackgroundExecutor, diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 08e6bf9a68..469c4176ab 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -138,7 +138,7 @@ impl TestServer { (server, client_a, client_b, channel_id) } - pub async fn start1<'a>(cx: &'a mut TestAppContext) -> TestClient { + pub async fn start1(cx: &mut TestAppContext) -> TestClient { let mut server = Self::start(cx.executor().clone()).await; server.create_client(cx, "user_a").await } @@ -466,7 +466,7 @@ impl TestServer { let active_call_a = cx_a.read(ActiveCall::global); for (client_b, cx_b) in right { - let user_id_b = client_b.current_user_id(*cx_b).to_proto(); + let user_id_b = client_b.current_user_id(cx_b).to_proto(); active_call_a .update(*cx_a, |call, cx| call.invite(user_id_b, None, cx)) .await @@ -589,19 +589,19 @@ impl TestClient { .await; } - pub fn local_projects<'a>(&'a self) -> impl Deref>> + 'a { + pub fn local_projects(&self) -> impl Deref>> + '_ { Ref::map(self.state.borrow(), |state| &state.local_projects) } - pub fn remote_projects<'a>(&'a self) -> impl Deref>> + 'a { + pub fn remote_projects(&self) -> impl Deref>> + '_ { Ref::map(self.state.borrow(), |state| &state.remote_projects) } - pub fn local_projects_mut<'a>(&'a self) -> impl DerefMut>> + 'a { + pub fn local_projects_mut(&self) -> impl DerefMut>> + '_ { RefMut::map(self.state.borrow_mut(), |state| &mut state.local_projects) } - pub fn remote_projects_mut<'a>(&'a self) -> impl DerefMut>> + 'a { + pub fn remote_projects_mut(&self) -> impl DerefMut>> + '_ { RefMut::map(self.state.borrow_mut(), |state| &mut state.remote_projects) } @@ -614,16 +614,14 @@ impl TestClient { }) } - pub fn buffers<'a>( - &'a self, - ) -> impl DerefMut, HashSet>>> + 'a + pub fn buffers( + &self, + ) -> impl DerefMut, HashSet>>> + '_ { RefMut::map(self.state.borrow_mut(), |state| &mut state.buffers) } - pub fn channel_buffers<'a>( - &'a self, - ) -> impl DerefMut>> + 'a { + pub fn channel_buffers(&self) -> impl DerefMut>> + '_ { RefMut::map(self.state.borrow_mut(), |state| &mut state.channel_buffers) } diff --git a/crates/collab_ui/Cargo.toml b/crates/collab_ui/Cargo.toml index e88d191af5..ab8b85321e 100644 --- a/crates/collab_ui/Cargo.toml +++ b/crates/collab_ui/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/collab_ui.rs" doctest = false diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index f591c67f7c..ac0793715f 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -171,10 +171,8 @@ impl ChannelView { let this = this.clone(); Some(ui::ContextMenu::build(cx, move |menu, _| { menu.entry("Copy link to section", None, move |cx| { - this.update(cx, |this, cx| { - this.copy_link_for_position(position.clone(), cx) - }) - .ok(); + this.update(cx, |this, cx| this.copy_link_for_position(position, cx)) + .ok(); }) })) }); @@ -267,7 +265,7 @@ impl ChannelView { return; }; - let link = channel.notes_link(closest_heading.map(|heading| heading.text)); + let link = channel.notes_link(closest_heading.map(|heading| heading.text), cx); cx.write_to_clipboard(ClipboardItem::new(link)); self.workspace .update(cx, |workspace, cx| { @@ -378,7 +376,7 @@ impl Item for ChannelView { (_, false) => format!("#{} (disconnected)", channel.name), } } else { - format!("channel notes (disconnected)") + "channel notes (disconnected)".to_string() }; Label::new(label) .color(if selected { diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 69baee2491..77d0198cad 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -447,8 +447,7 @@ impl ChatPanel { let reply_to_message = message .reply_to_message_id - .map(|id| active_chat.read(cx).find_loaded_message(id)) - .flatten() + .and_then(|id| active_chat.read(cx).find_loaded_message(id)) .cloned(); let replied_to_you = @@ -635,14 +634,12 @@ impl ChatPanel { "Copy message text", None, cx.handler_for(&this, move |this, cx| { - this.active_chat().map(|active_chat| { - if let Some(message) = - active_chat.read(cx).find_loaded_message(message_id) - { - let text = message.body.clone(); - cx.write_to_clipboard(ClipboardItem::new(text)) - } - }); + if let Some(message) = this.active_chat().and_then(|active_chat| { + active_chat.read(cx).find_loaded_message(message_id) + }) { + let text = message.body.clone(); + cx.write_to_clipboard(ClipboardItem::new(text)) + } }), ) .when(can_modify_message, |menu| { @@ -944,16 +941,11 @@ impl Render for ChatPanel { .when_some(reply_to_message_id, |el, reply_to_message_id| { let reply_message = self .active_chat() - .map(|active_chat| { - active_chat.read(cx).messages().iter().find_map(|m| { - if m.id == ChannelMessageId::Saved(reply_to_message_id) { - Some(m) - } else { - None - } + .and_then(|active_chat| { + active_chat.read(cx).messages().iter().find(|message| { + message.id == ChannelMessageId::Saved(reply_to_message_id) }) }) - .flatten() .cloned(); el.when_some(reply_message, |el, reply_message| { diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 8a732402ee..0f4c14da9d 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -151,9 +151,9 @@ impl MessageEditor { ) { self.editor.update(cx, |editor, cx| { if let Some(channel_name) = channel_name { - editor.set_placeholder_text(format!("Message #{}", channel_name), cx); + editor.set_placeholder_text(format!("Message #{channel_name}"), cx); } else { - editor.set_placeholder_text(format!("Message Channel"), cx); + editor.set_placeholder_text("Message Channel", cx); } }); self.channel_id = Some(channel_id); @@ -324,7 +324,7 @@ impl MessageEditor { for range in ranges { text.clear(); text.extend(buffer.text_for_range(range.clone())); - if let Some(username) = text.strip_prefix("@") { + if let Some(username) = text.strip_prefix('@') { if let Some(user_id) = this.channel_members.get(username) { let start = multi_buffer.anchor_after(range.start); let end = multi_buffer.anchor_after(range.end); @@ -371,7 +371,7 @@ impl Render for MessageEditor { font_size: UiTextSize::Small.rems().into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.3).into(), + line_height: relative(1.3), background_color: None, underline: None, strikethrough: None, diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 2f85a71b15..4f242d68bb 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -7,8 +7,8 @@ use crate::{ CollaborationPanelSettings, }; use call::ActiveCall; -use channel::{Channel, ChannelEvent, ChannelStore, HostedProjectId}; -use client::{ChannelId, Client, Contact, User, UserStore}; +use channel::{Channel, ChannelEvent, ChannelStore}; +use client::{ChannelId, Client, Contact, HostedProjectId, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; @@ -441,17 +441,13 @@ impl CollabPanel { // Populate remote participants. self.match_candidates.clear(); self.match_candidates - .extend( - room.remote_participants() - .iter() - .filter_map(|(_, participant)| { - Some(StringMatchCandidate { - id: participant.user.id as usize, - string: participant.user.github_login.clone(), - char_bag: participant.user.github_login.chars().collect(), - }) - }), - ); + .extend(room.remote_participants().values().map(|participant| { + StringMatchCandidate { + id: participant.user.id as usize, + string: participant.user.github_login.clone(), + char_bag: participant.user.github_login.chars().collect(), + } + })); let mut matches = executor.block(match_strings( &self.match_candidates, &query, @@ -915,7 +911,7 @@ impl CollabPanel { this.workspace .update(cx, |workspace, cx| { let app_state = workspace.app_state().clone(); - workspace::join_remote_project(project_id, host_user_id, app_state, cx) + workspace::join_in_room_project(project_id, host_user_id, app_state, cx) .detach_and_prompt_err("Failed to join project", cx, |_, _| None); }) .ok(); @@ -956,7 +952,7 @@ impl CollabPanel { }) .ok(); })) - .tooltip(move |cx| Tooltip::text(format!("Open shared screen"), cx)) + .tooltip(move |cx| Tooltip::text("Open shared screen", cx)) }) } @@ -1051,8 +1047,15 @@ impl CollabPanel { .indent_level(2) .indent_step_size(px(20.)) .selected(is_selected) - .on_click(cx.listener(move |_this, _, _cx| { - // todo() + .on_click(cx.listener(move |this, _, cx| { + if let Some(workspace) = this.workspace.upgrade() { + let app_state = workspace.read(cx).app_state().clone(); + workspace::join_hosted_project(id, app_state, cx).detach_and_prompt_err( + "Failed to open project", + cx, + |_, _| None, + ) + } })) .start_slot( h_flex() @@ -1465,7 +1468,7 @@ impl CollabPanel { } => { if let Some(workspace) = self.workspace.upgrade() { let app_state = workspace.read(cx).app_state().clone(); - workspace::join_remote_project( + workspace::join_in_room_project( *project_id, *host_user_id, app_state, @@ -1633,7 +1636,7 @@ impl CollabPanel { self.toggle_channel_collapsed(id, cx) } - fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { + fn toggle_channel_collapsed(&mut self, channel_id: ChannelId, cx: &mut ViewContext) { match self.collapsed_channels.binary_search(&channel_id) { Ok(ix) => { self.collapsed_channels.remove(ix); @@ -2027,7 +2030,7 @@ impl CollabPanel { let Some(channel) = channel_store.channel_for_id(channel_id) else { return; }; - let item = ClipboardItem::new(channel.link()); + let item = ClipboardItem::new(channel.link(cx)); cx.write_to_clipboard(item) } @@ -2175,7 +2178,7 @@ impl CollabPanel { font_size: rems(0.875).into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.3).into(), + line_height: relative(1.3), background_color: None, underline: None, strikethrough: None, @@ -2210,7 +2213,7 @@ impl CollabPanel { let channel = self.channel_store.read(cx).channel_for_id(channel_id)?; - channel_link = Some(channel.link()); + channel_link = Some(channel.link(cx)); (channel_icon, channel_tooltip_text) = match channel.visibility { proto::ChannelVisibility::Public => { (Some("icons/public.svg"), Some("Copy public channel link.")) @@ -2224,7 +2227,7 @@ impl CollabPanel { }); if let Some(name) = channel_name { - SharedString::from(format!("{}", name)) + SharedString::from(name.to_string()) } else { SharedString::from("Current Call") } @@ -2510,7 +2513,7 @@ impl CollabPanel { .map(|channel| channel.visibility) == Some(proto::ChannelVisibility::Public); let disclosed = - has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok()); + has_children.then(|| self.collapsed_channels.binary_search(&channel.id).is_err()); let has_messages_notification = channel_store.has_new_messages(channel_id); let has_notes_notification = channel_store.has_channel_buffer_changed(channel_id); diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 125e8c64f3..4f9b18198b 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -197,7 +197,7 @@ impl Render for ChannelModal { .read(cx) .channel_for_id(channel_id) { - let item = ClipboardItem::new(channel.link()); + let item = ClipboardItem::new(channel.link(cx)); cx.write_to_clipboard(item); } })), diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 854809994b..2df240564e 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -403,7 +403,7 @@ impl CollabTitlebarItem { ) }) .on_click({ - let host_peer_id = host.peer_id.clone(); + let host_peer_id = host.peer_id; cx.listener(move |this, _, cx| { this.workspace .update(cx, |workspace, cx| { @@ -478,6 +478,7 @@ impl CollabTitlebarItem { ) } + #[allow(clippy::too_many_arguments)] fn render_collaborator( &self, user: &Arc, diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 15ec8410bc..6bd4abe588 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -778,7 +778,7 @@ fn format_timestamp( "just now".to_string() } } else if date.next_day() == Some(today) { - format!("yesterday") + "yesterday".to_string() } else { format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year()) } diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index f66194c52a..a8ba20c1e5 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -82,7 +82,7 @@ impl IncomingCallNotificationState { if let Some(project_id) = initial_project_id { cx.update(|cx| { if let Some(app_state) = app_state.upgrade() { - workspace::join_remote_project( + workspace::join_in_room_project( project_id, caller_user_id, app_state, @@ -119,7 +119,7 @@ impl Render for IncomingCallNotification { let theme_settings = ThemeSettings::get_global(cx); ( theme_settings.ui_font.family.clone(), - theme_settings.ui_font_size.clone(), + theme_settings.ui_font_size, ) }; diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index b8ceefcd76..46c5c8ce8a 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -98,7 +98,7 @@ impl ProjectSharedNotification { fn join(&mut self, cx: &mut ViewContext) { if let Some(app_state) = self.app_state.upgrade() { - workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx) + workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx) .detach_and_log_err(cx); } } @@ -123,7 +123,7 @@ impl Render for ProjectSharedNotification { let theme_settings = ThemeSettings::get_global(cx); ( theme_settings.ui_font.family.clone(), - theme_settings.ui_font_size.clone(), + theme_settings.ui_font_size, ) }; diff --git a/crates/collections/Cargo.toml b/crates/collections/Cargo.toml index 85cee90e12..b16b4c1300 100644 --- a/crates/collections/Cargo.toml +++ b/crates/collections/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/collections.rs" doctest = false diff --git a/crates/color/Cargo.toml b/crates/color/Cargo.toml index d3e4a125f4..a68a5fb518 100644 --- a/crates/color/Cargo.toml +++ b/crates/color/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [features] default = [] diff --git a/crates/command_palette/Cargo.toml b/crates/command_palette/Cargo.toml index 4d268545e8..eecd80b6e1 100644 --- a/crates/command_palette/Cargo.toml +++ b/crates/command_palette/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/command_palette.rs" doctest = false @@ -18,7 +21,6 @@ gpui.workspace = true picker.workspace = true postage.workspace = true project.workspace = true -release_channel.workspace = true serde.workspace = true settings.workspace = true theme.workspace = true diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 197ad90586..c8a98962c9 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -4,7 +4,7 @@ use std::{ time::Duration, }; -use client::telemetry::Telemetry; +use client::{parse_zed_link, telemetry::Telemetry}; use collections::HashMap; use command_palette_hooks::{ CommandInterceptResult, CommandPaletteFilter, CommandPaletteInterceptor, @@ -17,7 +17,6 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use postage::{sink::Sink, stream::Stream}; -use release_channel::parse_zed_link; use ui::{h_flex, prelude::*, v_flex, HighlightedLabel, KeyBinding, ListItem, ListItemSpacing}; use util::ResultExt; use workspace::{ModalView, Workspace}; @@ -26,6 +25,7 @@ use zed_actions::OpenZedUrl; actions!(command_palette, [Toggle]); pub fn init(cx: &mut AppContext) { + client::init_settings(cx); cx.set_global(HitCounts::default()); cx.set_global(CommandPaletteFilter::default()); cx.observe_new_views(CommandPalette::register).detach(); @@ -192,7 +192,7 @@ impl CommandPaletteDelegate { None }; - if parse_zed_link(&query).is_some() { + if parse_zed_link(&query, cx).is_some() { intercept_result = Some(CommandInterceptResult { action: OpenZedUrl { url: query.clone() }.boxed_clone(), string: query.clone(), @@ -396,6 +396,7 @@ impl PickerDelegate for CommandPaletteDelegate { .child( h_flex() .w_full() + .py_px() .justify_between() .child(HighlightedLabel::new( command.name.clone(), diff --git a/crates/command_palette_hooks/Cargo.toml b/crates/command_palette_hooks/Cargo.toml index 8dd8b7bf69..cc4f396bf8 100644 --- a/crates/command_palette_hooks/Cargo.toml +++ b/crates/command_palette_hooks/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/command_palette_hooks.rs" doctest = false diff --git a/crates/copilot/Cargo.toml b/crates/copilot/Cargo.toml index eca15d3c56..609bd0e3a8 100644 --- a/crates/copilot/Cargo.toml +++ b/crates/copilot/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/copilot.rs" doctest = false diff --git a/crates/copilot_ui/Cargo.toml b/crates/copilot_ui/Cargo.toml index cc83e09aa6..cc184b2dfc 100644 --- a/crates/copilot_ui/Cargo.toml +++ b/crates/copilot_ui/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/copilot_ui.rs" doctest = false diff --git a/crates/copilot_ui/src/copilot_button.rs b/crates/copilot_ui/src/copilot_button.rs index 8ea106a3ff..afc39c3d19 100644 --- a/crates/copilot_ui/src/copilot_button.rs +++ b/crates/copilot_ui/src/copilot_button.rs @@ -119,7 +119,9 @@ impl Render for CopilotButton { impl CopilotButton { pub fn new(fs: Arc, cx: &mut ViewContext) -> Self { - Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach()); + if let Some(copilot) = Copilot::global(cx) { + cx.observe(&copilot, |_, _, cx| cx.notify()).detach() + } cx.observe_global::(move |_, cx| cx.notify()) .detach(); @@ -238,7 +240,7 @@ impl CopilotButton { impl StatusItemView for CopilotButton { fn set_active_pane_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext) { - if let Some(editor) = item.map(|item| item.act_as::(cx)).flatten() { + if let Some(editor) = item.and_then(|item| item.act_as::(cx)) { self.editor_subscription = Some(( cx.observe(&editor, Self::update_enabled), editor.entity_id().as_u64() as usize, @@ -309,7 +311,7 @@ async fn configure_disabled_globs( fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None); update_settings_file::(fs, cx, move |file| { - file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) + file.defaults.show_copilot_suggestions = Some(!show_copilot_suggestions) }); } @@ -330,7 +332,7 @@ fn hide_copilot(fs: Arc, cx: &mut AppContext) { }); } -fn initiate_sign_in(cx: &mut WindowContext) { +pub fn initiate_sign_in(cx: &mut WindowContext) { let Some(copilot) = Copilot::global(cx) else { return; }; diff --git a/crates/copilot_ui/src/copilot_ui.rs b/crates/copilot_ui/src/copilot_ui.rs index 64dd068d5a..f55090ebcb 100644 --- a/crates/copilot_ui/src/copilot_ui.rs +++ b/crates/copilot_ui/src/copilot_ui.rs @@ -1,4 +1,4 @@ -mod copilot_button; +pub mod copilot_button; mod sign_in; pub use copilot_button::*; diff --git a/crates/db/Cargo.toml b/crates/db/Cargo.toml index 0b01e691b1..f31609277d 100644 --- a/crates/db/Cargo.toml +++ b/crates/db/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/db.rs" doctest = false diff --git a/crates/diagnostics/Cargo.toml b/crates/diagnostics/Cargo.toml index e4e408b665..fcaacfd62a 100644 --- a/crates/diagnostics/Cargo.toml +++ b/crates/diagnostics/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/diagnostics.rs" doctest = false diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index d50daebb8a..6d6a946fa0 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -347,7 +347,7 @@ impl ProjectDiagnosticsEditor { .diagnostic_groups .last() .unwrap(); - prev_path_last_group.excerpts.last().unwrap().clone() + *prev_path_last_group.excerpts.last().unwrap() } else { ExcerptId::min() }; @@ -451,10 +451,10 @@ impl ProjectDiagnosticsEditor { .pop() .unwrap(); - prev_excerpt_id = excerpt_id.clone(); - first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone()); - group_state.excerpts.push(excerpt_id.clone()); - let header_position = (excerpt_id.clone(), language::Anchor::MIN); + prev_excerpt_id = excerpt_id; + first_excerpt_id.get_or_insert_with(|| prev_excerpt_id); + group_state.excerpts.push(excerpt_id); + let header_position = (excerpt_id, language::Anchor::MIN); if is_first_excerpt_for_group { is_first_excerpt_for_group = false; @@ -483,7 +483,7 @@ impl ProjectDiagnosticsEditor { if !diagnostic.message.is_empty() { group_state.block_count += 1; blocks_to_add.push(BlockProperties { - position: (excerpt_id.clone(), entry.range.start), + position: (excerpt_id, entry.range.start), height: diagnostic.message.matches('\n').count() as u8 + 1, style: BlockStyle::Fixed, render: diagnostic_block_renderer(diagnostic, true), @@ -506,8 +506,8 @@ impl ProjectDiagnosticsEditor { group_ixs_to_remove.push(group_ix); blocks_to_remove.extend(group_state.blocks.iter().copied()); } else if let Some((_, group)) = to_keep { - prev_excerpt_id = group.excerpts.last().unwrap().clone(); - first_excerpt_id.get_or_insert_with(|| prev_excerpt_id.clone()); + prev_excerpt_id = *group.excerpts.last().unwrap(); + first_excerpt_id.get_or_insert_with(|| prev_excerpt_id); } } @@ -591,7 +591,7 @@ impl ProjectDiagnosticsEditor { if let Some(group) = groups.get(group_ix) { let offset = excerpts_snapshot .anchor_in_excerpt( - group.excerpts[group.primary_excerpt_ix].clone(), + group.excerpts[group.primary_excerpt_ix], group.primary_diagnostic.range.start, ) .to_offset(&excerpts_snapshot); @@ -735,8 +735,13 @@ impl Item for ProjectDiagnosticsEditor { true } - fn save(&mut self, project: Model, cx: &mut ViewContext) -> Task> { - self.editor.save(project, cx) + fn save( + &mut self, + format: bool, + project: Model, + cx: &mut ViewContext, + ) -> Task> { + self.editor.save(format, project, cx) } fn save_as( @@ -797,7 +802,7 @@ impl Item for ProjectDiagnosticsEditor { fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock { let (message, code_ranges) = highlight_diagnostic_message(&diagnostic); - let message: SharedString = message.into(); + let message: SharedString = message; Arc::new(move |cx| { let highlight_style: HighlightStyle = cx.theme().colors().text_accent.into(); h_flex() diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 8266e12ba7..78aeae8ca9 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/editor.rs" doctest = false diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index ae7cd99c07..d0bbab0335 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -119,6 +119,7 @@ impl_actions!( gpui::actions!( editor, [ + AcceptPartialCopilotSuggestion, AddSelectionAbove, AddSelectionBelow, Backspace, diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 344abeda65..c0213bc331 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -326,7 +326,7 @@ impl DisplayMap { .read(cx) .as_singleton() .and_then(|buffer| buffer.read(cx).language()); - language_settings(language.as_deref(), None, cx).tab_size + language_settings(language, None, cx).tab_size } #[cfg(test)] @@ -502,7 +502,7 @@ impl DisplaySnapshot { /// Returns text chunks starting at the end of the given display row in reverse until the start of the file pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator { - (0..=display_row).into_iter().rev().flat_map(|row| { + (0..=display_row).rev().flat_map(|row| { self.block_snapshot .chunks(row..row + 1, false, Highlights::default()) .map(|h| h.text) @@ -512,13 +512,13 @@ impl DisplaySnapshot { }) } - pub fn chunks<'a>( - &'a self, + pub fn chunks( + &self, display_rows: Range, language_aware: bool, inlay_highlight_style: Option, suggestion_highlight_style: Option, - ) -> DisplayChunks<'a> { + ) -> DisplayChunks<'_> { self.block_snapshot.chunks( display_rows, language_aware, @@ -847,7 +847,7 @@ impl DisplaySnapshot { self.block_snapshot.longest_row() } - pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option { + pub fn fold_for_line(&self, buffer_row: u32) -> Option { if self.is_line_folded(buffer_row) { Some(FoldStatus::Folded) } else if self.is_foldable(buffer_row) { @@ -857,7 +857,7 @@ impl DisplaySnapshot { } } - pub fn is_foldable(self: &Self, buffer_row: u32) -> bool { + pub fn is_foldable(&self, buffer_row: u32) -> bool { let max_row = self.buffer_snapshot.max_buffer_row(); if buffer_row >= max_row { return false; @@ -880,7 +880,7 @@ impl DisplaySnapshot { false } - pub fn foldable_range(self: &Self, buffer_row: u32) -> Option> { + pub fn foldable_range(&self, buffer_row: u32) -> Option> { let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row)); if self.is_foldable(start.row) && !self.is_line_folded(start.row) { let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row); @@ -1291,7 +1291,7 @@ pub mod tests { let mut cx = EditorTestContext::new(cx).await; let editor = cx.editor.clone(); - let window = cx.window.clone(); + let window = cx.window; _ = cx.update_window(window, |_, cx| { let text_layout_details = @@ -1455,10 +1455,8 @@ pub mod tests { }"# .unindent(); - let theme = SyntaxTheme::new_test(vec![ - ("mod.body", Hsla::red().into()), - ("fn.name", Hsla::blue().into()), - ]); + let theme = + SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]); let language = Arc::new( Language::new( LanguageConfig { @@ -1545,10 +1543,8 @@ pub mod tests { }"# .unindent(); - let theme = SyntaxTheme::new_test(vec![ - ("mod.body", Hsla::red().into()), - ("fn.name", Hsla::blue().into()), - ]); + let theme = + SyntaxTheme::new_test(vec![("mod.body", Hsla::red()), ("fn.name", Hsla::blue())]); let language = Arc::new( Language::new( LanguageConfig { @@ -1616,10 +1612,8 @@ pub mod tests { async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) { cx.update(|cx| init_test(cx, |_| {})); - let theme = SyntaxTheme::new_test(vec![ - ("operator", Hsla::red().into()), - ("string", Hsla::green().into()), - ]); + let theme = + SyntaxTheme::new_test(vec![("operator", Hsla::red()), ("string", Hsla::green())]); let language = Arc::new( Language::new( LanguageConfig { @@ -1832,10 +1826,10 @@ pub mod tests { ) } - fn syntax_chunks<'a>( + fn syntax_chunks( rows: Range, map: &Model, - theme: &'a SyntaxTheme, + theme: &SyntaxTheme, cx: &mut AppContext, ) -> Vec<(String, Option)> { chunks(rows, map, theme, cx) @@ -1844,10 +1838,10 @@ pub mod tests { .collect() } - fn chunks<'a>( + fn chunks( rows: Range, map: &Model, - theme: &'a SyntaxTheme, + theme: &SyntaxTheme, cx: &mut AppContext, ) -> Vec<(String, Option, Option)> { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 6effe23a8f..2d7280114f 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -987,7 +987,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { if row >= target { break; } - offset += line.len() as usize; + offset += line.len(); } (row, offset) } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 4c38ff1416..29da2b41bb 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -884,10 +884,10 @@ impl sum_tree::Item for Fold { fn summary(&self) -> Self::Summary { FoldSummary { - start: self.range.start.clone(), - end: self.range.end.clone(), - min_start: self.range.start.clone(), - max_end: self.range.end.clone(), + start: self.range.start, + end: self.range.end, + min_start: self.range.start, + max_end: self.range.end, count: 1, } } @@ -919,10 +919,10 @@ impl sum_tree::Summary for FoldSummary { fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less { - self.min_start = other.min_start.clone(); + self.min_start = other.min_start; } if other.max_end.cmp(&self.max_end, buffer) == Ordering::Greater { - self.max_end = other.max_end.clone(); + self.max_end = other.max_end; } #[cfg(debug_assertions)] @@ -934,16 +934,16 @@ impl sum_tree::Summary for FoldSummary { } } - self.start = other.start.clone(); - self.end = other.end.clone(); + self.start = other.start; + self.end = other.end; self.count += other.count; } } impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange { fn add_summary(&mut self, summary: &'a FoldSummary, _: &MultiBufferSnapshot) { - self.0.start = summary.start.clone(); - self.0.end = summary.end.clone(); + self.0.start = summary.start; + self.0.end = summary.end; } } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 5dcae58f48..19c6e8970d 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -283,7 +283,7 @@ impl<'a> Iterator for InlayChunks<'a> { self.output_offset.0 += prefix.len(); let mut prefix = Chunk { text: prefix, - ..chunk.clone() + ..*chunk }; if !self.active_highlights.is_empty() { let mut highlight_style = HighlightStyle::default(); @@ -322,7 +322,7 @@ impl<'a> Iterator for InlayChunks<'a> { next_inlay_highlight_endpoint = range.end - offset_in_inlay.0; highlight_style .get_or_insert_with(|| Default::default()) - .highlight(style.clone()); + .highlight(*style); } } else { next_inlay_highlight_endpoint = usize::MAX; @@ -982,7 +982,7 @@ impl InlaySnapshot { summary } - pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { + pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> { let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); let inlay_point = InlayPoint::new(row, 0); cursor.seek(&inlay_point, Bias::Left, &()); diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index c761c9ba60..018797386d 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -296,7 +296,7 @@ impl TabSnapshot { let (collapsed, expanded_char_column, to_next_stop) = self.collapse_tabs(chars, expanded, bias); ( - FoldPoint::new(output.row(), collapsed as u32), + FoldPoint::new(output.row(), collapsed), expanded_char_column, to_next_stop, ) @@ -513,7 +513,7 @@ impl<'a> Iterator for TabChunks<'a> { } else { self.chunk.text = &self.chunk.text[1..]; let tab_size = if self.input_column < self.max_expansion_column { - self.tab_size.get() as u32 + self.tab_size.get() } else { 1 }; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b5555c5a2f..deaae27678 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -89,6 +89,7 @@ pub use multi_buffer::{ use ordered_float::OrderedFloat; use parking_lot::{Mutex, RwLock}; use project::project_settings::{GitGutterSetting, ProjectSettings}; +use project::Item; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::prelude::*; use rpc::proto::*; @@ -957,14 +958,14 @@ impl CompletionsMenu { .selected(item_ix == selected_item) .on_click(cx.listener(move |editor, _event, cx| { cx.stop_propagation(); - editor - .confirm_completion( - &ConfirmCompletion { - item_ix: Some(item_ix), - }, - cx, - ) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.confirm_completion( + &ConfirmCompletion { + item_ix: Some(item_ix), + }, + cx, + ) { + task.detach_and_log_err(cx) + } })) .child(h_flex().overflow_hidden().child(completion_label)) .end_slot::
(documentation_label), @@ -1175,14 +1176,14 @@ impl CodeActionsMenu { MouseButton::Left, cx.listener(move |editor, _, cx| { cx.stop_propagation(); - editor - .confirm_code_action( - &ConfirmCodeAction { - item_ix: Some(item_ix), - }, - cx, - ) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = editor.confirm_code_action( + &ConfirmCodeAction { + item_ix: Some(item_ix), + }, + cx, + ) { + task.detach_and_log_err(cx) + } }), ) // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here. @@ -1627,6 +1628,10 @@ impl Editor { key_context.set("extension", extension.to_string()); } + if self.has_active_copilot_suggestion(cx) { + key_context.add("copilot_suggestion"); + } + key_context } @@ -1701,18 +1706,14 @@ impl Editor { } } - pub fn language_at<'a, T: ToOffset>( - &self, - point: T, - cx: &'a AppContext, - ) -> Option> { + pub fn language_at(&self, point: T, cx: &AppContext) -> Option> { self.buffer.read(cx).language_at(point, cx) } - pub fn file_at<'a, T: ToOffset>( + pub fn file_at( &self, point: T, - cx: &'a AppContext, + cx: &AppContext, ) -> Option> { self.buffer.read(cx).read(cx).file_at(point).cloned() } @@ -1873,7 +1874,7 @@ impl Editor { let new_cursor_position = self.selections.newest_anchor().head(); self.push_to_nav_history( - old_cursor_position.clone(), + *old_cursor_position, Some(new_cursor_position.to_point(buffer)), cx, ); @@ -1892,8 +1893,7 @@ impl Editor { if let Some(completion_menu) = completion_menu { let cursor_position = new_cursor_position.to_offset(buffer); - let (word_range, kind) = - buffer.surrounding_word(completion_menu.initial_position.clone()); + let (word_range, kind) = buffer.surrounding_word(completion_menu.initial_position); if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) { @@ -2114,7 +2114,7 @@ impl Editor { match click_count { 1 => { start = buffer.anchor_before(position.to_point(&display_map)); - end = start.clone(); + end = start; mode = SelectMode::Character; auto_scroll = true; } @@ -2122,7 +2122,7 @@ impl Editor { let range = movement::surrounding_word(&display_map, position); start = buffer.anchor_before(range.start.to_point(&display_map)); end = buffer.anchor_before(range.end.to_point(&display_map)); - mode = SelectMode::Word(start.clone()..end.clone()); + mode = SelectMode::Word(start..end); auto_scroll = true; } 3 => { @@ -2136,7 +2136,7 @@ impl Editor { ); start = buffer.anchor_before(line_start); end = buffer.anchor_before(next_line_start); - mode = SelectMode::Line(start.clone()..end.clone()); + mode = SelectMode::Line(start..end); auto_scroll = true; } _ => { @@ -2732,9 +2732,8 @@ impl Editor { let mut edits = Vec::new(); let mut rows = Vec::new(); - let mut rows_inserted = 0; - for selection in self.selections.all_adjusted(cx) { + for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() { let cursor = selection.head(); let row = cursor.row; @@ -2743,8 +2742,7 @@ impl Editor { let newline = "\n".to_string(); edits.push((start_of_line..start_of_line, newline)); - rows.push(row + rows_inserted); - rows_inserted += 1; + rows.push(row + rows_inserted as u32); } self.transact(cx, |editor, cx| { @@ -3208,7 +3206,7 @@ impl Editor { let (buffer, buffer_position) = self .buffer .read(cx) - .text_anchor_for_position(position.clone(), cx)?; + .text_anchor_for_position(position, cx)?; // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances, // hence we do LSP request & edit on host side only — add formats to host's history. @@ -3252,17 +3250,14 @@ impl Editor { }; let position = self.selections.newest_anchor().head(); - let (buffer, buffer_position) = if let Some(output) = self - .buffer - .read(cx) - .text_anchor_for_position(position.clone(), cx) - { - output - } else { - return; - }; + let (buffer, buffer_position) = + if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) { + output + } else { + return; + }; - let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone()); + let query = Self::completion_query(&self.buffer.read(cx).read(cx), position); let completions = provider.completions(&buffer, buffer_position, cx); let id = post_inc(&mut self.next_completion_id); @@ -3700,7 +3695,7 @@ impl Editor { let newest_selection = self.selections.newest_anchor().clone(); let cursor_position = newest_selection.head(); let (cursor_buffer, cursor_buffer_position) = - buffer.text_anchor_for_position(cursor_position.clone(), cx)?; + buffer.text_anchor_for_position(cursor_position, cx)?; let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?; if cursor_buffer != tail_buffer { return None; @@ -3758,7 +3753,7 @@ impl Editor { let range = Anchor { buffer_id, - excerpt_id: excerpt_id.clone(), + excerpt_id: excerpt_id, text_anchor: start, }..Anchor { buffer_id, @@ -3969,6 +3964,39 @@ impl Editor { } } + fn accept_partial_copilot_suggestion( + &mut self, + _: &AcceptPartialCopilotSuggestion, + cx: &mut ViewContext, + ) { + if self.selections.count() == 1 && self.has_active_copilot_suggestion(cx) { + if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { + let mut partial_suggestion = suggestion + .text + .chars() + .by_ref() + .take_while(|c| c.is_alphabetic()) + .collect::(); + if partial_suggestion.is_empty() { + partial_suggestion = suggestion + .text + .chars() + .by_ref() + .take_while(|c| c.is_whitespace() || !c.is_alphabetic()) + .collect::(); + } + + cx.emit(EditorEvent::InputHandled { + utf16_range_to_replace: None, + text: partial_suggestion.clone().into(), + }); + self.insert_with_autoindent_mode(&partial_suggestion, None, cx); + self.refresh_copilot_suggestions(true, cx); + cx.notify(); + } + } + } + fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { if let Some(copilot) = Copilot::global(cx) { @@ -4107,7 +4135,7 @@ impl Editor { fold_data .map(|(fold_status, buffer_row, active)| { (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { - IconButton::new(ix as usize, ui::IconName::ChevronDown) + IconButton::new(ix, ui::IconName::ChevronDown) .on_click({ let view = editor_view.clone(); move |_e, cx| { @@ -4741,7 +4769,7 @@ impl Editor { row_range.end - 1, snapshot.line_len(row_range.end - 1), )); - cursor_positions.push(anchor.clone()..anchor); + cursor_positions.push(anchor..anchor); } self.transact(cx, |this, cx| { @@ -4845,7 +4873,7 @@ impl Editor { .text_for_range(start_point..end_point) .collect::(); - let mut lines = text.split("\n").collect_vec(); + let mut lines = text.split('\n').collect_vec(); let lines_before = lines.len(); callback(&mut lines); @@ -4913,7 +4941,7 @@ impl Editor { self.manipulate_text(cx, |text| { // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary // https://github.com/rutrum/convert-case/issues/16 - text.split("\n") + text.split('\n') .map(|line| line.to_case(Case::Title)) .join("\n") }) @@ -4935,7 +4963,7 @@ impl Editor { self.manipulate_text(cx, |text| { // Hack to get around the fact that to_case crate doesn't support '\n' as a word boundary // https://github.com/rutrum/convert-case/issues/16 - text.split("\n") + text.split('\n') .map(|line| line.to_case(Case::UpperCamel)) .join("\n") }) @@ -6448,10 +6476,9 @@ impl Editor { && !movement::is_inside_word(&display_map, display_range.end)) { // TODO: This is n^2, because we might check all the selections - if selections + if !selections .iter() - .find(|selection| selection.range().overlaps(&offset_range)) - .is_none() + .any(|selection| selection.range().overlaps(&offset_range)) { next_selected_range = Some(offset_range); break; @@ -7447,10 +7474,8 @@ impl Editor { pub fn open_url(&mut self, _: &OpenUrl, cx: &mut ViewContext) { let position = self.selections.newest_anchor().head(); - let Some((buffer, buffer_position)) = self - .buffer - .read(cx) - .text_anchor_for_position(position.clone(), cx) + let Some((buffer, buffer_position)) = + self.buffer.read(cx).text_anchor_for_position(position, cx) else { return; }; @@ -7912,7 +7937,7 @@ impl Editor { let block_id = this.insert_blocks( [BlockProperties { style: BlockStyle::Flex, - position: range.start.clone(), + position: range.start, height: 1, render: Arc::new({ let rename_editor = rename_editor.clone(); @@ -7976,11 +8001,11 @@ impl Editor { let (start_buffer, start) = self .buffer .read(cx) - .text_anchor_for_position(rename.range.start.clone(), cx)?; + .text_anchor_for_position(rename.range.start, cx)?; let (end_buffer, end) = self .buffer .read(cx) - .text_anchor_for_position(rename.range.end.clone(), cx)?; + .text_anchor_for_position(rename.range.end, cx)?; if start_buffer != end_buffer { return None; } @@ -8633,22 +8658,23 @@ impl Editor { fn get_permalink_to_line(&mut self, cx: &mut ViewContext) -> Result { use git::permalink::{build_permalink, BuildPermalinkParams}; - let project = self.project.clone().ok_or_else(|| anyhow!("no project"))?; - let project = project.read(cx); - - let worktree = project - .visible_worktrees(cx) - .next() - .ok_or_else(|| anyhow!("no worktree"))?; - - let mut cwd = worktree.read(cx).abs_path().to_path_buf(); - cwd.push(".git"); + let (path, repo) = maybe!({ + let project_handle = self.project.as_ref()?.clone(); + let project = project_handle.read(cx); + let buffer = self.buffer().read(cx).as_singleton()?; + let path = buffer + .read(cx) + .file()? + .as_local()? + .path() + .to_str()? + .to_string(); + let repo = project.get_repo(&buffer.read(cx).project_path(cx)?, cx)?; + Some((path, repo)) + }) + .ok_or_else(|| anyhow!("unable to open git repository"))?; const REMOTE_NAME: &str = "origin"; - let repo = project - .fs() - .open_repo(&cwd) - .ok_or_else(|| anyhow!("no Git repo"))?; let origin_url = repo .lock() .remote_url(REMOTE_NAME) @@ -8657,14 +8683,6 @@ impl Editor { .lock() .head_sha() .ok_or_else(|| anyhow!("failed to read HEAD SHA"))?; - - let path = maybe!({ - let buffer = self.buffer().read(cx).as_singleton()?; - let file = buffer.read(cx).file().and_then(|f| f.as_local())?; - file.path().to_str().map(|path| path.to_string()) - }) - .ok_or_else(|| anyhow!("failed to determine file path"))?; - let selections = self.selections.all::(cx); let selection = selections.iter().peekable().next(); @@ -8702,7 +8720,7 @@ impl Editor { match permalink { Ok(permalink) => { - cx.open_url(&permalink.to_string()); + cx.open_url(permalink.as_ref()); } Err(err) => { let message = format!("Failed to open permalink: {err}"); @@ -8817,7 +8835,6 @@ impl Editor { Ok(i) | Err(i) => i, }; - let right_position = right_position.clone(); ranges[start_ix..] .iter() .take_while(move |range| range.start.cmp(&right_position, buffer).is_le()) @@ -9387,7 +9404,7 @@ impl Editor { let highlight = chunk .syntax_highlight_id .and_then(|id| id.name(&style.syntax)); - let mut chunk_lines = chunk.text.split("\n").peekable(); + let mut chunk_lines = chunk.text.split('\n').peekable(); while let Some(text) = chunk_lines.next() { let mut merged_with_last_token = false; if let Some(last_token) = line.back_mut() { @@ -10060,7 +10077,7 @@ impl ViewInputHandler for Editor { .disjoint_anchors() .iter() .map(|selection| { - selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot) + selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot) }) .collect::>() }; @@ -10412,7 +10429,7 @@ pub fn styled_runs_for_code_label<'a>( }) } -pub(crate) fn split_words<'a>(text: &'a str) -> impl std::iter::Iterator + 'a { +pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator + '_ { let mut index = 0; let mut codepoints = text.char_indices().peekable(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 98e1ff2991..36daaec5d9 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3999,7 +3999,7 @@ async fn test_select_all_matches(cx: &mut gpui::TestAppContext) { let mut cx = EditorTestContext::new(cx).await; cx.set_state("abc\nˇabc abc\ndefabc\nabc"); - cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches::default(), cx)) + cx.update_editor(|e, cx| e.select_all_matches(&SelectAllMatches, cx)) .unwrap(); cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»"); } @@ -5265,7 +5265,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { assert!(cx.read(|cx| editor.is_dirty(cx))); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { @@ -5303,7 +5303,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { unreachable!() }); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); cx.executor().advance_clock(super::FORMAT_TIMEOUT); cx.executor().start_waiting(); @@ -5326,7 +5326,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { }); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { @@ -5379,7 +5379,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { assert!(cx.read(|cx| editor.is_dirty(cx))); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { @@ -5418,7 +5418,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { }, ); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); cx.executor().advance_clock(super::FORMAT_TIMEOUT); cx.executor().start_waiting(); @@ -5441,7 +5441,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) { }); let save = editor - .update(cx, |editor, cx| editor.save(project.clone(), cx)) + .update(cx, |editor, cx| editor.save(true, project.clone(), cx)) .unwrap(); fake_server .handle_request::(move |params, _| async move { @@ -6752,8 +6752,8 @@ async fn test_following(cx: &mut gpui::TestAppContext) { cx.open_window( WindowOptions { bounds: WindowBounds::Fixed(Bounds::from_corners( - gpui::Point::new((0. as f64).into(), (0. as f64).into()), - gpui::Point::new((10. as f64).into(), (80. as f64).into()), + gpui::Point::new(0_f64.into(), 0_f64.into()), + gpui::Point::new(10_f64.into(), 80_f64.into()), )), ..Default::default() }, @@ -6774,7 +6774,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) { move |_, leader, event, cx| { leader .read(cx) - .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx); + .add_event_to_update_proto(event, &mut update.borrow_mut(), cx); }, ) .detach(); @@ -6943,7 +6943,7 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { cx.subscribe(&leader, move |_, leader, event, cx| { leader .read(cx) - .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx); + .add_event_to_update_proto(event, &mut update.borrow_mut(), cx); }) .detach(); } @@ -7310,7 +7310,7 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext) #[test] fn test_split_words() { - fn split<'a>(text: &'a str) -> Vec<&'a str> { + fn split(text: &str) -> Vec<&str> { split_words(text).collect() } @@ -7623,6 +7623,128 @@ async fn test_copilot(executor: BackgroundExecutor, cx: &mut gpui::TestAppContex }); } +#[gpui::test(iterations = 10)] +async fn test_accept_partial_copilot_suggestion( + executor: BackgroundExecutor, + cx: &mut gpui::TestAppContext, +) { + // flaky + init_test(cx, |_| {}); + + let (copilot, copilot_lsp) = Copilot::fake(cx); + _ = cx.update(|cx| Copilot::set_global(copilot, cx)); + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string(), ":".to_string()]), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; + + // Setup the editor with a completion request. + cx.set_state(indoc! {" + oneˇ + two + three + "}); + cx.simulate_keystroke("."); + let _ = handle_completion_request( + &mut cx, + indoc! {" + one.|<> + two + three + "}, + vec![], + ); + handle_copilot_completion_request( + &copilot_lsp, + vec![copilot::request::Completion { + text: "one.copilot1".into(), + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)), + ..Default::default() + }], + vec![], + ); + executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); + cx.update_editor(|editor, cx| { + assert!(editor.has_active_copilot_suggestion(cx)); + + // Accepting the first word of the suggestion should only accept the first word and still show the rest. + editor.accept_partial_copilot_suggestion(&Default::default(), cx); + assert!(editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.text(cx), "one.copilot\ntwo\nthree\n"); + assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n"); + + // Accepting next word should accept the non-word and copilot suggestion should be gone + editor.accept_partial_copilot_suggestion(&Default::default(), cx); + assert!(!editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.text(cx), "one.copilot1\ntwo\nthree\n"); + assert_eq!(editor.display_text(cx), "one.copilot1\ntwo\nthree\n"); + }); + + // Reset the editor and check non-word and whitespace completion + cx.set_state(indoc! {" + oneˇ + two + three + "}); + cx.simulate_keystroke("."); + let _ = handle_completion_request( + &mut cx, + indoc! {" + one.|<> + two + three + "}, + vec![], + ); + handle_copilot_completion_request( + &copilot_lsp, + vec![copilot::request::Completion { + text: "one.123. copilot\n 456".into(), + range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)), + ..Default::default() + }], + vec![], + ); + executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); + cx.update_editor(|editor, cx| { + assert!(editor.has_active_copilot_suggestion(cx)); + + // Accepting the first word (non-word) of the suggestion should only accept the first word and still show the rest. + editor.accept_partial_copilot_suggestion(&Default::default(), cx); + assert!(editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.text(cx), "one.123. \ntwo\nthree\n"); + assert_eq!( + editor.display_text(cx), + "one.123. copilot\n 456\ntwo\nthree\n" + ); + + // Accepting next word should accept the next word and copilot suggestion should still exist + editor.accept_partial_copilot_suggestion(&Default::default(), cx); + assert!(editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.text(cx), "one.123. copilot\ntwo\nthree\n"); + assert_eq!( + editor.display_text(cx), + "one.123. copilot\n 456\ntwo\nthree\n" + ); + + // Accepting the whitespace should accept the non-word/whitespaces with newline and copilot suggestion should be gone + editor.accept_partial_copilot_suggestion(&Default::default(), cx); + assert!(!editor.has_active_copilot_suggestion(cx)); + assert_eq!(editor.text(cx), "one.123. copilot\n 456\ntwo\nthree\n"); + assert_eq!( + editor.display_text(cx), + "one.123. copilot\n 456\ntwo\nthree\n" + ); + }); +} + #[gpui::test] async fn test_copilot_completion_invalidation( executor: BackgroundExecutor, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 0082ff343c..d46f0f2a31 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -338,6 +338,7 @@ impl EditorElement { register_action(view, cx, Editor::display_cursor_names); register_action(view, cx, Editor::unique_lines_case_insensitive); register_action(view, cx, Editor::unique_lines_case_sensitive); + register_action(view, cx, Editor::accept_partial_copilot_suggestion); } fn register_key_listeners( @@ -1586,7 +1587,7 @@ impl EditorElement { let new_y = event.position.y; if (track_bounds.top()..track_bounds.bottom()).contains(&y) { let mut position = editor.scroll_position(cx); - position.y += (new_y - y) * (max_row as f32) / height; + position.y += (new_y - y) * max_row / height; if position.y < 0.0 { position.y = 0.0; } @@ -1633,8 +1634,7 @@ impl EditorElement { let y = event.position.y; if y < thumb_top || thumb_bottom < y { - let center_row = - ((y - top) * max_row as f32 / height).round() as u32; + let center_row = ((y - top) * max_row / height).round() as u32; let top_row = center_row .saturating_sub((row_range.end - row_range.start) as u32 / 2); let mut position = editor.scroll_position(cx); @@ -1964,7 +1964,7 @@ impl EditorElement { chunks, &self.style.text, MAX_LINE_LEN, - rows.len() as usize, + rows.len(), line_number_layouts, snapshot.mode, cx, @@ -2005,7 +2005,7 @@ impl EditorElement { let text_width = bounds.size.width - gutter_dimensions.width; let overscroll = size(em_width, px(0.)); let _snapshot = { - editor.set_visible_line_count((bounds.size.height / line_height).into(), cx); + editor.set_visible_line_count(bounds.size.height / line_height, cx); let editor_width = text_width - gutter_dimensions.margin - overscroll.width - em_width; let wrap_width = match editor.soft_wrap_mode(cx) { @@ -2038,7 +2038,7 @@ impl EditorElement { // The scroll position is a fractional point, the whole number of which represents // the top of the window in terms of display rows. let start_row = scroll_position.y as u32; - let height_in_lines = f32::from(bounds.size.height / line_height); + let height_in_lines = bounds.size.height / line_height; let max_row = snapshot.max_point().row(); // Add 1 to ensure selections bleed off screen @@ -2091,7 +2091,7 @@ impl EditorElement { editor.cursor_shape, &snapshot.display_snapshot, is_newest, - true, + editor.leader_peer_id.is_none(), None, ); if is_newest { @@ -2263,7 +2263,7 @@ impl EditorElement { }); let scroll_max = point( - f32::from((scroll_width - text_size.width) / em_width).max(0.0), + ((scroll_width - text_size.width) / em_width).max(0.0), max_row as f32, ); @@ -2723,11 +2723,8 @@ impl EditorElement { }; let scroll_position = position_map.snapshot.scroll_position(); - let x = f32::from( - (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width, - ); - let y = - f32::from((scroll_position.y * line_height - delta.y) / line_height); + let x = (scroll_position.x * max_glyph_width - delta.x) / max_glyph_width; + let y = (scroll_position.y * line_height - delta.y) / line_height; let scroll_position = point(x, y).clamp(&point(0., 0.), &position_map.scroll_max); editor.scroll(scroll_position, axis, cx); @@ -2884,7 +2881,7 @@ impl LineWithInvisibles { .unwrap(); layouts.push(Self { line: shaped_line, - invisibles: invisibles.drain(..).collect(), + invisibles: std::mem::take(&mut invisibles), }); line.clear(); @@ -2993,6 +2990,7 @@ impl LineWithInvisibles { ); } + #[allow(clippy::too_many_arguments)] fn draw_invisibles( &self, selection_ranges: &[Range], @@ -3268,12 +3266,12 @@ impl PositionMap { let position = position - text_bounds.origin; let y = position.y.max(px(0.)).min(self.size.height); let x = position.x + (scroll_position.x * self.em_width); - let row = (f32::from(y / self.line_height) + scroll_position.y) as u32; + let row = ((y / self.line_height) + scroll_position.y) as u32; let (column, x_overshoot_after_line_end) = if let Some(line) = self .line_layouts .get(row as usize - scroll_position.y as usize) - .map(|&LineWithInvisibles { ref line, .. }| line) + .map(|LineWithInvisibles { line, .. }| line) { if let Some(ix) = line.index_for_x(x) { (ix as u32, px(0.)) @@ -4084,8 +4082,7 @@ mod tests { .position_map .line_layouts .iter() - .map(|line_with_invisibles| &line_with_invisibles.invisibles) - .flatten() + .flat_map(|line_with_invisibles| &line_with_invisibles.invisibles) .cloned() .collect() } diff --git a/crates/editor/src/git/permalink.rs b/crates/editor/src/git/permalink.rs index ec2a925d1b..90704b43d0 100644 --- a/crates/editor/src/git/permalink.rs +++ b/crates/editor/src/git/permalink.rs @@ -105,7 +105,7 @@ fn parse_git_remote_url(url: &str) -> Option { .trim_start_matches("https://github.com/") .trim_end_matches(".git"); - let (owner, repo) = repo_with_owner.split_once("/")?; + let (owner, repo) = repo_with_owner.split_once('/')?; return Some(ParsedGitRemote { provider: GitHostingProvider::Github, @@ -120,7 +120,7 @@ fn parse_git_remote_url(url: &str) -> Option { .trim_start_matches("https://gitlab.com/") .trim_end_matches(".git"); - let (owner, repo) = repo_with_owner.split_once("/")?; + let (owner, repo) = repo_with_owner.split_once('/')?; return Some(ParsedGitRemote { provider: GitHostingProvider::Gitlab, @@ -135,7 +135,7 @@ fn parse_git_remote_url(url: &str) -> Option { .trim_start_matches("https://gitee.com/") .trim_end_matches(".git"); - let (owner, repo) = repo_with_owner.split_once("/")?; + let (owner, repo) = repo_with_owner.split_once('/')?; return Some(ParsedGitRemote { provider: GitHostingProvider::Gitee, @@ -147,9 +147,9 @@ fn parse_git_remote_url(url: &str) -> Option { if url.contains("bitbucket.org") { let (_, repo_with_owner) = url.trim_end_matches(".git").split_once("bitbucket.org")?; let (owner, repo) = repo_with_owner - .trim_start_matches("/") - .trim_start_matches(":") - .split_once("/")?; + .trim_start_matches('/') + .trim_start_matches(':') + .split_once('/')?; return Some(ParsedGitRemote { provider: GitHostingProvider::Bitbucket, @@ -166,7 +166,7 @@ fn parse_git_remote_url(url: &str) -> Option { .trim_start_matches("git@git.sr.ht:~") .trim_start_matches("https://git.sr.ht/~"); - let (owner, repo) = repo_with_owner.split_once("/")?; + let (owner, repo) = repo_with_owner.split_once('/')?; return Some(ParsedGitRemote { provider: GitHostingProvider::Sourcehut, @@ -181,7 +181,7 @@ fn parse_git_remote_url(url: &str) -> Option { .trim_start_matches("https://codeberg.org/") .trim_end_matches(".git"); - let (owner, repo) = repo_with_owner.split_once("/")?; + let (owner, repo) = repo_with_owner.split_once('/')?; return Some(ParsedGitRemote { provider: GitHostingProvider::Codeberg, diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index 0c5e36dc68..e1afaa6591 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -381,7 +381,7 @@ pub fn show_link_definition( let Some((buffer, buffer_position)) = editor .buffer .read(cx) - .text_anchor_for_position(trigger_anchor.clone(), cx) + .text_anchor_for_position(*trigger_anchor, cx) else { return; }; @@ -389,7 +389,7 @@ pub fn show_link_definition( let Some((excerpt_id, _, _)) = editor .buffer() .read(cx) - .excerpt_containing(trigger_anchor.clone(), cx) + .excerpt_containing(*trigger_anchor, cx) else { return; }; @@ -424,9 +424,8 @@ pub fn show_link_definition( TriggerPoint::Text(_) => { if let Some((url_range, url)) = find_url(&buffer, buffer_position, cx.clone()) { this.update(&mut cx, |_, _| { - let start = - snapshot.anchor_in_excerpt(excerpt_id.clone(), url_range.start); - let end = snapshot.anchor_in_excerpt(excerpt_id.clone(), url_range.end); + let start = snapshot.anchor_in_excerpt(excerpt_id, url_range.start); + let end = snapshot.anchor_in_excerpt(excerpt_id, url_range.end); ( Some(RangeInEditor::Text(start..end)), vec![HoverLink::Url(url)], @@ -451,14 +450,10 @@ pub fn show_link_definition( ( definition_result.iter().find_map(|link| { link.origin.as_ref().map(|origin| { - let start = snapshot.anchor_in_excerpt( - excerpt_id.clone(), - origin.range.start, - ); - let end = snapshot.anchor_in_excerpt( - excerpt_id.clone(), - origin.range.end, - ); + let start = snapshot + .anchor_in_excerpt(excerpt_id, origin.range.start); + let end = snapshot + .anchor_in_excerpt(excerpt_id, origin.range.end); RangeInEditor::Text(start..end) }) }), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 9d0b70f5a1..b2a168fb83 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -297,10 +297,10 @@ fn show_hover( let range = if let Some(range) = hover_result.range { let start = snapshot .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.start); + .anchor_in_excerpt(excerpt_id, range.start); let end = snapshot .buffer_snapshot - .anchor_in_excerpt(excerpt_id.clone(), range.end); + .anchor_in_excerpt(excerpt_id, range.end); start..end } else { @@ -597,7 +597,7 @@ impl DiagnosticPopover { .as_ref() .unwrap_or(&self.local_diagnostic); - (entry.diagnostic.group_id, entry.range.start.clone()) + (entry.diagnostic.group_id, entry.range.start) } } diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs index 0db43d24de..a796c39a25 100644 --- a/crates/editor/src/inlay_hint_cache.rs +++ b/crates/editor/src/inlay_hint_cache.rs @@ -2197,7 +2197,7 @@ pub mod tests { "another change #3", ] { expected_changes.push(async_later_change); - let task_editor = editor.clone(); + let task_editor = editor; edits.push(cx.spawn(|mut cx| async move { task_editor .update(&mut cx, |editor, cx| { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index a6ff8dd043..07ef6a9d84 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -593,7 +593,7 @@ impl Item for Editor { None } - fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option { + fn tab_description(&self, detail: usize, cx: &AppContext) -> Option { let path = path_for_buffer(&self.buffer, detail, true, cx)?; Some(path.to_string_lossy().to_string().into()) } @@ -702,14 +702,21 @@ impl Item for Editor { } } - fn save(&mut self, project: Model, cx: &mut ViewContext) -> Task> { + fn save( + &mut self, + format: bool, + project: Model, + cx: &mut ViewContext, + ) -> Task> { self.report_editor_event("save", None, cx); let buffers = self.buffer().clone().read(cx).all_buffers(); cx.spawn(|this, mut cx| async move { - this.update(&mut cx, |this, cx| { - this.perform_format(project.clone(), FormatTrigger::Save, cx) - })? - .await?; + if format { + this.update(&mut cx, |this, cx| { + this.perform_format(project.clone(), FormatTrigger::Save, cx) + })? + .await?; + } if buffers.len() == 1 { project @@ -952,14 +959,14 @@ impl Item for Editor { let buffer = project_item .downcast::() .map_err(|_| anyhow!("Project item at stored path was not a buffer"))?; - Ok(pane.update(&mut cx, |_, cx| { + pane.update(&mut cx, |_, cx| { cx.new_view(|cx| { let mut editor = Editor::for_buffer(buffer, Some(project), cx); editor.read_scroll_position_from_db(item_id, workspace_id, cx); editor }) - })?) + }) }) }) .unwrap_or_else(|error| Task::ready(Err(error))) @@ -1147,8 +1154,8 @@ impl SearchableItem for Editor { let end = excerpt .buffer .anchor_before(excerpt_range.start + range.end); - buffer.anchor_in_excerpt(excerpt.id.clone(), start) - ..buffer.anchor_in_excerpt(excerpt.id.clone(), end) + buffer.anchor_in_excerpt(excerpt.id, start) + ..buffer.anchor_in_excerpt(excerpt.id, end) }), ); } @@ -1179,9 +1186,9 @@ pub fn active_match_index( None } else { match ranges.binary_search_by(|probe| { - if probe.end.cmp(cursor, &*buffer).is_lt() { + if probe.end.cmp(cursor, buffer).is_lt() { Ordering::Less - } else if probe.start.cmp(cursor, &*buffer).is_gt() { + } else if probe.start.cmp(cursor, buffer).is_gt() { Ordering::Greater } else { Ordering::Equal diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 65a6854ba9..34299ffad6 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -688,31 +688,30 @@ mod tests { // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary let mut id = 0; let inlays = (0..buffer_snapshot.len()) - .map(|offset| { + .flat_map(|offset| { [ Inlay { id: InlayId::Suggestion(post_inc(&mut id)), position: buffer_snapshot.anchor_at(offset, Bias::Left), - text: format!("test").into(), + text: "test".into(), }, Inlay { id: InlayId::Suggestion(post_inc(&mut id)), position: buffer_snapshot.anchor_at(offset, Bias::Right), - text: format!("test").into(), + text: "test".into(), }, Inlay { id: InlayId::Hint(post_inc(&mut id)), position: buffer_snapshot.anchor_at(offset, Bias::Left), - text: format!("test").into(), + text: "test".into(), }, Inlay { id: InlayId::Hint(post_inc(&mut id)), position: buffer_snapshot.anchor_at(offset, Bias::Right), - text: format!("test").into(), + text: "test".into(), }, ] }) - .flatten() .collect(); let snapshot = display_map.update(cx, |map, cx| { map.splice_inlays(Vec::new(), inlays, cx); @@ -841,7 +840,7 @@ mod tests { surrounding_word(&snapshot, display_points[1]), display_points[0]..display_points[2], "{}", - marked_text.to_string() + marked_text ); } @@ -863,7 +862,7 @@ mod tests { let mut cx = EditorTestContext::new(cx).await; let editor = cx.editor.clone(); - let window = cx.window.clone(); + let window = cx.window; _ = cx.update_window(window, |_, cx| { let text_layout_details = editor.update(cx, |editor, cx| editor.text_layout_details(cx)); diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index 31622a8c5e..1a950e0ea4 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -62,7 +62,6 @@ pub fn expand_macro_recursively( project .read(cx) .language_servers_for_buffer(buffer.read(cx), cx) - .into_iter() .find_map(|(adapter, server)| { if adapter.name.0.as_ref() == "rust-analyzer" { Some(( diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index c354f98150..4ac7374651 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -470,7 +470,7 @@ impl Editor { .buffer() .read(cx) .snapshot(cx) - .anchor_at(Point::new(top_row as u32, 0), Bias::Left); + .anchor_at(Point::new(top_row, 0), Bias::Left); let scroll_anchor = ScrollAnchor { offset: gpui::Point::new(x, y), anchor: top_anchor, diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index 191dbd04dc..3dfe2d250a 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/crates/editor/src/scroll/autoscroll.rs @@ -62,7 +62,7 @@ impl Editor { line_height: Pixels, cx: &mut ViewContext, ) -> bool { - let visible_lines = f32::from(viewport_height / line_height); + let visible_lines = viewport_height / line_height; let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut scroll_position = self.scroll_manager.scroll_position(&display_map); let max_scroll_top = if matches!(self.mode, EditorMode::AutoHeight { .. }) { @@ -241,11 +241,10 @@ impl Editor { let scroll_right = scroll_left + viewport_width; if target_left < scroll_left { - self.scroll_manager.anchor.offset.x = (target_left / max_glyph_width).into(); + self.scroll_manager.anchor.offset.x = target_left / max_glyph_width; true } else if target_right > scroll_right { - self.scroll_manager.anchor.offset.x = - ((target_right - viewport_width) / max_glyph_width).into(); + self.scroll_manager.anchor.offset.x = (target_right - viewport_width) / max_glyph_width; true } else { false diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index b0595ee778..d4d40b8563 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -478,7 +478,7 @@ impl<'a> MutableSelectionsCollection<'a> { if !oldest.start.cmp(&oldest.end, &self.buffer()).is_eq() { let head = oldest.head(); - oldest.start = head.clone(); + oldest.start = head; oldest.end = head; self.collection.disjoint = Arc::from([oldest]); self.selections_changed = true; @@ -794,8 +794,8 @@ impl<'a> MutableSelectionsCollection<'a> { let adjusted_disjoint: Vec<_> = anchors_with_status .chunks(2) .map(|selection_anchors| { - let (anchor_ix, start, kept_start) = selection_anchors[0].clone(); - let (_, end, kept_end) = selection_anchors[1].clone(); + let (anchor_ix, start, kept_start) = selection_anchors[0]; + let (_, end, kept_end) = selection_anchors[1]; let selection = &self.disjoint[anchor_ix / 2]; let kept_head = if selection.reversed { kept_start @@ -826,8 +826,8 @@ impl<'a> MutableSelectionsCollection<'a> { let buffer = self.buffer(); let anchors = buffer.refresh_anchors([&pending.selection.start, &pending.selection.end]); - let (_, start, kept_start) = anchors[0].clone(); - let (_, end, kept_end) = anchors[1].clone(); + let (_, start, kept_start) = anchors[0]; + let (_, end, kept_end) = anchors[1]; let kept_head = if pending.selection.reversed { kept_start } else { diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index 11ee49ac95..d4a5ee7c6e 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -236,7 +236,7 @@ impl EditorTestContext { pub fn set_state(&mut self, marked_text: &str) -> ContextHandle { let state_context = self.add_assertion_context(format!( "Initial Editor State: \"{}\"", - marked_text.escape_debug().to_string() + marked_text.escape_debug() )); let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update(&mut self.cx, |editor, cx| { @@ -252,7 +252,7 @@ impl EditorTestContext { pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle { let state_context = self.add_assertion_context(format!( "Initial Editor State: \"{}\"", - marked_text.escape_debug().to_string() + marked_text.escape_debug() )); let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true); self.editor.update(&mut self.cx, |editor, cx| { diff --git a/crates/extension/Cargo.toml b/crates/extension/Cargo.toml index 7c0e2d7afa..d5ceeeff4b 100644 --- a/crates/extension/Cargo.toml +++ b/crates/extension/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/extension_store.rs" diff --git a/crates/extension/src/extension_lsp_adapter.rs b/crates/extension/src/extension_lsp_adapter.rs index 7e3cc9cdc5..a981facef0 100644 --- a/crates/extension/src/extension_lsp_adapter.rs +++ b/crates/extension/src/extension_lsp_adapter.rs @@ -52,7 +52,7 @@ impl LspAdapter for ExtensionLspAdapter { .map_err(|e| anyhow!("{}", e))?; Ok(LanguageServerBinary { - path: self.work_dir.join(&command.command).into(), + path: self.work_dir.join(&command.command), arguments: command.args.into_iter().map(|arg| arg.into()).collect(), env: Some(command.env.into_iter().collect()), }) diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index 1386ae016c..e80ac6f5a4 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -555,6 +555,7 @@ impl ExtensionStore { language_name.clone(), language.grammar.clone(), language.matcher.clone(), + None, move || { let config = std::fs::read_to_string(language_path.join("config.toml"))?; let config: LanguageConfig = ::toml::from_str(&config)?; @@ -798,8 +799,8 @@ impl ExtensionStore { }, grammars: manifest_json .grammars - .into_iter() - .map(|(grammar_name, _)| (grammar_name, Default::default())) + .into_keys() + .map(|grammar_name| (grammar_name, Default::default())) .collect(), language_servers: Default::default(), }; diff --git a/crates/extension_api/Cargo.toml b/crates/extension_api/Cargo.toml index 1adbd0c0ee..84d2064eb5 100644 --- a/crates/extension_api/Cargo.toml +++ b/crates/extension_api/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2021" license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/extension_api.rs" diff --git a/crates/extensions_ui/Cargo.toml b/crates/extensions_ui/Cargo.toml index df55a5091a..7c2320717f 100644 --- a/crates/extensions_ui/Cargo.toml +++ b/crates/extensions_ui/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/extensions_ui.rs" diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 45778c85d6..cd07f8244e 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -368,7 +368,7 @@ impl ExtensionsPage { font_size: rems(0.875).into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.3).into(), + line_height: relative(1.3), background_color: None, underline: None, strikethrough: None, diff --git a/crates/feature_flags/Cargo.toml b/crates/feature_flags/Cargo.toml index cf0f9475af..101e90c646 100644 --- a/crates/feature_flags/Cargo.toml +++ b/crates/feature_flags/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/feature_flags.rs" diff --git a/crates/feature_flags/src/feature_flags.rs b/crates/feature_flags/src/feature_flags.rs index 0a3df28c69..700f70be78 100644 --- a/crates/feature_flags/src/feature_flags.rs +++ b/crates/feature_flags/src/feature_flags.rs @@ -8,7 +8,7 @@ struct FeatureFlags { impl FeatureFlags { fn has_flag(&self, flag: &str) -> bool { - self.staff || self.flags.iter().find(|f| f.as_str() == flag).is_some() + self.staff || self.flags.iter().any(|f| f.as_str() == flag) } } diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index b3c89e2c54..05465ba95b 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/feedback.rs" diff --git a/crates/file_finder/Cargo.toml b/crates/file_finder/Cargo.toml index 377bc2dc2a..283b7c5495 100644 --- a/crates/file_finder/Cargo.toml +++ b/crates/file_finder/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/file_finder.rs" doctest = false diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index dcade2781e..5f2c874457 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -701,7 +701,7 @@ impl PickerDelegate for FileFinderDelegate { raw_query: String, cx: &mut ViewContext>, ) -> Task<()> { - let raw_query = raw_query.replace(" ", ""); + let raw_query = raw_query.replace(' ', ""); let raw_query = raw_query.trim(); if raw_query.is_empty() { let project = self.project.read(cx); @@ -880,6 +880,7 @@ impl PickerDelegate for FileFinderDelegate { .child( h_flex() .gap_2() + .py_px() .child(HighlightedLabel::new(file_name, file_name_positions)) .child( HighlightedLabel::new(full_path, full_path_positions) diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 4b48dbc2bf..4a26a13e5f 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/fs.rs" diff --git a/crates/fsevent/Cargo.toml b/crates/fsevent/Cargo.toml index 23490e8fa5..6a5a01843d 100644 --- a/crates/fsevent/Cargo.toml +++ b/crates/fsevent/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "fsevent" -version = "2.0.2" -license = "MIT" +version = "0.1.0" edition = "2021" publish = false +license = "GPL-3.0-or-later" +[lints] +workspace = true [lib] path = "src/fsevent.rs" diff --git a/crates/fuzzy/Cargo.toml b/crates/fuzzy/Cargo.toml index 3b323afdaa..e3a016c98b 100644 --- a/crates/fuzzy/Cargo.toml +++ b/crates/fuzzy/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/fuzzy.rs" doctest = false diff --git a/crates/git/Cargo.toml b/crates/git/Cargo.toml index 720a0cdd32..4a47b74d59 100644 --- a/crates/git/Cargo.toml +++ b/crates/git/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/git.rs" diff --git a/crates/git/src/diff.rs b/crates/git/src/diff.rs index 07b0240f60..20f425f42c 100644 --- a/crates/git/src/diff.rs +++ b/crates/git/src/diff.rs @@ -208,8 +208,8 @@ impl BufferDiff { } } - fn process_patch_hunk<'a>( - patch: &GitPatch<'a>, + fn process_patch_hunk( + patch: &GitPatch<'_>, hunk_index: usize, buffer: &text::BufferSnapshot, buffer_row_divergence: &mut i64, diff --git a/crates/go_to_line/Cargo.toml b/crates/go_to_line/Cargo.toml index a009e27547..921434a407 100644 --- a/crates/go_to_line/Cargo.toml +++ b/crates/go_to_line/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/go_to_line.rs" doctest = false diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 70d4fe6e57..c4a2432ece 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -7,6 +7,9 @@ description = "Zed's GPU-accelerated UI framework" publish = false license = "Apache-2.0" +[lints] +workspace = true + [features] test-support = [ "backtrace", @@ -92,18 +95,8 @@ media.workspace = true metal = "0.25" objc = "0.2" -[target.'cfg(target_os = "linux")'.dependencies] +[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies] flume = "0.11" -open = "5.0.1" -ashpd = "0.7.0" -xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] } -wayland-client= { version = "0.31.2" } -wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] } -wayland-backend = { version = "0.3.3", features = ["client_system"] } -xkbcommon = { version = "0.7", features = ["wayland", "x11"] } -as-raw-xcb-connection = "1" -calloop = "0.12.4" -calloop-wayland-source = "0.2.0" #TODO: use these on all platforms blade-graphics.workspace = true blade-macros.workspace = true @@ -111,6 +104,24 @@ blade-rwh.workspace = true bytemuck = "1" cosmic-text = "0.10.0" +[target.'cfg(target_os = "linux")'.dependencies] +open = "5.0.1" +ashpd = "0.7.0" +xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] } +wayland-client= { version = "0.31.2" } +wayland-cursor = "0.31.1" +wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] } +wayland-backend = { version = "0.3.3", features = ["client_system"] } +xkbcommon = { version = "0.7", features = ["wayland", "x11"] } +as-raw-xcb-connection = "1" +calloop = "0.12.4" +calloop-wayland-source = "0.2.0" +copypasta = "0.10.1" +oo7 = "0.3.0" + +[target.'cfg(windows)'.dependencies] +windows.workspace = true + [[example]] name = "hello_world" path = "examples/hello_world.rs" diff --git a/crates/gpui/README.md b/crates/gpui/README.md index 5278b024ae..ac2c04bf07 100644 --- a/crates/gpui/README.md +++ b/crates/gpui/README.md @@ -21,7 +21,7 @@ GPUI offers three different [registers](https://en.wikipedia.org/wiki/Register_( - High level, declarative UI with Views. All UI in GPUI starts with a View. A view is simply a model that can be rendered, via the `Render` trait. At the start of each frame, GPUI will call this render method on the root view of a given window. Views build a tree of `elements`, lay them out and style them with a tailwind-style API, and then give them to GPUI to turn into pixels. See the `div` element for an all purpose swiss-army knife of rendering. -- Low level, imperative UI with Elements. Elements are the building blocks of UI in GPUI, and they provide a nice wrapper around an imperative API that provides as much flexibility and control as you need. Elements have total control over how they and their child elements are rendered and and can be used for making efficient views into large lists, implement custom layouting for a code editor, and anything else you can think of. See the `element` module for more information. +- Low level, imperative UI with Elements. Elements are the building blocks of UI in GPUI, and they provide a nice wrapper around an imperative API that provides as much flexibility and control as you need. Elements have total control over how they and their child elements are rendered and can be used for making efficient views into large lists, implement custom layouting for a code editor, and anything else you can think of. See the `element` module for more information. Each of these registers has one or more corresponding contexts that can be accessed from all GPUI services. This context is your main interface to GPUI, and is used extensively throughout the framework. diff --git a/crates/gpui/build.rs b/crates/gpui/build.rs index 6d6f384bc5..8f63787c8b 100644 --- a/crates/gpui/build.rs +++ b/crates/gpui/build.rs @@ -125,7 +125,7 @@ fn emit_stitched_shaders(header_path: &Path) { let shader_contents = std::fs::read_to_string(shader_path)?; let stitched_contents = format!("{header_contents}\n{shader_contents}"); let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("stitched_shaders.metal"); - let _ = std::fs::write(&out_path, stitched_contents)?; + std::fs::write(&out_path, stitched_contents)?; Ok(out_path) } let shader_source_path = "./src/platform/mac/shaders.metal"; diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs index d0578e6681..9361cbf437 100644 --- a/crates/gpui/examples/hello_world.rs +++ b/crates/gpui/examples/hello_world.rs @@ -23,7 +23,15 @@ impl Render for HelloWorld { fn main() { App::new().run(|cx: &mut AppContext| { - cx.open_window(WindowOptions::default(), |cx| { + let options = WindowOptions { + bounds: WindowBounds::Fixed(Bounds { + size: size(px(600.0), px(600.0)).into(), + origin: Default::default(), + }), + center: true, + ..Default::default() + }; + cx.open_window(options, |cx| { cx.new_view(|_cx| HelloWorld { text: "World".into(), }) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index f30e5264f1..9373ad66e8 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -566,6 +566,14 @@ impl AppContext { self.platform.open_url(url); } + /// register_url_scheme requests that the given scheme (e.g. `zed` for `zed://` urls) + /// is opened by the current app. + /// On some platforms (e.g. macOS) you may be able to register URL schemes as part of app + /// distribution, but this method exists to let you register schemes at runtime. + pub fn register_url_scheme(&self, scheme: &str) -> Task> { + self.platform.register_url_scheme(scheme) + } + /// Returns the full pathname of the current app bundle. /// If the app is not being run from a bundle, returns an error. pub fn app_path(&self) -> Result { diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 3a99bec94d..2b7d170e04 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -160,7 +160,7 @@ impl TestAppContext { /// Gives you an `&AppContext` for the duration of the closure pub fn read(&self, f: impl FnOnce(&AppContext) -> R) -> R { let cx = self.app.borrow(); - f(&*cx) + f(&cx) } /// Adds a new window. The Window will always be backed by a `TestWindow` which @@ -331,11 +331,11 @@ impl TestAppContext { /// This will also run the background executor until it's parked. pub fn simulate_keystrokes(&mut self, window: AnyWindowHandle, keystrokes: &str) { for keystroke in keystrokes - .split(" ") + .split(' ') .map(Keystroke::parse) .map(Result::unwrap) { - self.dispatch_keystroke(window, keystroke.into()); + self.dispatch_keystroke(window, keystroke); } self.background_executor.run_until_parked() @@ -347,7 +347,7 @@ impl TestAppContext { /// This will also run the background executor until it's parked. pub fn simulate_input(&mut self, window: AnyWindowHandle, input: &str) { for keystroke in input.split("").map(Keystroke::parse).map(Result::unwrap) { - self.dispatch_keystroke(window, keystroke.into()); + self.dispatch_keystroke(window, keystroke); } self.background_executor.run_until_parked() @@ -575,7 +575,7 @@ pub struct VisualTestContext { window: AnyWindowHandle, } -impl<'a> VisualTestContext { +impl VisualTestContext { /// Get the underlying window handle underlying this context. pub fn handle(&self) -> AnyWindowHandle { self.window diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index f449862668..90dd8da0c2 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -241,7 +241,7 @@ impl ListState { let mut cursor = state.items.cursor::(); cursor.seek(&Count(ix + 1), Bias::Right, &()); let bottom = cursor.start().height + padding.top; - let goal_top = px(0.).max(bottom - height); + let goal_top = px(0.).max(bottom - height + padding.bottom); cursor.seek(&Height(goal_top), Bias::Left, &()); let start_ix = cursor.start().count; diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 8a6651524b..2d708f270e 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -221,10 +221,10 @@ impl Element for UniformList { let item_top = item_height * ix + padding.top; let item_bottom = item_top + item_height; let scroll_top = -updated_scroll_offset.y; - if item_top < scroll_top { - updated_scroll_offset.y = -item_top; - } else if item_bottom > scroll_top + list_height { - updated_scroll_offset.y = -(item_bottom - list_height); + if item_top < scroll_top + padding.top { + updated_scroll_offset.y = -(item_top) + padding.top; + } else if item_bottom > scroll_top + list_height - padding.bottom { + updated_scroll_offset.y = -(item_bottom - list_height) - padding.bottom; } scroll_offset = *updated_scroll_offset; } diff --git a/crates/gpui/src/gpui.rs b/crates/gpui/src/gpui.rs index ff1daa59ea..ab9898d3da 100644 --- a/crates/gpui/src/gpui.rs +++ b/crates/gpui/src/gpui.rs @@ -35,7 +35,7 @@ //! //! - Low level, imperative UI with Elements. Elements are the building blocks of UI in GPUI, and they //! provide a nice wrapper around an imperative API that provides as much flexibility and control as -//! you need. Elements have total control over how they and their child elements are rendered and and +//! you need. Elements have total control over how they and their child elements are rendered and //! can be used for making efficient views into large lists, implement custom layouting for a code editor, //! and anything else you can think of. See the [`element`] module for more information. //! diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 346687bbf2..1e1b66fffe 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -12,12 +12,15 @@ mod linux; #[cfg(target_os = "macos")] mod mac; -#[cfg(any(target_os = "linux", feature = "macos-blade"))] +#[cfg(any(target_os = "linux", target_os = "windows", feature = "macos-blade"))] mod blade; #[cfg(any(test, feature = "test-support"))] mod test; +#[cfg(target_os = "windows")] +mod windows; + use crate::{ Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, @@ -54,6 +57,8 @@ pub(crate) use mac::*; pub(crate) use test::*; use time::UtcOffset; pub use util::SemanticVersion; +#[cfg(target_os = "windows")] +pub(crate) use windows::*; #[cfg(target_os = "macos")] pub(crate) fn current_platform() -> Rc { @@ -66,7 +71,7 @@ pub(crate) fn current_platform() -> Rc { // todo("windows") #[cfg(target_os = "windows")] pub(crate) fn current_platform() -> Rc { - unimplemented!() + Rc::new(WindowsPlatform::new()) } pub(crate) trait Platform: 'static { @@ -96,6 +101,8 @@ pub(crate) trait Platform: 'static { fn open_url(&self, url: &str); fn on_open_urls(&self, callback: Box)>); + fn register_url_scheme(&self, url: &str) -> Task>; + fn prompt_for_paths( &self, options: PathPromptOptions, diff --git a/crates/gpui/src/platform/blade/blade_renderer.rs b/crates/gpui/src/platform/blade/blade_renderer.rs index 554479ef1c..192f487d77 100644 --- a/crates/gpui/src/platform/blade/blade_renderer.rs +++ b/crates/gpui/src/platform/blade/blade_renderer.rs @@ -60,6 +60,7 @@ pub unsafe fn new_renderer( gpu::ContextDesc { validation: cfg!(debug_assertions), capture: false, + overlay: false, }, ) .unwrap(), diff --git a/crates/gpui/src/platform/linux/client.rs b/crates/gpui/src/platform/linux/client.rs index 0d71e4ef00..b5d154b7a7 100644 --- a/crates/gpui/src/platform/linux/client.rs +++ b/crates/gpui/src/platform/linux/client.rs @@ -1,7 +1,10 @@ +use std::cell::RefCell; use std::rc::Rc; +use copypasta::ClipboardProvider; + use crate::platform::PlatformWindow; -use crate::{AnyWindowHandle, DisplayId, PlatformDisplay, WindowOptions}; +use crate::{AnyWindowHandle, CursorStyle, DisplayId, PlatformDisplay, WindowOptions}; pub trait Client { fn displays(&self) -> Vec>; @@ -11,4 +14,7 @@ pub trait Client { handle: AnyWindowHandle, options: WindowOptions, ) -> Box; + fn set_cursor_style(&self, style: CursorStyle); + fn get_clipboard(&self) -> Rc>; + fn get_primary(&self) -> Rc>; } diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 74a2bd301f..2a7b8bfb2e 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -9,6 +9,7 @@ use std::{ time::Duration, }; +use anyhow::anyhow; use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest}; use async_task::Runnable; use calloop::{EventLoop, LoopHandle, LoopSignal}; @@ -107,6 +108,8 @@ impl LinuxPlatform { } } +const KEYRING_LABEL: &str = "zed-github-account"; + impl Platform for LinuxPlatform { fn background_executor(&self) -> BackgroundExecutor { self.inner.background_executor.clone() @@ -321,8 +324,11 @@ impl Platform for LinuxPlatform { }) } + //todo!(linux) fn app_path(&self) -> Result { - unimplemented!() + Err(anyhow::Error::msg( + "Platform::app_path is not implemented yet", + )) } // todo(linux) @@ -332,41 +338,113 @@ impl Platform for LinuxPlatform { UtcOffset::UTC } + //todo!(linux) fn path_for_auxiliary_executable(&self, name: &str) -> Result { - unimplemented!() + Err(anyhow::Error::msg( + "Platform::path_for_auxiliary_executable is not implemented yet", + )) } - // todo(linux) - fn set_cursor_style(&self, style: CursorStyle) {} + fn set_cursor_style(&self, style: CursorStyle) { + self.client.set_cursor_style(style) + } // todo(linux) fn should_auto_hide_scrollbars(&self) -> bool { false } - // todo(linux) - fn write_to_clipboard(&self, item: ClipboardItem) {} + fn write_to_clipboard(&self, item: ClipboardItem) { + let clipboard = self.client.get_clipboard(); + clipboard.borrow_mut().set_contents(item.text); + } - // todo(linux) fn read_from_clipboard(&self) -> Option { - None + let clipboard = self.client.get_clipboard(); + let contents = clipboard.borrow_mut().get_contents(); + match contents { + Ok(text) => Some(ClipboardItem { + metadata: None, + text, + }), + _ => None, + } } fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { - unimplemented!() + let url = url.to_string(); + let username = username.to_string(); + let password = password.to_vec(); + self.background_executor().spawn(async move { + let keyring = oo7::Keyring::new().await?; + keyring.unlock().await?; + keyring + .create_item( + KEYRING_LABEL, + &vec![("url", &url), ("username", &username)], + password, + true, + ) + .await?; + Ok(()) + }) } + //todo!(linux): add trait methods for accessing the primary selection + fn read_credentials(&self, url: &str) -> Task)>>> { - unimplemented!() + let url = url.to_string(); + self.background_executor().spawn(async move { + let keyring = oo7::Keyring::new().await?; + keyring.unlock().await?; + + let items = keyring.search_items(&vec![("url", &url)]).await?; + + for item in items.into_iter() { + if item.label().await.is_ok_and(|label| label == KEYRING_LABEL) { + let attributes = item.attributes().await?; + let username = attributes + .get("username") + .ok_or_else(|| anyhow!("Cannot find username in stored credentials"))?; + let secret = item.secret().await?; + + // we lose the zeroizing capabilities at this boundary, + // a current limitation GPUI's credentials api + return Ok(Some((username.to_string(), secret.to_vec()))); + } else { + continue; + } + } + Ok(None) + }) } fn delete_credentials(&self, url: &str) -> Task> { - unimplemented!() + let url = url.to_string(); + self.background_executor().spawn(async move { + let keyring = oo7::Keyring::new().await?; + keyring.unlock().await?; + + let items = keyring.search_items(&vec![("url", &url)]).await?; + + for item in items.into_iter() { + if item.label().await.is_ok_and(|label| label == KEYRING_LABEL) { + item.delete().await?; + return Ok(()); + } + } + + Ok(()) + }) } fn window_appearance(&self) -> crate::WindowAppearance { crate::WindowAppearance::Light } + + fn register_url_scheme(&self, _: &str) -> Task> { + Task::ready(Err(anyhow!("register_url_scheme unimplemented"))) + } } #[cfg(test)] diff --git a/crates/gpui/src/platform/linux/text_system.rs b/crates/gpui/src/platform/linux/text_system.rs index 8fd1be323b..70caa7175b 100644 --- a/crates/gpui/src/platform/linux/text_system.rs +++ b/crates/gpui/src/platform/linux/text_system.rs @@ -9,6 +9,8 @@ use cosmic_text::{ fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont, FontSystem, SwashCache, }; + +use itertools::Itertools; use parking_lot::{RwLock, RwLockUpgradableReadGuard}; use pathfinder_geometry::{ rect::{RectF, RectI}, @@ -71,9 +73,14 @@ impl PlatformTextSystem for LinuxTextSystem { .collect() } - // todo(linux) fn all_font_families(&self) -> Vec { - Vec::new() + self.0 + .read() + .font_system + .db() + .faces() + .filter_map(|face| Some(face.families.get(0)?.0.clone())) + .collect_vec() } fn font_id(&self, font: &Font) -> Result { diff --git a/crates/gpui/src/platform/linux/wayland.rs b/crates/gpui/src/platform/linux/wayland.rs index 79f3aa223c..ebb592d375 100644 --- a/crates/gpui/src/platform/linux/wayland.rs +++ b/crates/gpui/src/platform/linux/wayland.rs @@ -4,5 +4,6 @@ pub(crate) use client::*; mod client; +mod cursor; mod display; mod window; diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 957515c8ff..e8f20ebc0e 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -6,6 +6,8 @@ use std::time::Duration; use calloop::timer::{TimeoutAction, Timer}; use calloop::LoopHandle; use calloop_wayland_source::WaylandSource; +use copypasta::wayland_clipboard::{create_clipboards_from_external, Clipboard, Primary}; +use copypasta::ClipboardProvider; use wayland_backend::client::ObjectId; use wayland_backend::protocol::WEnum; use wayland_client::globals::{registry_queue_init, GlobalListContents}; @@ -32,12 +34,13 @@ use xkbcommon::xkb::ffi::XKB_KEYMAP_FORMAT_TEXT_V1; use xkbcommon::xkb::{self, Keycode, KEYMAP_COMPILE_NO_FLAGS}; use crate::platform::linux::client::Client; +use crate::platform::linux::wayland::cursor::Cursor; use crate::platform::linux::wayland::window::{WaylandDecorationState, WaylandWindow}; use crate::platform::{LinuxPlatformInner, PlatformWindow}; use crate::{ - platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, DisplayId, KeyDownEvent, - KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta, + platform::linux::wayland::window::WaylandWindowState, AnyWindowHandle, CursorStyle, DisplayId, + KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay, PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase, WindowOptions, }; @@ -47,6 +50,7 @@ const MIN_KEYCODE: u32 = 8; pub(crate) struct WaylandClientStateInner { compositor: wl_compositor::WlCompositor, wm_base: xdg_wm_base::XdgWmBase, + shm: wl_shm::WlShm, viewporter: Option, fractional_scale_manager: Option, decoration_manager: Option, @@ -63,8 +67,18 @@ pub(crate) struct WaylandClientStateInner { loop_handle: Rc>, } +pub(crate) struct CursorState { + cursor_icon_name: String, + cursor: Option, +} + #[derive(Clone)] -pub(crate) struct WaylandClientState(Rc>); +pub(crate) struct WaylandClientState { + client_state_inner: Rc>, + cursor_state: Rc>, + clipboard: Rc>, + primary: Rc>, +} pub(crate) struct KeyRepeat { characters_per_second: u32, @@ -101,9 +115,13 @@ impl WaylandClient { } }); + let display = conn.backend().display_ptr() as *mut std::ffi::c_void; + let (primary, clipboard) = unsafe { create_clipboards_from_external(display) }; + let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner { compositor: globals.bind(&qh, 1..=1, ()).unwrap(), wm_base: globals.bind(&qh, 1..=1, ()).unwrap(), + shm: globals.bind(&qh, 1..=1, ()).unwrap(), viewporter: globals.bind(&qh, 1..=1, ()).ok(), fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(), decoration_manager: globals.bind(&qh, 1..=1, ()).ok(), @@ -131,19 +149,30 @@ impl WaylandClient { loop_handle: Rc::clone(&linux_platform_inner.loop_handle), })); + let mut cursor_state = Rc::new(RefCell::new(CursorState { + cursor_icon_name: "arrow".to_string(), + cursor: None, + })); + let source = WaylandSource::new(conn, event_queue); - let mut state = WaylandClientState(Rc::clone(&state_inner)); + let mut state = WaylandClientState { + client_state_inner: Rc::clone(&state_inner), + cursor_state: Rc::clone(&cursor_state), + clipboard: Rc::new(RefCell::new(clipboard)), + primary: Rc::new(RefCell::new(primary)), + }; + let mut state_loop = state.clone(); linux_platform_inner .loop_handle .insert_source(source, move |_, queue, _| { - queue.dispatch_pending(&mut state) + queue.dispatch_pending(&mut state_loop) }) .unwrap(); Self { platform_inner: linux_platform_inner, - state: WaylandClientState(state_inner), + state, qh: Arc::new(qh), } } @@ -163,7 +192,7 @@ impl Client for WaylandClient { handle: AnyWindowHandle, options: WindowOptions, ) -> Box { - let mut state = self.state.0.borrow_mut(); + let mut state = self.state.client_state_inner.borrow_mut(); let wl_surface = state.compositor.create_surface(&self.qh, ()); let xdg_surface = state.wm_base.get_xdg_surface(&wl_surface, &self.qh, ()); @@ -217,6 +246,40 @@ impl Client for WaylandClient { state.windows.push((xdg_surface, Rc::clone(&window_state))); Box::new(WaylandWindow(window_state)) } + + fn set_cursor_style(&self, style: CursorStyle) { + let cursor_icon_name = match style { + CursorStyle::Arrow => "arrow".to_string(), + CursorStyle::IBeam => "text".to_string(), + CursorStyle::Crosshair => "crosshair".to_string(), + CursorStyle::ClosedHand => "grabbing".to_string(), + CursorStyle::OpenHand => "openhand".to_string(), + CursorStyle::PointingHand => "hand".to_string(), + CursorStyle::ResizeLeft => "w-resize".to_string(), + CursorStyle::ResizeRight => "e-resize".to_string(), + CursorStyle::ResizeLeftRight => "ew-resize".to_string(), + CursorStyle::ResizeUp => "n-resize".to_string(), + CursorStyle::ResizeDown => "s-resize".to_string(), + CursorStyle::ResizeUpDown => "ns-resize".to_string(), + CursorStyle::DisappearingItem => "grabbing".to_string(), // todo!(linux) - couldn't find equivalent icon in linux + CursorStyle::IBeamCursorForVerticalLayout => "vertical-text".to_string(), + CursorStyle::OperationNotAllowed => "not-allowed".to_string(), + CursorStyle::DragLink => "dnd-link".to_string(), + CursorStyle::DragCopy => "dnd-copy".to_string(), + CursorStyle::ContextualMenu => "context-menu".to_string(), + }; + + let mut cursor_state = self.state.cursor_state.borrow_mut(); + cursor_state.cursor_icon_name = cursor_icon_name; + } + + fn get_clipboard(&self) -> Rc> { + self.state.clipboard.clone() + } + + fn get_primary(&self) -> Rc> { + self.state.primary.clone() + } } impl Dispatch for WaylandClientState { @@ -263,7 +326,7 @@ impl Dispatch> for WaylandClientState { _: &Connection, qh: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let wl_callback::Event::Done { .. } = event { for window in &state.windows { if window.1.surface.id() == surf.id() { @@ -285,7 +348,7 @@ impl Dispatch for WaylandClientState { _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let xdg_surface::Event::Configure { serial, .. } = event { xdg_surface.ack_configure(serial); for window in &state.windows { @@ -308,7 +371,7 @@ impl Dispatch for WaylandClientState { _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let xdg_toplevel::Event::Configure { width, height, @@ -386,7 +449,7 @@ impl Dispatch for WaylandClientState { conn: &Connection, qh: &QueueHandle, ) { - let mut state = this.0.borrow_mut(); + let mut state = this.client_state_inner.borrow_mut(); match event { wl_keyboard::Event::RepeatInfo { rate, delay } => { state.repeat.characters_per_second = rate as u32; @@ -497,7 +560,7 @@ impl Dispatch for WaylandClientState { let this = this.clone(); let timer = Timer::from_duration(delay); - let state_ = Rc::clone(&this.0); + let state_ = Rc::clone(&this.client_state_inner); state .loop_handle .insert_source(timer, move |event, _metadata, shared_data| { @@ -510,11 +573,12 @@ impl Dispatch for WaylandClientState { return TimeoutAction::Drop; } - state_ - .keyboard_focused_window - .as_ref() - .unwrap() - .handle_input(input.clone()); + let focused_window = + state_.keyboard_focused_window.as_ref().unwrap().clone(); + + drop(state_); + + focused_window.handle_input(input.clone()); TimeoutAction::ToDuration(Duration::from_secs(1) / rate) }) @@ -567,9 +631,18 @@ impl Dispatch for WaylandClientState { conn: &Connection, qh: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut cursor_state = state.cursor_state.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); + + if cursor_state.cursor.is_none() { + cursor_state.cursor = Some(Cursor::new(&conn, &state.compositor, &qh, &state.shm, 24)); + } + let cursor_icon_name = cursor_state.cursor_icon_name.clone(); + let mut cursor: &mut Cursor = cursor_state.cursor.as_mut().unwrap(); + match event { wl_pointer::Event::Enter { + serial, surface, surface_x, surface_y, @@ -578,11 +651,14 @@ impl Dispatch for WaylandClientState { let mut mouse_focused_window = None; for window in &state.windows { if window.1.surface.id() == surface.id() { + window.1.set_focused(true); mouse_focused_window = Some(Rc::clone(&window.1)); } } if mouse_focused_window.is_some() { state.mouse_focused_window = mouse_focused_window; + cursor.set_serial_id(serial); + cursor.set_icon(&wl_pointer, cursor_icon_name); } state.mouse_location = Some(Point { @@ -610,6 +686,7 @@ impl Dispatch for WaylandClientState { modifiers: state.modifiers, }), ); + cursor.set_icon(&wl_pointer, cursor_icon_name); } wl_pointer::Event::Button { button, @@ -693,6 +770,7 @@ impl Dispatch for WaylandClientState { pressed_button: None, modifiers: Modifiers::default(), })); + focused_window.set_focused(false); } state.mouse_focused_window = None; state.mouse_location = None; @@ -711,7 +789,7 @@ impl Dispatch for Wayland _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let wp_fractional_scale_v1::Event::PreferredScale { scale, .. } = event { for window in &state.windows { if window.0.id() == *id { @@ -734,7 +812,7 @@ impl Dispatch _: &Connection, _: &QueueHandle, ) { - let mut state = state.0.borrow_mut(); + let mut state = state.client_state_inner.borrow_mut(); if let zxdg_toplevel_decoration_v1::Event::Configure { mode, .. } = event { for window in &state.windows { if window.0.id() == *surface_id { diff --git a/crates/gpui/src/platform/linux/wayland/cursor.rs b/crates/gpui/src/platform/linux/wayland/cursor.rs new file mode 100644 index 0000000000..8b641972e3 --- /dev/null +++ b/crates/gpui/src/platform/linux/wayland/cursor.rs @@ -0,0 +1,67 @@ +use crate::platform::linux::wayland::WaylandClientState; +use wayland_backend::client::InvalidId; +use wayland_client::protocol::wl_compositor::WlCompositor; +use wayland_client::protocol::wl_pointer::WlPointer; +use wayland_client::protocol::wl_shm::WlShm; +use wayland_client::protocol::wl_surface::WlSurface; +use wayland_client::{Connection, QueueHandle}; +use wayland_cursor::{CursorImageBuffer, CursorTheme}; + +pub(crate) struct Cursor { + theme: Result, + current_icon_name: String, + surface: WlSurface, + serial_id: u32, +} + +impl Cursor { + pub fn new( + connection: &Connection, + compositor: &WlCompositor, + qh: &QueueHandle, + shm: &WlShm, + size: u32, + ) -> Self { + Self { + theme: CursorTheme::load(&connection, shm.clone(), size), + current_icon_name: "".to_string(), + surface: compositor.create_surface(qh, ()), + serial_id: 0, + } + } + + pub fn set_serial_id(&mut self, serial_id: u32) { + self.serial_id = serial_id; + } + + pub fn set_icon(&mut self, wl_pointer: &WlPointer, cursor_icon_name: String) { + if self.current_icon_name != cursor_icon_name { + if self.theme.is_ok() { + if let Some(cursor) = self.theme.as_mut().unwrap().get_cursor(&cursor_icon_name) { + let buffer: &CursorImageBuffer = &cursor[0]; + let (width, height) = buffer.dimensions(); + let (hot_x, hot_y) = buffer.hotspot(); + + wl_pointer.set_cursor( + self.serial_id, + Some(&self.surface), + hot_x as i32, + hot_y as i32, + ); + self.surface.attach(Some(&buffer), 0, 0); + self.surface.damage(0, 0, width as i32, height as i32); + self.surface.commit(); + + self.current_icon_name = cursor_icon_name; + } else { + log::warn!( + "Linux: Wayland: Unable to get cursor icon: {}", + cursor_icon_name + ); + } + } else { + log::warn!("Linux: Wayland: Unable to load cursor themes"); + } + } + } +} diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 11e1743b03..9af7aa1ed8 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -83,6 +83,7 @@ impl WaylandWindowInner { gpu::ContextDesc { validation: false, capture: false, + overlay: false, }, ) } diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 8af38b512d..53fbc8747f 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -6,12 +6,14 @@ use xcb::{x, Xid as _}; use xkbcommon::xkb; use collections::HashMap; +use copypasta::x11_clipboard::{Clipboard, Primary, X11ClipboardContext}; +use copypasta::ClipboardProvider; use crate::platform::linux::client::Client; use crate::platform::{LinuxPlatformInner, PlatformWindow}; use crate::{ - AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size, - TouchPhase, WindowOptions, + AnyWindowHandle, Bounds, CursorStyle, DisplayId, PlatformDisplay, PlatformInput, Point, + ScrollDelta, Size, TouchPhase, WindowOptions, }; use super::{X11Display, X11Window, X11WindowState, XcbAtoms}; @@ -28,6 +30,8 @@ struct WindowRef { struct X11ClientState { windows: HashMap, xkb: xkbcommon::xkb::State, + clipboard: Rc>>, + primary: Rc>>, } pub(crate) struct X11Client { @@ -70,6 +74,9 @@ impl X11Client { xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id) }; + let clipboard = X11ClipboardContext::::new().unwrap(); + let primary = X11ClipboardContext::::new().unwrap(); + let client: Rc = Rc::new(Self { platform_inner: inner.clone(), xcb_connection: Rc::clone(&xcb_connection), @@ -78,6 +85,8 @@ impl X11Client { state: RefCell::new(X11ClientState { windows: HashMap::default(), xkb: xkb_state, + clipboard: Rc::new(RefCell::new(clipboard)), + primary: Rc::new(RefCell::new(primary)), }), }); @@ -351,6 +360,17 @@ impl Client for X11Client { self.state.borrow_mut().windows.insert(x_window, window_ref); Box::new(X11Window(window_ptr)) } + + //todo!(linux) + fn set_cursor_style(&self, _style: CursorStyle) {} + + fn get_clipboard(&self) -> Rc> { + self.state.borrow().clipboard.clone() + } + + fn get_primary(&self) -> Rc> { + self.state.borrow().primary.clone() + } } // Adatpted from: diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index f60f7f19b9..7c719c9ac7 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -237,6 +237,7 @@ impl X11WindowState { gpu::ContextDesc { validation: false, capture: false, + overlay: false, }, ) } diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8b35114b7f..d293f4cf40 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -496,11 +496,14 @@ impl Platform for MacPlatform { handle: AnyWindowHandle, options: WindowOptions, ) -> Box { + // Clippy thinks that this evaluates to `()`, for some reason. + #[allow(clippy::unit_arg, clippy::clone_on_copy)] + let renderer_context = self.0.lock().renderer_context.clone(); Box::new(MacWindow::open( handle, options, self.foreground_executor(), - self.0.lock().renderer_context.clone(), + renderer_context, )) } @@ -522,6 +525,49 @@ impl Platform for MacPlatform { } } + fn register_url_scheme(&self, scheme: &str) -> Task> { + // API only available post Monterey + // https://developer.apple.com/documentation/appkit/nsworkspace/3753004-setdefaultapplicationaturl + let (done_tx, done_rx) = oneshot::channel(); + if self.os_version().ok() < Some(SemanticVersion::new(12, 0, 0)) { + return Task::ready(Err(anyhow!( + "macOS 12.0 or later is required to register URL schemes" + ))); + } + + let bundle_id = unsafe { + let bundle: id = msg_send![class!(NSBundle), mainBundle]; + let bundle_id: id = msg_send![bundle, bundleIdentifier]; + if bundle_id == nil { + return Task::ready(Err(anyhow!("Can only register URL scheme in bundled apps"))); + } + bundle_id + }; + + unsafe { + let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace]; + let scheme: id = ns_string(scheme); + let app: id = msg_send![workspace, URLForApplicationWithBundleIdentifier: bundle_id]; + let done_tx = Cell::new(Some(done_tx)); + let block = ConcreteBlock::new(move |error: id| { + let result = if error == nil { + Ok(()) + } else { + let msg: id = msg_send![error, localizedDescription]; + Err(anyhow!("Failed to register: {:?}", msg)) + }; + + if let Some(done_tx) = done_tx.take() { + let _ = done_tx.send(result); + } + }); + let _: () = msg_send![workspace, setDefaultApplicationAtURL: app toOpenURLsWithScheme: scheme completionHandler: block]; + } + + self.background_executor() + .spawn(async { crate::Flatten::flatten(done_rx.await.map_err(|e| anyhow!(e))) }) + } + fn on_open_urls(&self, callback: Box)>) { self.0.lock().open_urls = Some(callback); } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index bdfd41a46f..8e48cbef30 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -530,8 +530,27 @@ impl MacWindow { } } + let window_rect = match options.bounds { + WindowBounds::Fullscreen => { + // Set a temporary size as we will asynchronously resize the window + NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)) + } + WindowBounds::Maximized => { + let display_bounds = display.bounds(); + global_bounds_to_ns_rect(display_bounds) + } + WindowBounds::Fixed(bounds) => { + let display_bounds = display.bounds(); + if bounds.intersects(&display_bounds) { + global_bounds_to_ns_rect(bounds) + } else { + global_bounds_to_ns_rect(display_bounds) + } + } + }; + let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_( - NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)), + window_rect, style_mask, NSBackingStoreBuffered, NO, @@ -685,25 +704,10 @@ impl MacWindow { native_window.orderFront_(nil); } - let screen = native_window.screen(); - match options.bounds { - WindowBounds::Fullscreen => { - // We need to toggle full screen asynchronously as doing so may - // call back into the platform handlers. - window.toggle_full_screen() - } - WindowBounds::Maximized => { - native_window.setFrame_display_(screen.visibleFrame(), YES); - } - WindowBounds::Fixed(bounds) => { - let display_bounds = display.bounds(); - let frame = if bounds.intersects(&display_bounds) { - global_bounds_to_ns_rect(bounds) - } else { - global_bounds_to_ns_rect(display_bounds) - }; - native_window.setFrame_display_(frame, YES); - } + if options.bounds == WindowBounds::Fullscreen { + // We need to toggle full screen asynchronously as doing so may + // call back into the platform handlers. + window.toggle_full_screen(); } window.0.lock().move_traffic_light(); diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index d97a4fc5ab..5df416f9ab 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -298,4 +298,8 @@ impl Platform for TestPlatform { fn double_click_interval(&self) -> std::time::Duration { Duration::from_millis(500) } + + fn register_url_scheme(&self, _: &str) -> Task> { + unimplemented!() + } } diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs new file mode 100644 index 0000000000..4347b9169a --- /dev/null +++ b/crates/gpui/src/platform/windows.rs @@ -0,0 +1,13 @@ +mod dispatcher; +mod display; +mod platform; +mod text_system; +mod util; +mod window; + +pub(crate) use dispatcher::*; +pub(crate) use display::*; +pub(crate) use platform::*; +pub(crate) use text_system::*; +pub(crate) use util::*; +pub(crate) use window::*; diff --git a/crates/gpui/src/platform/windows/dispatcher.rs b/crates/gpui/src/platform/windows/dispatcher.rs new file mode 100644 index 0000000000..16fdcd2b85 --- /dev/null +++ b/crates/gpui/src/platform/windows/dispatcher.rs @@ -0,0 +1,159 @@ +use std::{ + cmp::Ordering, + thread::{current, JoinHandle, ThreadId}, + time::{Duration, Instant}, +}; + +use async_task::Runnable; +use collections::BinaryHeap; +use flume::{RecvTimeoutError, Sender}; +use parking::Parker; +use parking_lot::Mutex; +use windows::Win32::{Foundation::HANDLE, System::Threading::SetEvent}; + +use crate::{PlatformDispatcher, TaskLabel}; + +pub(crate) struct WindowsDispatcher { + background_sender: Sender<(Runnable, Option)>, + main_sender: Sender, + timer_sender: Sender<(Runnable, Duration)>, + background_threads: Vec>, + timer_thread: JoinHandle<()>, + parker: Mutex, + main_thread_id: ThreadId, + event: HANDLE, +} + +impl WindowsDispatcher { + pub(crate) fn new(main_sender: Sender, event: HANDLE) -> Self { + let parker = Mutex::new(Parker::new()); + let (background_sender, background_receiver) = + flume::unbounded::<(Runnable, Option)>(); + let background_threads = (0..std::thread::available_parallelism() + .map(|i| i.get()) + .unwrap_or(1)) + .map(|_| { + let receiver = background_receiver.clone(); + std::thread::spawn(move || { + for (runnable, label) in receiver { + if let Some(label) = label { + log::debug!("TaskLabel: {label:?}"); + } + runnable.run(); + } + }) + }) + .collect::>(); + let (timer_sender, timer_receiver) = flume::unbounded::<(Runnable, Duration)>(); + let timer_thread = std::thread::spawn(move || { + let mut runnables = BinaryHeap::::new(); + let mut timeout_dur = None; + loop { + let recv = if let Some(dur) = timeout_dur { + match timer_receiver.recv_timeout(dur) { + Ok(recv) => Some(recv), + Err(RecvTimeoutError::Timeout) => None, + Err(RecvTimeoutError::Disconnected) => break, + } + } else if let Ok(recv) = timer_receiver.recv() { + Some(recv) + } else { + break; + }; + let now = Instant::now(); + if let Some((runnable, dur)) = recv { + runnables.push(RunnableAfter { + runnable, + instant: now + dur, + }); + while let Ok((runnable, dur)) = timer_receiver.try_recv() { + runnables.push(RunnableAfter { + runnable, + instant: now + dur, + }) + } + } + while runnables.peek().is_some_and(|entry| entry.instant <= now) { + runnables.pop().unwrap().runnable.run(); + } + timeout_dur = runnables.peek().map(|entry| entry.instant - now); + } + }); + let main_thread_id = current().id(); + Self { + background_sender, + main_sender, + timer_sender, + background_threads, + timer_thread, + parker, + main_thread_id, + event, + } + } +} + +impl PlatformDispatcher for WindowsDispatcher { + fn is_main_thread(&self) -> bool { + current().id() == self.main_thread_id + } + + fn dispatch(&self, runnable: Runnable, label: Option) { + self.background_sender + .send((runnable, label)) + .inspect_err(|e| log::error!("Dispatch failed: {e}")) + .ok(); + } + + fn dispatch_on_main_thread(&self, runnable: Runnable) { + self.main_sender + .send(runnable) + .inspect_err(|e| log::error!("Dispatch failed: {e}")) + .ok(); + unsafe { SetEvent(self.event) }.ok(); + } + + fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) { + self.timer_sender + .send((runnable, duration)) + .inspect_err(|e| log::error!("Dispatch failed: {e}")) + .ok(); + } + + fn tick(&self, _background_only: bool) -> bool { + false + } + + fn park(&self) { + self.parker.lock().park(); + } + + fn unparker(&self) -> parking::Unparker { + self.parker.lock().unparker() + } +} + +struct RunnableAfter { + runnable: Runnable, + instant: Instant, +} + +impl PartialEq for RunnableAfter { + fn eq(&self, other: &Self) -> bool { + self.instant == other.instant + } +} + +impl Eq for RunnableAfter {} + +impl Ord for RunnableAfter { + fn cmp(&self, other: &Self) -> Ordering { + self.instant.cmp(&other.instant).reverse() + } +} + +impl PartialOrd for RunnableAfter { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/crates/gpui/src/platform/windows/display.rs b/crates/gpui/src/platform/windows/display.rs new file mode 100644 index 0000000000..448433b6fb --- /dev/null +++ b/crates/gpui/src/platform/windows/display.rs @@ -0,0 +1,36 @@ +use anyhow::{anyhow, Result}; +use uuid::Uuid; + +use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size}; + +#[derive(Debug)] +pub(crate) struct WindowsDisplay; + +impl WindowsDisplay { + pub(crate) fn new() -> Self { + Self + } +} + +impl PlatformDisplay for WindowsDisplay { + // todo!("windows") + fn id(&self) -> DisplayId { + DisplayId(1) + } + + // todo!("windows") + fn uuid(&self) -> Result { + Err(anyhow!("not implemented yet.")) + } + + // todo!("windows") + fn bounds(&self) -> Bounds { + Bounds::new( + Point::new(0.0.into(), 0.0.into()), + Size { + width: 1920.0.into(), + height: 1280.0.into(), + }, + ) + } +} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs new file mode 100644 index 0000000000..26fda0be37 --- /dev/null +++ b/crates/gpui/src/platform/windows/platform.rs @@ -0,0 +1,427 @@ +// todo!("windows"): remove +#![allow(unused_variables)] + +use std::{ + cell::RefCell, + collections::HashSet, + ffi::{c_uint, c_void}, + path::{Path, PathBuf}, + rc::Rc, + sync::Arc, + time::Duration, +}; + +use anyhow::{anyhow, Result}; +use async_task::Runnable; +use futures::channel::oneshot::Receiver; +use parking_lot::Mutex; +use time::UtcOffset; +use util::{ResultExt, SemanticVersion}; +use windows::Win32::{ + Foundation::{CloseHandle, GetLastError, HANDLE, HWND, WAIT_EVENT}, + System::Threading::{CreateEventW, INFINITE}, + UI::WindowsAndMessaging::{ + DispatchMessageW, GetMessageW, MsgWaitForMultipleObjects, PostQuitMessage, + SystemParametersInfoW, TranslateMessage, MSG, QS_ALLINPUT, SPI_GETWHEELSCROLLCHARS, + SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE, + }, +}; + +use crate::{ + try_get_window_inner, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, + ForegroundExecutor, Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, + PlatformTextSystem, PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher, + WindowsDisplay, WindowsTextSystem, WindowsWindow, +}; + +pub(crate) struct WindowsPlatform { + inner: Rc, +} + +/// Windows settings pulled from SystemParametersInfo +/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow +#[derive(Default, Debug)] +pub(crate) struct WindowsPlatformSystemSettings { + /// SEE: SPI_GETWHEELSCROLLCHARS + pub(crate) wheel_scroll_chars: u32, + + /// SEE: SPI_GETWHEELSCROLLLINES + pub(crate) wheel_scroll_lines: u32, +} + +pub(crate) struct WindowsPlatformInner { + background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, + main_receiver: flume::Receiver, + text_system: Arc, + callbacks: Mutex, + pub(crate) window_handles: RefCell>, + pub(crate) event: HANDLE, + pub(crate) settings: RefCell, +} + +impl Drop for WindowsPlatformInner { + fn drop(&mut self) { + unsafe { CloseHandle(self.event) }.ok(); + } +} + +#[derive(Default)] +struct Callbacks { + open_urls: Option)>>, + become_active: Option>, + resign_active: Option>, + quit: Option>, + reopen: Option>, + event: Option bool>>, + app_menu_action: Option>, + will_open_app_menu: Option>, + validate_app_menu_command: Option bool>>, +} + +enum WindowsMessageWaitResult { + ForegroundExecution, + WindowsMessage(MSG), + Error, +} + +impl WindowsPlatformSystemSettings { + fn new() -> Self { + let mut settings = Self::default(); + settings.update_all(); + settings + } + + pub(crate) fn update_all(&mut self) { + self.update_wheel_scroll_lines(); + self.update_wheel_scroll_chars(); + } + + pub(crate) fn update_wheel_scroll_lines(&mut self) { + let mut value = c_uint::default(); + let result = unsafe { + SystemParametersInfoW( + SPI_GETWHEELSCROLLLINES, + 0, + Some((&mut value) as *mut c_uint as *mut c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(), + ) + }; + + if result.log_err() != None { + self.wheel_scroll_lines = value; + } + } + + pub(crate) fn update_wheel_scroll_chars(&mut self) { + let mut value = c_uint::default(); + let result = unsafe { + SystemParametersInfoW( + SPI_GETWHEELSCROLLCHARS, + 0, + Some((&mut value) as *mut c_uint as *mut c_void), + SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(), + ) + }; + + if result.log_err() != None { + self.wheel_scroll_chars = value; + } + } +} + +impl WindowsPlatform { + pub(crate) fn new() -> Self { + let (main_sender, main_receiver) = flume::unbounded::(); + let event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); + let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, event)); + let background_executor = BackgroundExecutor::new(dispatcher.clone()); + let foreground_executor = ForegroundExecutor::new(dispatcher); + let text_system = Arc::new(WindowsTextSystem::new()); + let callbacks = Mutex::new(Callbacks::default()); + let window_handles = RefCell::new(HashSet::new()); + let settings = RefCell::new(WindowsPlatformSystemSettings::new()); + let inner = Rc::new(WindowsPlatformInner { + background_executor, + foreground_executor, + main_receiver, + text_system, + callbacks, + window_handles, + event, + settings, + }); + Self { inner } + } + + /// runs message handlers that should be processed before dispatching to prevent translating unnecessary messages + /// returns true if message is handled and should not dispatch + fn run_immediate_msg_handlers(&self, msg: &MSG) -> bool { + if msg.message == WM_SETTINGCHANGE { + self.inner.settings.borrow_mut().update_all(); + return true; + } + + if let Some(inner) = try_get_window_inner(msg.hwnd) { + inner.handle_immediate_msg(msg.message, msg.wParam, msg.lParam) + } else { + false + } + } + + fn wait_message(&self) -> WindowsMessageWaitResult { + let wait_result = unsafe { + MsgWaitForMultipleObjects(Some(&[self.inner.event]), false, INFINITE, QS_ALLINPUT) + }; + + match wait_result { + WAIT_EVENT(0) => WindowsMessageWaitResult::ForegroundExecution, + WAIT_EVENT(1) => { + let mut msg = MSG::default(); + unsafe { GetMessageW(&mut msg, HWND::default(), 0, 0) }; + WindowsMessageWaitResult::WindowsMessage(msg) + } + _ => { + log::error!("unhandled windows wait message: {}", wait_result.0); + WindowsMessageWaitResult::Error + } + } + } +} + +impl Platform for WindowsPlatform { + fn background_executor(&self) -> BackgroundExecutor { + self.inner.background_executor.clone() + } + + fn foreground_executor(&self) -> ForegroundExecutor { + self.inner.foreground_executor.clone() + } + + fn text_system(&self) -> Arc { + self.inner.text_system.clone() + } + + fn run(&self, on_finish_launching: Box) { + on_finish_launching(); + loop { + match self.wait_message() { + WindowsMessageWaitResult::ForegroundExecution => { + for runnable in self.inner.main_receiver.drain() { + runnable.run(); + } + } + WindowsMessageWaitResult::WindowsMessage(msg) => { + if msg.message == WM_QUIT { + break; + } + + if !self.run_immediate_msg_handlers(&msg) { + unsafe { TranslateMessage(&msg) }; + unsafe { DispatchMessageW(&msg) }; + } + } + WindowsMessageWaitResult::Error => {} + } + } + + let mut callbacks = self.inner.callbacks.lock(); + if let Some(callback) = callbacks.quit.as_mut() { + callback() + } + } + + fn quit(&self) { + self.foreground_executor() + .spawn(async { unsafe { PostQuitMessage(0) } }) + .detach(); + } + + // todo!("windows") + fn restart(&self) { + unimplemented!() + } + + // todo!("windows") + fn activate(&self, ignoring_other_apps: bool) {} + + // todo!("windows") + fn hide(&self) { + unimplemented!() + } + + // todo!("windows") + fn hide_other_apps(&self) { + unimplemented!() + } + + // todo!("windows") + fn unhide_other_apps(&self) { + unimplemented!() + } + + // todo!("windows") + fn displays(&self) -> Vec> { + vec![Rc::new(WindowsDisplay::new())] + } + + // todo!("windows") + fn display(&self, id: crate::DisplayId) -> Option> { + Some(Rc::new(WindowsDisplay::new())) + } + + // todo!("windows") + fn active_window(&self) -> Option { + unimplemented!() + } + + fn open_window( + &self, + handle: AnyWindowHandle, + options: WindowOptions, + ) -> Box { + Box::new(WindowsWindow::new(self.inner.clone(), handle, options)) + } + + // todo!("windows") + fn window_appearance(&self) -> WindowAppearance { + WindowAppearance::Dark + } + + // todo!("windows") + fn open_url(&self, url: &str) { + // todo!("windows") + } + + // todo!("windows") + fn on_open_urls(&self, callback: Box)>) { + self.inner.callbacks.lock().open_urls = Some(callback); + } + + // todo!("windows") + fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver>> { + unimplemented!() + } + + // todo!("windows") + fn prompt_for_new_path(&self, directory: &Path) -> Receiver> { + unimplemented!() + } + + // todo!("windows") + fn reveal_path(&self, path: &Path) { + unimplemented!() + } + + fn on_become_active(&self, callback: Box) { + self.inner.callbacks.lock().become_active = Some(callback); + } + + fn on_resign_active(&self, callback: Box) { + self.inner.callbacks.lock().resign_active = Some(callback); + } + + fn on_quit(&self, callback: Box) { + self.inner.callbacks.lock().quit = Some(callback); + } + + fn on_reopen(&self, callback: Box) { + self.inner.callbacks.lock().reopen = Some(callback); + } + + fn on_event(&self, callback: Box bool>) { + self.inner.callbacks.lock().event = Some(callback); + } + + // todo!("windows") + fn set_menus(&self, menus: Vec, keymap: &Keymap) {} + + fn on_app_menu_action(&self, callback: Box) { + self.inner.callbacks.lock().app_menu_action = Some(callback); + } + + fn on_will_open_app_menu(&self, callback: Box) { + self.inner.callbacks.lock().will_open_app_menu = Some(callback); + } + + fn on_validate_app_menu_command(&self, callback: Box bool>) { + self.inner.callbacks.lock().validate_app_menu_command = Some(callback); + } + + fn os_name(&self) -> &'static str { + "Windows" + } + + fn os_version(&self) -> Result { + Ok(SemanticVersion { + major: 1, + minor: 0, + patch: 0, + }) + } + + fn app_version(&self) -> Result { + Ok(SemanticVersion { + major: 1, + minor: 0, + patch: 0, + }) + } + + // todo!("windows") + fn app_path(&self) -> Result { + Err(anyhow!("not yet implemented")) + } + + // todo!("windows") + fn local_timezone(&self) -> UtcOffset { + UtcOffset::from_hms(9, 0, 0).unwrap() + } + + // todo!("windows") + fn double_click_interval(&self) -> Duration { + Duration::from_millis(100) + } + + // todo!("windows") + fn path_for_auxiliary_executable(&self, name: &str) -> Result { + Err(anyhow!("not yet implemented")) + } + + // todo!("windows") + fn set_cursor_style(&self, style: CursorStyle) {} + + // todo!("windows") + fn should_auto_hide_scrollbars(&self) -> bool { + false + } + + // todo!("windows") + fn write_to_clipboard(&self, item: ClipboardItem) { + unimplemented!() + } + + // todo!("windows") + fn read_from_clipboard(&self) -> Option { + unimplemented!() + } + + // todo!("windows") + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { + Task::Ready(Some(Err(anyhow!("not implemented yet.")))) + } + + // todo!("windows") + fn read_credentials(&self, url: &str) -> Task)>>> { + Task::Ready(Some(Err(anyhow!("not implemented yet.")))) + } + + // todo!("windows") + fn delete_credentials(&self, url: &str) -> Task> { + Task::Ready(Some(Err(anyhow!("not implemented yet.")))) + } + + fn register_url_scheme(&self, _: &str) -> Task> { + Task::ready(Err(anyhow!("register_url_scheme unimplemented"))) + } +} diff --git a/crates/gpui/src/platform/windows/text_system.rs b/crates/gpui/src/platform/windows/text_system.rs new file mode 100644 index 0000000000..411e1a8d28 --- /dev/null +++ b/crates/gpui/src/platform/windows/text_system.rs @@ -0,0 +1,440 @@ +use crate::{ + point, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, + FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, + ShapedGlyph, SharedString, Size, +}; +use anyhow::{anyhow, Context, Ok, Result}; +use collections::HashMap; +use cosmic_text::{ + fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont, + FontSystem, SwashCache, +}; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; +use pathfinder_geometry::{ + rect::{RectF, RectI}, + vector::{Vector2F, Vector2I}, +}; +use smallvec::SmallVec; +use std::{borrow::Cow, sync::Arc}; + +pub(crate) struct WindowsTextSystem(RwLock); + +struct WindowsTextSystemState { + swash_cache: SwashCache, + font_system: FontSystem, + fonts: Vec>, + font_selections: HashMap, + font_ids_by_family_name: HashMap>, + postscript_names_by_font_id: HashMap, +} + +impl WindowsTextSystem { + pub(crate) fn new() -> Self { + let mut font_system = FontSystem::new(); + + // todo!("windows") make font loading non-blocking + font_system.db_mut().load_system_fonts(); + + Self(RwLock::new(WindowsTextSystemState { + font_system, + swash_cache: SwashCache::new(), + fonts: Vec::new(), + font_selections: HashMap::default(), + // font_ids_by_postscript_name: HashMap::default(), + font_ids_by_family_name: HashMap::default(), + postscript_names_by_font_id: HashMap::default(), + })) + } +} + +impl Default for WindowsTextSystem { + fn default() -> Self { + Self::new() + } +} + +#[allow(unused)] +impl PlatformTextSystem for WindowsTextSystem { + fn add_fonts(&self, fonts: Vec>) -> Result<()> { + self.0.write().add_fonts(fonts) + } + + // todo!("windows") ensure that this integrates with platform font loading + // do we need to do more than call load_system_fonts()? + fn all_font_names(&self) -> Vec { + self.0 + .read() + .font_system + .db() + .faces() + .map(|face| face.post_script_name.clone()) + .collect() + } + + // todo!("windows") + fn all_font_families(&self) -> Vec { + Vec::new() + } + + fn font_id(&self, font: &Font) -> Result { + // todo!("windows"): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit? + let lock = self.0.upgradable_read(); + if let Some(font_id) = lock.font_selections.get(font) { + Ok(*font_id) + } else { + let mut lock = RwLockUpgradableReadGuard::upgrade(lock); + let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family) + { + font_ids.as_slice() + } else { + let font_ids = lock.load_family(&font.family, font.features)?; + lock.font_ids_by_family_name + .insert(font.family.clone(), font_ids); + lock.font_ids_by_family_name[&font.family].as_ref() + }; + + let id = lock + .font_system + .db() + .query(&Query { + families: &[Family::Name(&font.family)], + weight: font.weight.into(), + style: font.style.into(), + stretch: Default::default(), + }) + .context("no font")?; + + let font_id = if let Some(font_id) = lock.fonts.iter().position(|font| font.id() == id) + { + FontId(font_id) + } else { + // Font isn't in fonts so add it there, this is because we query all the fonts in the db + // and maybe we haven't loaded it yet + let font_id = FontId(lock.fonts.len()); + let font = lock.font_system.get_font(id).unwrap(); + lock.fonts.push(font); + font_id + }; + + lock.font_selections.insert(font.clone(), font_id); + Ok(font_id) + } + } + + fn font_metrics(&self, font_id: FontId) -> FontMetrics { + let metrics = self.0.read().fonts[font_id.0].as_swash().metrics(&[]); + + FontMetrics { + units_per_em: metrics.units_per_em as u32, + ascent: metrics.ascent, + descent: -metrics.descent, // todo!("windows") confirm this is correct + line_gap: metrics.leading, + underline_position: metrics.underline_offset, + underline_thickness: metrics.stroke_size, + cap_height: metrics.cap_height, + x_height: metrics.x_height, + // todo!("windows"): Compute this correctly + bounding_box: Bounds { + origin: point(0.0, 0.0), + size: size(metrics.max_width, metrics.ascent + metrics.descent), + }, + } + } + + fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + let lock = self.0.read(); + let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]); + let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]); + let glyph_id = glyph_id.0 as u16; + // todo!("windows"): Compute this correctly + // see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620 + Ok(Bounds { + origin: point(0.0, 0.0), + size: size( + glyph_metrics.advance_width(glyph_id), + glyph_metrics.advance_height(glyph_id), + ), + }) + } + + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + self.0.read().advance(font_id, glyph_id) + } + + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { + self.0.read().glyph_for_char(font_id, ch) + } + + fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result> { + self.0.write().raster_bounds(params) + } + + fn rasterize_glyph( + &self, + params: &RenderGlyphParams, + raster_bounds: Bounds, + ) -> Result<(Size, Vec)> { + self.0.write().rasterize_glyph(params, raster_bounds) + } + + fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout { + self.0.write().layout_line(text, font_size, runs) + } + + // todo!("windows") Confirm that this has been superseded by the LineWrapper + fn wrap_line( + &self, + text: &str, + font_id: FontId, + font_size: Pixels, + width: Pixels, + ) -> Vec { + unimplemented!() + } +} + +impl WindowsTextSystemState { + #[profiling::function] + fn add_fonts(&mut self, fonts: Vec>) -> Result<()> { + let db = self.font_system.db_mut(); + for bytes in fonts { + match bytes { + Cow::Borrowed(embedded_font) => { + db.load_font_data(embedded_font.to_vec()); + } + Cow::Owned(bytes) => { + db.load_font_data(bytes); + } + } + } + Ok(()) + } + + #[profiling::function] + fn load_family( + &mut self, + name: &SharedString, + _features: FontFeatures, + ) -> Result> { + let mut font_ids = SmallVec::new(); + let family = self + .font_system + .get_font_matches(Attrs::new().family(cosmic_text::Family::Name(name))); + for font in family.as_ref() { + let font = self.font_system.get_font(*font).unwrap(); + if font.as_swash().charmap().map('m') == 0 { + self.font_system.db_mut().remove_face(font.id()); + continue; + }; + + let font_id = FontId(self.fonts.len()); + font_ids.push(font_id); + self.fonts.push(font); + } + Ok(font_ids) + } + + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + let width = self.fonts[font_id.0] + .as_swash() + .glyph_metrics(&[]) + .advance_width(glyph_id.0 as u16); + let height = self.fonts[font_id.0] + .as_swash() + .glyph_metrics(&[]) + .advance_height(glyph_id.0 as u16); + Ok(Size { width, height }) + } + + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { + let glyph_id = self.fonts[font_id.0].as_swash().charmap().map(ch); + if glyph_id == 0 { + None + } else { + Some(GlyphId(glyph_id.into())) + } + } + + fn is_emoji(&self, font_id: FontId) -> bool { + // todo!("windows"): implement this correctly + self.postscript_names_by_font_id + .get(&font_id) + .map_or(false, |postscript_name| { + postscript_name == "AppleColorEmoji" + }) + } + + // todo!("windows") both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system + fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result> { + let font = &self.fonts[params.font_id.0]; + let font_system = &mut self.font_system; + let image = self + .swash_cache + .get_image( + font_system, + CacheKey::new( + font.id(), + params.glyph_id.0 as u16, + (params.font_size * params.scale_factor).into(), + (0.0, 0.0), + ) + .0, + ) + .clone() + .unwrap(); + Ok(Bounds { + origin: point(image.placement.left.into(), (-image.placement.top).into()), + size: size(image.placement.width.into(), image.placement.height.into()), + }) + } + + #[profiling::function] + fn rasterize_glyph( + &mut self, + params: &RenderGlyphParams, + glyph_bounds: Bounds, + ) -> Result<(Size, Vec)> { + if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { + Err(anyhow!("glyph bounds are empty")) + } else { + // todo!("windows") handle subpixel variants + let bitmap_size = glyph_bounds.size; + let font = &self.fonts[params.font_id.0]; + let font_system = &mut self.font_system; + let image = self + .swash_cache + .get_image( + font_system, + CacheKey::new( + font.id(), + params.glyph_id.0 as u16, + (params.font_size * params.scale_factor).into(), + (0.0, 0.0), + ) + .0, + ) + .clone() + .unwrap(); + + Ok((bitmap_size, image.data)) + } + } + + // todo!("windows") This is all a quick first pass, maybe we should be using cosmic_text::Buffer + #[profiling::function] + fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { + let mut attrs_list = AttrsList::new(Attrs::new()); + let mut offs = 0; + for run in font_runs { + // todo!("windows") We need to check we are doing utf properly + let font = &self.fonts[run.font_id.0]; + let font = self.font_system.db().face(font.id()).unwrap(); + attrs_list.add_span( + offs..offs + run.len, + Attrs::new() + .family(Family::Name(&font.families.first().unwrap().0)) + .stretch(font.stretch) + .style(font.style) + .weight(font.weight), + ); + offs += run.len; + } + let mut line = BufferLine::new(text, attrs_list, cosmic_text::Shaping::Advanced); + let layout = line.layout( + &mut self.font_system, + font_size.0, + f32::MAX, // todo!("windows") we don't have a width cause this should technically not be wrapped I believe + cosmic_text::Wrap::None, + ); + let mut runs = Vec::new(); + // todo!("windows") what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering + let layout = layout.first().unwrap(); + for glyph in &layout.glyphs { + let font_id = glyph.font_id; + let font_id = FontId( + self.fonts + .iter() + .position(|font| font.id() == font_id) + .unwrap(), + ); + let mut glyphs = SmallVec::new(); + // todo!("windows") this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction + glyphs.push(ShapedGlyph { + id: GlyphId(glyph.glyph_id as u32), + position: point((glyph.x).into(), glyph.y.into()), + index: glyph.start, + is_emoji: self.is_emoji(font_id), + }); + runs.push(crate::ShapedRun { font_id, glyphs }); + } + LineLayout { + font_size, + width: layout.w.into(), + ascent: layout.max_ascent.into(), + descent: layout.max_descent.into(), + runs, + len: text.len(), + } + } +} + +impl From for Bounds { + fn from(rect: RectF) -> Self { + Bounds { + origin: point(rect.origin_x(), rect.origin_y()), + size: size(rect.width(), rect.height()), + } + } +} + +impl From for Bounds { + fn from(rect: RectI) -> Self { + Bounds { + origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())), + size: size(DevicePixels(rect.width()), DevicePixels(rect.height())), + } + } +} + +impl From for Size { + fn from(value: Vector2I) -> Self { + size(value.x().into(), value.y().into()) + } +} + +impl From for Bounds { + fn from(rect: RectI) -> Self { + Bounds { + origin: point(rect.origin_x(), rect.origin_y()), + size: size(rect.width(), rect.height()), + } + } +} + +impl From> for Vector2I { + fn from(size: Point) -> Self { + Vector2I::new(size.x as i32, size.y as i32) + } +} + +impl From for Size { + fn from(vec: Vector2F) -> Self { + size(vec.x(), vec.y()) + } +} + +impl From for cosmic_text::Weight { + fn from(value: FontWeight) -> Self { + cosmic_text::Weight(value.0 as u16) + } +} + +impl From for cosmic_text::Style { + fn from(style: FontStyle) -> Self { + match style { + FontStyle::Normal => cosmic_text::Style::Normal, + FontStyle::Italic => cosmic_text::Style::Italic, + FontStyle::Oblique => cosmic_text::Style::Oblique, + } + } +} diff --git a/crates/gpui/src/platform/windows/util.rs b/crates/gpui/src/platform/windows/util.rs new file mode 100644 index 0000000000..3a3ebccb64 --- /dev/null +++ b/crates/gpui/src/platform/windows/util.rs @@ -0,0 +1,44 @@ +use windows::Win32::Foundation::{LPARAM, WPARAM}; + +pub(crate) trait HiLoWord { + fn hiword(&self) -> u16; + fn loword(&self) -> u16; + fn signed_hiword(&self) -> i16; + fn signed_loword(&self) -> i16; +} + +impl HiLoWord for WPARAM { + fn hiword(&self) -> u16 { + ((self.0 >> 16) & 0xFFFF) as u16 + } + + fn loword(&self) -> u16 { + (self.0 & 0xFFFF) as u16 + } + + fn signed_hiword(&self) -> i16 { + ((self.0 >> 16) & 0xFFFF) as i16 + } + + fn signed_loword(&self) -> i16 { + (self.0 & 0xFFFF) as i16 + } +} + +impl HiLoWord for LPARAM { + fn hiword(&self) -> u16 { + ((self.0 >> 16) & 0xFFFF) as u16 + } + + fn loword(&self) -> u16 { + (self.0 & 0xFFFF) as u16 + } + + fn signed_hiword(&self) -> i16 { + ((self.0 >> 16) & 0xFFFF) as i16 + } + + fn signed_loword(&self) -> i16 { + (self.0 & 0xFFFF) as i16 + } +} diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs new file mode 100644 index 0000000000..8747311d8b --- /dev/null +++ b/crates/gpui/src/platform/windows/window.rs @@ -0,0 +1,928 @@ +#![deny(unsafe_op_in_unsafe_fn)] +// todo!("windows"): remove +#![allow(unused_variables)] + +use std::{ + any::Any, + cell::{Cell, RefCell}, + ffi::c_void, + num::NonZeroIsize, + rc::{Rc, Weak}, + sync::{Arc, Once}, +}; + +use blade_graphics as gpu; +use futures::channel::oneshot::Receiver; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; +use windows::{ + core::{w, HSTRING, PCWSTR}, + Win32::{ + Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM}, + System::SystemServices::{ + MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_XBUTTON1, MK_XBUTTON2, MODIFIERKEYS_FLAGS, + }, + UI::{ + Input::KeyboardAndMouse::{ + GetKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, VK_ESCAPE, VK_F1, + VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, VK_PRIOR, + VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_SPACE, VK_TAB, VK_UP, + }, + WindowsAndMessaging::{ + CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, + RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW, + CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WHEEL_DELTA, + WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WM_CHAR, WM_CLOSE, WM_DESTROY, WM_KEYDOWN, + WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, + WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOVE, WM_NCCREATE, WM_NCDESTROY, + WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN, + WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_OVERLAPPEDWINDOW, + WS_VISIBLE, XBUTTON1, XBUTTON2, + }, + }, + }, +}; + +use crate::{ + platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, KeyDownEvent, + KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, + NavigationDirection, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, + PlatformInputHandler, PlatformWindow, Point, PromptLevel, Scene, ScrollDelta, Size, TouchPhase, + WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay, WindowsPlatformInner, +}; + +#[derive(PartialEq)] +pub(crate) enum CallbackResult { + /// handled by system or user callback + Handled { + /// `true` if user callback handled event + by_callback: bool, + }, + Unhandled, +} + +impl CallbackResult { + pub fn is_handled(&self) -> bool { + match self { + Self::Handled { by_callback: _ } => true, + _ => false, + } + } +} + +pub(crate) struct WindowsWindowInner { + hwnd: HWND, + origin: Cell>, + size: Cell>, + mouse_position: Cell>, + input_handler: Cell>, + renderer: RefCell, + callbacks: RefCell, + platform_inner: Rc, + handle: AnyWindowHandle, +} + +impl WindowsWindowInner { + fn new( + hwnd: HWND, + cs: &CREATESTRUCTW, + platform_inner: Rc, + handle: AnyWindowHandle, + ) -> Self { + let origin = Cell::new(Point::new((cs.x as f64).into(), (cs.y as f64).into())); + let size = Cell::new(Size { + width: (cs.cx as f64).into(), + height: (cs.cy as f64).into(), + }); + let mouse_position = Cell::new(Point::default()); + let input_handler = Cell::new(None); + struct RawWindow { + hwnd: *mut c_void, + } + unsafe impl blade_rwh::HasRawWindowHandle for RawWindow { + fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle { + let mut handle = blade_rwh::Win32WindowHandle::empty(); + handle.hwnd = self.hwnd; + handle.into() + } + } + unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow { + fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle { + blade_rwh::WindowsDisplayHandle::empty().into() + } + } + let raw = RawWindow { hwnd: hwnd.0 as _ }; + let gpu = Arc::new( + unsafe { + gpu::Context::init_windowed( + &raw, + gpu::ContextDesc { + validation: false, + capture: false, + overlay: false, + }, + ) + } + .unwrap(), + ); + let extent = gpu::Extent { + width: 1, + height: 1, + depth: 1, + }; + let renderer = RefCell::new(BladeRenderer::new(gpu, extent)); + let callbacks = RefCell::new(Callbacks::default()); + Self { + hwnd, + origin, + size, + mouse_position, + input_handler, + renderer, + callbacks, + platform_inner, + handle, + } + } + + fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool { + unsafe { GetKeyState(vkey.0 as i32) < 0 } + } + + fn current_modifiers(&self) -> Modifiers { + Modifiers { + control: self.is_virtual_key_pressed(VK_CONTROL), + alt: self.is_virtual_key_pressed(VK_MENU), + shift: self.is_virtual_key_pressed(VK_SHIFT), + command: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN), + function: false, + } + } + + /// returns true if message is handled and should not dispatch + pub(crate) fn handle_immediate_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> bool { + match msg { + WM_KEYDOWN | WM_SYSKEYDOWN => self.handle_keydown_msg(wparam).is_handled(), + WM_KEYUP | WM_SYSKEYUP => self.handle_keyup_msg(wparam).is_handled(), + _ => false, + } + } + + fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0); + match msg { + WM_MOVE => self.handle_move_msg(lparam), + WM_SIZE => self.handle_size_msg(lparam), + WM_PAINT => self.handle_paint_msg(), + WM_CLOSE => self.handle_close_msg(msg, wparam, lparam), + WM_DESTROY => self.handle_destroy_msg(), + WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam), + WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam), + WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam), + WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam), + WM_XBUTTONDOWN => { + let nav_dir = match wparam.hiword() { + XBUTTON1 => Some(NavigationDirection::Forward), + XBUTTON2 => Some(NavigationDirection::Back), + _ => None, + }; + + if let Some(nav_dir) = nav_dir { + self.handle_mouse_down_msg(MouseButton::Navigate(nav_dir), lparam) + } else { + LRESULT(1) + } + } + WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam), + WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam), + WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam), + WM_XBUTTONUP => { + let nav_dir = match wparam.hiword() { + XBUTTON1 => Some(NavigationDirection::Back), + XBUTTON2 => Some(NavigationDirection::Forward), + _ => None, + }; + + if let Some(nav_dir) = nav_dir { + self.handle_mouse_up_msg(MouseButton::Navigate(nav_dir), lparam) + } else { + LRESULT(1) + } + } + WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam), + WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam), + WM_CHAR | WM_SYSCHAR => self.handle_char_msg(wparam), + // These events are handled by the immediate handler + WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => LRESULT(0), + _ => unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }, + } + } + + fn handle_move_msg(&self, lparam: LPARAM) -> LRESULT { + let x = lparam.signed_loword() as f64; + let y = lparam.signed_hiword() as f64; + self.origin.set(Point::new(x.into(), y.into())); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.moved.as_mut() { + callback() + } + LRESULT(0) + } + + fn handle_size_msg(&self, lparam: LPARAM) -> LRESULT { + let width = lparam.loword().max(1) as f64; + let height = lparam.hiword().max(1) as f64; + self.renderer + .borrow_mut() + .update_drawable_size(Size { width, height }); + let width = width.into(); + let height = height.into(); + self.size.set(Size { width, height }); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.resize.as_mut() { + callback( + Size { + width: Pixels(width.0), + height: Pixels(height.0), + }, + 1.0, + ) + } + LRESULT(0) + } + + fn handle_paint_msg(&self) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.request_frame.as_mut() { + callback() + } + LRESULT(0) + } + + fn handle_close_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.should_close.as_mut() { + if callback() { + return LRESULT(0); + } + } + drop(callbacks); + unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) } + } + + fn handle_destroy_msg(&self) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.close.take() { + callback() + } + let mut window_handles = self.platform_inner.window_handles.borrow_mut(); + window_handles.remove(&self.handle); + if window_handles.is_empty() { + self.platform_inner + .foreground_executor + .spawn(async { + unsafe { PostQuitMessage(0) }; + }) + .detach(); + } + LRESULT(1) + } + + fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> LRESULT { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + self.mouse_position.set(Point { x, y }); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) { + flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left), + flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right), + flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle), + flags if flags.contains(MK_XBUTTON1) => { + Some(MouseButton::Navigate(NavigationDirection::Back)) + } + flags if flags.contains(MK_XBUTTON2) => { + Some(MouseButton::Navigate(NavigationDirection::Forward)) + } + _ => None, + }; + let event = MouseMoveEvent { + position: Point { x, y }, + pressed_button, + modifiers: self.current_modifiers(), + }; + if callback(PlatformInput::MouseMove(event)) { + return LRESULT(0); + } + } + LRESULT(1) + } + + fn parse_key_msg_keystroke(&self, wparam: WPARAM) -> Option { + let vk_code = wparam.loword(); + + // 0-9 https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + if vk_code >= 0x30 && vk_code <= 0x39 { + let modifiers = self.current_modifiers(); + + if modifiers.shift { + return None; + } + + let digit_char = (b'0' + ((vk_code - 0x30) as u8)) as char; + return Some(Keystroke { + modifiers, + key: digit_char.to_string(), + ime_key: Some(digit_char.to_string()), + }); + } + + // A-Z https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes + if vk_code >= 0x41 && vk_code <= 0x5A { + let offset = (vk_code - 0x41) as u8; + let alpha_char = (b'a' + offset) as char; + let alpha_char_upper = (b'A' + offset) as char; + let modifiers = self.current_modifiers(); + return Some(Keystroke { + modifiers, + key: alpha_char.to_string(), + ime_key: Some(if modifiers.shift { + alpha_char_upper.to_string() + } else { + alpha_char.to_string() + }), + }); + } + + if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 { + let offset = vk_code - VK_F1.0; + return Some(Keystroke { + modifiers: self.current_modifiers(), + key: format!("f{}", offset + 1), + ime_key: None, + }); + } + + let key = match VIRTUAL_KEY(vk_code) { + VK_SPACE => Some(("space", Some(" "))), + VK_TAB => Some(("tab", Some("\t"))), + VK_BACK => Some(("backspace", None)), + VK_RETURN => Some(("enter", None)), + VK_UP => Some(("up", None)), + VK_DOWN => Some(("down", None)), + VK_RIGHT => Some(("right", None)), + VK_LEFT => Some(("left", None)), + VK_HOME => Some(("home", None)), + VK_END => Some(("end", None)), + VK_PRIOR => Some(("pageup", None)), + VK_NEXT => Some(("pagedown", None)), + VK_ESCAPE => Some(("escape", None)), + VK_INSERT => Some(("insert", None)), + _ => None, + }; + + if let Some((key, ime_key)) = key { + Some(Keystroke { + modifiers: self.current_modifiers(), + key: key.to_string(), + ime_key: ime_key.map(|k| k.to_string()), + }) + } else { + None + } + } + + fn handle_keydown_msg(&self, wparam: WPARAM) -> CallbackResult { + let mut callbacks = self.callbacks.borrow_mut(); + let keystroke = self.parse_key_msg_keystroke(wparam); + if let Some(keystroke) = keystroke { + if let Some(callback) = callbacks.input.as_mut() { + let ime_key = keystroke.ime_key.clone(); + let event = KeyDownEvent { + keystroke, + is_held: true, + }; + + if callback(PlatformInput::KeyDown(event)) { + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + CallbackResult::Handled { by_callback: true } + } else if let Some(mut input_handler) = self.input_handler.take() { + if let Some(ime_key) = ime_key { + input_handler.replace_text_in_range(None, &ime_key); + } + self.input_handler.set(Some(input_handler)); + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + CallbackResult::Handled { by_callback: true } + } else { + CallbackResult::Handled { by_callback: false } + } + } else { + CallbackResult::Handled { by_callback: false } + } + } else { + CallbackResult::Unhandled + } + } + + fn handle_keyup_msg(&self, wparam: WPARAM) -> CallbackResult { + let mut callbacks = self.callbacks.borrow_mut(); + let keystroke = self.parse_key_msg_keystroke(wparam); + if let Some(keystroke) = keystroke { + if let Some(callback) = callbacks.input.as_mut() { + let event = KeyUpEvent { keystroke }; + CallbackResult::Handled { + by_callback: callback(PlatformInput::KeyUp(event)), + } + } else { + CallbackResult::Handled { by_callback: false } + } + } else { + CallbackResult::Unhandled + } + } + + fn handle_char_msg(&self, wparam: WPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let modifiers = self.current_modifiers(); + let msg_char = wparam.0 as u8 as char; + let keystroke = Keystroke { + modifiers, + key: msg_char.to_string(), + ime_key: Some(msg_char.to_string()), + }; + let ime_key = keystroke.ime_key.clone(); + let event = KeyDownEvent { + keystroke, + is_held: false, + }; + + if callback(PlatformInput::KeyDown(event)) { + return LRESULT(0); + } + + if let Some(mut input_handler) = self.input_handler.take() { + if let Some(ime_key) = ime_key { + input_handler.replace_text_in_range(None, &ime_key); + } + self.input_handler.set(Some(input_handler)); + return LRESULT(0); + } + } + return LRESULT(1); + } + + fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let event = MouseDownEvent { + button, + position: Point { x, y }, + modifiers: self.current_modifiers(), + click_count: 1, + }; + if callback(PlatformInput::MouseDown(event)) { + return LRESULT(0); + } + } + LRESULT(1) + } + + fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let event = MouseUpEvent { + button, + position: Point { x, y }, + modifiers: self.current_modifiers(), + click_count: 1, + }; + if callback(PlatformInput::MouseUp(event)) { + return LRESULT(0); + } + } + LRESULT(1) + } + + fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) + * self.platform_inner.settings.borrow().wheel_scroll_lines as f32; + let event = crate::ScrollWheelEvent { + position: Point { x, y }, + delta: ScrollDelta::Lines(Point { + x: 0.0, + y: wheel_distance, + }), + modifiers: self.current_modifiers(), + touch_phase: TouchPhase::Moved, + }; + if callback(PlatformInput::ScrollWheel(event)) { + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + return LRESULT(0); + } + } + LRESULT(1) + } + + fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.input.as_mut() { + let x = Pixels::from(lparam.signed_loword() as f32); + let y = Pixels::from(lparam.signed_hiword() as f32); + let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) + * self.platform_inner.settings.borrow().wheel_scroll_chars as f32; + let event = crate::ScrollWheelEvent { + position: Point { x, y }, + delta: ScrollDelta::Lines(Point { + x: wheel_distance, + y: 0.0, + }), + modifiers: self.current_modifiers(), + touch_phase: TouchPhase::Moved, + }; + if callback(PlatformInput::ScrollWheel(event)) { + if let Some(request_frame) = callbacks.request_frame.as_mut() { + request_frame(); + } + return LRESULT(0); + } + } + LRESULT(1) + } +} + +#[derive(Default)] +struct Callbacks { + request_frame: Option>, + input: Option bool>>, + active_status_change: Option>, + resize: Option, f32)>>, + fullscreen: Option>, + moved: Option>, + should_close: Option bool>>, + close: Option>, + appearance_changed: Option>, +} + +pub(crate) struct WindowsWindow { + inner: Rc, +} + +struct WindowCreateContext { + inner: Option>, + platform_inner: Rc, + handle: AnyWindowHandle, +} + +impl WindowsWindow { + pub(crate) fn new( + platform_inner: Rc, + handle: AnyWindowHandle, + options: WindowOptions, + ) -> Self { + let dwexstyle = WINDOW_EX_STYLE::default(); + let classname = register_wnd_class(); + let windowname = HSTRING::from( + options + .titlebar + .as_ref() + .and_then(|titlebar| titlebar.title.as_ref()) + .map(|title| title.as_ref()) + .unwrap_or(""), + ); + let dwstyle = WS_OVERLAPPEDWINDOW & !WS_VISIBLE; + let mut x = CW_USEDEFAULT; + let mut y = CW_USEDEFAULT; + let mut nwidth = CW_USEDEFAULT; + let mut nheight = CW_USEDEFAULT; + match options.bounds { + WindowBounds::Fullscreen => {} + WindowBounds::Maximized => {} + WindowBounds::Fixed(bounds) => { + x = bounds.origin.x.0 as i32; + y = bounds.origin.y.0 as i32; + nwidth = bounds.size.width.0 as i32; + nheight = bounds.size.height.0 as i32; + } + }; + let hwndparent = HWND::default(); + let hmenu = HMENU::default(); + let hinstance = HINSTANCE::default(); + let mut context = WindowCreateContext { + inner: None, + platform_inner: platform_inner.clone(), + handle, + }; + let lpparam = Some(&context as *const _ as *const _); + unsafe { + CreateWindowExW( + dwexstyle, + classname, + &windowname, + dwstyle, + x, + y, + nwidth, + nheight, + hwndparent, + hmenu, + hinstance, + lpparam, + ) + }; + let wnd = Self { + inner: context.inner.unwrap(), + }; + platform_inner.window_handles.borrow_mut().insert(handle); + match options.bounds { + WindowBounds::Fullscreen => wnd.toggle_full_screen(), + WindowBounds::Maximized => wnd.maximize(), + WindowBounds::Fixed(_) => {} + } + unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) }; + wnd + } + + fn maximize(&self) { + unsafe { ShowWindow(self.inner.hwnd, SW_MAXIMIZE) }; + } +} + +impl HasWindowHandle for WindowsWindow { + fn window_handle( + &self, + ) -> Result, raw_window_handle::HandleError> { + let raw = raw_window_handle::Win32WindowHandle::new(unsafe { + NonZeroIsize::new_unchecked(self.inner.hwnd.0) + }) + .into(); + Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) }) + } +} + +// todo!("windows") +impl HasDisplayHandle for WindowsWindow { + fn display_handle( + &self, + ) -> Result, raw_window_handle::HandleError> { + unimplemented!() + } +} + +impl PlatformWindow for WindowsWindow { + fn bounds(&self) -> WindowBounds { + WindowBounds::Fixed(Bounds { + origin: self.inner.origin.get(), + size: self.inner.size.get(), + }) + } + + // todo!("windows") + fn content_size(&self) -> Size { + let size = self.inner.size.get(); + Size { + width: size.width.0.into(), + height: size.height.0.into(), + } + } + + // todo!("windows") + fn scale_factor(&self) -> f32 { + 1.0 + } + + // todo!("windows") + fn titlebar_height(&self) -> Pixels { + 20.0.into() + } + + // todo!("windows") + fn appearance(&self) -> WindowAppearance { + WindowAppearance::Dark + } + + // todo!("windows") + fn display(&self) -> Rc { + Rc::new(WindowsDisplay::new()) + } + + fn mouse_position(&self) -> Point { + self.inner.mouse_position.get() + } + + // todo!("windows") + fn modifiers(&self) -> Modifiers { + Modifiers::none() + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + // todo!("windows") + fn set_input_handler(&mut self, input_handler: PlatformInputHandler) { + self.inner.input_handler.set(Some(input_handler)); + } + + // todo!("windows") + fn take_input_handler(&mut self) -> Option { + self.inner.input_handler.take() + } + + // todo!("windows") + fn prompt( + &self, + level: PromptLevel, + msg: &str, + detail: Option<&str>, + answers: &[&str], + ) -> Receiver { + unimplemented!() + } + + // todo!("windows") + fn activate(&self) {} + + // todo!("windows") + fn set_title(&mut self, title: &str) { + unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) } + .inspect_err(|e| log::error!("Set title failed: {e}")) + .ok(); + } + + // todo!("windows") + fn set_edited(&mut self, edited: bool) {} + + // todo!("windows") + fn show_character_palette(&self) {} + + // todo!("windows") + fn minimize(&self) {} + + // todo!("windows") + fn zoom(&self) {} + + // todo!("windows") + fn toggle_full_screen(&self) {} + + // todo!("windows") + fn on_request_frame(&self, callback: Box) { + self.inner.callbacks.borrow_mut().request_frame = Some(callback); + } + + // todo!("windows") + fn on_input(&self, callback: Box bool>) { + self.inner.callbacks.borrow_mut().input = Some(callback); + } + + // todo!("windows") + fn on_active_status_change(&self, callback: Box) { + self.inner.callbacks.borrow_mut().active_status_change = Some(callback); + } + + // todo!("windows") + fn on_resize(&self, callback: Box, f32)>) { + self.inner.callbacks.borrow_mut().resize = Some(callback); + } + + // todo!("windows") + fn on_fullscreen(&self, callback: Box) { + self.inner.callbacks.borrow_mut().fullscreen = Some(callback); + } + + // todo!("windows") + fn on_moved(&self, callback: Box) { + self.inner.callbacks.borrow_mut().moved = Some(callback); + } + + // todo!("windows") + fn on_should_close(&self, callback: Box bool>) { + self.inner.callbacks.borrow_mut().should_close = Some(callback); + } + + // todo!("windows") + fn on_close(&self, callback: Box) { + self.inner.callbacks.borrow_mut().close = Some(callback); + } + + // todo!("windows") + fn on_appearance_changed(&self, callback: Box) { + self.inner.callbacks.borrow_mut().appearance_changed = Some(callback); + } + + // todo!("windows") + fn is_topmost_for_position(&self, position: Point) -> bool { + true + } + + // todo!("windows") + fn draw(&self, scene: &Scene) { + self.inner.renderer.borrow_mut().draw(scene) + } + + // todo!("windows") + fn sprite_atlas(&self) -> Arc { + self.inner.renderer.borrow().sprite_atlas().clone() + } +} + +fn register_wnd_class() -> PCWSTR { + const CLASS_NAME: PCWSTR = w!("Zed::Window"); + + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + let wc = WNDCLASSW { + lpfnWndProc: Some(wnd_proc), + hCursor: unsafe { LoadCursorW(None, IDC_ARROW).ok().unwrap() }, + lpszClassName: PCWSTR(CLASS_NAME.as_ptr()), + ..Default::default() + }; + unsafe { RegisterClassW(&wc) }; + }); + + CLASS_NAME +} + +unsafe extern "system" fn wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + if msg == WM_NCCREATE { + let cs = lparam.0 as *const CREATESTRUCTW; + let cs = unsafe { &*cs }; + let ctx = cs.lpCreateParams as *mut WindowCreateContext; + let ctx = unsafe { &mut *ctx }; + let inner = Rc::new(WindowsWindowInner::new( + hwnd, + cs, + ctx.platform_inner.clone(), + ctx.handle, + )); + let weak = Box::new(Rc::downgrade(&inner)); + unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) }; + ctx.inner = Some(inner); + return LRESULT(1); + } + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + if ptr.is_null() { + return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; + } + let inner = unsafe { &*ptr }; + let r = if let Some(inner) = inner.upgrade() { + inner.handle_msg(msg, wparam, lparam) + } else { + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } + }; + if msg == WM_NCDESTROY { + unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) }; + unsafe { std::mem::drop(Box::from_raw(ptr)) }; + } + r +} + +pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option> { + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + if !ptr.is_null() { + let inner = unsafe { &*ptr }; + inner.upgrade() + } else { + None + } +} + +unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize { + #[cfg(target_pointer_width = "64")] + unsafe { + GetWindowLongPtrW(hwnd, nindex) + } + #[cfg(target_pointer_width = "32")] + unsafe { + GetWindowLongW(hwnd, nindex) as isize + } +} + +unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize { + #[cfg(target_pointer_width = "64")] + unsafe { + SetWindowLongPtrW(hwnd, nindex, dwnewlong) + } + #[cfg(target_pointer_width = "32")] + unsafe { + SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize + } +} diff --git a/crates/gpui/src/styled.rs b/crates/gpui/src/styled.rs index 3896052cc4..928f9f0a23 100644 --- a/crates/gpui/src/styled.rs +++ b/crates/gpui/src/styled.rs @@ -1,11 +1,11 @@ use crate::{ self as gpui, hsla, point, px, relative, rems, AbsoluteLength, AlignItems, CursorStyle, - DefiniteLength, Fill, FlexDirection, FontWeight, Hsla, JustifyContent, Length, Position, - SharedString, StyleRefinement, Visibility, WhiteSpace, + DefiniteLength, Fill, FlexDirection, FlexWrap, FontWeight, Hsla, JustifyContent, Length, + Position, SharedString, StyleRefinement, Visibility, WhiteSpace, }; use crate::{BoxShadow, TextStyleRefinement}; use smallvec::{smallvec, SmallVec}; -use taffy::style::{Display, Overflow}; +use taffy::style::{AlignContent, Display, Overflow}; /// A trait for elements that can be styled. /// Use this to opt-in to a CSS-like styling API. @@ -333,6 +333,27 @@ pub trait Styled: Sized { self } + /// Sets the element to allow flex items to wrap. + /// [Docs](https://tailwindcss.com/docs/flex-wrap#wrap-normally) + fn flex_wrap(mut self) -> Self { + self.style().flex_wrap = Some(FlexWrap::Wrap); + self + } + + /// Sets the element wrap flex items in the reverse direction. + /// [Docs](https://tailwindcss.com/docs/flex-wrap#wrap-reversed) + fn flex_wrap_reverse(mut self) -> Self { + self.style().flex_wrap = Some(FlexWrap::WrapReverse); + self + } + + /// Sets the element to prevent flex items from wrapping, causing inflexible items to overflow the container if necessary. + /// [Docs](https://tailwindcss.com/docs/flex-wrap#dont-wrap) + fn flex_nowrap(mut self) -> Self { + self.style().flex_wrap = Some(FlexWrap::NoWrap); + self + } + /// Sets the element to align flex items to the start of the container's cross axis. /// [Docs](https://tailwindcss.com/docs/align-items#start) fn items_start(mut self) -> Self { @@ -391,6 +412,65 @@ pub trait Styled: Sized { self } + /// Sets the element to pack content items in their default position as if no align-content value was set. + /// [Docs](https://tailwindcss.com/docs/align-content#normal) + fn content_normal(mut self) -> Self { + self.style().align_content = None; + self + } + + /// Sets the element to pack content items in the center of the container's cross axis. + /// [Docs](https://tailwindcss.com/docs/align-content#center) + fn content_center(mut self) -> Self { + self.style().align_content = Some(AlignContent::Center); + self + } + + /// Sets the element to pack content items against the start of the container's cross axis. + /// [Docs](https://tailwindcss.com/docs/align-content#start) + fn content_start(mut self) -> Self { + self.style().align_content = Some(AlignContent::FlexStart); + self + } + + /// Sets the element to pack content items against the end of the container's cross axis. + /// [Docs](https://tailwindcss.com/docs/align-content#end) + fn content_end(mut self) -> Self { + self.style().align_content = Some(AlignContent::FlexEnd); + self + } + + /// Sets the element to pack content items along the container's cross axis + /// such that there is an equal amount of space between each item. + /// [Docs](https://tailwindcss.com/docs/align-content#space-between) + fn content_between(mut self) -> Self { + self.style().align_content = Some(AlignContent::SpaceBetween); + self + } + + /// Sets the element to pack content items along the container's cross axis + /// such that there is an equal amount of space on each side of each item. + /// [Docs](https://tailwindcss.com/docs/align-content#space-around) + fn content_around(mut self) -> Self { + self.style().align_content = Some(AlignContent::SpaceAround); + self + } + + /// Sets the element to pack content items along the container's cross axis + /// such that there is an equal amount of space between each item. + /// [Docs](https://tailwindcss.com/docs/align-content#space-evenly) + fn content_evenly(mut self) -> Self { + self.style().align_content = Some(AlignContent::SpaceEvenly); + self + } + + /// Sets the element to allow content items to fill the available space along the container's cross axis. + /// [Docs](https://tailwindcss.com/docs/align-content#stretch) + fn content_stretch(mut self) -> Self { + self.style().align_content = Some(AlignContent::Stretch); + self + } + /// Sets the background color of the element. fn bg(mut self, fill: F) -> Self where diff --git a/crates/gpui/src/test.rs b/crates/gpui/src/test.rs index 77540704f9..3f267542f8 100644 --- a/crates/gpui/src/test.rs +++ b/crates/gpui/src/test.rs @@ -46,10 +46,10 @@ pub fn run_test( let starting_seed = env::var("SEED") .map(|seed| seed.parse().expect("invalid SEED variable")) .unwrap_or(0); - let is_randomized = num_iterations > 1; if let Ok(iterations) = env::var("ITERATIONS") { num_iterations = iterations.parse().expect("invalid ITERATIONS variable"); } + let is_randomized = num_iterations > 1; for seed in starting_seed..starting_seed + num_iterations { let mut retry = 0; @@ -72,7 +72,9 @@ pub fn run_test( if is_randomized { eprintln!("failing seed: {}", seed); } - on_fail_fn.map(|f| f()); + if let Some(f) = on_fail_fn { + f() + } panic::resume_unwind(error); } } diff --git a/crates/gpui_macros/Cargo.toml b/crates/gpui_macros/Cargo.toml index f5a7589139..b48da0429e 100644 --- a/crates/gpui_macros/Cargo.toml +++ b/crates/gpui_macros/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/gpui_macros.rs" proc-macro = true diff --git a/crates/install_cli/Cargo.toml b/crates/install_cli/Cargo.toml index 8afccceaaf..4edeb01dcd 100644 --- a/crates/install_cli/Cargo.toml +++ b/crates/install_cli/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/install_cli.rs" diff --git a/crates/install_cli/src/install_cli.rs b/crates/install_cli/src/install_cli.rs index 61c5aa2fb9..506de309ef 100644 --- a/crates/install_cli/src/install_cli.rs +++ b/crates/install_cli/src/install_cli.rs @@ -1,18 +1,18 @@ use anyhow::{anyhow, Result}; use gpui::{actions, AsyncAppContext}; -use std::path::Path; +use std::path::{Path, PathBuf}; use util::ResultExt; -actions!(cli, [Install]); +actions!(cli, [Install, RegisterZedScheme]); -pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { +pub async fn install_cli(cx: &AsyncAppContext) -> Result { let cli_path = cx.update(|cx| cx.path_for_auxiliary_executable("cli"))??; let link_path = Path::new("/usr/local/bin/zed"); let bin_dir_path = link_path.parent().unwrap(); // Don't re-create symlink if it points to the same CLI binary. if smol::fs::read_link(link_path).await.ok().as_ref() == Some(&cli_path) { - return Ok(()); + return Ok(link_path.into()); } // If the symlink is not there or is outdated, first try replacing it @@ -26,7 +26,7 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { .log_err() .is_some() { - return Ok(()); + return Ok(link_path.into()); } } @@ -51,7 +51,7 @@ pub async fn install_cli(cx: &AsyncAppContext) -> Result<()> { .await? .status; if status.success() { - Ok(()) + Ok(link_path.into()) } else { Err(anyhow!("error running osascript")) } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index 325e6b5297..e5dd558fa8 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/journal.rs" doctest = false diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 77abad1ecf..e6f2a45ea5 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/language.rs" doctest = false diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 428e4b33f9..0727123e84 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1328,7 +1328,7 @@ impl Buffer { self.edit(edits, None, cx); } - /// Create a minimal edit that will cause the the given row to be indented + /// Create a minimal edit that will cause the given row to be indented /// with the given size. After applying this edit, the length of the line /// will always be at least `new_size.len`. pub fn edit_for_indent_size_adjustment( @@ -2839,10 +2839,10 @@ impl BufferSnapshot { } /// Returns bracket range pairs overlapping or adjacent to `range` - pub fn bracket_ranges<'a, T: ToOffset>( - &'a self, + pub fn bracket_ranges( + &self, range: Range, - ) -> impl Iterator, Range)> + 'a { + ) -> impl Iterator, Range)> + '_ { // Find bracket pairs that *inclusively* contain the given range. let range = range.start.to_offset(self).saturating_sub(1) ..self.len().min(range.end.to_offset(self) + 1); @@ -2935,10 +2935,10 @@ impl BufferSnapshot { /// Returns anchor ranges for any matches of the redaction query. /// The buffer can be associated with multiple languages, and the redaction query associated with each /// will be run on the relevant section of the buffer. - pub fn redacted_ranges<'a, T: ToOffset>( - &'a self, + pub fn redacted_ranges( + &self, range: Range, - ) -> impl Iterator> + 'a { + ) -> impl Iterator> + '_ { let offset_range = range.start.to_offset(self)..range.end.to_offset(self); let mut syntax_matches = self.syntax.matches(offset_range, self, |grammar| { grammar @@ -3015,28 +3015,28 @@ impl BufferSnapshot { /// Returns all the Git diff hunks intersecting the given /// row range. - pub fn git_diff_hunks_in_row_range<'a>( - &'a self, + pub fn git_diff_hunks_in_row_range( + &self, range: Range, - ) -> impl 'a + Iterator> { + ) -> impl '_ + Iterator> { self.git_diff.hunks_in_row_range(range, self) } /// Returns all the Git diff hunks intersecting the given /// range. - pub fn git_diff_hunks_intersecting_range<'a>( - &'a self, + pub fn git_diff_hunks_intersecting_range( + &self, range: Range, - ) -> impl 'a + Iterator> { + ) -> impl '_ + Iterator> { self.git_diff.hunks_intersecting_range(range, self) } /// Returns all the Git diff hunks intersecting the given /// range, in reverse order. - pub fn git_diff_hunks_intersecting_range_rev<'a>( - &'a self, + pub fn git_diff_hunks_intersecting_range_rev( + &self, range: Range, - ) -> impl 'a + Iterator> { + ) -> impl '_ + Iterator> { self.git_diff.hunks_intersecting_range_rev(range, self) } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 9750cec9bf..bc3eb692fa 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -1110,7 +1110,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC b(); | " - .replace("|", "") // marker to preserve trailing whitespace + .replace('|', "") // marker to preserve trailing whitespace .unindent(), ) .with_language(Arc::new(rust_lang()), cx); @@ -1787,7 +1787,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) { // In a JSX expression: use the default config. let expression_in_element_config = snapshot - .language_scope_at(text.find("{").unwrap() + 1) + .language_scope_at(text.find('{').unwrap() + 1) .unwrap(); assert_eq!( expression_in_element_config @@ -2321,7 +2321,7 @@ fn test_trailing_whitespace_ranges(mut rng: StdRng) { actual_ranges, expected_ranges, "wrong ranges for text lines:\n{:?}", - text.split("\n").collect::>() + text.split('\n').collect::>() ); } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 38d64ccf0c..ee137abe43 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -120,6 +120,46 @@ pub struct Location { pub range: Range, } +pub struct LanguageContext { + pub package: Option, + pub symbol: Option, +} + +pub trait LanguageContextProvider: Send + Sync { + fn build_context(&self, location: Location, cx: &mut AppContext) -> Result; +} + +/// A context provider that fills out LanguageContext without inspecting the contents. +pub struct DefaultContextProvider; + +impl LanguageContextProvider for DefaultContextProvider { + fn build_context( + &self, + location: Location, + cx: &mut AppContext, + ) -> gpui::Result { + let symbols = location + .buffer + .read(cx) + .snapshot() + .symbols_containing(location.range.start, None); + let symbol = symbols.and_then(|symbols| { + symbols.last().map(|symbol| { + let range = symbol + .name_ranges + .last() + .cloned() + .unwrap_or(0..symbol.text.len()); + symbol.text[range].to_string() + }) + }); + Ok(LanguageContext { + package: None, + symbol, + }) + } +} + /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. @@ -727,6 +767,7 @@ pub struct Language { pub(crate) id: LanguageId, pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, + pub(crate) context_provider: Option>, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] @@ -841,9 +882,18 @@ impl Language { highlight_map: Default::default(), }) }), + context_provider: None, } } + pub fn with_context_provider( + mut self, + provider: Option>, + ) -> Self { + self.context_provider = provider; + self + } + pub fn with_queries(mut self, queries: LanguageQueries) -> Result { if let Some(query) = queries.highlights { self = self @@ -1139,6 +1189,10 @@ impl Language { self.config.name.clone() } + pub fn context_provider(&self) -> Option> { + self.context_provider.clone() + } + pub fn highlight_text<'a>( self: &'a Arc, text: &'a Rope, diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 8bc29f943f..d32b0f3346 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -1,6 +1,6 @@ use crate::{ - CachedLspAdapter, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, - LspAdapter, LspAdapterDelegate, PARSER, PLAIN_TEXT, + CachedLspAdapter, Language, LanguageConfig, LanguageContextProvider, LanguageId, + LanguageMatcher, LanguageServerName, LspAdapter, LspAdapterDelegate, PARSER, PLAIN_TEXT, }; use anyhow::{anyhow, Context as _, Result}; use collections::{hash_map, HashMap}; @@ -78,6 +78,7 @@ struct AvailableLanguage { matcher: LanguageMatcher, load: Arc Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync>, loaded: bool, + context_provider: Option>, } enum AvailableGrammar { @@ -188,6 +189,7 @@ impl LanguageRegistry { config.name.clone(), config.grammar.clone(), config.matcher.clone(), + None, move || Ok((config.clone(), Default::default())), ) } @@ -237,6 +239,7 @@ impl LanguageRegistry { name: Arc, grammar_name: Option>, matcher: LanguageMatcher, + context_provider: Option>, load: impl Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync, ) { let load = Arc::new(load); @@ -257,6 +260,8 @@ impl LanguageRegistry { grammar: grammar_name, matcher, load, + + context_provider, loaded: false, }); state.version += 1; @@ -422,6 +427,7 @@ impl LanguageRegistry { .spawn(async move { let id = language.id; let name = language.name.clone(); + let provider = language.context_provider.clone(); let language = async { let (config, queries) = (language.load)()?; @@ -431,7 +437,9 @@ impl LanguageRegistry { None }; - Language::new_with_id(id, config, grammar).with_queries(queries) + Language::new_with_id(id, config, grammar) + .with_context_provider(provider) + .with_queries(queries) } .await; diff --git a/crates/language/src/outline.rs b/crates/language/src/outline.rs index 014b32676a..685fd297fc 100644 --- a/crates/language/src/outline.rs +++ b/crates/language/src/outline.rs @@ -43,7 +43,7 @@ impl Outline { let candidate_text = item .name_ranges .iter() - .map(|range| &item.text[range.start as usize..range.end as usize]) + .map(|range| &item.text[range.start..range.end]) .collect::(); path_candidates.push(StringMatchCandidate::new(id, path_text.clone())); @@ -97,11 +97,11 @@ impl Outline { let mut name_range = name_ranges.next().unwrap(); let mut preceding_ranges_len = 0; for position in &mut string_match.positions { - while *position >= preceding_ranges_len + name_range.len() as usize { + while *position >= preceding_ranges_len + name_range.len() { preceding_ranges_len += name_range.len(); name_range = name_ranges.next().unwrap(); } - *position = name_range.start as usize + (*position - preceding_ranges_len); + *position = name_range.start + (*position - preceding_ranges_len); } } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index 045e475f07..926c4a36ed 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -614,9 +614,12 @@ impl SyntaxSnapshot { Some(old_tree.clone()), ); changed_ranges = join_ranges( - invalidated_ranges.iter().cloned().filter(|range| { - range.start <= step_end_byte && range.end >= step_start_byte - }), + invalidated_ranges + .iter() + .filter(|&range| { + range.start <= step_end_byte && range.end >= step_start_byte + }) + .cloned(), old_tree.changed_ranges(&tree).map(|r| { step_start_byte + r.start_byte..step_start_byte + r.end_byte }), @@ -766,7 +769,7 @@ impl SyntaxSnapshot { SyntaxMapCaptures::new( range.clone(), buffer.as_rope(), - self.layers_for_range(range, buffer).into_iter(), + self.layers_for_range(range, buffer), query, ) } @@ -780,7 +783,7 @@ impl SyntaxSnapshot { SyntaxMapMatches::new( range.clone(), buffer.as_rope(), - self.layers_for_range(range, buffer).into_iter(), + self.layers_for_range(range, buffer), query, ) } @@ -1180,6 +1183,7 @@ fn parse_text( }) } +#[allow(clippy::too_many_arguments)] fn get_injections( config: &InjectionConfig, text: &BufferSnapshot, @@ -1412,7 +1416,7 @@ fn insert_newlines_between_ranges( continue; } - let range_b = ranges[ix].clone(); + let range_b = ranges[ix]; let range_a = &mut ranges[ix - 1]; if range_a.end_point.column == 0 { continue; @@ -1421,7 +1425,7 @@ fn insert_newlines_between_ranges( if range_a.end_point.row < range_b.start_point.row { let end_point = start_point + Point::from_ts_point(range_a.end_point); let line_end = Point::new(end_point.row, text.line_len(end_point.row)); - if end_point.column as u32 >= line_end.column { + if end_point.column >= line_end.column { range_a.end_byte += 1; range_a.end_point.row += 1; range_a.end_point.column = 0; diff --git a/crates/language_selector/Cargo.toml b/crates/language_selector/Cargo.toml index 5365be61a9..b864ffc31f 100644 --- a/crates/language_selector/Cargo.toml +++ b/crates/language_selector/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/language_selector.rs" doctest = false diff --git a/crates/language_tools/Cargo.toml b/crates/language_tools/Cargo.toml index 2afad658c8..6d0a1199b3 100644 --- a/crates/language_tools/Cargo.toml +++ b/crates/language_tools/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/language_tools.rs" doctest = false diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index d2bdd95d7d..f1590b6cb8 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -756,8 +756,8 @@ impl Render for LspLogToolbarItemView { .trigger(Button::new( "language_server_menu_header", current_server - .and_then(|row| { - Some(Cow::Owned(format!( + .map(|row| { + Cow::Owned(format!( "{} ({}) - {}", row.server_name.0, row.worktree_root_name, @@ -766,7 +766,7 @@ impl Render for LspLogToolbarItemView { } else { SERVER_LOGS }, - ))) + )) }) .unwrap_or_else(|| "No server selected".into()), )) diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 082e77fc36..bca193bc94 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -284,7 +284,7 @@ impl Render for SyntaxTreeView { move |this, range, cx| { let mut items = Vec::new(); let mut cursor = layer.node().walk(); - let mut descendant_ix = range.start as usize; + let mut descendant_ix = range.start; cursor.goto_descendant(descendant_ix); let mut depth = cursor.depth(); let mut visited_children = false; diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 8dd4078abc..122607791e 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] anyhow.workspace = true async-compression.workspace = true diff --git a/crates/languages/src/astro.rs b/crates/languages/src/astro.rs index 220b294e8e..2ed3853e07 100644 --- a/crates/languages/src/astro.rs +++ b/crates/languages/src/astro.rs @@ -12,7 +12,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::ResultExt; +use util::{async_maybe, ResultExt}; const SERVER_PATH: &str = "node_modules/@astrojs/language-server/bin/nodeServer.js"; @@ -105,7 +105,7 @@ async fn get_cached_server_binary( container_dir: PathBuf, node: &dyn NodeRuntime, ) -> Option { - (|| async move { + async_maybe!({ let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { @@ -128,7 +128,7 @@ async fn get_cached_server_binary( last_version_dir )) } - })() + }) .await .log_err() } diff --git a/crates/languages/src/c.rs b/crates/languages/src/c.rs index bad4e0a076..28ceedf7ad 100644 --- a/crates/languages/src/c.rs +++ b/crates/languages/src/c.rs @@ -1,10 +1,10 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; use futures::StreamExt; pub use language::*; use lsp::LanguageServerBinary; use smol::fs::{self, File}; -use std::{any::Any, path::PathBuf, sync::Arc}; +use std::{any::Any, env::consts, path::PathBuf, sync::Arc}; use util::{ async_maybe, fs::remove_matching, @@ -26,7 +26,12 @@ impl super::LspAdapter for CLspAdapter { ) -> Result> { let release = latest_github_release("clangd/clangd", true, false, delegate.http_client()).await?; - let asset_name = format!("clangd-mac-{}.zip", release.tag_name); + let os_suffix = match consts::OS { + "macos" => "mac", + "linux" => "linux", + other => bail!("Running on unsupported os: {other}"), + }; + let asset_name = format!("clangd-{}-{}.zip", os_suffix, release.tag_name); let asset = release .assets .iter() diff --git a/crates/languages/src/elm.rs b/crates/languages/src/elm.rs index 27b9c6d409..37b156db91 100644 --- a/crates/languages/src/elm.rs +++ b/crates/languages/src/elm.rs @@ -15,7 +15,7 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::ResultExt; +use util::{async_maybe, ResultExt}; const SERVER_NAME: &str = "elm-language-server"; const SERVER_PATH: &str = "node_modules/@elm-tooling/elm-language-server/out/node/index.js"; @@ -117,7 +117,7 @@ async fn get_cached_server_binary( container_dir: PathBuf, node: &dyn NodeRuntime, ) -> Option { - (|| async move { + async_maybe!({ let mut last_version_dir = None; let mut entries = fs::read_dir(&container_dir).await?; while let Some(entry) = entries.next().await { @@ -140,7 +140,7 @@ async fn get_cached_server_binary( last_version_dir )) } - })() + }) .await .log_err() } diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index f47bf33214..6aef9f6d16 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -122,212 +122,245 @@ pub fn init( ("dart", tree_sitter_dart::language()), ]); - let language = |asset_dir_name: &'static str, adapters: Vec>| { - let config = load_config(asset_dir_name); - for adapter in adapters { - languages.register_lsp_adapter(config.name.clone(), adapter); - } - languages.register_language( - config.name.clone(), - config.grammar.clone(), - config.matcher.clone(), - move || Ok((config.clone(), load_queries(asset_dir_name))), - ); - }; - - language( + macro_rules! language { + ($name:literal) => { + let config = load_config($name); + languages.register_language( + config.name.clone(), + config.grammar.clone(), + config.matcher.clone(), + Some(Arc::new(language::DefaultContextProvider)), + move || Ok((config.clone(), load_queries($name))), + ); + }; + ($name:literal, $adapters:expr) => { + let config = load_config($name); + // typeck helper + let adapters: Vec> = $adapters; + for adapter in adapters { + languages.register_lsp_adapter(config.name.clone(), adapter); + } + languages.register_language( + config.name.clone(), + config.grammar.clone(), + config.matcher.clone(), + Some(Arc::new(language::DefaultContextProvider)), + move || Ok((config.clone(), load_queries($name))), + ); + }; + ($name:literal, $adapters:expr, $context_provider:expr) => { + let config = load_config($name); + // typeck helper + let adapters: Vec> = $adapters; + for adapter in $adapters { + languages.register_lsp_adapter(config.name.clone(), adapter); + } + languages.register_language( + config.name.clone(), + config.grammar.clone(), + config.matcher.clone(), + Some(Arc::new($context_provider)), + move || Ok((config.clone(), load_queries($name))), + ); + }; + } + language!( "astro", vec![ Arc::new(astro::AstroLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language("bash", vec![]); - language("c", vec![Arc::new(c::CLspAdapter) as Arc]); - language("clojure", vec![Arc::new(clojure::ClojureLspAdapter)]); - language("cpp", vec![Arc::new(c::CLspAdapter)]); - language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]); - language( + language!("bash"); + language!("c", vec![Arc::new(c::CLspAdapter) as Arc]); + language!("clojure", vec![Arc::new(clojure::ClojureLspAdapter)]); + language!("cpp", vec![Arc::new(c::CLspAdapter)]); + language!("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]); + language!( "css", vec![ Arc::new(css::CssLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language( + language!( "dockerfile", vec![Arc::new(dockerfile::DockerfileLspAdapter::new( node_runtime.clone(), - ))], + ))] ); match &ElixirSettings::get(None, cx).lsp { - elixir::ElixirLspSetting::ElixirLs => language( - "elixir", - vec![ - Arc::new(elixir::ElixirLspAdapter), - Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], - ), - elixir::ElixirLspSetting::NextLs => { - language("elixir", vec![Arc::new(elixir::NextLspAdapter)]) + elixir::ElixirLspSetting::ElixirLs => { + language!( + "elixir", + vec![ + Arc::new(elixir::ElixirLspAdapter), + Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), + ] + ); + } + elixir::ElixirLspSetting::NextLs => { + language!("elixir", vec![Arc::new(elixir::NextLspAdapter)]); + } + elixir::ElixirLspSetting::Local { path, arguments } => { + language!( + "elixir", + vec![Arc::new(elixir::LocalLspAdapter { + path: path.clone(), + arguments: arguments.clone(), + })] + ); } - elixir::ElixirLspSetting::Local { path, arguments } => language( - "elixir", - vec![Arc::new(elixir::LocalLspAdapter { - path: path.clone(), - arguments: arguments.clone(), - })], - ), } - language("gitcommit", vec![]); - language("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]); + language!("gitcommit"); + language!("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]); - language("gleam", vec![Arc::new(gleam::GleamLspAdapter)]); - language("go", vec![Arc::new(go::GoLspAdapter)]); - language("gomod", vec![]); - language("gowork", vec![]); - language("zig", vec![Arc::new(zig::ZlsAdapter)]); - language( + language!("gleam", vec![Arc::new(gleam::GleamLspAdapter)]); + language!("go", vec![Arc::new(go::GoLspAdapter)]); + language!("gomod"); + language!("gowork"); + language!("zig", vec![Arc::new(zig::ZlsAdapter)]); + language!( "heex", vec![ Arc::new(elixir::ElixirLspAdapter), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language( + language!( "json", vec![Arc::new(json::JsonLspAdapter::new( node_runtime.clone(), languages.clone(), - ))], + ))] ); - language("markdown", vec![]); - language( + language!("markdown"); + language!( "python", vec![Arc::new(python::PythonLspAdapter::new( node_runtime.clone(), - ))], + ))] ); - language("rust", vec![Arc::new(rust::RustLspAdapter)]); - language("toml", vec![Arc::new(toml::TaploLspAdapter)]); + language!("rust", vec![Arc::new(rust::RustLspAdapter)]); + language!("toml", vec![Arc::new(toml::TaploLspAdapter)]); match &DenoSettings::get(None, cx).enable { true => { - language( + language!( "tsx", vec![ Arc::new(deno::DenoLspAdapter::new()), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language("typescript", vec![Arc::new(deno::DenoLspAdapter::new())]); - language( + language!("typescript", vec![Arc::new(deno::DenoLspAdapter::new())]); + language!( "javascript", vec![ Arc::new(deno::DenoLspAdapter::new()), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); } false => { - language( + language!( "tsx", vec![ Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language( + language!( "typescript", vec![ Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), - ], + ] ); - language( + language!( "javascript", vec![ Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())), Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); } } - language("haskell", vec![Arc::new(haskell::HaskellLanguageServer {})]); - language( + language!("haskell", vec![Arc::new(haskell::HaskellLanguageServer {})]); + language!( "html", vec![ Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language("ruby", vec![Arc::new(ruby::RubyLanguageServer)]); - language( + language!("ruby", vec![Arc::new(ruby::RubyLanguageServer)]); + language!( "erb", vec![ Arc::new(ruby::RubyLanguageServer), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language("scheme", vec![]); - language("racket", vec![]); - language("lua", vec![Arc::new(lua::LuaLspAdapter)]); - language( + language!("scheme"); + language!("racket"); + language!("lua", vec![Arc::new(lua::LuaLspAdapter)]); + language!( "yaml", - vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))], + vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))] ); - language( + language!( "svelte", vec![ Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language( + language!( "php", vec![ Arc::new(php::IntelephenseLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), - ], + ] ); - language( + language!( "purescript", vec![Arc::new(purescript::PurescriptLspAdapter::new( node_runtime.clone(), - ))], + ))] ); - language( + language!( "elm", - vec![Arc::new(elm::ElmLspAdapter::new(node_runtime.clone()))], + vec![Arc::new(elm::ElmLspAdapter::new(node_runtime.clone()))] ); - language("glsl", vec![]); - language("nix", vec![]); - language("nu", vec![Arc::new(nu::NuLanguageServer {})]); - language("ocaml", vec![Arc::new(ocaml::OCamlLspAdapter)]); - language("ocaml-interface", vec![Arc::new(ocaml::OCamlLspAdapter)]); - language( + language!("glsl"); + language!("nix"); + language!("nu", vec![Arc::new(nu::NuLanguageServer {})]); + language!("ocaml", vec![Arc::new(ocaml::OCamlLspAdapter)]); + language!("ocaml-interface", vec![Arc::new(ocaml::OCamlLspAdapter)]); + language!( "vue", - vec![Arc::new(vue::VueLspAdapter::new(node_runtime.clone()))], + vec![Arc::new(vue::VueLspAdapter::new(node_runtime.clone()))] ); - language("uiua", vec![Arc::new(uiua::UiuaLanguageServer {})]); - language("proto", vec![]); - language("terraform", vec![Arc::new(terraform::TerraformLspAdapter)]); - language( + language!("uiua", vec![Arc::new(uiua::UiuaLanguageServer {})]); + language!("proto"); + language!("terraform", vec![Arc::new(terraform::TerraformLspAdapter)]); + language!( "terraform-vars", - vec![Arc::new(terraform::TerraformLspAdapter)], + vec![Arc::new(terraform::TerraformLspAdapter)] ); - language("hcl", vec![]); - language( + language!("hcl", vec![]); + language!( "prisma", vec![Arc::new(prisma::PrismaLspAdapter::new( node_runtime.clone(), - ))], + ))] ); - language("dart", vec![Arc::new(dart::DartLanguageServer {})]); + language!("dart", vec![Arc::new(dart::DartLanguageServer {})]); } #[cfg(any(test, feature = "test-support"))] diff --git a/crates/languages/src/ocaml.rs b/crates/languages/src/ocaml.rs index 3b965b9ba0..0dc4bfcd2b 100644 --- a/crates/languages/src/ocaml.rs +++ b/crates/languages/src/ocaml.rs @@ -62,7 +62,7 @@ impl LspAdapter for OCamlLspAdapter { language: &Arc, ) -> Option { let name = &completion.label; - let detail = completion.detail.as_ref().map(|s| s.replace("\n", " ")); + let detail = completion.detail.as_ref().map(|s| s.replace('\n', " ")); match completion.kind.zip(detail) { // Error of 'b : ('a, 'b) result @@ -124,7 +124,7 @@ impl LspAdapter for OCamlLspAdapter { // version : string // NOTE: (~|?) are omitted as we don't use them in the fuzzy filtering Some((CompletionItemKind::FIELD, detail)) - if name.starts_with("~") || name.starts_with("?") => + if name.starts_with('~') || name.starts_with('?') => { let label = name.trim_start_matches(&['~', '?']); let text = format!("{} : {}", label, detail); @@ -140,7 +140,7 @@ impl LspAdapter for OCamlLspAdapter { } let mut label_highlight = vec![( - 0..0 + label.len(), + 0..label.len(), language.grammar()?.highlight_id_for_name("property")?, )]; diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 09c3ddb312..bf1cae7d9d 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -83,7 +83,7 @@ impl LspAdapter for PythonLspAdapter { // Where `XX` is the sorting category, `YYYY` is based on most recent usage, // and `name` is the symbol name itself. // - // Because the the symbol name is included, there generally are not ties when + // Because the symbol name is included, there generally are not ties when // sorting by the `sortText`, so the symbol's fuzzy match score is not taken // into account. Here, we remove the symbol name from the sortText in order // to allow our own fuzzy score to be used to break ties. diff --git a/crates/languages/src/terraform.rs b/crates/languages/src/terraform.rs index 6d685ca55c..d201b8aeff 100644 --- a/crates/languages/src/terraform.rs +++ b/crates/languages/src/terraform.rs @@ -129,7 +129,7 @@ impl LspAdapter for TerraformLspAdapter { } fn build_download_url(version: String) -> Result { - let v = version.strip_prefix("v").unwrap_or(&version); + let v = version.strip_prefix('v').unwrap_or(&version); let os = match std::env::consts::OS { "linux" => "linux", "macos" => "darwin", diff --git a/crates/live_kit_client/Cargo.toml b/crates/live_kit_client/Cargo.toml index a18199fe5a..708fd61d41 100644 --- a/crates/live_kit_client/Cargo.toml +++ b/crates/live_kit_client/Cargo.toml @@ -6,6 +6,9 @@ description = "Bindings to LiveKit Swift client SDK" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/live_kit_client.rs" doctest = false diff --git a/crates/live_kit_server/Cargo.toml b/crates/live_kit_server/Cargo.toml index 3c742111c4..bdac22a85c 100644 --- a/crates/live_kit_server/Cargo.toml +++ b/crates/live_kit_server/Cargo.toml @@ -6,6 +6,9 @@ description = "SDK for the LiveKit server API" publish = false license = "AGPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/live_kit_server.rs" doctest = false diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 902105341b..23b2f204ef 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/lsp.rs" doctest = false diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 638ed9286f..b1099cda5f 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -229,7 +229,7 @@ impl LanguageServer { let stdout = server.stdout.take().unwrap(); let stderr = server.stderr.take().unwrap(); let mut server = Self::new_internal( - server_id.clone(), + server_id, stdin, stdout, Some(stderr), @@ -261,6 +261,7 @@ impl LanguageServer { Ok(server) } + #[allow(clippy::too_many_arguments)] fn new_internal( server_id: LanguageServerId, stdin: Stdin, @@ -340,7 +341,7 @@ impl LanguageServer { io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), - server: Arc::new(Mutex::new(server.map(|server| server))), + server: Arc::new(Mutex::new(server)), } } @@ -377,7 +378,7 @@ impl LanguageServer { let headers = std::str::from_utf8(&buffer)?; let message_len = headers - .split("\n") + .split('\n') .find(|line| line.starts_with(CONTENT_LEN_HEADER)) .and_then(|line| line.strip_prefix(CONTENT_LEN_HEADER)) .ok_or_else(|| anyhow!("invalid LSP message header {headers:?}"))? @@ -569,7 +570,14 @@ impl LanguageServer { }), data_support: Some(true), resolve_support: Some(CodeActionCapabilityResolveSupport { - properties: vec!["edit".to_string(), "command".to_string()], + properties: vec![ + "kind".to_string(), + "diagnostics".to_string(), + "isPreferred".to_string(), + "disabled".to_string(), + "edit".to_string(), + "command".to_string(), + ], }), ..Default::default() }), @@ -977,7 +985,7 @@ impl LanguageServer { Self::notify_internal::( &outbound_tx, CancelParams { - id: NumberOrString::Number(id as i32), + id: NumberOrString::Number(id), }, ) .log_err(); diff --git a/crates/markdown_preview/Cargo.toml b/crates/markdown_preview/Cargo.toml index 5ac5ae3cb5..f3192a79ca 100644 --- a/crates/markdown_preview/Cargo.toml +++ b/crates/markdown_preview/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/markdown_preview.rs" diff --git a/crates/markdown_preview/src/markdown_parser.rs b/crates/markdown_preview/src/markdown_parser.rs index b4e9d9829c..87e3266a22 100644 --- a/crates/markdown_preview/src/markdown_parser.rs +++ b/crates/markdown_preview/src/markdown_parser.rs @@ -105,7 +105,7 @@ impl<'a> MarkdownParser<'a> { classes: _, attrs: _, } => { - let level = level.clone(); + let level = *level; self.cursor += 1; let heading = self.parse_heading(level); Some(ParsedMarkdownElement::Heading(heading)) @@ -117,7 +117,7 @@ impl<'a> MarkdownParser<'a> { Some(ParsedMarkdownElement::Table(table)) } Tag::List(order) => { - let order = order.clone(); + let order = *order; self.cursor += 1; let list = self.parse_list(1, order); Some(ParsedMarkdownElement::List(list)) @@ -421,7 +421,7 @@ impl<'a> MarkdownParser<'a> { let (current, _source_range) = self.current().unwrap(); match current { Event::Start(Tag::List(order)) => { - let order = order.clone(); + let order = *order; self.cursor += 1; let inner_list = self.parse_list(depth + 1, order); @@ -467,7 +467,7 @@ impl<'a> MarkdownParser<'a> { let item_type = if let Some(checked) = task_item { ParsedMarkdownListItemType::Task(checked) - } else if let Some(order) = order.clone() { + } else if let Some(order) = order { ParsedMarkdownListItemType::Ordered(order) } else { ParsedMarkdownListItemType::Unordered diff --git a/crates/markdown_preview/src/markdown_preview_view.rs b/crates/markdown_preview/src/markdown_preview_view.rs index 2b0296fc30..50eb958987 100644 --- a/crates/markdown_preview/src/markdown_preview_view.rs +++ b/crates/markdown_preview/src/markdown_preview_view.rs @@ -132,7 +132,7 @@ impl MarkdownPreviewView { workspace, contents, list_state, - tab_description: tab_description.into(), + tab_description: tab_description, } }) } diff --git a/crates/media/Cargo.toml b/crates/media/Cargo.toml index 784139f129..ac22eccde6 100644 --- a/crates/media/Cargo.toml +++ b/crates/media/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/media.rs" doctest = false diff --git a/crates/menu/Cargo.toml b/crates/menu/Cargo.toml index cf17727242..ce12455710 100644 --- a/crates/menu/Cargo.toml +++ b/crates/menu/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/menu.rs" doctest = false diff --git a/crates/multi_buffer/Cargo.toml b/crates/multi_buffer/Cargo.toml index 322ff38a5a..f866217da8 100644 --- a/crates/multi_buffer/Cargo.toml +++ b/crates/multi_buffer/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/multi_buffer.rs" doctest = false diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index 877f74c21b..ee0862b957 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -55,12 +55,12 @@ impl Anchor { if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { return Self { buffer_id: self.buffer_id, - excerpt_id: self.excerpt_id.clone(), + excerpt_id: self.excerpt_id, text_anchor: self.text_anchor.bias_left(&excerpt.buffer), }; } } - self.clone() + *self } pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { @@ -68,12 +68,12 @@ impl Anchor { if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { return Self { buffer_id: self.buffer_id, - excerpt_id: self.excerpt_id.clone(), + excerpt_id: self.excerpt_id, text_anchor: self.text_anchor.bias_right(&excerpt.buffer), }; } } - self.clone() + *self } pub fn summary(&self, snapshot: &MultiBufferSnapshot) -> D diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index eedced9c2a..60b8af4a53 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -195,7 +195,7 @@ struct Excerpt { /// /// Contains methods for getting the [`Buffer`] of the excerpt, /// as well as mapping offsets to/from buffer and multibuffer coordinates. -#[derive(Copy, Clone)] +#[derive(Clone)] pub struct MultiBufferExcerpt<'a> { excerpt: &'a Excerpt, excerpt_offset: usize, @@ -950,12 +950,12 @@ impl MultiBuffer { for range in ranges.by_ref().take(range_count) { let start = Anchor { buffer_id: Some(buffer_id), - excerpt_id: excerpt_id.clone(), + excerpt_id: excerpt_id, text_anchor: range.start, }; let end = Anchor { buffer_id: Some(buffer_id), - excerpt_id: excerpt_id.clone(), + excerpt_id: excerpt_id, text_anchor: range.end, }; if tx.send(start..end).await.is_err() { @@ -1005,12 +1005,12 @@ impl MultiBuffer { anchor_ranges.extend(ranges.by_ref().take(range_count).map(|range| { let start = Anchor { buffer_id: Some(buffer_id), - excerpt_id: excerpt_id.clone(), + excerpt_id: excerpt_id, text_anchor: buffer_snapshot.anchor_after(range.start), }; let end = Anchor { buffer_id: Some(buffer_id), - excerpt_id: excerpt_id.clone(), + excerpt_id: excerpt_id, text_anchor: buffer_snapshot.anchor_after(range.end), }; start..end @@ -1206,7 +1206,7 @@ impl MultiBuffer { cursor.seek_forward(&Some(locator), Bias::Left, &()); if let Some(excerpt) = cursor.item() { if excerpt.locator == *locator { - excerpts.push((excerpt.id.clone(), excerpt.range.clone())); + excerpts.push((excerpt.id, excerpt.range.clone())); } } } @@ -1238,7 +1238,7 @@ impl MultiBuffer { .or_else(|| snapshot.excerpts.last()) .map(|excerpt| { ( - excerpt.id.clone(), + excerpt.id, self.buffers .borrow() .get(&excerpt.buffer_id) @@ -1525,11 +1525,7 @@ impl MultiBuffer { .unwrap_or(false) } - pub fn language_at<'a, T: ToOffset>( - &self, - point: T, - cx: &'a AppContext, - ) -> Option> { + pub fn language_at(&self, point: T, cx: &AppContext) -> Option> { self.point_to_buffer_offset(point, cx) .and_then(|(buffer, offset, _)| buffer.read(cx).language_at(offset)) } @@ -1859,7 +1855,7 @@ impl MultiBuffer { .cloned() .collect::>(); let snapshot = self.snapshot.borrow(); - excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &*snapshot)); + excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &snapshot)); drop(snapshot); log::info!("Removing excerpts {:?}", excerpts_to_remove); self.remove_excerpts(excerpts_to_remove, cx); @@ -1920,7 +1916,7 @@ impl MultiBuffer { for (ix, entry) in excerpt_ids.iter().enumerate() { if ix == 0 { - if entry.id.cmp(&ExcerptId::min(), &*snapshot).is_le() { + if entry.id.cmp(&ExcerptId::min(), &snapshot).is_le() { panic!("invalid first excerpt id {:?}", entry.id); } } else { @@ -2689,7 +2685,7 @@ impl MultiBufferSnapshot { if !kept_position { for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { if excerpt.contains(&anchor) { - anchor.excerpt_id = excerpt.id.clone(); + anchor.excerpt_id = excerpt.id; kept_position = true; break; } @@ -2713,7 +2709,7 @@ impl MultiBufferSnapshot { } Anchor { buffer_id: Some(excerpt.buffer_id), - excerpt_id: excerpt.id.clone(), + excerpt_id: excerpt.id, text_anchor, } } else if let Some(excerpt) = prev_excerpt { @@ -2730,7 +2726,7 @@ impl MultiBufferSnapshot { } Anchor { buffer_id: Some(excerpt.buffer_id), - excerpt_id: excerpt.id.clone(), + excerpt_id: excerpt.id, text_anchor, } } else if anchor.text_anchor.bias == Bias::Left { @@ -2760,7 +2756,7 @@ impl MultiBufferSnapshot { if let Some((excerpt_id, buffer_id, buffer)) = self.as_singleton() { return Anchor { buffer_id: Some(buffer_id), - excerpt_id: excerpt_id.clone(), + excerpt_id: *excerpt_id, text_anchor: buffer.anchor_at(offset, bias), }; } @@ -2782,7 +2778,7 @@ impl MultiBufferSnapshot { excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias)); Anchor { buffer_id: Some(excerpt.buffer_id), - excerpt_id: excerpt.id.clone(), + excerpt_id: excerpt.id, text_anchor, } } else if offset == 0 && bias == Bias::Left { @@ -2828,10 +2824,10 @@ impl MultiBufferSnapshot { .map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone())) } - fn excerpts_for_range<'a, T: ToOffset>( - &'a self, + fn excerpts_for_range( + &self, range: Range, - ) -> impl Iterator + 'a { + ) -> impl Iterator + '_ { let range = range.start.to_offset(self)..range.end.to_offset(self); let mut cursor = self.excerpts.cursor::(); @@ -2895,7 +2891,7 @@ impl MultiBufferSnapshot { let excerpt = cursor.item()?; let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id; let boundary = ExcerptBoundary { - id: excerpt.id.clone(), + id: excerpt.id, row: cursor.start().1.row, buffer: excerpt.buffer.clone(), range: excerpt.range.clone(), @@ -2956,10 +2952,10 @@ impl MultiBufferSnapshot { /// Returns enclosing bracket ranges containing the given range or returns None if the range is /// not contained in a single excerpt - pub fn enclosing_bracket_ranges<'a, T: ToOffset>( - &'a self, + pub fn enclosing_bracket_ranges( + &self, range: Range, - ) -> Option, Range)> + 'a> { + ) -> Option, Range)> + '_> { let range = range.start.to_offset(self)..range.end.to_offset(self); let excerpt = self.excerpt_containing(range.clone())?; @@ -2967,16 +2963,25 @@ impl MultiBufferSnapshot { excerpt .buffer() .enclosing_bracket_ranges(excerpt.map_range_to_buffer(range)) - .filter(move |(open, close)| excerpt.contains_buffer_range(open.start..close.end)), + .filter_map(move |(open, close)| { + if excerpt.contains_buffer_range(open.start..close.end) { + Some(( + excerpt.map_range_from_buffer(open), + excerpt.map_range_from_buffer(close), + )) + } else { + None + } + }), ) } /// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is /// not contained in a single excerpt - pub fn bracket_ranges<'a, T: ToOffset>( - &'a self, + pub fn bracket_ranges( + &self, range: Range, - ) -> Option, Range)> + 'a> { + ) -> Option, Range)> + '_> { let range = range.start.to_offset(self)..range.end.to_offset(self); let excerpt = self.excerpt_containing(range.clone())?; @@ -3041,12 +3046,12 @@ impl MultiBufferSnapshot { self.trailing_excerpt_update_count } - pub fn file_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc> { + pub fn file_at(&self, point: T) -> Option<&Arc> { self.point_to_buffer_offset(point) .and_then(|(buffer, _)| buffer.file()) } - pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc> { + pub fn language_at(&self, point: T) -> Option<&Arc> { self.point_to_buffer_offset(point) .and_then(|(buffer, offset)| buffer.language_at(offset)) } @@ -3065,7 +3070,7 @@ impl MultiBufferSnapshot { language_settings(language, file, cx) } - pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + pub fn language_scope_at(&self, point: T) -> Option { self.point_to_buffer_offset(point) .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) } @@ -3133,10 +3138,10 @@ impl MultiBufferSnapshot { false } - pub fn git_diff_hunks_in_range_rev<'a>( - &'a self, + pub fn git_diff_hunks_in_range_rev( + &self, row_range: Range, - ) -> impl 'a + Iterator> { + ) -> impl Iterator> + '_ { let mut cursor = self.excerpts.cursor::(); cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &()); @@ -3172,7 +3177,7 @@ impl MultiBufferSnapshot { let buffer_hunks = excerpt .buffer .git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end) - .filter_map(move |hunk| { + .map(move |hunk| { let start = multibuffer_start.row + hunk .buffer_range @@ -3185,10 +3190,10 @@ impl MultiBufferSnapshot { .min(excerpt_end_point.row + 1) .saturating_sub(excerpt_start_point.row); - Some(DiffHunk { + DiffHunk { buffer_range: start..end, diff_base_byte_range: hunk.diff_base_byte_range.clone(), - }) + } }); cursor.prev(&()); @@ -3198,10 +3203,10 @@ impl MultiBufferSnapshot { .flatten() } - pub fn git_diff_hunks_in_range<'a>( - &'a self, + pub fn git_diff_hunks_in_range( + &self, row_range: Range, - ) -> impl 'a + Iterator> { + ) -> impl Iterator> + '_ { let mut cursor = self.excerpts.cursor::(); cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &()); @@ -3234,7 +3239,7 @@ impl MultiBufferSnapshot { let buffer_hunks = excerpt .buffer .git_diff_hunks_intersecting_range(buffer_start..buffer_end) - .filter_map(move |hunk| { + .map(move |hunk| { let start = multibuffer_start.row + hunk .buffer_range @@ -3247,10 +3252,10 @@ impl MultiBufferSnapshot { .min(excerpt_end_point.row + 1) .saturating_sub(excerpt_start_point.row); - Some(DiffHunk { + DiffHunk { buffer_range: start..end, diff_base_byte_range: hunk.diff_base_byte_range.clone(), - }) + } }); cursor.next(&()); @@ -3280,8 +3285,8 @@ impl MultiBufferSnapshot { .into_iter() .map(|item| OutlineItem { depth: item.depth, - range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start) - ..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end), + range: self.anchor_in_excerpt(*excerpt_id, item.range.start) + ..self.anchor_in_excerpt(*excerpt_id, item.range.end), text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, @@ -3317,7 +3322,7 @@ impl MultiBufferSnapshot { )) } - fn excerpt_locator_for_id<'a>(&'a self, id: ExcerptId) -> &'a Locator { + fn excerpt_locator_for_id(&self, id: ExcerptId) -> &Locator { if id == ExcerptId::min() { Locator::min_ref() } else if id == ExcerptId::max() { @@ -3342,7 +3347,7 @@ impl MultiBufferSnapshot { Some(&self.excerpt(excerpt_id)?.buffer) } - fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> { + fn excerpt(&self, excerpt_id: ExcerptId) -> Option<&Excerpt> { let mut cursor = self.excerpts.cursor::>(); let locator = self.excerpt_locator_for_id(excerpt_id); cursor.seek(&Some(locator), Bias::Left, &()); @@ -3402,19 +3407,19 @@ impl MultiBufferSnapshot { selections.map(move |selection| { let mut start = Anchor { buffer_id: Some(excerpt.buffer_id), - excerpt_id: excerpt.id.clone(), + excerpt_id: excerpt.id, text_anchor: selection.start, }; let mut end = Anchor { buffer_id: Some(excerpt.buffer_id), - excerpt_id: excerpt.id.clone(), + excerpt_id: excerpt.id, text_anchor: selection.end, }; if range.start.cmp(&start, self).is_gt() { - start = range.start.clone(); + start = range.start; } if range.end.cmp(&end, self).is_lt() { - end = range.end.clone(); + end = range.end; } ( diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index 1c608b703f..7e713a3e2d 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/node_runtime.rs" doctest = false diff --git a/crates/notifications/Cargo.toml b/crates/notifications/Cargo.toml index 40d61087fa..3e1c856e40 100644 --- a/crates/notifications/Cargo.toml +++ b/crates/notifications/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/notification_store.rs" doctest = false diff --git a/crates/outline/Cargo.toml b/crates/outline/Cargo.toml index 28dc5d6a1b..540e979ab3 100644 --- a/crates/outline/Cargo.toml +++ b/crates/outline/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/outline.rs" doctest = false diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 627c4a0de2..0a8b9a08ec 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -279,7 +279,7 @@ impl PickerDelegate for OutlineViewDelegate { font_size: settings.buffer_font_size(cx).into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.).into(), + line_height: relative(1.), background_color: None, underline: None, strikethrough: None, diff --git a/crates/picker/Cargo.toml b/crates/picker/Cargo.toml index 510ce14a59..435e4459e9 100644 --- a/crates/picker/Cargo.toml +++ b/crates/picker/Cargo.toml @@ -5,11 +5,15 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/picker.rs" doctest = false [dependencies] +anyhow.workspace = true editor.workspace = true gpui.workspace = true menu.workspace = true diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 033f6c7661..6ff9f62e0f 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use editor::Editor; use gpui::{ div, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent, DismissEvent, @@ -15,11 +16,16 @@ enum ElementContainer { UniformList(UniformListScrollHandle), } +struct PendingUpdateMatches { + delegate_update_matches: Option>, + _task: Task>, +} + pub struct Picker { pub delegate: D, element_container: ElementContainer, editor: View, - pending_update_matches: Option>, + pending_update_matches: Option, confirm_on_update: Option, width: Option, max_height: Option, @@ -281,15 +287,32 @@ impl Picker { } pub fn update_matches(&mut self, query: String, cx: &mut ViewContext) { - let update = self.delegate.update_matches(query, cx); + let delegate_pending_update_matches = self.delegate.update_matches(query, cx); + self.matches_updated(cx); - self.pending_update_matches = Some(cx.spawn(|this, mut cx| async move { - update.await; - this.update(&mut cx, |this, cx| { - this.matches_updated(cx); - }) - .ok(); - })); + // This struct ensures that we can synchronously drop the task returned by the + // delegate's `update_matches` method and the task that the picker is spawning. + // If we simply capture the delegate's task into the picker's task, when the picker's + // task gets synchronously dropped, the delegate's task would keep running until + // the picker's task has a chance of being scheduled, because dropping a task happens + // asynchronously. + self.pending_update_matches = Some(PendingUpdateMatches { + delegate_update_matches: Some(delegate_pending_update_matches), + _task: cx.spawn(|this, mut cx| async move { + let delegate_pending_update_matches = this.update(&mut cx, |this, _| { + this.pending_update_matches + .as_mut() + .unwrap() + .delegate_update_matches + .take() + .unwrap() + })?; + delegate_pending_update_matches.await; + this.update(&mut cx, |this, cx| { + this.matches_updated(cx); + }) + }), + }); } fn matches_updated(&mut self, cx: &mut ViewContext) { @@ -325,6 +348,7 @@ impl Picker { fn render_element(&self, cx: &mut ViewContext, ix: usize) -> impl IntoElement { div() .id(("item", ix)) + .cursor_pointer() .on_click(cx.listener(move |this, event: &ClickEvent, cx| { this.handle_click(ix, event.down.modifiers.command, cx) })) diff --git a/crates/prettier/Cargo.toml b/crates/prettier/Cargo.toml index 242a65ba7b..586ff0770f 100644 --- a/crates/prettier/Cargo.toml +++ b/crates/prettier/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/prettier.rs" doctest = false diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index a2a168fb2d..676ed6d1ac 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -197,7 +197,7 @@ impl Prettier { arguments: vec![prettier_server.into(), prettier_dir.as_path().into()], env: None, }, - Path::new("/"), + &prettier_dir, None, cx.clone(), ) @@ -245,7 +245,7 @@ impl Prettier { ); let plugin_name_into_path = |plugin_name: &str| { let prettier_plugin_dir = prettier_node_modules.join(plugin_name); - for possible_plugin_path in [ + [ prettier_plugin_dir.join("dist").join("index.mjs"), prettier_plugin_dir.join("dist").join("index.js"), prettier_plugin_dir.join("dist").join("plugin.js"), @@ -255,12 +255,9 @@ impl Prettier { // this one is for @prettier/plugin-php prettier_plugin_dir.join("standalone.js"), prettier_plugin_dir, - ] { - if possible_plugin_path.is_file() { - return Some(possible_plugin_path); - } - } - None + ] + .into_iter() + .find(|possible_plugin_path| possible_plugin_path.is_file()) }; let (parser, located_plugins) = match parser_with_plugins { Some((parser, plugins)) => { @@ -338,9 +335,9 @@ impl Prettier { .collect(); log::debug!( "Formatting file {:?} with prettier, plugins :{:?}, options: {:?}", + buffer.file().map(|f| f.full_path(cx)), plugins, prettier_options, - buffer.file().map(|f| f.full_path(cx)) ); anyhow::Ok(FormatParams { diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index c76424144f..875ebb2de4 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/project.rs" doctest = false diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 128a11d5f3..8cbd83a50c 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1408,7 +1408,7 @@ impl LspCommand for GetHover { if let Some(range) = range.as_ref() { buffer .update(&mut cx, |buffer, _| { - buffer.wait_for_anchors([range.start.clone(), range.end.clone()]) + buffer.wait_for_anchors([range.start, range.end]) })? .await?; } @@ -1523,7 +1523,7 @@ impl LspCommand for GetCompletions { }); let range = if let Some(range) = default_edit_range { - let range = range_from_lsp(range.clone()); + let range = range_from_lsp(*range); let start = snapshot.clip_point_utf16(range.start, Bias::Left); let end = snapshot.clip_point_utf16(range.end, Bias::Left); if start != range.start.0 || end != range.end.0 { @@ -1834,6 +1834,19 @@ impl LspCommand for GetCodeActions { } } +impl GetCodeActions { + pub fn can_resolve_actions(capabilities: &ServerCapabilities) -> bool { + capabilities + .code_action_provider + .as_ref() + .and_then(|options| match options { + lsp::CodeActionProviderCapability::Simple(_is_supported) => None, + lsp::CodeActionProviderCapability::Options(options) => options.resolve_provider, + }) + .unwrap_or(false) + } +} + #[async_trait(?Send)] impl LspCommand for OnTypeFormatting { type Response = Option; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 45aca64a5a..a82b156b03 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -11,11 +11,15 @@ mod project_tests; use anyhow::{anyhow, bail, Context as _, Result}; use async_trait::async_trait; -use client::{proto, Client, Collaborator, TypedEnvelope, UserStore}; +use client::{ + proto, Client, Collaborator, HostedProjectId, PendingEntitySubscription, TypedEnvelope, + UserStore, +}; use clock::ReplicaId; use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque}; use copilot::Copilot; use debounced_delay::DebouncedDelay; +use fs::repository::GitRepository; use futures::{ channel::mpsc::{self, UnboundedReceiver}, future::{try_join_all, Shared}, @@ -36,11 +40,11 @@ use language::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, serialize_anchor, serialize_version, split_operations, }, - range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, - CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, - Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, - LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, - PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, + range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeAction, + CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation, + Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, + LspAdapterDelegate, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, + ToOffset, ToPointUtf16, Transaction, Unclipped, }; use log::error; use lsp::{ @@ -167,6 +171,7 @@ pub struct Project { prettiers_per_worktree: HashMap>>, prettier_instances: HashMap, tasks: Model, + hosted_project_id: Option, } pub enum LanguageServerToQuery { @@ -605,6 +610,7 @@ impl Project { prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), tasks, + hosted_project_id: None, } }) } @@ -615,8 +621,7 @@ impl Project { user_store: Model, languages: Arc, fs: Arc, - role: proto::ChannelRole, - mut cx: AsyncAppContext, + cx: AsyncAppContext, ) -> Result> { client.authenticate_and_connect(true, &cx).await?; @@ -626,6 +631,28 @@ impl Project { project_id: remote_id, }) .await?; + Self::from_join_project_response( + response, + subscription, + client, + user_store, + languages, + fs, + cx, + ) + .await + } + async fn from_join_project_response( + response: TypedEnvelope, + subscription: PendingEntitySubscription, + client: Arc, + user_store: Model, + languages: Arc, + fs: Arc, + mut cx: AsyncAppContext, + ) -> Result> { + let remote_id = response.payload.project_id; + let role = response.payload.role(); let this = cx.new_model(|cx| { let replica_id = response.payload.replica_id as ReplicaId; let tasks = Inventory::new(cx); @@ -714,6 +741,7 @@ impl Project { prettiers_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), tasks, + hosted_project_id: None, }; this.set_role(role, cx); for worktree in worktrees { @@ -742,6 +770,32 @@ impl Project { Ok(this) } + pub async fn hosted( + _hosted_project_id: HostedProjectId, + _user_store: Model, + _client: Arc, + _languages: Arc, + _fs: Arc, + _cx: AsyncAppContext, + ) -> Result> { + // let response = client + // .request_envelope(proto::JoinHostedProject { + // id: hosted_project_id.0, + // }) + // .await?; + // Self::from_join_project_response( + // response, + // Some(hosted_project_id), + // client, + // user_store, + // languages, + // fs, + // cx, + // ) + // .await + Err(anyhow!("disabled")) + } + fn release(&mut self, cx: &mut AppContext) { match &self.client_state { ProjectClientState::Local => {} @@ -987,6 +1041,10 @@ impl Project { } } + pub fn hosted_project_id(&self) -> Option { + self.hosted_project_id + } + pub fn replica_id(&self) -> ReplicaId { match self.client_state { ProjectClientState::Remote { replica_id, .. } => replica_id, @@ -1016,7 +1074,7 @@ impl Project { } /// Collect all worktrees, including ones that don't appear in the project panel - pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator> { + pub fn worktrees(&self) -> impl '_ + DoubleEndedIterator> { self.worktrees .iter() .filter_map(move |worktree| worktree.upgrade()) @@ -2775,7 +2833,7 @@ impl Project { let project_settings = ProjectSettings::get(Some((worktree_id.to_proto() as usize, Path::new(""))), cx); let lsp = project_settings.lsp.get(&adapter.name.0); - let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); + let override_options = lsp.and_then(|s| s.initialization_options.clone()); let server_id = pending_server.server_id; let container_dir = pending_server.container_dir.clone(); @@ -2909,6 +2967,7 @@ impl Project { })) } + #[allow(clippy::too_many_arguments)] async fn setup_and_insert_language_server( this: WeakModel, worktree_path: &Path, @@ -3665,7 +3724,7 @@ impl Project { proto::LspWorkStart { token, message: report.message, - percentage: report.percentage.map(|p| p as u32), + percentage: report.percentage, }, ), }) @@ -3691,7 +3750,7 @@ impl Project { proto::LspWorkProgress { token, message: report.message, - percentage: report.percentage.map(|p| p as u32), + percentage: report.percentage, }, ), }) @@ -4282,6 +4341,7 @@ impl Project { }) .collect(); + #[allow(clippy::nonminimal_bool)] if !code_actions.is_empty() && !(trigger == FormatTrigger::Save && settings.format_on_save == FormatOnSave::Off) @@ -4300,7 +4360,10 @@ impl Project { })? .await?; - for action in actions { + for mut action in actions { + Self::try_resolve_code_action(&language_server, &mut action) + .await + .context("resolving a formatting code action")?; if let Some(edit) = action.lsp_action.edit { if edit.changes.is_none() && edit.document_changes.is_none() { continue; @@ -4320,6 +4383,7 @@ impl Project { project_transaction.0.extend(new.0); } + // TODO kb here too: if let Some(command) = action.lsp_action.command { project.update(&mut cx, |this, _| { this.last_workspace_edits_by_language_server @@ -5362,33 +5426,10 @@ impl Project { } else { return Task::ready(Ok(Default::default())); }; - let range = action.range.to_point_utf16(buffer); - cx.spawn(move |this, mut cx| async move { - if let Some(lsp_range) = action - .lsp_action - .data - .as_mut() - .and_then(|d| d.get_mut("codeActionParams")) - .and_then(|d| d.get_mut("range")) - { - *lsp_range = serde_json::to_value(&range_to_lsp(range)).unwrap(); - action.lsp_action = lang_server - .request::(action.lsp_action) - .await?; - } else { - let actions = this - .update(&mut cx, |this, cx| { - this.code_actions(&buffer_handle, action.range, cx) - })? - .await?; - action.lsp_action = actions - .into_iter() - .find(|a| a.lsp_action.title == action.lsp_action.title) - .ok_or_else(|| anyhow!("code action is outdated"))? - .lsp_action; - } - + Self::try_resolve_code_action(&lang_server, &mut action) + .await + .context("resolving a code action")?; if let Some(edit) = action.lsp_action.edit { if edit.changes.is_some() || edit.document_changes.is_some() { return Self::deserialize_workspace_edit( @@ -5422,11 +5463,11 @@ impl Project { return Err(err); } - return Ok(this.update(&mut cx, |this, _| { + return this.update(&mut cx, |this, _| { this.last_workspace_edits_by_language_server .remove(&lang_server.server_id()) .unwrap_or_default() - })?); + }); } Ok(ProjectTransaction::default()) @@ -6182,6 +6223,7 @@ impl Project { } /// Pick paths that might potentially contain a match of a given search query. + #[allow(clippy::too_many_arguments)] async fn background_search( unnamed_buffers: Vec>, opened_buffers: HashMap, (Model, BufferSnapshot)>, @@ -7000,7 +7042,7 @@ impl Project { .spawn(async move { future_buffers .into_iter() - .filter_map(|e| e) + .flatten() .chain(current_buffers) .filter_map(|(buffer, path)| { let (work_directory, repo) = @@ -7121,7 +7163,7 @@ impl Project { .set_local_settings( worktree_id.as_u64() as usize, directory.clone(), - file_content.as_ref().map(String::as_str), + file_content.as_deref(), cx, ) .log_err(); @@ -7254,6 +7296,18 @@ impl Project { }) } + pub fn get_repo( + &self, + project_path: &ProjectPath, + cx: &AppContext, + ) -> Option>> { + self.worktree_for_id(project_path.worktree_id, cx)? + .read(cx) + .as_local()? + .snapshot() + .local_git_repo(&project_path.path) + } + // RPC message handlers async fn handle_unshare_project( @@ -7422,7 +7476,7 @@ impl Project { .set_local_settings( worktree.entity_id().as_u64() as usize, PathBuf::from(&envelope.payload.path).into(), - envelope.payload.content.as_ref().map(String::as_str), + envelope.payload.content.as_deref(), cx, ) .log_err(); @@ -7862,13 +7916,13 @@ impl Project { this.update(&mut cx, |this, cx| this.save_buffer(buffer.clone(), cx))? .await?; - Ok(buffer.update(&mut cx, |buffer, _| proto::BufferSaved { + buffer.update(&mut cx, |buffer, _| proto::BufferSaved { project_id, buffer_id: buffer_id.into(), version: serialize_version(buffer.saved_version()), mtime: Some(buffer.saved_mtime().into()), fingerprint: language::proto::serialize_fingerprint(buffer.saved_version_fingerprint()), - })?) + }) } async fn handle_reload_buffers( @@ -8203,7 +8257,7 @@ impl Project { .await .context("inlay hints fetch")?; - Ok(this.update(&mut cx, |project, cx| { + this.update(&mut cx, |project, cx| { InlayHints::response_to_proto( buffer_hints, project, @@ -8211,7 +8265,7 @@ impl Project { &buffer.read(cx).version(), cx, ) - })?) + }) } async fn handle_resolve_inlay_hint( @@ -8249,6 +8303,23 @@ impl Project { }) } + async fn try_resolve_code_action( + lang_server: &LanguageServer, + action: &mut CodeAction, + ) -> anyhow::Result<()> { + if GetCodeActions::can_resolve_actions(&lang_server.capabilities()) { + if action.lsp_action.data.is_some() + && (action.lsp_action.command.is_none() || action.lsp_action.edit.is_none()) + { + action.lsp_action = lang_server + .request::(action.lsp_action.clone()) + .await?; + } + } + + anyhow::Ok(()) + } + async fn handle_refresh_inlay_hints( this: Model, _: TypedEnvelope, @@ -9152,7 +9223,7 @@ fn subscribe_for_copilot_events( ) } -fn glob_literal_prefix<'a>(glob: &'a str) -> &'a str { +fn glob_literal_prefix(glob: &str) -> &str { let mut literal_end = 0; for (i, part) in glob.split(path::MAIN_SEPARATOR).enumerate() { if part.contains(&['*', '?', '{', '}']) { @@ -9376,7 +9447,7 @@ fn relativize_path(base: &Path, path: &Path) -> PathBuf { } (None, _) => components.push(Component::ParentDir), (Some(a), Some(b)) if components.is_empty() && a == b => (), - (Some(a), Some(b)) if b == Component::CurDir => components.push(a), + (Some(a), Some(Component::CurDir)) => components.push(a), (Some(a), Some(_)) => { components.push(Component::ParentDir); for _ in base_components { @@ -9453,23 +9524,38 @@ async fn load_shell_environment(dir: &Path) -> Result> { let shell = env::var("SHELL").context( "SHELL environment variable is not assigned so we can't source login environment variables", )?; + + // What we're doing here is to spawn a shell and then `cd` into + // the project directory to get the env in there as if the user + // `cd`'d into it. We do that because tools like direnv, asdf, ... + // hook into `cd` and only set up the env after that. + // + // In certain shells we need to execute additional_command in order to + // trigger the behavior of direnv, etc. + // + // + // The `exit 0` is the result of hours of debugging, trying to find out + // why running this command here, without `exit 0`, would mess + // up signal process for our process so that `ctrl-c` doesn't work + // anymore. + // + // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would + // do that, but it does, and `exit 0` helps. + let additional_command = PathBuf::from(&shell) + .file_name() + .and_then(|f| f.to_str()) + .and_then(|shell| match shell { + "fish" => Some("emit fish_prompt;"), + _ => None, + }); + + let command = format!( + "cd {dir:?};{} echo {marker}; /usr/bin/env -0; exit 0;", + additional_command.unwrap_or("") + ); + let output = smol::process::Command::new(&shell) - .args([ - "-i", - "-c", - // What we're doing here is to spawn a shell and then `cd` into - // the project directory to get the env in there as if the user - // `cd`'d into it. We do that because tools like direnv, asdf, ... - // hook into `cd` and only set up the env after that. - // - // The `exit 0` is the result of hours of debugging, trying to find out - // why running this command here, without `exit 0`, would mess - // up signal process for our process so that `ctrl-c` doesn't work - // anymore. - // We still don't know why `$SHELL -l -i -c '/usr/bin/env -0'` would - // do that, but it does, and `exit 0` helps. - &format!("cd {dir:?}; echo {marker}; /usr/bin/env -0; exit 0;"), - ]) + .args(["-i", "-c", &command]) .output() .await .context("failed to spawn login shell to source login environment variables")?; diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 1af8fca291..c513b9a2c4 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2475,8 +2475,21 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { let language_registry = project.read_with(cx, |project, _| project.languages().clone()); language_registry.add(typescript_lang()); - let mut fake_language_servers = - language_registry.register_fake_lsp_adapter("TypeScript", Default::default()); + let mut fake_language_servers = language_registry.register_fake_lsp_adapter( + "TypeScript", + FakeLspAdapter { + capabilities: lsp::ServerCapabilities { + code_action_provider: Some(lsp::CodeActionProviderCapability::Options( + lsp::CodeActionOptions { + resolve_provider: Some(true), + ..lsp::CodeActionOptions::default() + }, + )), + ..lsp::ServerCapabilities::default() + }, + ..FakeLspAdapter::default() + }, + ); let buffer = project .update(cx, |p, cx| p.open_local_buffer("/dir/a.ts", cx)) @@ -2492,16 +2505,14 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { Ok(Some(vec![ lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { title: "The code action".into(), - command: Some(lsp::Command { - title: "The command".into(), - command: "_the/command".into(), - arguments: Some(vec![json!("the-argument")]), - }), - ..Default::default() + data: Some(serde_json::json!({ + "command": "_the/command", + })), + ..lsp::CodeAction::default() }), lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction { title: "two".into(), - ..Default::default() + ..lsp::CodeAction::default() }), ])) }) @@ -2516,7 +2527,16 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) { // Resolving the code action does not populate its edits. In absence of // edits, we must execute the given command. fake_server.handle_request::( - |action, _| async move { Ok(action) }, + |mut action, _| async move { + if action.data.is_some() { + action.command = Some(lsp::Command { + title: "The command".into(), + command: "_the/command".into(), + arguments: Some(vec![json!("the-argument")]), + }); + } + Ok(action) + }, ); // While executing the command, the language server sends the editor diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 54b3e80a8b..b51cdf2ba4 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -10,13 +10,13 @@ use collections::{HashMap, VecDeque}; use gpui::{AppContext, Context, Model, ModelContext, Subscription}; use itertools::Itertools; use project_core::worktree::WorktreeId; -use task::{Task, TaskId, TaskSource}; +use task::{Task, TaskContext, TaskId, TaskSource}; use util::{post_inc, NumericPrefixWithSuffix}; /// Inventory tracks available tasks for a given project. pub struct Inventory { sources: Vec, - last_scheduled_tasks: VecDeque, + last_scheduled_tasks: VecDeque<(TaskId, TaskContext)>, } struct SourceInInventory { @@ -133,17 +133,20 @@ impl Inventory { ) -> Vec<(TaskSourceKind, Arc)> { let mut lru_score = 0_u32; let tasks_by_usage = if lru { - self.last_scheduled_tasks - .iter() - .rev() - .fold(HashMap::default(), |mut tasks, id| { - tasks.entry(id).or_insert_with(|| post_inc(&mut lru_score)); + self.last_scheduled_tasks.iter().rev().fold( + HashMap::default(), + |mut tasks, (id, context)| { tasks - }) + .entry(id) + .or_insert_with(|| (post_inc(&mut lru_score), Some(context))); + tasks + }, + ) } else { HashMap::default() }; - let not_used_score = post_inc(&mut lru_score); + let not_used_task_context = None; + let not_used_score = (post_inc(&mut lru_score), not_used_task_context); self.sources .iter() .filter(|source| { @@ -171,7 +174,8 @@ impl Inventory { .sorted_unstable_by( |((kind_a, task_a), usages_a), ((kind_b, task_b), usages_b)| { usages_a - .cmp(usages_b) + .0 + .cmp(&usages_b.0) .then( kind_a .worktree() @@ -200,19 +204,21 @@ impl Inventory { } /// Returns the last scheduled task, if any of the sources contains one with the matching id. - pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option> { - self.last_scheduled_tasks.back().and_then(|id| { - // TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future. - self.list_tasks(None, None, false, cx) - .into_iter() - .find(|(_, task)| task.id() == id) - .map(|(_, task)| task) - }) + pub fn last_scheduled_task(&self, cx: &mut AppContext) -> Option<(Arc, TaskContext)> { + self.last_scheduled_tasks + .back() + .and_then(|(id, task_context)| { + // TODO straighten the `Path` story to understand what has to be passed here: or it will break in the future. + self.list_tasks(None, None, false, cx) + .into_iter() + .find(|(_, task)| task.id() == id) + .map(|(_, task)| (task, task_context.clone())) + }) } /// Registers task "usage" as being scheduled – to be used for LRU sorting when listing all tasks. - pub fn task_scheduled(&mut self, id: TaskId) { - self.last_scheduled_tasks.push_back(id); + pub fn task_scheduled(&mut self, id: TaskId, task_context: TaskContext) { + self.last_scheduled_tasks.push_back((id, task_context)); if self.last_scheduled_tasks.len() > 5_000 { self.last_scheduled_tasks.pop_front(); } @@ -221,14 +227,11 @@ impl Inventory { #[cfg(any(test, feature = "test-support"))] pub mod test_inventory { - use std::{ - path::{Path, PathBuf}, - sync::Arc, - }; + use std::{path::Path, sync::Arc}; use gpui::{AppContext, Context as _, Model, ModelContext, TestAppContext}; use project_core::worktree::WorktreeId; - use task::{Task, TaskId, TaskSource}; + use task::{Task, TaskContext, TaskId, TaskSource}; use crate::Inventory; @@ -249,11 +252,11 @@ pub mod test_inventory { &self.name } - fn cwd(&self) -> Option<&Path> { + fn cwd(&self) -> Option<&str> { None } - fn exec(&self, _cwd: Option) -> Option { + fn exec(&self, _cwd: TaskContext) -> Option { None } } @@ -327,7 +330,7 @@ pub mod test_inventory { .into_iter() .find(|(_, task)| task.name() == task_name) .unwrap_or_else(|| panic!("Failed to find task with name {task_name}")); - inventory.task_scheduled(task.1.id().clone()); + inventory.task_scheduled(task.1.id().clone(), TaskContext::default()); }); } diff --git a/crates/project_core/Cargo.toml b/crates/project_core/Cargo.toml index c72fe909da..b82e9a26eb 100644 --- a/crates/project_core/Cargo.toml +++ b/crates/project_core/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] test-support = [ diff --git a/crates/project_core/src/worktree.rs b/crates/project_core/src/worktree.rs index 2bd3d10cc9..1f9e9a8e15 100644 --- a/crates/project_core/src/worktree.rs +++ b/crates/project_core/src/worktree.rs @@ -220,7 +220,7 @@ impl Deref for WorkDirectoryEntry { } } -impl<'a> From for WorkDirectoryEntry { +impl From for WorkDirectoryEntry { fn from(value: ProjectEntryId) -> Self { WorkDirectoryEntry(value) } @@ -991,7 +991,7 @@ impl LocalWorktree { pub fn scan_complete(&self) -> impl Future { let mut is_scanning_rx = self.is_scanning.1.clone(); async move { - let mut is_scanning = is_scanning_rx.borrow().clone(); + let mut is_scanning = *is_scanning_rx.borrow(); while is_scanning { if let Some(value) = is_scanning_rx.recv().await { is_scanning = value; @@ -2116,6 +2116,11 @@ impl LocalSnapshot { Some((path, self.git_repositories.get(&repo.work_directory_id())?)) } + pub fn local_git_repo(&self, path: &Path) -> Option>> { + self.local_repo_for_path(path) + .map(|(_, entry)| entry.repo_ptr.clone()) + } + fn build_update( &self, project_id: u64, @@ -3299,6 +3304,7 @@ enum BackgroundScannerPhase { } impl BackgroundScanner { + #[allow(clippy::too_many_arguments)] fn new( snapshot: LocalSnapshot, next_entry_id: Arc, @@ -3917,16 +3923,14 @@ impl BackgroundScanner { let repository = dotgit_path.and_then(|path| state.build_git_repository(path, self.fs.as_ref())); - for new_job in new_jobs { - if let Some(mut new_job) = new_job { - if let Some(containing_repository) = &repository { - new_job.containing_repository = Some(containing_repository.clone()); - } - - job.scan_queue - .try_send(new_job) - .expect("channel is unbounded"); + for mut new_job in new_jobs.into_iter().flatten() { + if let Some(containing_repository) = &repository { + new_job.containing_repository = Some(containing_repository.clone()); } + + job.scan_queue + .try_send(new_job) + .expect("channel is unbounded"); } Ok(()) diff --git a/crates/project_panel/Cargo.toml b/crates/project_panel/Cargo.toml index 093d47e124..2772fc18a2 100644 --- a/crates/project_panel/Cargo.toml +++ b/crates/project_panel/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/project_panel.rs" doctest = false diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 96117be2a4..481086a8bb 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -166,13 +166,7 @@ impl ProjectPanel { fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { let project = workspace.project().clone(); let project_panel = cx.new_view(|cx: &mut ViewContext| { - cx.observe(&project, |this, _, cx| { - this.update_visible_entries(None, cx); - cx.notify(); - }) - .detach(); let focus_handle = cx.focus_handle(); - cx.on_focus(&focus_handle, Self::focus_in).detach(); cx.subscribe(&project, |this, project, event, cx| match event { @@ -193,6 +187,10 @@ impl ProjectPanel { this.update_visible_entries(None, cx); cx.notify(); } + project::Event::WorktreeUpdatedEntries(_, _) | project::Event::WorktreeAdded => { + this.update_visible_entries(None, cx); + cx.notify(); + } _ => {} }) .detach(); @@ -607,7 +605,7 @@ impl ProjectPanel { worktree_id, entry_id: NEW_ENTRY_ID, }); - let new_path = entry.path.join(&filename.trim_start_matches("/")); + let new_path = entry.path.join(&filename.trim_start_matches('/')); if path_already_exists(new_path.as_path()) { return None; } @@ -1028,7 +1026,7 @@ impl ProjectPanel { cx.foreground_executor().spawn(task).detach_and_log_err(cx); } - Some(project.worktree_id_for_entry(destination, cx)?) + project.worktree_id_for_entry(destination, cx) }); if let Some(destination_worktree) = destination_worktree { diff --git a/crates/project_symbols/Cargo.toml b/crates/project_symbols/Cargo.toml index 93b122eaf0..cadbdd83aa 100644 --- a/crates/project_symbols/Cargo.toml +++ b/crates/project_symbols/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/project_symbols.rs" doctest = false diff --git a/crates/quick_action_bar/Cargo.toml b/crates/quick_action_bar/Cargo.toml index e1f8c66d10..3d5d56a318 100644 --- a/crates/quick_action_bar/Cargo.toml +++ b/crates/quick_action_bar/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/quick_action_bar.rs" doctest = false diff --git a/crates/recent_projects/Cargo.toml b/crates/recent_projects/Cargo.toml index f96dfbdd7d..3e8f63d133 100644 --- a/crates/recent_projects/Cargo.toml +++ b/crates/recent_projects/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/recent_projects.rs" doctest = false diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index eecd251537..a9ceefee6d 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -16,10 +16,14 @@ use workspace::{ModalView, Workspace, WorkspaceId, WorkspaceLocation, WORKSPACE_ #[derive(PartialEq, Clone, Deserialize, Default)] pub struct OpenRecent { - #[serde(default)] + #[serde(default = "default_create_new_window")] pub create_new_window: bool, } +fn default_create_new_window() -> bool { + true +} + gpui::impl_actions!(projects, [OpenRecent]); pub fn init(cx: &mut AppContext) { @@ -129,7 +133,6 @@ impl Render for RecentProjects { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_flex() .w(rems(self.rem_width)) - .cursor_pointer() .child(self.picker.clone()) .on_mouse_down_out(cx.listener(|this, _, cx| { this.picker.update(cx, |this, cx| { @@ -180,7 +183,7 @@ impl PickerDelegate for RecentProjectsDelegate { ) }; Arc::from(format!( - "{reuse_window} reuses the window, {create_window} opens a new one", + "{reuse_window} reuses this window, {create_window} opens a new one", )) } @@ -269,7 +272,7 @@ impl PickerDelegate for RecentProjectsDelegate { workspace .update(&mut cx, |workspace, cx| { workspace.open_workspace_for_paths( - replace_current_window, + true, candidate_paths, cx, ) @@ -280,11 +283,7 @@ impl PickerDelegate for RecentProjectsDelegate { } }) } else { - workspace.open_workspace_for_paths( - replace_current_window, - candidate_paths, - cx, - ) + workspace.open_workspace_for_paths(false, candidate_paths, cx) } } else { Task::ready(Ok(())) @@ -325,10 +324,7 @@ impl PickerDelegate for RecentProjectsDelegate { .unzip(); let highlighted_match = HighlightedMatchWithPaths { - match_label: HighlightedText::join( - match_labels.into_iter().filter_map(|name| name), - ", ", - ), + match_label: HighlightedText::join(match_labels.into_iter().flatten(), ", "), paths: if self.render_paths { paths } else { Vec::new() }, }; Some( @@ -539,7 +535,7 @@ mod tests { !cx.has_pending_prompt(), "Should have no pending prompt on dirty project before opening the new recent project" ); - cx.dispatch_action((*workspace).into(), menu::Confirm); + cx.dispatch_action(*workspace, menu::Confirm); workspace .update(cx, |workspace, cx| { assert!( diff --git a/crates/refineable/Cargo.toml b/crates/refineable/Cargo.toml index 97f03f1f2f..bda47e7a8a 100644 --- a/crates/refineable/Cargo.toml +++ b/crates/refineable/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/refineable.rs" doctest = false diff --git a/crates/refineable/derive_refineable/Cargo.toml b/crates/refineable/derive_refineable/Cargo.toml index f0d6d6d2dc..00502ec19a 100644 --- a/crates/refineable/derive_refineable/Cargo.toml +++ b/crates/refineable/derive_refineable/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/derive_refineable.rs" proc-macro = true diff --git a/crates/release_channel/Cargo.toml b/crates/release_channel/Cargo.toml index b40ce36346..acea552d63 100644 --- a/crates/release_channel/Cargo.toml +++ b/crates/release_channel/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] gpui.workspace = true once_cell = "1.19.0" diff --git a/crates/release_channel/src/lib.rs b/crates/release_channel/src/lib.rs index 78b17ba997..864df387c0 100644 --- a/crates/release_channel/src/lib.rs +++ b/crates/release_channel/src/lib.rs @@ -139,26 +139,6 @@ impl ReleaseChannel { } } - /// Returns the URL scheme for this [`ReleaseChannel`]. - pub fn url_scheme(&self) -> &'static str { - match self { - ReleaseChannel::Dev => "zed-dev://", - ReleaseChannel::Nightly => "zed-nightly://", - ReleaseChannel::Preview => "zed-preview://", - ReleaseChannel::Stable => "zed://", - } - } - - /// Returns the link prefix for this [`ReleaseChannel`]. - pub fn link_prefix(&self) -> &'static str { - match self { - ReleaseChannel::Dev => "https://zed.dev/dev/", - ReleaseChannel::Nightly => "https://zed.dev/nightly/", - ReleaseChannel::Preview => "https://zed.dev/preview/", - ReleaseChannel::Stable => "https://zed.dev/", - } - } - /// Returns the query parameter for this [`ReleaseChannel`]. pub fn release_query_param(&self) -> Option<&'static str> { match self { @@ -169,24 +149,3 @@ impl ReleaseChannel { } } } - -/// Parses the given link into a Zed link. -/// -/// Returns a [`Some`] containing the unprefixed link if the link is a Zed link. -/// Returns [`None`] otherwise. -pub fn parse_zed_link(link: &str) -> Option<&str> { - for release in [ - ReleaseChannel::Dev, - ReleaseChannel::Nightly, - ReleaseChannel::Preview, - ReleaseChannel::Stable, - ] { - if let Some(stripped) = link.strip_prefix(release.link_prefix()) { - return Some(stripped); - } - if let Some(stripped) = link.strip_prefix(release.url_scheme()) { - return Some(stripped); - } - } - None -} diff --git a/crates/rich_text/Cargo.toml b/crates/rich_text/Cargo.toml index 5a12d0e56f..146fdcbd8a 100644 --- a/crates/rich_text/Cargo.toml +++ b/crates/rich_text/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/rich_text.rs" doctest = false diff --git a/crates/rich_text/src/rich_text.rs b/crates/rich_text/src/rich_text.rs index effa671590..2e5a28351d 100644 --- a/crates/rich_text/src/rich_text.rs +++ b/crates/rich_text/src/rich_text.rs @@ -124,6 +124,7 @@ impl RichText { } } +#[allow(clippy::too_many_arguments)] pub fn render_markdown_mut( block: &str, mut mentions: &[Mention], diff --git a/crates/rope/Cargo.toml b/crates/rope/Cargo.toml index 9190341f53..b1ce369416 100644 --- a/crates/rope/Cargo.toml +++ b/crates/rope/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/rope.rs" diff --git a/crates/rpc/Cargo.toml b/crates/rpc/Cargo.toml index 7f6750bce5..a4af21df11 100644 --- a/crates/rpc/Cargo.toml +++ b/crates/rpc/Cargo.toml @@ -6,6 +6,9 @@ version = "0.1.0" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/rpc.rs" doctest = false diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 2b67a37370..c189cc967c 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -197,6 +197,8 @@ message Envelope { GetImplementation get_implementation = 162; GetImplementationResponse get_implementation_response = 163; + + JoinHostedProject join_hosted_project = 164; } reserved 158 to 161; @@ -231,6 +233,7 @@ enum ErrorCode { CircularNesting = 10; WrongMoveTarget = 11; UnsharedItem = 12; + NoSuchProject = 13; reserved 6; } @@ -405,11 +408,17 @@ message JoinProject { uint64 project_id = 1; } +message JoinHostedProject { + uint64 id = 1; +} + message JoinProjectResponse { + uint64 project_id = 5; uint32 replica_id = 1; repeated WorktreeMetadata worktrees = 2; repeated Collaborator collaborators = 3; repeated LanguageServer language_servers = 4; + ChannelRole role = 6; } message LeaveProject { diff --git a/crates/rpc/src/conn.rs b/crates/rpc/src/conn.rs index ae5c9fd226..18bce4abba 100644 --- a/crates/rpc/src/conn.rs +++ b/crates/rpc/src/conn.rs @@ -84,7 +84,6 @@ impl Connection { }); let rx = rx.then({ - let killed = killed; let executor = executor.clone(); move |msg| { let killed = killed.clone(); diff --git a/crates/rpc/src/macros.rs b/crates/rpc/src/macros.rs index 85e2b0cf87..f85889a97b 100644 --- a/crates/rpc/src/macros.rs +++ b/crates/rpc/src/macros.rs @@ -1,7 +1,7 @@ #[macro_export] macro_rules! messages { ($(($name:ident, $priority:ident)),* $(,)?) => { - pub fn build_typed_envelope(sender_id: ConnectionId, envelope: Envelope) -> Option> { + pub fn build_typed_envelope(sender_id: ConnectionId, received_at: Instant, envelope: Envelope) -> Option> { match envelope.payload { $(Some(envelope::Payload::$name(payload)) => { Some(Box::new(TypedEnvelope { @@ -12,6 +12,7 @@ macro_rules! messages { }), message_id: envelope.id, payload, + received_at, })) }, )* _ => None diff --git a/crates/rpc/src/peer.rs b/crates/rpc/src/peer.rs index c73f52a99e..486e758a3c 100644 --- a/crates/rpc/src/peer.rs +++ b/crates/rpc/src/peer.rs @@ -13,7 +13,7 @@ use futures::{ }; use parking_lot::{Mutex, RwLock}; use serde::{ser::SerializeStruct, Serialize}; -use std::{fmt, sync::atomic::Ordering::SeqCst}; +use std::{fmt, sync::atomic::Ordering::SeqCst, time::Instant}; use std::{ future::Future, marker::PhantomData, @@ -79,6 +79,7 @@ pub struct TypedEnvelope { pub original_sender_id: Option, pub message_id: u32, pub payload: T, + pub received_at: Instant, } impl TypedEnvelope { @@ -111,8 +112,16 @@ pub struct ConnectionState { next_message_id: Arc, #[allow(clippy::type_complexity)] #[serde(skip)] - response_channels: - Arc)>>>>>, + response_channels: Arc< + Mutex< + Option< + HashMap< + u32, + oneshot::Sender<(proto::Envelope, std::time::Instant, oneshot::Sender<()>)>, + >, + >, + >, + >, } const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(1); @@ -154,7 +163,7 @@ impl Peer { #[cfg(any(test, feature = "test-support"))] const INCOMING_BUFFER_SIZE: usize = 1; #[cfg(not(any(test, feature = "test-support")))] - const INCOMING_BUFFER_SIZE: usize = 64; + const INCOMING_BUFFER_SIZE: usize = 256; let (mut incoming_tx, incoming_rx) = mpsc::channel(INCOMING_BUFFER_SIZE); let (outgoing_tx, mut outgoing_rx) = mpsc::unbounded(); @@ -238,10 +247,10 @@ impl Peer { tracing::trace!(%connection_id, "incoming rpc message: received"); tracing::trace!(%connection_id, "receive timeout: resetting"); receive_timeout.set(create_timer(RECEIVE_TIMEOUT).fuse()); - if let proto::Message::Envelope(incoming) = incoming { + if let (proto::Message::Envelope(incoming), received_at) = incoming { tracing::trace!(%connection_id, "incoming rpc message: processing"); futures::select_biased! { - result = incoming_tx.send(incoming).fuse() => match result { + result = incoming_tx.send((incoming, received_at)).fuse() => match result { Ok(_) => { tracing::trace!(%connection_id, "incoming rpc message: processed"); } @@ -272,7 +281,7 @@ impl Peer { .write() .insert(connection_id, connection_state); - let incoming_rx = incoming_rx.filter_map(move |incoming| { + let incoming_rx = incoming_rx.filter_map(move |(incoming, received_at)| { let response_channels = response_channels.clone(); async move { let message_id = incoming.id; @@ -291,7 +300,7 @@ impl Peer { let channel = response_channels.lock().as_mut()?.remove(&responding_to); if let Some(tx) = channel { let requester_resumed = oneshot::channel(); - if let Err(error) = tx.send((incoming, requester_resumed.0)) { + if let Err(error) = tx.send((incoming, received_at, requester_resumed.0)) { tracing::trace!( %connection_id, message_id, @@ -315,8 +324,9 @@ impl Peer { "incoming response: requester resumed" ); } else { - let message_type = proto::build_typed_envelope(connection_id, incoming) - .map(|p| p.payload_type_name()); + let message_type = + proto::build_typed_envelope(connection_id, received_at, incoming) + .map(|p| p.payload_type_name()); tracing::warn!( %connection_id, message_id, @@ -329,14 +339,16 @@ impl Peer { None } else { tracing::trace!(%connection_id, message_id, "incoming message: received"); - proto::build_typed_envelope(connection_id, incoming).or_else(|| { - tracing::error!( - %connection_id, - message_id, - "unable to construct a typed envelope" - ); - None - }) + proto::build_typed_envelope(connection_id, received_at, incoming).or_else( + || { + tracing::error!( + %connection_id, + message_id, + "unable to construct a typed envelope" + ); + None + }, + ) } } }); @@ -425,7 +437,8 @@ impl Peer { }); async move { send?; - let (response, _barrier) = rx.await.map_err(|_| anyhow!("connection was closed"))?; + let (response, received_at, _barrier) = + rx.await.map_err(|_| anyhow!("connection was closed"))?; if let Some(proto::envelope::Payload::Error(error)) = &response.payload { Err(RpcError::from_proto(&error, T::NAME)) @@ -436,6 +449,7 @@ impl Peer { original_sender_id: response.original_sender_id, payload: T::Response::from_envelope(response) .ok_or_else(|| anyhow!("received response of the wrong type"))?, + received_at, }) } } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 6555ab6a97..07378415f3 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -8,6 +8,7 @@ use futures::{SinkExt as _, StreamExt as _}; use prost::Message as _; use serde::Serialize; use std::any::{Any, TypeId}; +use std::time::Instant; use std::{ cmp, fmt::Debug, @@ -206,6 +207,7 @@ messages!( (JoinChannelChat, Foreground), (JoinChannelChatResponse, Foreground), (JoinProject, Foreground), + (JoinHostedProject, Foreground), (JoinProjectResponse, Foreground), (JoinRoom, Foreground), (JoinRoomResponse, Foreground), @@ -330,6 +332,7 @@ request_messages!( (JoinChannel, JoinRoomResponse), (JoinChannelBuffer, JoinChannelBufferResponse), (JoinChannelChat, JoinChannelChatResponse), + (JoinHostedProject, JoinProjectResponse), (JoinProject, JoinProjectResponse), (JoinRoom, JoinRoomResponse), (LeaveChannelBuffer, Ack), @@ -516,8 +519,9 @@ impl MessageStream where S: futures::Stream> + Unpin, { - pub async fn read(&mut self) -> Result { + pub async fn read(&mut self) -> Result<(Message, Instant), anyhow::Error> { while let Some(bytes) = self.stream.next().await { + let received_at = Instant::now(); match bytes? { WebSocketMessage::Binary(bytes) => { zstd::stream::copy_decode(bytes.as_slice(), &mut self.encoding_buffer).unwrap(); @@ -526,10 +530,10 @@ where self.encoding_buffer.clear(); self.encoding_buffer.shrink_to(MAX_BUFFER_LEN); - return Ok(Message::Envelope(envelope)); + return Ok((Message::Envelope(envelope), received_at)); } - WebSocketMessage::Ping(_) => return Ok(Message::Ping), - WebSocketMessage::Pong(_) => return Ok(Message::Pong), + WebSocketMessage::Ping(_) => return Ok((Message::Ping, received_at)), + WebSocketMessage::Pong(_) => return Ok((Message::Pong, received_at)), WebSocketMessage::Close(_) => break, _ => {} } diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 8daa5e743e..e54b5e0338 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/search.rs" doctest = false diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 1cb5af4b7e..7d7626dd2f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -98,7 +98,7 @@ impl BufferSearchBar { font_size: rems(0.875).into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.3).into(), + line_height: relative(1.3), background_color: None, underline: None, strikethrough: None, diff --git a/crates/search/src/history.rs b/crates/search/src/history.rs index 5571313acb..9d76d48e85 100644 --- a/crates/search/src/history.rs +++ b/crates/search/src/history.rs @@ -16,7 +16,7 @@ impl SearchHistory { } if let Some(previously_searched) = self.history.last_mut() { - if search_string.find(previously_searched.as_str()).is_some() { + if search_string.contains(previously_searched.as_str()) { *previously_searched = search_string; self.selected = Some(self.history.len() - 1); return; diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b3c52476fe..b9b7d95218 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -219,7 +219,7 @@ impl ProjectSearch { active_query: self.active_query.clone(), search_id: self.search_id, search_history: self.search_history.clone(), - no_results: self.no_results.clone(), + no_results: self.no_results, }) } @@ -493,7 +493,7 @@ impl Item for ProjectSearchView { }); let tab_name = last_query .filter(|query| !query.is_empty()) - .unwrap_or_else(|| "Project search".into()); + .unwrap_or_else(|| "Project Search".into()); h_flex() .gap_2() .child(Icon::new(IconName::MagnifyingGlass).color(if selected { @@ -539,11 +539,12 @@ impl Item for ProjectSearchView { fn save( &mut self, + format: bool, project: Model, cx: &mut ViewContext, ) -> Task> { self.results_editor - .update(cx, |editor, cx| editor.save(project, cx)) + .update(cx, |editor, cx| editor.save(format, project, cx)) } fn save_as( @@ -1279,7 +1280,7 @@ impl ProjectSearchView { fn focus_results_editor(&mut self, cx: &mut ViewContext) { self.query_editor.update(cx, |query_editor, cx| { let cursor = query_editor.selections.newest_anchor().head(); - query_editor.change_selections(None, cx, |s| s.select_ranges([cursor.clone()..cursor])); + query_editor.change_selections(None, cx, |s| s.select_ranges([cursor..cursor])); }); self.query_editor_was_focused = false; let results_handle = self.results_editor.focus_handle(cx); @@ -1299,7 +1300,6 @@ impl ProjectSearchView { if is_new_search { let range_to_select = match_ranges .first() - .clone() .map(|range| editor.range_for_match(range)); editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges(range_to_select) @@ -1632,7 +1632,7 @@ impl ProjectSearchBar { font_size: rems(0.875).into(), font_weight: FontWeight::NORMAL, font_style: FontStyle::Normal, - line_height: relative(1.3).into(), + line_height: relative(1.3), background_color: None, underline: None, strikethrough: None, @@ -2245,7 +2245,7 @@ pub mod tests { .await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); - let workspace = window.clone(); + let workspace = window; let search_bar = window.build_view(cx, |_| ProjectSearchBar::new()); let active_item = cx.read(|cx| { @@ -2475,7 +2475,7 @@ pub mod tests { .await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let window = cx.add_window(|cx| Workspace::test_new(project, cx)); - let workspace = window.clone(); + let workspace = window; let search_bar = window.build_view(cx, |_| ProjectSearchBar::new()); let active_item = cx.read(|cx| { @@ -3410,7 +3410,7 @@ pub mod tests { let search_view = cx.add_window(|cx| ProjectSearchView::new(search.clone(), cx, None)); // First search - perform_search(search_view.clone(), "A", cx); + perform_search(search_view, "A", cx); search_view .update(cx, |search_view, cx| { search_view.results_editor.update(cx, |results_editor, cx| { @@ -3428,7 +3428,7 @@ pub mod tests { .expect("unable to update search view"); // Second search - perform_search(search_view.clone(), "B", cx); + perform_search(search_view, "B", cx); search_view .update(cx, |search_view, cx| { search_view.results_editor.update(cx, |results_editor, cx| { diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index 5c922a503a..957a5e3cdf 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/semantic_index.rs" doctest = false diff --git a/crates/semantic_index/src/db.rs b/crates/semantic_index/src/db.rs index f34baeaaae..242e80026a 100644 --- a/crates/semantic_index/src/db.rs +++ b/crates/semantic_index/src/db.rs @@ -125,7 +125,7 @@ impl VectorDatabase { // Delete existing tables, if SEMANTIC_INDEX_VERSION is bumped let version_query = db.prepare("SELECT version from semantic_index_config"); let version = version_query - .and_then(|mut query| query.query_row([], |row| Ok(row.get::<_, i64>(0)?))); + .and_then(|mut query| query.query_row([], |row| row.get::<_, i64>(0))); if version.map_or(false, |version| version == SEMANTIC_INDEX_VERSION as i64) { log::trace!("vector database schema up to date"); return Ok(()); @@ -275,14 +275,10 @@ impl VectorDatabase { self.transact(move |db| { let mut worktree_query = db.prepare("SELECT id FROM worktrees WHERE absolute_path = ?1")?; - let worktree_id = worktree_query - .query_row(params![worktree_root_path], |row| Ok(row.get::<_, i64>(0)?)); + let worktree_id = + worktree_query.query_row(params![worktree_root_path], |row| row.get::<_, i64>(0)); - if worktree_id.is_ok() { - return Ok(true); - } else { - return Ok(false); - } + Ok(worktree_id.is_ok()) }) } @@ -302,17 +298,15 @@ impl VectorDatabase { let digests = Rc::new( digests .into_iter() - .map(|p| Value::Blob(p.0.to_vec())) + .map(|digest| Value::Blob(digest.0.to_vec())) .collect::>(), ); let rows = query.query_map(params![digests], |row| { Ok((row.get::<_, SpanDigest>(0)?, row.get::<_, Embedding>(1)?)) })?; - for row in rows { - if let Ok(row) = row { - embeddings_by_digest.insert(row.0, row.1); - } + for (digest, embedding) in rows.flatten() { + embeddings_by_digest.insert(digest, embedding); } Ok(embeddings_by_digest) @@ -344,10 +338,8 @@ impl VectorDatabase { Ok((row.get::<_, SpanDigest>(0)?, row.get::<_, Embedding>(1)?)) })?; - for row in rows { - if let Ok(row) = row { - embeddings_by_digest.insert(row.0, row.1); - } + for (digest, embedding) in rows.flatten() { + embeddings_by_digest.insert(digest, embedding); } } @@ -364,7 +356,7 @@ impl VectorDatabase { db.prepare("SELECT id FROM worktrees WHERE absolute_path = ?1")?; let worktree_id = worktree_query .query_row(params![worktree_root_path.to_string_lossy()], |row| { - Ok(row.get::<_, i64>(0)?) + row.get::<_, i64>(0) }); if worktree_id.is_ok() { @@ -456,8 +448,7 @@ impl VectorDatabase { if batch_ids.len() == batch_n { let embeddings = std::mem::take(&mut batch_embeddings); let ids = std::mem::take(&mut batch_ids); - let array = - Array2::from_shape_vec((ids.len(), embedding_len.clone()), embeddings); + let array = Array2::from_shape_vec((ids.len(), embedding_len), embeddings); match array { Ok(array) => { batches.push((ids, array)); diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index df277fbc9b..e57da7bc9b 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -329,7 +329,7 @@ impl SemanticIndex { SemanticIndexStatus::Indexed } else { SemanticIndexStatus::Indexing { - remaining_files: project_state.pending_file_count_rx.borrow().clone(), + remaining_files: *project_state.pending_file_count_rx.borrow(), rate_limit_expiry: self.embedding_provider.rate_limit_expiration(), } } @@ -497,7 +497,7 @@ impl SemanticIndex { changes: Arc<[(Arc, ProjectEntryId, PathChange)]>, cx: &mut ModelContext, ) { - let Some(worktree) = project.read(cx).worktree_for_id(worktree_id.clone(), cx) else { + let Some(worktree) = project.read(cx).worktree_for_id(worktree_id, cx) else { return; }; let project = project.downgrade(); @@ -657,9 +657,9 @@ impl SemanticIndex { if register.await.log_err().is_none() { // Stop tracking this worktree if the registration failed. this.update(&mut cx, |this, _| { - this.projects.get_mut(&project).map(|project_state| { + if let Some(project_state) = this.projects.get_mut(&project) { project_state.worktrees.remove(&worktree_id); - }); + } }) .ok(); } @@ -840,7 +840,6 @@ impl SemanticIndex { let mut batch_results = Vec::new(); for batch in file_ids.chunks(batch_size) { let batch = batch.into_iter().map(|v| *v).collect::>(); - let limit = limit.clone(); let fs = fs.clone(); let db_path = db_path.clone(); let query = query.clone(); diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 23ed45ff1d..f660aa8fb3 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -414,7 +414,7 @@ async fn test_code_context_retrieval_json() { } }"# .unindent(), - text.find("{").unwrap(), + text.find('{').unwrap(), )], ); @@ -443,7 +443,7 @@ async fn test_code_context_retrieval_json() { "age": 42 }]"# .unindent(), - text.find("[").unwrap(), + text.find('[').unwrap(), )], ); } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index d9ad9d3f9a..3177716196 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/settings.rs" doctest = false diff --git a/crates/snippet/Cargo.toml b/crates/snippet/Cargo.toml index c2c62cc01e..f12c7dded3 100644 --- a/crates/snippet/Cargo.toml +++ b/crates/snippet/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/snippet.rs" doctest = false diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 71c67af095..98b05a06e4 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] anyhow.workspace = true collections.workspace = true diff --git a/crates/sqlez_macros/Cargo.toml b/crates/sqlez_macros/Cargo.toml index aab2596ddd..969fe26c88 100644 --- a/crates/sqlez_macros/Cargo.toml +++ b/crates/sqlez_macros/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/sqlez_macros.rs" proc-macro = true diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index fc1754c253..419a447c13 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] gpui.workspace = true itertools = { package = "itertools", version = "0.10" } diff --git a/crates/storybook/Cargo.toml b/crates/storybook/Cargo.toml index a9186978b4..38cc3ddc98 100644 --- a/crates/storybook/Cargo.toml +++ b/crates/storybook/Cargo.toml @@ -5,13 +5,16 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [[bin]] name = "storybook" path = "src/storybook.rs" [dependencies] anyhow.workspace = true -clap = { version = "4.4", features = ["derive", "string"] } +clap = { workspace = true, features = ["derive", "string"] } collab_ui = { workspace = true, features = ["stories"] } ctrlc = "3.4" dialoguer = { version = "0.11.0", features = ["fuzzy-select"] } diff --git a/crates/storybook/src/stories/scroll.rs b/crates/storybook/src/stories/scroll.rs index a0318dc30e..096afaccf6 100644 --- a/crates/storybook/src/stories/scroll.rs +++ b/crates/storybook/src/stories/scroll.rs @@ -38,7 +38,7 @@ impl Render for ScrollStory { .id(id) .tooltip(move |cx| Tooltip::text(format!("{}, {}", row, column), cx)) .bg(bg) - .size(px(100. as f32)) + .size(px(100_f32)) .when(row >= 5 && column >= 5, |d| { d.overflow_scroll() .child(div().size(px(50.)).bg(color_1)) diff --git a/crates/storybook/src/storybook.rs b/crates/storybook/src/storybook.rs index 33b91a5eaa..2bd60cc680 100644 --- a/crates/storybook/src/storybook.rs +++ b/crates/storybook/src/storybook.rs @@ -42,7 +42,7 @@ fn main() { menu::init(); let args = Args::parse(); - let story_selector = args.story.clone().unwrap_or_else(|| { + let story_selector = args.story.unwrap_or_else(|| { let stories = ComponentStory::iter().collect::>(); ctrlc::set_handler(move || {}).unwrap(); diff --git a/crates/sum_tree/Cargo.toml b/crates/sum_tree/Cargo.toml index ac24e0d298..b370e6df18 100644 --- a/crates/sum_tree/Cargo.toml +++ b/crates/sum_tree/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/sum_tree.rs" doctest = false diff --git a/crates/sum_tree/src/tree_map.rs b/crates/sum_tree/src/tree_map.rs index f39e3ed19a..b46150e3c3 100644 --- a/crates/sum_tree/src/tree_map.rs +++ b/crates/sum_tree/src/tree_map.rs @@ -357,8 +357,8 @@ mod tests { .collect::>(); assert_eq!(result.len(), 2); - assert!(result.iter().find(|(k, _)| k == &&"baa").is_some()); - assert!(result.iter().find(|(k, _)| k == &&"baaab").is_some()); + assert!(result.iter().any(|(k, _)| k == &&"baa")); + assert!(result.iter().any(|(k, _)| k == &&"baaab")); let result = map .iter_from(&"c") @@ -366,7 +366,7 @@ mod tests { .collect::>(); assert_eq!(result.len(), 1); - assert!(result.iter().find(|(k, _)| k == &&"c").is_some()); + assert!(result.iter().any(|(k, _)| k == &&"c")); } #[test] diff --git a/crates/task/Cargo.toml b/crates/task/Cargo.toml index aeafaba3f0..8f4da57a63 100644 --- a/crates/task/Cargo.toml +++ b/crates/task/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] anyhow.workspace = true collections.workspace = true @@ -13,6 +16,7 @@ gpui.workspace = true schemars.workspace = true serde.workspace = true serde_json_lenient.workspace = true +subst = "0.3.0" util.workspace = true [dev-dependencies] diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 84d7bd4934..37107e2569 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -34,8 +34,15 @@ pub struct SpawnInTerminal { pub use_new_terminal: bool, /// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish. pub allow_concurrent_runs: bool, - /// Whether the command should be spawned in a separate shell instance. - pub separate_shell: bool, +} + +/// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function) +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TaskContext { + /// A path to a directory in which the task should be executed. + pub cwd: Option, + /// Additional environment variables associated with a given task. + pub env: HashMap, } /// Represents a short lived recipe of a task, whose main purpose @@ -46,10 +53,10 @@ pub trait Task { /// Human readable name of the task to display in the UI. fn name(&self) -> &str; /// Task's current working directory. If `None`, current project's root will be used. - fn cwd(&self) -> Option<&Path>; + fn cwd(&self) -> Option<&str>; /// Sets up everything needed to spawn the task in the given directory (`cwd`). /// If a task is intended to be spawned in the terminal, it should return the corresponding struct filled with the data necessary. - fn exec(&self, cwd: Option) -> Option; + fn exec(&self, cx: TaskContext) -> Option; } /// [`Source`] produces tasks that can be scheduled. diff --git a/crates/task/src/oneshot_source.rs b/crates/task/src/oneshot_source.rs index 829c2e2f3e..85257bee54 100644 --- a/crates/task/src/oneshot_source.rs +++ b/crates/task/src/oneshot_source.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use crate::{SpawnInTerminal, Task, TaskId, TaskSource}; +use crate::{SpawnInTerminal, Task, TaskContext, TaskId, TaskSource}; use gpui::{AppContext, Context, Model}; /// A storage and source of tasks generated out of user command prompt inputs. @@ -30,24 +30,24 @@ impl Task for OneshotTask { &self.id.0 } - fn cwd(&self) -> Option<&std::path::Path> { + fn cwd(&self) -> Option<&str> { None } - fn exec(&self, cwd: Option) -> Option { + fn exec(&self, cx: TaskContext) -> Option { if self.id().0.is_empty() { return None; } + let TaskContext { cwd, env } = cx; Some(SpawnInTerminal { id: self.id().clone(), label: self.name().to_owned(), command: self.id().0.clone(), args: vec![], cwd, - env: Default::default(), + env, use_new_terminal: Default::default(), allow_concurrent_runs: Default::default(), - separate_shell: true, }) } } diff --git a/crates/task/src/static_source.rs b/crates/task/src/static_source.rs index 7a97560541..dedf5a6384 100644 --- a/crates/task/src/static_source.rs +++ b/crates/task/src/static_source.rs @@ -1,10 +1,6 @@ //! A source of tasks, based on a static configuration, deserialized from the tasks config file, and related infrastructure for tracking changes to the file. -use std::{ - borrow::Cow, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::{borrow::Cow, path::Path, sync::Arc}; use collections::HashMap; use futures::StreamExt; @@ -13,7 +9,7 @@ use schemars::{gen::SchemaSettings, JsonSchema}; use serde::{Deserialize, Serialize}; use util::ResultExt; -use crate::{SpawnInTerminal, Task, TaskId, TaskSource}; +use crate::{SpawnInTerminal, Task, TaskContext, TaskId, TaskSource}; use futures::channel::mpsc::UnboundedReceiver; /// A single config file entry with the deserialized task definition. @@ -24,7 +20,16 @@ struct StaticTask { } impl Task for StaticTask { - fn exec(&self, cwd: Option) -> Option { + fn exec(&self, cx: TaskContext) -> Option { + let TaskContext { cwd, env } = cx; + let cwd = self + .definition + .cwd + .clone() + .and_then(|path| subst::substitute(&path, &env).map(Into::into).ok()) + .or(cwd); + let mut definition_env = self.definition.env.clone(); + definition_env.extend(env); Some(SpawnInTerminal { id: self.id.clone(), cwd, @@ -33,8 +38,7 @@ impl Task for StaticTask { label: self.definition.label.clone(), command: self.definition.command.clone(), args: self.definition.args.clone(), - env: self.definition.env.clone(), - separate_shell: false, + env: definition_env, }) } @@ -46,7 +50,7 @@ impl Task for StaticTask { &self.id } - fn cwd(&self) -> Option<&Path> { + fn cwd(&self) -> Option<&str> { self.definition.cwd.as_deref() } } @@ -73,7 +77,7 @@ pub(crate) struct Definition { pub env: HashMap, /// Current working directory to spawn the command into, defaults to current project root. #[serde(default)] - pub cwd: Option, + pub cwd: Option, /// Whether to use a new terminal tab or reuse the existing one to spawn the process. #[serde(default)] pub use_new_terminal: bool, diff --git a/crates/tasks_ui/Cargo.toml b/crates/tasks_ui/Cargo.toml index 6c350ff931..446179890c 100644 --- a/crates/tasks_ui/Cargo.toml +++ b/crates/tasks_ui/Cargo.toml @@ -5,8 +5,12 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] anyhow.workspace = true +editor.workspace = true fuzzy.workspace = true gpui.workspace = true menu.workspace = true @@ -17,10 +21,14 @@ serde.workspace = true ui.workspace = true util.workspace = true workspace.workspace = true +language.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } language = { workspace = true, features = ["test-support"] } project = { workspace = true, features = ["test-support"] } serde_json.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-typescript.workspace = true workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/tasks_ui/src/lib.rs b/crates/tasks_ui/src/lib.rs index 5f517fdedf..278d01ef39 100644 --- a/crates/tasks_ui/src/lib.rs +++ b/crates/tasks_ui/src/lib.rs @@ -1,8 +1,11 @@ -use std::path::PathBuf; +use std::{collections::HashMap, path::PathBuf}; +use editor::Editor; use gpui::{AppContext, ViewContext, WindowContext}; +use language::Point; use modal::TasksModal; -use task::Task; +use project::{Location, WorktreeId}; +use task::{Task, TaskContext}; use util::ResultExt; use workspace::Workspace; @@ -15,16 +18,28 @@ pub fn init(cx: &mut AppContext) { .register_action(|workspace, _: &modal::Spawn, cx| { let inventory = workspace.project().read(cx).task_inventory().clone(); let workspace_handle = workspace.weak_handle(); - workspace - .toggle_modal(cx, |cx| TasksModal::new(inventory, workspace_handle, cx)) + let cwd = task_cwd(workspace, cx).log_err().flatten(); + let task_context = task_context(workspace, cwd, cx); + workspace.toggle_modal(cx, |cx| { + TasksModal::new(inventory, task_context, workspace_handle, cx) + }) }) - .register_action(move |workspace, _: &modal::Rerun, cx| { - if let Some(task) = workspace.project().update(cx, |project, cx| { - project - .task_inventory() - .update(cx, |inventory, cx| inventory.last_scheduled_task(cx)) - }) { - schedule_task(workspace, task.as_ref(), cx) + .register_action(move |workspace, action: &modal::Rerun, cx| { + if let Some((task, old_context)) = + workspace.project().update(cx, |project, cx| { + project + .task_inventory() + .update(cx, |inventory, cx| inventory.last_scheduled_task(cx)) + }) + { + let task_context = if action.reevaluate_context { + let cwd = task_cwd(workspace, cx).log_err().flatten(); + task_context(workspace, cwd, cx) + } else { + old_context + }; + + schedule_task(workspace, task.as_ref(), task_context, cx) }; }); }, @@ -32,16 +47,117 @@ pub fn init(cx: &mut AppContext) { .detach(); } -fn schedule_task(workspace: &Workspace, task: &dyn Task, cx: &mut ViewContext<'_, Workspace>) { - let cwd = match task.cwd() { - Some(cwd) => Some(cwd.to_path_buf()), - None => task_cwd(workspace, cx).log_err().flatten(), - }; - let spawn_in_terminal = task.exec(cwd); +fn task_context( + workspace: &Workspace, + cwd: Option, + cx: &mut WindowContext<'_>, +) -> TaskContext { + let current_editor = workspace + .active_item(cx) + .and_then(|item| item.act_as::(cx)) + .clone(); + if let Some(current_editor) = current_editor { + (|| { + let editor = current_editor.read(cx); + let selection = editor.selections.newest::(cx); + let (buffer, _, _) = editor + .buffer() + .read(cx) + .point_to_buffer_offset(selection.start, cx)?; + + current_editor.update(cx, |editor, cx| { + let snapshot = editor.snapshot(cx); + let selection_range = selection.range(); + let start = snapshot + .display_snapshot + .buffer_snapshot + .anchor_after(selection_range.start) + .text_anchor; + let end = snapshot + .display_snapshot + .buffer_snapshot + .anchor_after(selection_range.end) + .text_anchor; + let Point { row, column } = snapshot + .display_snapshot + .buffer_snapshot + .offset_to_point(selection_range.start); + let row = row + 1; + let column = column + 1; + let location = Location { + buffer: buffer.clone(), + range: start..end, + }; + + let current_file = location + .buffer + .read(cx) + .file() + .map(|file| file.path().to_string_lossy().to_string()); + let worktree_id = location + .buffer + .read(cx) + .file() + .map(|file| WorktreeId::from_usize(file.worktree_id())); + let context = buffer + .read(cx) + .language() + .and_then(|language| language.context_provider()) + .and_then(|provider| provider.build_context(location, cx).ok()); + + let worktree_path = worktree_id.and_then(|worktree_id| { + workspace + .project() + .read(cx) + .worktree_for_id(worktree_id, cx) + .map(|worktree| worktree.read(cx).abs_path().to_string_lossy().to_string()) + }); + + let mut env = HashMap::from_iter([ + ("ZED_ROW".into(), row.to_string()), + ("ZED_COLUMN".into(), column.to_string()), + ]); + if let Some(path) = current_file { + env.insert("ZED_FILE".into(), path); + } + if let Some(worktree_path) = worktree_path { + env.insert("ZED_WORKTREE_ROOT".into(), worktree_path); + } + if let Some(language_context) = context { + if let Some(symbol) = language_context.symbol { + env.insert("ZED_SYMBOL".into(), symbol); + } + } + + Some(TaskContext { + cwd: cwd.clone(), + env, + }) + }) + })() + .unwrap_or_else(|| TaskContext { + cwd, + env: Default::default(), + }) + } else { + TaskContext { + cwd, + env: Default::default(), + } + } +} + +fn schedule_task( + workspace: &Workspace, + task: &dyn Task, + task_cx: TaskContext, + cx: &mut ViewContext<'_, Workspace>, +) { + let spawn_in_terminal = task.exec(task_cx.clone()); if let Some(spawn_in_terminal) = spawn_in_terminal { workspace.project().update(cx, |project, cx| { project.task_inventory().update(cx, |inventory, _| { - inventory.task_scheduled(task.id().clone()); + inventory.task_scheduled(task.id().clone(), task_cx); }) }); cx.emit(workspace::Event::SpawnTask(spawn_in_terminal)); @@ -82,3 +198,176 @@ fn task_cwd(workspace: &Workspace, cx: &mut WindowContext) -> anyhow::Result Arc { + cx.update(|cx| { + let state = AppState::test(cx); + language::init(cx); + crate::init(cx); + editor::init(cx); + workspace::init_settings(cx); + Project::init_settings(cx); + state + }) + } +} diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index dc308de1b2..491ed05971 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -2,23 +2,36 @@ use std::{path::PathBuf, sync::Arc}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, InteractiveElement, - Model, ParentElement, Render, SharedString, Styled, Subscription, View, ViewContext, - VisualContext, WeakView, + actions, impl_actions, rems, AppContext, DismissEvent, EventEmitter, FocusableView, + InteractiveElement, Model, ParentElement, Render, SharedString, Styled, Subscription, View, + ViewContext, VisualContext, WeakView, }; use picker::{ highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText}, Picker, PickerDelegate, }; use project::{Inventory, ProjectPath, TaskSourceKind}; -use task::{oneshot_source::OneshotSource, Task}; +use task::{oneshot_source::OneshotSource, Task, TaskContext}; use ui::{v_flex, ListItem, ListItemSpacing, RenderOnce, Selectable, WindowContext}; use util::{paths::PathExt, ResultExt}; use workspace::{ModalView, Workspace}; use crate::schedule_task; +use serde::Deserialize; +actions!(task, [Spawn]); -actions!(task, [Spawn, Rerun]); +/// Rerun last task +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct Rerun { + #[serde(default)] + /// Controls whether the task context is reevaluated prior to execution of a task. + /// If it is not, environment variables such as ZED_COLUMN, ZED_FILE are gonna be the same as in the last execution of a task + /// If it is, these variables will be updated to reflect current state of editor at the time task::Rerun is executed. + /// default: false + pub reevaluate_context: bool, +} + +impl_actions!(task, [Rerun]); /// A modal used to spawn new tasks. pub(crate) struct TasksModalDelegate { @@ -28,10 +41,15 @@ pub(crate) struct TasksModalDelegate { selected_index: usize, workspace: WeakView, prompt: String, + task_context: TaskContext, } impl TasksModalDelegate { - fn new(inventory: Model, workspace: WeakView) -> Self { + fn new( + inventory: Model, + task_context: TaskContext, + workspace: WeakView, + ) -> Self { Self { inventory, workspace, @@ -39,6 +57,7 @@ impl TasksModalDelegate { matches: Vec::new(), selected_index: 0, prompt: String::default(), + task_context, } } @@ -79,11 +98,16 @@ pub(crate) struct TasksModal { impl TasksModal { pub(crate) fn new( inventory: Model, + task_context: TaskContext, workspace: WeakView, cx: &mut ViewContext, ) -> Self { - let picker = cx - .new_view(|cx| Picker::uniform_list(TasksModalDelegate::new(inventory, workspace), cx)); + let picker = cx.new_view(|cx| { + Picker::uniform_list( + TasksModalDelegate::new(inventory, task_context, workspace), + cx, + ) + }); let _subscription = cx.subscribe(&picker, |_, _, _, cx| { cx.emit(DismissEvent); }); @@ -223,7 +247,7 @@ impl PickerDelegate for TasksModalDelegate { self.workspace .update(cx, |workspace, cx| { - schedule_task(workspace, task.as_ref(), cx); + schedule_task(workspace, task.as_ref(), self.task_context.clone(), cx); }) .ok(); cx.emit(DismissEvent); @@ -279,13 +303,12 @@ mod tests { use gpui::{TestAppContext, VisualTestContext}; use project::{FakeFs, Project}; use serde_json::json; - use workspace::AppState; use super::*; #[gpui::test] async fn test_spawn_tasks_modal_query_reuse(cx: &mut TestAppContext) { - init_test(cx); + crate::tests::init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( "/dir", @@ -431,16 +454,4 @@ mod tests { .collect::>() }) } - - fn init_test(cx: &mut TestAppContext) -> Arc { - cx.update(|cx| { - let state = AppState::test(cx); - language::init(cx); - crate::init(cx); - editor::init(cx); - workspace::init_settings(cx); - Project::init_settings(cx); - state - }) - } } diff --git a/crates/telemetry_events/Cargo.toml b/crates/telemetry_events/Cargo.toml index 6893e7c183..b202a60b25 100644 --- a/crates/telemetry_events/Cargo.toml +++ b/crates/telemetry_events/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/telemetry_events.rs" diff --git a/crates/terminal/Cargo.toml b/crates/terminal/Cargo.toml index 7123264948..4686266557 100644 --- a/crates/terminal/Cargo.toml +++ b/crates/terminal/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/terminal.rs" doctest = false @@ -30,5 +33,8 @@ theme.workspace = true thiserror.workspace = true util.workspace = true +[target.'cfg(windows)'.dependencies] +windows.workspace = true + [dev-dependencies] rand.workspace = true diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs index af3e6b640f..2d00431cad 100644 --- a/crates/terminal/src/mappings/mouse.rs +++ b/crates/terminal/src/mappings/mouse.rs @@ -183,7 +183,7 @@ pub fn grid_point_and_side( let col = min(col, cur_size.last_column()); let mut line = (pos.y / cur_size.line_height) as i32; if line > cur_size.bottommost_line() { - line = cur_size.bottommost_line().0 as i32; + line = cur_size.bottommost_line().0; side = Side::Right; } else if line < 0 { side = Side::Left; diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 68d43a430b..342d4c6cc2 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -158,11 +158,11 @@ impl TerminalSize { } pub fn num_lines(&self) -> usize { - f32::from((self.size.height / self.line_height).floor()) as usize + (self.size.height / self.line_height).floor() as usize } pub fn num_columns(&self) -> usize { - f32::from((self.size.width / self.cell_width).floor()) as usize + (self.size.width / self.cell_width).floor() as usize } pub fn height(&self) -> Pixels { @@ -303,6 +303,7 @@ pub struct TerminalBuilder { } impl TerminalBuilder { + #[allow(clippy::too_many_arguments)] pub fn new( working_directory: Option, task: Option, @@ -486,21 +487,21 @@ impl TerminalBuilder { } } - if events.is_empty() && wakeup == false { + if events.is_empty() && !wakeup { smol::future::yield_now().await; break 'outer; - } else { - terminal.update(&mut cx, |this, cx| { - if wakeup { - this.process_event(&AlacTermEvent::Wakeup, cx); - } - - for event in events { - this.process_event(&event, cx); - } - })?; - smol::future::yield_now().await; } + + terminal.update(&mut cx, |this, cx| { + if wakeup { + this.process_event(&AlacTermEvent::Wakeup, cx); + } + + for event in events { + this.process_event(&event, cx); + } + })?; + smol::future::yield_now().await; } } @@ -669,7 +670,7 @@ impl Terminal { let mut pid = unsafe { libc::tcgetpgrp(self.shell_fd as i32) }; // todo("windows") #[cfg(windows)] - let mut pid = -1; + let mut pid = unsafe { windows::Win32::System::Threading::GetCurrentProcessId() } as i32; if pid < 0 { pid = self.shell_pid as i32; } @@ -715,7 +716,7 @@ impl Terminal { new_size.size.height = cmp::max(new_size.line_height, new_size.height()); new_size.size.width = cmp::max(new_size.cell_width, new_size.width()); - self.last_content.size = new_size.clone(); + self.last_content.size = new_size; self.pty_tx.0.send(Msg::Resize(new_size.into())).ok(); @@ -1294,8 +1295,7 @@ impl Terminal { self.last_content.display_offset, ); - if let Some(scrolls) = - scroll_report(point, scroll_lines as i32, e, self.last_content.mode) + if let Some(scrolls) = scroll_report(point, scroll_lines, e, self.last_content.mode) { for scroll in scrolls { self.pty_tx.notify(scroll); @@ -1504,7 +1504,7 @@ pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla { 15 => colors.terminal_ansi_bright_white, // 16-231 are mapped to their RGB colors on a 0-5 range per channel 16..=231 => { - let (r, g, b) = rgb_for_index(&(index as u8)); // Split the index into its ANSI-RGB components + let (r, g, b) = rgb_for_index(index as u8); // Split the index into its ANSI-RGB components let step = (u8::MAX as f32 / 5.).floor() as u8; // Split the RGB range into 5 chunks, with floor so no overflow rgba_color(r * step, g * step, b * step) // Map the ANSI-RGB components to an RGB color } @@ -1543,8 +1543,8 @@ pub fn get_color_at_index(index: usize, theme: &Theme) -> Hsla { /// ``` /// /// This function does the reverse, calculating the `r`, `g`, and `b` components from a given index. -fn rgb_for_index(i: &u8) -> (u8, u8, u8) { - debug_assert!((&16..=&231).contains(&i)); +fn rgb_for_index(i: u8) -> (u8, u8, u8) { + debug_assert!((16..=231).contains(&i)); let i = i - 16; let r = (i - (i % 36)) / 36; let g = ((i % 36) - (i % 6)) / 6; @@ -1554,9 +1554,9 @@ fn rgb_for_index(i: &u8) -> (u8, u8, u8) { pub fn rgba_color(r: u8, g: u8, b: u8) -> Hsla { Rgba { - r: (r as f32 / 255.) as f32, - g: (g as f32 / 255.) as f32, - b: (b as f32 / 255.) as f32, + r: (r as f32 / 255.), + g: (g as f32 / 255.), + b: (b as f32 / 255.), a: 1., } .into() @@ -1577,9 +1577,9 @@ mod tests { #[test] fn test_rgb_for_index() { - //Test every possible value in the color cube + // Test every possible value in the color cube. for i in 16..=231 { - let (r, g, b) = rgb_for_index(&(i as u8)); + let (r, g, b) = rgb_for_index(i); assert_eq!(i, 16 + 36 * r + 6 * g + b); } } @@ -1663,9 +1663,9 @@ mod tests { fn get_cells(size: TerminalSize, rng: &mut ThreadRng) -> Vec> { let mut cells = Vec::new(); - for _ in 0..(f32::from(size.height() / size.line_height()) as usize) { + for _ in 0..((size.height() / size.line_height()) as usize) { let mut row_vec = Vec::new(); - for _ in 0..(f32::from(size.width() / size.cell_width()) as usize) { + for _ in 0..((size.width() / size.cell_width()) as usize) { let cell_char = rng.sample(Alphanumeric) as char; row_vec.push(cell_char) } diff --git a/crates/terminal_view/Cargo.toml b/crates/terminal_view/Cargo.toml index 8daf4b3f02..d9e3606d4d 100644 --- a/crates/terminal_view/Cargo.toml +++ b/crates/terminal_view/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/terminal_view.rs" doctest = false diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index cd439fca1f..c70cd87df1 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -365,7 +365,7 @@ impl TerminalElement { }; let mut result = TextRun { - len: indexed.c.len_utf8() as usize, + len: indexed.c.len_utf8(), color: fg, background_color: None, font: Font { @@ -406,11 +406,10 @@ impl TerminalElement { let font_features = terminal_settings .font_features - .clone() - .unwrap_or(settings.buffer_font.features.clone()); + .unwrap_or(settings.buffer_font.features); let line_height = terminal_settings.line_height.value(); - let font_size = terminal_settings.font_size.clone(); + let font_size = terminal_settings.font_size; let font_size = font_size.map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)); @@ -462,7 +461,7 @@ impl TerminalElement { .width; gutter = cell_width; - let mut size = bounds.size.clone(); + let mut size = bounds.size; size.width -= gutter; // https://github.com/zed-industries/zed/issues/2750 @@ -646,7 +645,6 @@ impl TerminalElement { }); cx.on_mouse_event({ - let bounds = bounds.clone(); let focus = self.focus.clone(); let terminal = self.terminal.clone(); move |e: &MouseMoveEvent, phase, cx| { @@ -828,7 +826,7 @@ impl Element for TerminalElement { start_y, //Need to change this line_height: layout.dimensions.line_height, lines: highlighted_range_lines, - color: color.clone(), + color: *color, //Copied from editor. TODO: move to theme or something corner_radius: 0.15 * layout.dimensions.line_height, }; @@ -1015,10 +1013,10 @@ fn to_highlighted_range_lines( let mut line_end = layout.dimensions.columns(); if line == clamped_start_line { - line_start = unclamped_start.column.0 as usize; + line_start = unclamped_start.column.0; } if line == clamped_end_line { - line_end = unclamped_end.column.0 as usize + 1; //+1 for inclusive + line_end = unclamped_end.column.0 + 1; // +1 for inclusive } highlighted_range_lines.push(HighlightedRangeLine { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 77eb0c2ef4..d4174d61c8 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -303,23 +303,25 @@ impl TerminalPanel { args: spawn_in_terminal.args.clone(), env: spawn_in_terminal.env.clone(), }; - if spawn_in_terminal.separate_shell { - let Some((shell, mut user_args)) = (match TerminalSettings::get_global(cx).shell.clone() - { - Shell::System => std::env::var("SHELL").ok().map(|shell| (shell, vec![])), - Shell::Program(shell) => Some((shell, vec![])), - Shell::WithArguments { program, args } => Some((program, args)), - }) else { - return; - }; + // Set up shell args unconditionally, as tasks are always spawned inside of a shell. + let Some((shell, mut user_args)) = (match TerminalSettings::get_global(cx).shell.clone() { + Shell::System => std::env::var("SHELL").ok().map(|shell| (shell, vec![])), + Shell::Program(shell) => Some((shell, vec![])), + Shell::WithArguments { program, args } => Some((program, args)), + }) else { + return; + }; - let command = std::mem::take(&mut spawn_task.command); - let args = std::mem::take(&mut spawn_task.args); - spawn_task.command = shell; - user_args.extend(["-i".to_owned(), "-c".to_owned(), command]); - user_args.extend(args); - spawn_task.args = user_args; + let mut command = std::mem::take(&mut spawn_task.command); + let args = std::mem::take(&mut spawn_task.args); + for arg in args { + command.push(' '); + command.push_str(&arg); } + spawn_task.command = shell; + user_args.extend(["-i".to_owned(), "-c".to_owned(), command]); + spawn_task.args = user_args; + let working_directory = spawn_in_terminal.cwd.clone(); let allow_concurrent_runs = spawn_in_terminal.allow_concurrent_runs; let use_new_terminal = spawn_in_terminal.use_new_terminal; diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index e756dd454b..00cda8fc32 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -454,7 +454,7 @@ fn subscribe_for_terminal_events( Event::TitleChanged => { cx.emit(ItemEvent::UpdateTab); let terminal = this.terminal().read(cx); - if !terminal.task().is_some() { + if terminal.task().is_none() { if let Some(foreground_info) = &terminal.foreground_process_info { let cwd = foreground_info.cwd.clone(); @@ -879,12 +879,9 @@ impl Item for TerminalView { .or_else(|| { cx.update(|cx| { let strategy = TerminalSettings::get_global(cx).working_directory.clone(); - workspace - .upgrade() - .map(|workspace| { - get_working_directory(workspace.read(cx), cx, strategy) - }) - .flatten() + workspace.upgrade().and_then(|workspace| { + get_working_directory(workspace.read(cx), cx, strategy) + }) }) .ok() .flatten() @@ -900,7 +897,7 @@ impl Item for TerminalView { } fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext) { - if !self.terminal().read(cx).task().is_some() { + if self.terminal().read(cx).task().is_none() { cx.background_executor() .spawn(TERMINAL_DB.update_workspace_id( workspace.database_id(), diff --git a/crates/text/Cargo.toml b/crates/text/Cargo.toml index 798b506b5e..4e57a78407 100644 --- a/crates/text/Cargo.toml +++ b/crates/text/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/text.rs" doctest = false diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index fff134af84..0ead51ba41 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [features] default = [] stories = ["dep:story"] diff --git a/crates/theme/src/one_themes.rs b/crates/theme/src/one_themes.rs index 73104b07c1..c556b87de2 100644 --- a/crates/theme/src/one_themes.rs +++ b/crates/theme/src/one_themes.rs @@ -72,7 +72,7 @@ pub(crate) fn one_dark() -> Theme { icon_muted: hsla(220.0 / 360., 12.1 / 100., 66.1 / 100., 1.0), icon_disabled: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0), icon_placeholder: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0), - icon_accent: blue.into(), + icon_accent: blue, status_bar_background: bg, title_bar_background: bg, toolbar_background: editor, @@ -218,7 +218,7 @@ pub(crate) fn one_dark() -> Theme { ( "link_uri".into(), HighlightStyle { - color: Some(teal.into()), + color: Some(teal), font_style: Some(FontStyle::Italic), ..HighlightStyle::default() }, diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index 06d8626f42..830a461726 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -134,7 +134,7 @@ impl ThemeRegistry { color: highlight .color .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), font_style: highlight.font_style.map(Into::into), font_weight: highlight.font_weight.map(Into::into), ..Default::default() @@ -248,7 +248,7 @@ impl ThemeRegistry { } pub async fn read_user_theme(theme_path: &Path, fs: Arc) -> Result { - let reader = fs.open_sync(&theme_path).await?; + let reader = fs.open_sync(theme_path).await?; let theme = serde_json_lenient::from_reader(reader)?; Ok(theme) diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index 87f71cc7ee..603cf035d7 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -91,7 +91,7 @@ impl ThemeStyleContent { color: style .color .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ..Default::default() }, ) @@ -489,351 +489,351 @@ impl ThemeColorsContent { border: self .border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), border_variant: self .border_variant .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), border_focused: self .border_focused .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), border_selected: self .border_selected .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), border_transparent: self .border_transparent .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), border_disabled: self .border_disabled .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), elevated_surface_background: self .elevated_surface_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), surface_background: self .surface_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), background: self .background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), element_background: self .element_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), element_hover: self .element_hover .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), element_active: self .element_active .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), element_selected: self .element_selected .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), element_disabled: self .element_disabled .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), drop_target_background: self .drop_target_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ghost_element_background: self .ghost_element_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ghost_element_hover: self .ghost_element_hover .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ghost_element_active: self .ghost_element_active .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ghost_element_selected: self .ghost_element_selected .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ghost_element_disabled: self .ghost_element_disabled .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), text: self .text .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), text_muted: self .text_muted .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), text_placeholder: self .text_placeholder .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), text_disabled: self .text_disabled .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), text_accent: self .text_accent .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), icon: self .icon .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), icon_muted: self .icon_muted .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), icon_disabled: self .icon_disabled .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), icon_placeholder: self .icon_placeholder .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), icon_accent: self .icon_accent .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), status_bar_background: self .status_bar_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), title_bar_background: self .title_bar_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), toolbar_background: self .toolbar_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), tab_bar_background: self .tab_bar_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), tab_inactive_background: self .tab_inactive_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), tab_active_background: self .tab_active_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), search_match_background: self .search_match_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), panel_background: self .panel_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), panel_focused_border: self .panel_focused_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), pane_focused_border: self .pane_focused_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), scrollbar_thumb_background: self .scrollbar_thumb_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), scrollbar_thumb_hover_background: self .scrollbar_thumb_hover_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), scrollbar_thumb_border: self .scrollbar_thumb_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), scrollbar_track_background: self .scrollbar_track_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), scrollbar_track_border: self .scrollbar_track_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_foreground: self .editor_foreground .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_background: self .editor_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_gutter_background: self .editor_gutter_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_subheader_background: self .editor_subheader_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_active_line_background: self .editor_active_line_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_highlighted_line_background: self .editor_highlighted_line_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_line_number: self .editor_line_number .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_active_line_number: self .editor_active_line_number .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_invisible: self .editor_invisible .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_wrap_guide: self .editor_wrap_guide .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_active_wrap_guide: self .editor_active_wrap_guide .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_document_highlight_read_background: self .editor_document_highlight_read_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), editor_document_highlight_write_background: self .editor_document_highlight_write_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_background: self .terminal_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_foreground: self .terminal_foreground .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_bright_foreground: self .terminal_bright_foreground .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_dim_foreground: self .terminal_dim_foreground .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_black: self .terminal_ansi_black .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_black: self .terminal_ansi_bright_black .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_black: self .terminal_ansi_dim_black .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_red: self .terminal_ansi_red .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_red: self .terminal_ansi_bright_red .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_red: self .terminal_ansi_dim_red .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_green: self .terminal_ansi_green .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_green: self .terminal_ansi_bright_green .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_green: self .terminal_ansi_dim_green .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_yellow: self .terminal_ansi_yellow .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_yellow: self .terminal_ansi_bright_yellow .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_yellow: self .terminal_ansi_dim_yellow .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_blue: self .terminal_ansi_blue .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_blue: self .terminal_ansi_bright_blue .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_blue: self .terminal_ansi_dim_blue .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_magenta: self .terminal_ansi_magenta .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_magenta: self .terminal_ansi_bright_magenta .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_magenta: self .terminal_ansi_dim_magenta .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_cyan: self .terminal_ansi_cyan .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_cyan: self .terminal_ansi_bright_cyan .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_cyan: self .terminal_ansi_dim_cyan .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_white: self .terminal_ansi_white .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_bright_white: self .terminal_ansi_bright_white .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), terminal_ansi_dim_white: self .terminal_ansi_dim_white .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), link_text_hover: self .link_text_hover .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), } } } @@ -990,171 +990,171 @@ impl StatusColorsContent { conflict: self .conflict .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), conflict_background: self .conflict_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), conflict_border: self .conflict_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), created: self .created .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), created_background: self .created_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), created_border: self .created_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), deleted: self .deleted .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), deleted_background: self .deleted_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), deleted_border: self .deleted_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), error: self .error .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), error_background: self .error_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), error_border: self .error_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), hidden: self .hidden .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), hidden_background: self .hidden_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), hidden_border: self .hidden_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), hint: self .hint .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), hint_background: self .hint_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), hint_border: self .hint_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ignored: self .ignored .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ignored_background: self .ignored_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), ignored_border: self .ignored_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), info: self .info .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), info_background: self .info_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), info_border: self .info_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), modified: self .modified .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), modified_background: self .modified_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), modified_border: self .modified_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), predictive: self .predictive .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), predictive_background: self .predictive_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), predictive_border: self .predictive_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), renamed: self .renamed .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), renamed_background: self .renamed_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), renamed_border: self .renamed_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), success: self .success .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), success_background: self .success_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), success_border: self .success_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), unreachable: self .unreachable .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), unreachable_background: self .unreachable_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), unreachable_border: self .unreachable_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), warning: self .warning .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), warning_background: self .warning_background .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), warning_border: self .warning_border .as_ref() - .and_then(|color| try_parse_color(&color).ok()), + .and_then(|color| try_parse_color(color).ok()), } } } @@ -1208,19 +1208,21 @@ impl JsonSchema for FontWeightContent { } fn json_schema(_: &mut SchemaGenerator) -> Schema { - let mut schema_object = SchemaObject::default(); - schema_object.enum_values = Some(vec![ - 100.into(), - 200.into(), - 300.into(), - 400.into(), - 500.into(), - 600.into(), - 700.into(), - 800.into(), - 900.into(), - ]); - schema_object.into() + SchemaObject { + enum_values: Some(vec![ + 100.into(), + 200.into(), + 300.into(), + 400.into(), + 500.into(), + 600.into(), + 700.into(), + 800.into(), + 900.into(), + ]), + ..Default::default() + } + .into() } } diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index d331b3b645..1b860dec92 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -48,14 +48,14 @@ impl ThemeSettings { // If the selected theme doesn't exist, fall back to a default theme // based on the system appearance. let theme_registry = ThemeRegistry::global(cx); - if theme_registry.get(&theme_name).ok().is_none() { + if theme_registry.get(theme_name).ok().is_none() { theme_name = match *system_appearance { Appearance::Light => "One Light", Appearance::Dark => "One Dark", }; }; - if let Some(_theme) = theme_settings.switch_theme(&theme_name, cx) { + if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) { ThemeSettings::override_global(theme_settings, cx); } } @@ -220,7 +220,7 @@ impl ThemeSettings { let mut new_theme = None; - if let Some(theme) = themes.get(&theme).log_err() { + if let Some(theme) = themes.get(theme).log_err() { self.active_theme = theme.clone(); new_theme = Some(theme); } @@ -313,13 +313,13 @@ impl settings::Settings for ThemeSettings { ui_font_size: defaults.ui_font_size.unwrap().into(), ui_font: Font { family: defaults.ui_font_family.clone().unwrap().into(), - features: defaults.ui_font_features.clone().unwrap(), + features: defaults.ui_font_features.unwrap(), weight: Default::default(), style: Default::default(), }, buffer_font: Font { family: defaults.buffer_font_family.clone().unwrap().into(), - features: defaults.buffer_font_features.clone().unwrap(), + features: defaults.buffer_font_features.unwrap(), weight: FontWeight::default(), style: FontStyle::default(), }, @@ -333,7 +333,7 @@ impl settings::Settings for ThemeSettings { theme_overrides: None, }; - for value in user_values.into_iter().copied().cloned() { + for value in user_values.iter().copied().cloned() { if let Some(value) = value.buffer_font_family { this.buffer_font.family = value.into(); } diff --git a/crates/theme/src/styles/players.rs b/crates/theme/src/styles/players.rs index 98e5f16699..e80c7161b1 100644 --- a/crates/theme/src/styles/players.rs +++ b/crates/theme/src/styles/players.rs @@ -151,19 +151,19 @@ impl PlayerColors { return; } - for (idx, player) in user_player_colors.into_iter().enumerate() { + for (idx, player) in user_player_colors.iter().enumerate() { let cursor = player .cursor .as_ref() - .and_then(|color| try_parse_color(&color).ok()); + .and_then(|color| try_parse_color(color).ok()); let background = player .background .as_ref() - .and_then(|color| try_parse_color(&color).ok()); + .and_then(|color| try_parse_color(color).ok()); let selection = player .selection .as_ref() - .and_then(|color| try_parse_color(&color).ok()); + .and_then(|color| try_parse_color(color).ok()); if let Some(player_color) = self.0.get_mut(idx) { *player_color = PlayerColor { diff --git a/crates/theme_importer/Cargo.toml b/crates/theme_importer/Cargo.toml index 75d9724342..07ed1695be 100644 --- a/crates/theme_importer/Cargo.toml +++ b/crates/theme_importer/Cargo.toml @@ -5,9 +5,12 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] anyhow.workspace = true -clap = { version = "4.4", features = ["derive"] } +clap = { workspace = true, features = ["derive"] } gpui.workspace = true indexmap = { version = "1.6.2", features = ["serde"] } log.workspace = true diff --git a/crates/theme_selector/Cargo.toml b/crates/theme_selector/Cargo.toml index 7e7e6fe9b6..3f9c9e6205 100644 --- a/crates/theme_selector/Cargo.toml +++ b/crates/theme_selector/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/theme_selector.rs" doctest = false diff --git a/crates/time_format/Cargo.toml b/crates/time_format/Cargo.toml index ea1fb336d5..1b5d36eb6c 100644 --- a/crates/time_format/Cargo.toml +++ b/crates/time_format/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/time_format.rs" doctest = false diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml index 1776f151d3..3acae49d95 100644 --- a/crates/ui/Cargo.toml +++ b/crates/ui/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] name = "ui" path = "src/ui.rs" diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 0bbbee2900..de4de73f42 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -51,8 +51,10 @@ impl PopoverMenu { cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| { if modal.focus_handle(cx).contains_focused(cx) { - if previous_focus_handle.is_some() { - cx.focus(previous_focus_handle.as_ref().unwrap()) + if let Some(previous_focus_handle) = + previous_focus_handle.as_ref() + { + cx.focus(previous_focus_handle); } } *menu2.borrow_mut() = None; diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index b08f3911cb..4f7f062ec4 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -132,8 +132,8 @@ impl Element for RightClickMenu { }; let menu = element_state.menu.clone(); let position = element_state.position.clone(); - let attach = self.attach.clone(); - let child_layout_id = element_state.child_layout_id.clone(); + let attach = self.attach; + let child_layout_id = element_state.child_layout_id; let child_bounds = cx.layout_bounds(child_layout_id.unwrap()); let interactive_bounds = InteractiveBounds { @@ -154,8 +154,8 @@ impl Element for RightClickMenu { cx.subscribe(&new_menu, move |modal, _: &DismissEvent, cx| { if modal.focus_handle(cx).contains_focused(cx) { - if previous_focus_handle.is_some() { - cx.focus(previous_focus_handle.as_ref().unwrap()) + if let Some(previous_focus_handle) = previous_focus_handle.as_ref() { + cx.focus(previous_focus_handle); } } *menu2.borrow_mut() = None; @@ -165,11 +165,12 @@ impl Element for RightClickMenu { cx.focus_view(&new_menu); *menu.borrow_mut() = Some(new_menu); - *position.borrow_mut() = if attach.is_some() && child_layout_id.is_some() { - attach.unwrap().corner(child_bounds) - } else { - cx.mouse_position() - }; + *position.borrow_mut() = + if let Some(attach) = attach.filter(|_| child_layout_id.is_some()) { + attach.corner(child_bounds) + } else { + cx.mouse_position() + }; cx.refresh(); } }); diff --git a/crates/ui/src/components/stories/keybinding.rs b/crates/ui/src/components/stories/keybinding.rs index 980a57d7da..f7a9a86cd0 100644 --- a/crates/ui/src/components/stories/keybinding.rs +++ b/crates/ui/src/components/stories/keybinding.rs @@ -42,7 +42,7 @@ impl Render for KeybindingStory { .gap_4() .py_3() .children(chunk.map(|permutation| { - KeyBinding::new(binding(&*(permutation.join("-") + "-x"))) + KeyBinding::new(binding(&(permutation.join("-") + "-x"))) })) }), ), diff --git a/crates/util/Cargo.toml b/crates/util/Cargo.toml index 9725320507..191d0beb56 100644 --- a/crates/util/Cargo.toml +++ b/crates/util/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "Apache-2.0" +[lints] +workspace = true + [lib] path = "src/util.rs" doctest = true diff --git a/crates/util/src/http.rs b/crates/util/src/http.rs index 62cf2d1239..3df8886fa4 100644 --- a/crates/util/src/http.rs +++ b/crates/util/src/http.rs @@ -128,22 +128,25 @@ impl HttpClient for isahc::HttpClient { } } +#[cfg(feature = "test-support")] +type FakeHttpHandler = Box< + dyn Fn(Request) -> BoxFuture<'static, Result, Error>> + + Send + + Sync + + 'static, +>; + #[cfg(feature = "test-support")] pub struct FakeHttpClient { - handler: Box< - dyn 'static - + Send - + Sync - + Fn(Request) -> BoxFuture<'static, Result, Error>>, - >, + handler: FakeHttpHandler, } #[cfg(feature = "test-support")] impl FakeHttpClient { pub fn create(handler: F) -> Arc where - Fut: 'static + Send + futures::Future, Error>>, - F: 'static + Send + Sync + Fn(Request) -> Fut, + Fut: futures::Future, Error>> + Send + 'static, + F: Fn(Request) -> Fut + Send + Sync + 'static, { Arc::new(HttpClientWithUrl { base_url: Mutex::new("http://test.example".into()), diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 9dbe64c418..107f852c91 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -8,17 +8,31 @@ use serde::{Deserialize, Serialize}; lazy_static::lazy_static! { pub static ref HOME: PathBuf = dirs::home_dir().expect("failed to determine home directory"); - pub static ref CONFIG_DIR: PathBuf = HOME.join(".config").join("zed"); + pub static ref CONFIG_DIR: PathBuf = if cfg!(target_os = "windows") { + dirs::config_dir() + .expect("failed to determine RoamingAppData directory") + .join("Zed") + } else { + HOME.join(".config").join("zed") + }; pub static ref CONVERSATIONS_DIR: PathBuf = CONFIG_DIR.join("conversations"); pub static ref EMBEDDINGS_DIR: PathBuf = CONFIG_DIR.join("embeddings"); pub static ref THEMES_DIR: PathBuf = CONFIG_DIR.join("themes"); pub static ref LOGS_DIR: PathBuf = if cfg!(target_os = "macos") { HOME.join("Library/Logs/Zed") + } else if cfg!(target_os = "windows") { + dirs::data_local_dir() + .expect("failed to determine LocalAppData directory") + .join("Zed/logs") } else { CONFIG_DIR.join("logs") }; pub static ref SUPPORT_DIR: PathBuf = if cfg!(target_os = "macos") { HOME.join("Library/Application Support/Zed") + } else if cfg!(target_os = "windows") { + dirs::config_dir() + .expect("failed to determine RoamingAppData directory") + .join("Zed") } else { CONFIG_DIR.clone() }; @@ -29,6 +43,10 @@ lazy_static::lazy_static! { pub static ref DB_DIR: PathBuf = SUPPORT_DIR.join("db"); pub static ref CRASHES_DIR: PathBuf = if cfg!(target_os = "macos") { HOME.join("Library/Logs/DiagnosticReports") + } else if cfg!(target_os = "windows") { + dirs::data_local_dir() + .expect("failed to determine LocalAppData directory") + .join("Zed/crashes") } else { CONFIG_DIR.join("crashes") }; @@ -45,7 +63,13 @@ lazy_static::lazy_static! { pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old"); pub static ref LOCAL_SETTINGS_RELATIVE_PATH: &'static Path = Path::new(".zed/settings.json"); pub static ref LOCAL_TASKS_RELATIVE_PATH: &'static Path = Path::new(".zed/tasks.json"); - pub static ref TEMP_DIR: PathBuf = HOME.join(".cache").join("zed"); + pub static ref TEMP_DIR: PathBuf = if cfg!(target_os = "widows") { + dirs::data_local_dir() + .expect("failed to determine LocalAppData directory") + .join("Temp/Zed") + } else { + HOME.join(".cache").join("zed") + }; } pub trait PathExt { @@ -459,7 +483,7 @@ mod tests { let path = Path::new("/work/node_modules"); let path_matcher = PathMatcher::new("**/node_modules/**").unwrap(); assert!( - path_matcher.is_match(&path), + path_matcher.is_match(path), "Path matcher {path_matcher} should match {path:?}" ); } @@ -469,7 +493,7 @@ mod tests { let path = Path::new("/Users/someonetoignore/work/zed/zed.dev/node_modules"); let path_matcher = PathMatcher::new("**/node_modules/**").unwrap(); assert!( - path_matcher.is_match(&path), + path_matcher.is_match(path), "Path matcher {path_matcher} should match {path:?}" ); } diff --git a/crates/util/src/test.rs b/crates/util/src/test.rs index e728281ea6..9915a6c159 100644 --- a/crates/util/src/test.rs +++ b/crates/util/src/test.rs @@ -29,8 +29,8 @@ fn write_tree(path: &Path, tree: serde_json::Value) { Value::Object(_) => { fs::create_dir(&path).unwrap(); - if path.file_name() == Some(&OsStr::new(".git")) { - git2::Repository::init(&path.parent().unwrap()).unwrap(); + if path.file_name() == Some(OsStr::new(".git")) { + git2::Repository::init(path.parent().unwrap()).unwrap(); } write_tree(&path, contents); diff --git a/crates/util/src/test/marked_text.rs b/crates/util/src/test/marked_text.rs index cf85a806e4..7ab45e9b20 100644 --- a/crates/util/src/test/marked_text.rs +++ b/crates/util/src/test/marked_text.rs @@ -12,7 +12,7 @@ pub fn marked_text_offsets_by( for char in marked_text.chars() { if markers.contains(&char) { - let char_offsets = extracted_markers.entry(char).or_insert(Vec::new()); + let char_offsets = extracted_markers.entry(char).or_default(); char_offsets.push(unmarked_text.len()); } else { unmarked_text.push(char); @@ -119,7 +119,7 @@ pub fn marked_text_ranges( let mut current_range_start = None; let mut current_range_cursor = None; - let marked_text = marked_text.replace("•", " "); + let marked_text = marked_text.replace('•', " "); for (marked_ix, marker) in marked_text.match_indices(&['«', '»', 'ˇ']) { unmarked_text.push_str(&marked_text[prev_marked_ix..marked_ix]); let unmarked_len = unmarked_text.len(); @@ -131,9 +131,9 @@ pub fn marked_text_ranges( if current_range_start.is_some() { if current_range_cursor.is_some() { panic!("duplicate point marker 'ˇ' at index {marked_ix}"); - } else { - current_range_cursor = Some(unmarked_len); } + + current_range_cursor = Some(unmarked_len); } else { ranges.push(unmarked_len..unmarked_len); } @@ -252,6 +252,7 @@ impl From<(char, char)> for TextRangeMarker { mod tests { use super::{generate_marked_text, marked_text_ranges}; + #[allow(clippy::reversed_empty_ranges)] #[test] fn test_marked_text() { let (text, ranges) = marked_text_ranges("one «ˇtwo» «threeˇ» «ˇfour» fiveˇ six", true); diff --git a/crates/vcs_menu/Cargo.toml b/crates/vcs_menu/Cargo.toml index 3258c229dd..786e2ee6fc 100644 --- a/crates/vcs_menu/Cargo.toml +++ b/crates/vcs_menu/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] anyhow.workspace = true fs.workspace = true diff --git a/crates/vcs_menu/src/lib.rs b/crates/vcs_menu/src/lib.rs index d193efa18d..2f88328084 100644 --- a/crates/vcs_menu/src/lib.rs +++ b/crates/vcs_menu/src/lib.rs @@ -9,7 +9,7 @@ use gpui::{ use picker::{Picker, PickerDelegate}; use std::{ops::Not, sync::Arc}; use ui::{ - h_flex, v_flex, Button, ButtonCommon, Clickable, HighlightedLabel, Label, LabelCommon, + h_flex, v_flex, Button, ButtonCommon, Clickable, Color, HighlightedLabel, Label, LabelCommon, LabelSize, ListItem, ListItemSpacing, Selectable, }; use util::ResultExt; @@ -67,7 +67,6 @@ impl Render for BranchList { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { v_flex() .w(rems(self.rem_width)) - .cursor_pointer() .child(self.picker.clone()) .on_mouse_down_out(cx.listener(|this, _, cx| { this.picker.update(cx, |this, cx| { @@ -293,11 +292,13 @@ impl PickerDelegate for BranchListDelegate { let label = if self.last_query.is_empty() { h_flex() .ml_3() - .child(Label::new("Recent branches").size(LabelSize::Small)) + .child(Label::new("Recent Branches").size(LabelSize::Small)) } else { let match_label = self.matches.is_empty().not().then(|| { let suffix = if self.matches.len() == 1 { "" } else { "es" }; - Label::new(format!("{} match{}", self.matches.len(), suffix)).size(LabelSize::Small) + Label::new(format!("{} match{}", self.matches.len(), suffix)) + .color(Color::Muted) + .size(LabelSize::Small) }); h_flex() .px_3() @@ -306,7 +307,7 @@ impl PickerDelegate for BranchListDelegate { .child(Label::new("Branches").size(LabelSize::Small)) .children(match_label) }; - Some(label.into_any()) + Some(label.mt_1().into_any()) } fn render_footer(&self, cx: &mut ViewContext>) -> Option { if self.last_query.is_empty() { diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index 071421e74e..4a78547d1e 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/vim.rs" doctest = false diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index af571dd632..d8623d9c75 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -41,7 +41,7 @@ pub fn command_interceptor(mut query: &str, cx: &AppContext) -> Option Option ("0", StartOfDocument.boxed_clone()), _ => { - if query.starts_with("/") || query.starts_with("?") { + if query.starts_with('/') || query.starts_with('?') { ( query, FindCommand { query: query[1..].to_string(), - backwards: query.starts_with("?"), + backwards: query.starts_with('?'), } .boxed_clone(), ) - } else if query.starts_with("%") { + } else if query.starts_with('%') { ( query, ReplaceCommand { @@ -358,7 +358,7 @@ pub fn command_interceptor(mut query: &str, cx: &AppContext) -> Option Vec { let mut positions = Vec::new(); - let mut chars = query.chars().into_iter(); + let mut chars = query.chars(); let Some(mut current) = chars.next() else { return positions; diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 177fbe4294..b46035ce2e 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1176,7 +1176,7 @@ fn window_middle( (visible_rows as u32).min(map.max_point().row() - first_visible_line.row()); let new_row = - (first_visible_line.row() + (max_visible_rows / 2) as u32).min(map.max_point().row()); + (first_visible_line.row() + (max_visible_rows / 2)).min(map.max_point().row()); let new_col = point.column().min(map.line_len(new_row)); let new_point = DisplayPoint::new(new_row, new_col); (map.clip_point(new_point, Bias::Left), SelectionGoal::None) diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 80e87ccaf9..b81c0b16a5 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -1,8 +1,12 @@ use crate::{motion::Motion, object::Object, utils::copy_selections_content, Vim}; use collections::{HashMap, HashSet}; -use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Bias}; +use editor::{ + display_map::{DisplaySnapshot, ToDisplayPoint}, + scroll::Autoscroll, + Bias, DisplayPoint, +}; use gpui::WindowContext; -use language::Point; +use language::{Point, Selection}; pub fn delete_motion(vim: &mut Vim, motion: Motion, times: Option, cx: &mut WindowContext) { vim.stop_recording(); @@ -72,6 +76,14 @@ pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo s.move_with(|map, selection| { object.expand_selection(map, selection, around); let offset_range = selection.map(|p| p.to_offset(map, Bias::Left)).range(); + let mut move_selection_start_to_previous_line = + |map: &DisplaySnapshot, selection: &mut Selection| { + let start = selection.start.to_offset(map, Bias::Left); + if selection.start.row() > 0 { + should_move_to_start.insert(selection.id); + selection.start = (start - '\n'.len_utf8()).to_display_point(map); + } + }; let contains_only_newlines = map .chars_at(selection.start) .take_while(|(_, p)| p < &selection.end) @@ -88,12 +100,23 @@ pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo // at the end or start if (around || object == Object::Sentence) && contains_only_newlines { if end_at_newline { - selection.end = - (offset_range.end + '\n'.len_utf8()).to_display_point(map); - } else if selection.start.row() > 0 { - should_move_to_start.insert(selection.id); - selection.start = - (offset_range.start - '\n'.len_utf8()).to_display_point(map); + move_selection_end_to_next_line(map, selection); + } else { + move_selection_start_to_previous_line(map, selection); + } + } + + // Does post-processing for the trailing newline and EOF + // when not cancelled. + let cancelled = around && selection.start == selection.end; + if object == Object::Paragraph && !cancelled { + // EOF check should be done before including a trailing newline. + if ends_at_eof(map, selection) { + move_selection_start_to_previous_line(map, selection); + } + + if end_at_newline { + move_selection_end_to_next_line(map, selection); } } }); @@ -117,6 +140,15 @@ pub fn delete_object(vim: &mut Vim, object: Object, around: bool, cx: &mut Windo }); } +fn move_selection_end_to_next_line(map: &DisplaySnapshot, selection: &mut Selection) { + let end = selection.end.to_offset(map, Bias::Left); + selection.end = (end + '\n'.len_utf8()).to_display_point(map); +} + +fn ends_at_eof(map: &DisplaySnapshot, selection: &mut Selection) -> bool { + selection.end.to_point(map) == map.buffer_snapshot.max_point() +} + #[cfg(test)] mod test { use indoc::indoc; diff --git a/crates/vim/src/normal/paste.rs b/crates/vim/src/normal/paste.rs index 9c15cc0cf4..b56bb33b5c 100644 --- a/crates/vim/src/normal/paste.rs +++ b/crates/vim/src/normal/paste.rs @@ -135,8 +135,8 @@ fn paste(_: &mut Workspace, action: &Paste, cx: &mut ViewContext) { } else { (clipboard_text.to_string(), first_selection_indent_column) }; - let line_mode = to_insert.ends_with("\n"); - let is_multiline = to_insert.contains("\n"); + let line_mode = to_insert.ends_with('\n'); + let is_multiline = to_insert.contains('\n'); if line_mode && !before { if selection.is_empty() { @@ -480,7 +480,7 @@ mod test { the_ ˇfox jumps over _dog"} - .replace("_", " "), // Hack for trailing whitespace + .replace('_', " "), // Hack for trailing whitespace ) .await; cx.assert_shared_clipboard("lazy").await; diff --git a/crates/vim/src/normal/repeat.rs b/crates/vim/src/normal/repeat.rs index 4850151d94..a91327e497 100644 --- a/crates/vim/src/normal/repeat.rs +++ b/crates/vim/src/normal/repeat.rs @@ -294,7 +294,7 @@ mod test { lsp::CompletionItem { label: "first".to_string(), text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new(position.clone(), position.clone()), + range: lsp::Range::new(position, position), new_text: "first".to_string(), })), ..Default::default() @@ -302,7 +302,7 @@ mod test { lsp::CompletionItem { label: "second".to_string(), text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new(position.clone(), position.clone()), + range: lsp::Range::new(position, position), new_text: "second".to_string(), })), ..Default::default() diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index e439df48f8..56b9499594 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -10,7 +10,7 @@ use editor::{ Bias, DisplayPoint, }; use gpui::{actions, impl_actions, ViewContext, WindowContext}; -use language::{char_kind, BufferSnapshot, CharKind, Selection}; +use language::{char_kind, BufferSnapshot, CharKind, Point, Selection}; use serde::Deserialize; use workspace::Workspace; @@ -18,6 +18,7 @@ use workspace::Workspace; pub enum Object { Word { ignore_punctuation: bool }, Sentence, + Paragraph, Quotes, BackQuotes, DoubleQuotes, @@ -43,6 +44,7 @@ actions!( vim, [ Sentence, + Paragraph, Quotes, BackQuotes, DoubleQuotes, @@ -65,6 +67,8 @@ pub fn register(workspace: &mut Workspace, _: &mut ViewContext) { workspace.register_action(|_: &mut Workspace, _: &Tag, cx: _| object(Object::Tag, cx)); workspace .register_action(|_: &mut Workspace, _: &Sentence, cx: _| object(Object::Sentence, cx)); + workspace + .register_action(|_: &mut Workspace, _: &Paragraph, cx: _| object(Object::Paragraph, cx)); workspace.register_action(|_: &mut Workspace, _: &Quotes, cx: _| object(Object::Quotes, cx)); workspace .register_action(|_: &mut Workspace, _: &BackQuotes, cx: _| object(Object::BackQuotes, cx)); @@ -109,6 +113,7 @@ impl Object { | Object::VerticalBars | Object::DoubleQuotes => false, Object::Sentence + | Object::Paragraph | Object::Parentheses | Object::Tag | Object::AngleBrackets @@ -120,7 +125,7 @@ impl Object { pub fn always_expands_both_ways(self) -> bool { match self { - Object::Word { .. } | Object::Sentence | Object::Argument => false, + Object::Word { .. } | Object::Sentence | Object::Paragraph | Object::Argument => false, Object::Quotes | Object::BackQuotes | Object::DoubleQuotes @@ -153,6 +158,7 @@ impl Object { | Object::VerticalBars | Object::Tag | Object::Argument => Mode::Visual, + Object::Paragraph => Mode::VisualLine, } } @@ -171,6 +177,7 @@ impl Object { } } Object::Sentence => sentence(map, relative_to, around), + Object::Paragraph => paragraph(map, relative_to, around), Object::Quotes => { surrounding_markers(map, relative_to, around, self.is_multiline(), '\'', '\'') } @@ -458,7 +465,7 @@ fn argument( parent_covers_bracket_range = covers_bracket_range; // Unable to find a child node with a parent that covers the bracket range, so no argument to select - if !cursor.goto_first_child_for_byte(offset).is_some() { + if cursor.goto_first_child_for_byte(offset).is_none() { return None; } } @@ -684,6 +691,99 @@ fn expand_to_include_whitespace( range } +/// If not `around` (i.e. inner), returns a range that surrounds the paragraph +/// where `relative_to` is in. If `around`, principally returns the range ending +/// at the end of the next paragraph. +/// +/// Here, the "paragraph" is defined as a block of non-blank lines or a block of +/// blank lines. If the paragraph ends with a trailing newline (i.e. not with +/// EOF), the returned range ends at the trailing newline of the paragraph (i.e. +/// the trailing newline is not subject to subsequent operations). +/// +/// Edge cases: +/// - If `around` and if the current paragraph is the last paragraph of the +/// file and is blank, then the selection results in an error. +/// - If `around` and if the current paragraph is the last paragraph of the +/// file and is not blank, then the returned range starts at the start of the +/// previous paragraph, if it exists. +fn paragraph( + map: &DisplaySnapshot, + relative_to: DisplayPoint, + around: bool, +) -> Option> { + let mut paragraph_start = start_of_paragraph(map, relative_to); + let mut paragraph_end = end_of_paragraph(map, relative_to); + + let paragraph_end_row = paragraph_end.row(); + let paragraph_ends_with_eof = paragraph_end_row == map.max_point().row(); + let point = relative_to.to_point(map); + let current_line_is_empty = map.buffer_snapshot.is_line_blank(point.row); + + if around { + if paragraph_ends_with_eof { + if current_line_is_empty { + return None; + } + + let paragraph_start_row = paragraph_start.row(); + if paragraph_start_row != 0 { + let previous_paragraph_last_line_start = + Point::new(paragraph_start_row - 1, 0).to_display_point(map); + paragraph_start = start_of_paragraph(map, previous_paragraph_last_line_start); + } + } else { + let next_paragraph_start = Point::new(paragraph_end_row + 1, 0).to_display_point(map); + paragraph_end = end_of_paragraph(map, next_paragraph_start); + } + } + + let range = paragraph_start..paragraph_end; + Some(range) +} + +/// Returns a position of the start of the current paragraph, where a paragraph +/// is defined as a run of non-blank lines or a run of blank lines. +pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { + let point = display_point.to_point(map); + if point.row == 0 { + return DisplayPoint::zero(); + } + + let is_current_line_blank = map.buffer_snapshot.is_line_blank(point.row); + + for row in (0..point.row).rev() { + let blank = map.buffer_snapshot.is_line_blank(row); + if blank != is_current_line_blank { + return Point::new(row + 1, 0).to_display_point(map); + } + } + + DisplayPoint::zero() +} + +/// Returns a position of the end of the current paragraph, where a paragraph +/// is defined as a run of non-blank lines or a run of blank lines. +/// The trailing newline is excluded from the paragraph. +pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint { + let point = display_point.to_point(map); + if point.row == map.max_buffer_row() { + return map.max_point(); + } + + let is_current_line_blank = map.buffer_snapshot.is_line_blank(point.row); + + for row in point.row + 1..map.max_buffer_row() + 1 { + let blank = map.buffer_snapshot.is_line_blank(row); + if blank != is_current_line_blank { + let previous_row = row - 1; + return Point::new(previous_row, map.buffer_snapshot.line_len(previous_row)) + .to_display_point(map); + } + } + + map.max_point() +} + fn surrounding_markers( map: &DisplaySnapshot, relative_to: DisplayPoint, @@ -1047,6 +1147,168 @@ mod test { } } + const PARAGRAPH_EXAMPLES: &[&'static str] = &[ + // Single line + "ˇThe quick brown fox jumpˇs over the lazy dogˇ.ˇ", + // Multiple lines without empty lines + indoc! {" + ˇThe quick brownˇ + ˇfox jumps overˇ + the lazy dog.ˇ + "}, + // Heading blank paragraph and trailing normal paragraph + indoc! {" + ˇ + ˇ + ˇThe quick brown fox jumps + ˇover the lazy dog. + ˇ + ˇ + ˇThe quick brown fox jumpsˇ + ˇover the lazy dog.ˇ + "}, + // Inserted blank paragraph and trailing blank paragraph + indoc! {" + ˇThe quick brown fox jumps + ˇover the lazy dog. + ˇ + ˇ + ˇ + ˇThe quick brown fox jumpsˇ + ˇover the lazy dog.ˇ + ˇ + ˇ + ˇ + "}, + // "Blank" paragraph with whitespace characters + indoc! {" + ˇThe quick brown fox jumps + over the lazy dog. + + ˇ \t + + ˇThe quick brown fox jumps + over the lazy dog.ˇ + ˇ + ˇ \t + \t \t + "}, + // Single line "paragraphs", where selection size might be zero. + indoc! {" + ˇThe quick brown fox jumps over the lazy dog. + ˇ + ˇThe quick brown fox jumpˇs over the lazy dog.ˇ + ˇ + "}, + ]; + + #[gpui::test] + async fn test_change_paragraph_object(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + for paragraph_example in PARAGRAPH_EXAMPLES { + cx.assert_binding_matches_all(["c", "i", "p"], paragraph_example) + .await; + cx.assert_binding_matches_all(["c", "a", "p"], paragraph_example) + .await; + } + } + + #[gpui::test] + async fn test_delete_paragraph_object(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + for paragraph_example in PARAGRAPH_EXAMPLES { + cx.assert_binding_matches_all(["d", "i", "p"], paragraph_example) + .await; + cx.assert_binding_matches_all(["d", "a", "p"], paragraph_example) + .await; + } + } + + #[gpui::test] + async fn test_paragraph_object_with_landing_positions_not_at_beginning_of_line( + cx: &mut gpui::TestAppContext, + ) { + // Landing position not at the beginning of the line + const PARAGRAPH_LANDING_POSITION_EXAMPLE: &'static str = indoc! {" + The quick brown fox jumpsˇ + over the lazy dog.ˇ + ˇ ˇ\tˇ + ˇ ˇ + ˇ\tˇ ˇ\tˇ + ˇThe quick brown fox jumpsˇ + ˇover the lazy dog.ˇ + ˇ ˇ\tˇ + ˇ + ˇ ˇ\tˇ + ˇ\tˇ ˇ\tˇ + "}; + + let mut cx = NeovimBackedTestContext::new(cx).await; + + cx.assert_binding_matches_all_exempted( + ["c", "i", "p"], + PARAGRAPH_LANDING_POSITION_EXAMPLE, + ExemptionFeatures::IncorrectLandingPosition, + ) + .await; + cx.assert_binding_matches_all_exempted( + ["c", "a", "p"], + PARAGRAPH_LANDING_POSITION_EXAMPLE, + ExemptionFeatures::IncorrectLandingPosition, + ) + .await; + cx.assert_binding_matches_all_exempted( + ["d", "i", "p"], + PARAGRAPH_LANDING_POSITION_EXAMPLE, + ExemptionFeatures::IncorrectLandingPosition, + ) + .await; + cx.assert_binding_matches_all_exempted( + ["d", "a", "p"], + PARAGRAPH_LANDING_POSITION_EXAMPLE, + ExemptionFeatures::IncorrectLandingPosition, + ) + .await; + } + + #[gpui::test] + async fn test_visual_paragraph_object(cx: &mut gpui::TestAppContext) { + let mut cx = NeovimBackedTestContext::new(cx).await; + + const EXAMPLES: &[&'static str] = &[ + indoc! {" + ˇThe quick brown + fox jumps over + the lazy dog. + "}, + indoc! {" + ˇ + + ˇThe quick brown fox jumps + over the lazy dog. + ˇ + + ˇThe quick brown fox jumps + over the lazy dog. + "}, + indoc! {" + ˇThe quick brown fox jumps over the lazy dog. + ˇ + ˇThe quick brown fox jumps over the lazy dog. + + "}, + ]; + + for paragraph_example in EXAMPLES { + cx.assert_binding_matches_all(["v", "i", "p"], paragraph_example) + .await; + cx.assert_binding_matches_all(["v", "a", "p"], paragraph_example) + .await; + } + } + // Test string with "`" for opening surrounders and "'" for closing surrounders const SURROUNDING_MARKER_STRING: &str = indoc! {" ˇTh'ˇe ˇ`ˇ'ˇquˇi`ˇck broˇ'wn` diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index 0a01a62742..f94770307b 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -72,7 +72,7 @@ impl NeovimBackedTestContext { let test_name = thread .name() .expect("thread is not named") - .split(":") + .split(':') .last() .unwrap() .to_string(); @@ -122,7 +122,7 @@ impl NeovimBackedTestContext { } pub async fn set_shared_state(&mut self, marked_text: &str) { - let mode = if marked_text.contains("»") { + let mode = if marked_text.contains('»') { Mode::Visual } else { Mode::Normal @@ -188,7 +188,7 @@ impl NeovimBackedTestContext { pub async fn assert_shared_state(&mut self, marked_text: &str) { self.is_dirty = false; - let marked_text = marked_text.replace("•", " "); + let marked_text = marked_text.replace('•', " "); let neovim = self.neovim_state().await; let neovim_mode = self.neovim_mode().await; let editor = self.editor_state(); diff --git a/crates/vim/src/test/neovim_connection.rs b/crates/vim/src/test/neovim_connection.rs index 5ea82e3661..3d47789fac 100644 --- a/crates/vim/src/test/neovim_connection.rs +++ b/crates/vim/src/test/neovim_connection.rs @@ -272,10 +272,7 @@ impl NeovimConnection { #[cfg(feature = "neovim")] pub async fn exec(&mut self, value: &str) { - self.nvim - .command_output(format!("{}", value).as_str()) - .await - .unwrap(); + self.nvim.command_output(value).await.unwrap(); self.data.push_back(NeovimData::Exec { command: value.to_string(), @@ -392,7 +389,7 @@ impl NeovimConnection { // the content of the selection via the "a register to get the shape correctly. self.nvim.input("\"aygv").await.unwrap(); let content = self.nvim.command_output("echo getreg('a')").await.unwrap(); - let lines = content.split("\n").collect::>(); + let lines = content.split('\n').collect::>(); let top = cmp::min(selection_row, cursor_row); let left = cmp::min(selection_col, cursor_col); for row in top..=cmp::max(selection_row, cursor_row) { diff --git a/crates/vim/src/test/vim_test_context.rs b/crates/vim/src/test/vim_test_context.rs index b06ef803b6..f8cc658394 100644 --- a/crates/vim/src/test/vim_test_context.rs +++ b/crates/vim/src/test/vim_test_context.rs @@ -90,7 +90,7 @@ impl VimTestContext { T: 'static, F: FnOnce(&mut T, &mut ViewContext) -> R + 'static, { - let window = self.window.clone(); + let window = self.window; self.update_window(window, move |_, cx| view.update(cx, update)) .unwrap() } diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index 64a14784cd..c316340058 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -9,7 +9,7 @@ use editor::{ Bias, DisplayPoint, Editor, }; use gpui::{actions, ViewContext, WindowContext}; -use language::{Selection, SelectionGoal}; +use language::{Point, Selection, SelectionGoal}; use workspace::Workspace; use crate::{ @@ -87,6 +87,7 @@ pub fn visual_motion(motion: Motion, times: Option, cx: &mut WindowContex // If the file ends with a newline (which is common) we don't do this. // so that if you go to the end of such a file you can use "up" to go // to the previous line and have it work somewhat as expected. + #[allow(clippy::nonminimal_bool)] if !selection.reversed && !selection.is_empty() && !(selection.end.column() == 0 && selection.end == map.max_point()) @@ -222,7 +223,7 @@ pub fn visual_block_motion( start: start.to_point(map), end: end.to_point(map), reversed: is_reversed, - goal: goal.clone(), + goal, }; selections.push(selection); @@ -278,6 +279,25 @@ pub fn visual_object(object: Object, cx: &mut WindowContext) { selection.end = range.end; } } + + // In the visual selection result of a paragraph object, the cursor is + // placed at the start of the last line. And in the visual mode, the + // selection end is located after the end character. So, adjustment of + // selection end is needed. + // + // We don't do this adjustment for a one-line blank paragraph since the + // trailing newline is included in its selection from the beginning. + if object == Object::Paragraph && range.start != range.end { + let row_of_selection_end_line = selection.end.to_point(map).row; + let new_selection_end = + if map.buffer_snapshot.line_len(row_of_selection_end_line) == 0 + { + Point::new(row_of_selection_end_line + 1, 0) + } else { + Point::new(row_of_selection_end_line, 1) + }; + selection.end = new_selection_end.to_display_point(map); + } } }); }); diff --git a/crates/vim/test_data/test_change_paragraph_object.json b/crates/vim/test_data/test_change_paragraph_object.json new file mode 100644 index 0000000000..7de16dac5b --- /dev/null +++ b/crates/vim/test_data/test_change_paragraph_object.json @@ -0,0 +1,430 @@ +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog."}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumpˇs over the lazy dog."}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dogˇ."}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.ˇ"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog."}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumpˇs over the lazy dog."}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dogˇ."}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.ˇ"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n","mode":"Insert"}} +{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n","mode":"Insert"}} +{"Put":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n","mode":"Insert"}} +{"Put":{"state":"The quick brown\nfox jumps overˇ\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n","mode":"Insert"}} +{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n","mode":"Insert"}} +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown\nfox jumps overˇ\nthe lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Insert"}} +{"Put":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n","mode":"Insert"}} +{"Put":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nˇover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ","mode":"Insert"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nˇover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇ\n\n \t\n\t \t\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇ\n\n \t\n\t \t\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ","mode":"Insert"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n \t\n\t \t\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\t \t\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\nThe quick brown fox jumps over the lazy dog.\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\nThe quick brown fox jumps over the lazy dog.\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumpˇs over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.ˇ\n\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\nˇ\n"}} +{"Key":"c"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\nˇ","mode":"Insert"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\nThe quick brown fox jumps over the lazy dog.\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\n\n","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumpˇs over the lazy dog.\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.ˇ\n\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ","mode":"Insert"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\nˇ\n"}} +{"Key":"c"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\nˇ\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_delete_paragraph_object.json b/crates/vim/test_data/test_delete_paragraph_object.json new file mode 100644 index 0000000000..2cf1402ae3 --- /dev/null +++ b/crates/vim/test_data/test_delete_paragraph_object.json @@ -0,0 +1,430 @@ +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog."}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumpˇs over the lazy dog."}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dogˇ."}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.ˇ"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog."}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumpˇs over the lazy dog."}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dogˇ."}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.ˇ"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown\nfox jumps overˇ\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brownˇ\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown\nˇfox jumps over\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown\nfox jumps overˇ\nthe lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown\nfox jumps over\nthe lazy dog.ˇ\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ","mode":"Normal"}} +{"Put":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ","mode":"Normal"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nˇover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nˇover the lazy dog.","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nˇover the lazy dog.","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nˇover the lazy dog.","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nˇover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ\nThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumpsˇ\nover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nˇover the lazy dog.\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇ\n","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇ\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇ\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nˇover the lazy dog.","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nˇover the lazy dog.","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\nˇ\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.ˇ\n\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\t \t\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps\nover the lazy dog.\n\n \t\n\nThe quick brown fox jumps\nover the lazy dog.\n\nˇ \t\n\t \t\n","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"ˇ\nThe quick brown fox jumps over the lazy dog.\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\nˇThe quick brown fox jumps over the lazy dog.\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumpˇs over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.ˇ\n\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇ\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\nˇ\n"}} +{"Key":"d"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇThe quick brown fox jumps over the lazy dog.","mode":"Normal"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"ˇThe quick brown fox jumps over the lazy dog.\n\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\n","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumpˇs over the lazy dog.\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.ˇ\n\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\nˇ","mode":"Normal"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\nˇ\n"}} +{"Key":"d"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\nˇ\n","mode":"Normal"}} diff --git a/crates/vim/test_data/test_paragraph_object_with_landing_positions_not_at_beginning_of_line.json b/crates/vim/test_data/test_paragraph_object_with_landing_positions_not_at_beginning_of_line.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/vim/test_data/test_visual_paragraph_object.json b/crates/vim/test_data/test_visual_paragraph_object.json new file mode 100644 index 0000000000..604d6dc93f --- /dev/null +++ b/crates/vim/test_data/test_visual_paragraph_object.json @@ -0,0 +1,80 @@ +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"«The quick brown\nfox jumps over\ntˇ»he lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"ˇThe quick brown\nfox jumps over\nthe lazy dog.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"«The quick brown\nfox jumps over\nthe lazy dog.\nˇ»","mode":"VisualLine"}} +{"Put":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"«\n\nˇ»The quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\n«The quick brown fox jumps\noˇ»ver the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n«\n\nˇ»The quick brown fox jumps\nover the lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n«The quick brown fox jumps\noˇ»ver the lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"ˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"«\n\nThe quick brown fox jumps\noˇ»ver the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"\n\nˇThe quick brown fox jumps\nover the lazy dog.\n\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\n«The quick brown fox jumps\nover the lazy dog.\n\n\nˇ»The quick brown fox jumps\nover the lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\nˇ\n\nThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n«\n\nThe quick brown fox jumps\noˇ»ver the lazy dog.\n","mode":"VisualLine"}} +{"Put":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\nˇThe quick brown fox jumps\nover the lazy dog.\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"\n\nThe quick brown fox jumps\nover the lazy dog.\n\n\n«The quick brown fox jumps\nover the lazy dog.\nˇ»","mode":"VisualLine"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"«Tˇ»he quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\n\n","mode":"VisualLine"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n«\nˇ»The quick brown fox jumps over the lazy dog.\n\n","mode":"VisualLine"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"v"} +{"Key":"i"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\n«Tˇ»he quick brown fox jumps over the lazy dog.\n\n","mode":"VisualLine"}} +{"Put":{"state":"ˇThe quick brown fox jumps over the lazy dog.\n\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"«The quick brown fox jumps over the lazy dog.\n\nˇ»The quick brown fox jumps over the lazy dog.\n\n","mode":"VisualLine"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\nˇ\nThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n«\nTˇ»he quick brown fox jumps over the lazy dog.\n\n","mode":"VisualLine"}} +{"Put":{"state":"The quick brown fox jumps over the lazy dog.\n\nˇThe quick brown fox jumps over the lazy dog.\n\n"}} +{"Key":"v"} +{"Key":"a"} +{"Key":"p"} +{"Get":{"state":"The quick brown fox jumps over the lazy dog.\n\n«The quick brown fox jumps over the lazy dog.\n\nˇ»","mode":"VisualLine"}} diff --git a/crates/welcome/Cargo.toml b/crates/welcome/Cargo.toml index e958bf26ff..6fd65c795e 100644 --- a/crates/welcome/Cargo.toml +++ b/crates/welcome/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/welcome.rs" @@ -14,6 +17,7 @@ test-support = [] [dependencies] anyhow.workspace = true client.workspace = true +copilot_ui.workspace = true db.workspace = true fuzzy.workspace = true gpui.workspace = true diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index 9a97fde6c3..01ffe2a166 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -2,6 +2,7 @@ mod base_keymap_picker; mod base_keymap_setting; use client::{telemetry::Telemetry, TelemetrySettings}; +use copilot_ui; use db::kvp::KEY_VALUE_STORE; use gpui::{ svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, @@ -134,6 +135,16 @@ impl Render for WelcomePage { }) .detach_and_log_err(cx); })), + ) + .child( + Button::new("sign-in-to-copilot", "Sign in to GitHub Copilot") + .full_width() + .on_click(cx.listener(|this, _, cx| { + this.telemetry.report_app_event( + "welcome page: sign in to copilot".to_string(), + ); + copilot_ui::initiate_sign_in(cx); + })), ), ) .child( diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 44c6a98859..db8d004b9e 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] path = "src/workspace.rs" doctest = false diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 70059e47b6..278ccded11 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -221,7 +221,7 @@ impl Dock { return; }; if panel.is_zoomed(cx) { - workspace.zoomed = Some(panel.to_any().downgrade().into()); + workspace.zoomed = Some(panel.to_any().downgrade()); workspace.zoomed_position = Some(position); } else { workspace.zoomed = None; diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index d5d8aed39d..07ca2c06a7 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -11,6 +11,7 @@ use client::{ proto::{self, PeerId}, Client, }; +use futures::{channel::mpsc, StreamExt}; use gpui::{ AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView, @@ -27,14 +28,13 @@ use std::{ ops::Range, path::PathBuf, rc::Rc, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::Arc, time::Duration, }; use theme::Theme; +pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200); + #[derive(Deserialize)] pub struct ItemSettings { pub git_status: bool, @@ -146,7 +146,12 @@ pub trait Item: FocusableView + EventEmitter { fn can_save(&self, _cx: &AppContext) -> bool { false } - fn save(&mut self, _project: Model, _cx: &mut ViewContext) -> Task> { + fn save( + &mut self, + _format: bool, + _project: Model, + _cx: &mut ViewContext, + ) -> Task> { unimplemented!("save() must be implemented if can_save() returns true") } fn save_as( @@ -258,7 +263,12 @@ pub trait ItemHandle: 'static + Send { fn is_dirty(&self, cx: &AppContext) -> bool; fn has_conflict(&self, cx: &AppContext) -> bool; fn can_save(&self, cx: &AppContext) -> bool; - fn save(&self, project: Model, cx: &mut WindowContext) -> Task>; + fn save( + &self, + format: bool, + project: Model, + cx: &mut WindowContext, + ) -> Task>; fn save_as( &self, project: Model, @@ -405,7 +415,7 @@ impl ItemHandle for View { followed_item.is_project_item(cx), proto::update_followers::Variant::CreateView(proto::View { id: followed_item - .remote_id(&workspace.app_state.client, cx) + .remote_id(&workspace.client(), cx) .map(|id| id.to_proto()), variant: Some(message), leader_id: workspace.leader_for_pane(&pane), @@ -421,8 +431,46 @@ impl ItemHandle for View { .is_none() { let mut pending_autosave = DelayedDebouncedEditAction::new(); + let (pending_update_tx, mut pending_update_rx) = mpsc::unbounded(); let pending_update = Rc::new(RefCell::new(None)); - let pending_update_scheduled = Arc::new(AtomicBool::new(false)); + + let mut send_follower_updates = None; + if let Some(item) = self.to_followable_item_handle(cx) { + let is_project_item = item.is_project_item(cx); + let item = item.downgrade(); + + send_follower_updates = Some(cx.spawn({ + let pending_update = pending_update.clone(); + |workspace, mut cx| async move { + while let Some(mut leader_id) = pending_update_rx.next().await { + while let Ok(Some(id)) = pending_update_rx.try_next() { + leader_id = id; + } + + workspace.update(&mut cx, |workspace, cx| { + let item = item.upgrade().expect( + "item to be alive, otherwise task would have been dropped", + ); + workspace.update_followers( + is_project_item, + proto::update_followers::Variant::UpdateView( + proto::UpdateView { + id: item + .remote_id(workspace.client(), cx) + .map(|id| id.to_proto()), + variant: pending_update.borrow_mut().take(), + leader_id, + }, + ), + cx, + ); + })?; + cx.background_executor().timer(LEADER_UPDATE_THROTTLE).await; + } + anyhow::Ok(()) + } + })); + } let mut event_subscription = Some(cx.subscribe(self, move |workspace, item, event, cx| { @@ -438,9 +486,7 @@ impl ItemHandle for View { }; if let Some(item) = item.to_followable_item_handle(cx) { - let is_project_item = item.is_project_item(cx); let leader_id = workspace.leader_for_pane(&pane); - let follow_event = item.to_follow_event(event); if leader_id.is_some() && matches!(follow_event, Some(FollowEvent::Unfollow)) @@ -448,35 +494,13 @@ impl ItemHandle for View { workspace.unfollow(&pane, cx); } - if item.focus_handle(cx).contains_focused(cx) - && item.add_event_to_update_proto( + if item.focus_handle(cx).contains_focused(cx) { + item.add_event_to_update_proto( event, - &mut *pending_update.borrow_mut(), + &mut pending_update.borrow_mut(), cx, - ) - && !pending_update_scheduled.load(Ordering::SeqCst) - { - pending_update_scheduled.store(true, Ordering::SeqCst); - cx.defer({ - let pending_update = pending_update.clone(); - let pending_update_scheduled = pending_update_scheduled.clone(); - move |this, cx| { - pending_update_scheduled.store(false, Ordering::SeqCst); - this.update_followers( - is_project_item, - proto::update_followers::Variant::UpdateView( - proto::UpdateView { - id: item - .remote_id(&this.app_state.client, cx) - .map(|id| id.to_proto()), - variant: pending_update.borrow_mut().take(), - leader_id, - }, - ), - cx, - ); - } - }); + ); + pending_update_tx.unbounded_send(leader_id).ok(); } } @@ -525,6 +549,7 @@ impl ItemHandle for View { cx.observe_release(self, move |workspace, _, _| { workspace.panes_by_item.remove(&item_id); event_subscription.take(); + send_follower_updates.take(); }) .detach(); } @@ -566,8 +591,13 @@ impl ItemHandle for View { self.read(cx).can_save(cx) } - fn save(&self, project: Model, cx: &mut WindowContext) -> Task> { - self.update(cx, |item, cx| item.save(project, cx)) + fn save( + &self, + format: bool, + project: Model, + cx: &mut WindowContext, + ) -> Task> { + self.update(cx, |item, cx| item.save(format, project, cx)) } fn save_as( @@ -700,6 +730,7 @@ pub trait FollowableItem: Item { pub trait FollowableItemHandle: ItemHandle { fn remote_id(&self, client: &Arc, cx: &WindowContext) -> Option; + fn downgrade(&self) -> Box; fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext); fn to_state_proto(&self, cx: &WindowContext) -> Option; fn add_event_to_update_proto( @@ -728,6 +759,10 @@ impl FollowableItemHandle for View { }) } + fn downgrade(&self) -> Box { + Box::new(self.downgrade()) + } + fn set_leader_peer_id(&self, leader_peer_id: Option, cx: &mut WindowContext) { self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx)) } @@ -767,6 +802,16 @@ impl FollowableItemHandle for View { } } +pub trait WeakFollowableItemHandle: Send + Sync { + fn upgrade(&self) -> Option>; +} + +impl WeakFollowableItemHandle for WeakView { + fn upgrade(&self) -> Option> { + Some(Box::new(self.upgrade()?)) + } +} + #[cfg(any(test, feature = "test-support"))] pub mod test { use super::{Item, ItemEvent}; @@ -1018,6 +1063,7 @@ pub mod test { fn save( &mut self, + _: bool, _: Model, _: &mut ViewContext, ) -> Task> { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 65315aec02..493a99dbe7 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -44,6 +44,8 @@ pub enum SaveIntent { /// write all files (even if unchanged) /// prompt before overwriting on-disk changes Save, + /// same as Save, but without auto formatting + SaveWithoutFormat, /// write any files that have local changes /// prompt before overwriting on-disk changes SaveAll, @@ -628,7 +630,7 @@ impl Pane { self.items.len() } - pub fn items(&self) -> impl Iterator> + DoubleEndedIterator { + pub fn items(&self) -> impl DoubleEndedIterator> { self.items.iter() } @@ -899,7 +901,7 @@ impl Pane { if not_shown_files == 1 { file_names.push(".. 1 file not shown".into()); } else { - file_names.push(format!(".. {} files not shown", not_shown_files).into()); + file_names.push(format!(".. {} files not shown", not_shown_files)); } } ( @@ -1122,7 +1124,7 @@ impl Pane { })?; // when saving a single buffer, we ignore whether or not it's dirty. - if save_intent == SaveIntent::Save { + if save_intent == SaveIntent::Save || save_intent == SaveIntent::SaveWithoutFormat { is_dirty = true; } @@ -1136,6 +1138,8 @@ impl Pane { has_conflict = false; } + let should_format = save_intent != SaveIntent::SaveWithoutFormat; + if has_conflict && can_save { let answer = pane.update(cx, |pane, cx| { pane.activate_item(item_ix, true, true, cx); @@ -1147,7 +1151,10 @@ impl Pane { ) })?; match answer.await { - Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?, + Ok(0) => { + pane.update(cx, |_, cx| item.save(should_format, project, cx))? + .await? + } Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?, _ => return Ok(false), } @@ -1157,7 +1164,7 @@ impl Pane { matches!( WorkspaceSettings::get_global(cx).autosave, AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange - ) && Self::can_autosave_item(&*item, cx) + ) && Self::can_autosave_item(item, cx) })?; if !will_autosave { let answer = pane.update(cx, |pane, cx| { @@ -1179,7 +1186,8 @@ impl Pane { } if can_save { - pane.update(cx, |_, cx| item.save(project, cx))?.await?; + pane.update(cx, |_, cx| item.save(should_format, project, cx))? + .await?; } else if can_save_as { let start_abs_path = project .update(cx, |project, cx| { @@ -1211,7 +1219,7 @@ impl Pane { cx: &mut WindowContext, ) -> Task> { if Self::can_autosave_item(item, cx) { - item.save(project, cx) + item.save(true, project, cx) } else { Task::ready(Ok(())) } @@ -1433,16 +1441,20 @@ impl Pane { "Close Clean", Some(Box::new(CloseCleanItems)), cx.handler_for(&pane, move |pane, cx| { - pane.close_clean_items(&CloseCleanItems, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_clean_items(&CloseCleanItems, cx) { + task.detach_and_log_err(cx) + } }), ) .entry( "Close All", Some(Box::new(CloseAllItems { save_intent: None })), cx.handler_for(&pane, |pane, cx| { - pane.close_all_items(&CloseAllItems { save_intent: None }, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = + pane.close_all_items(&CloseAllItems { save_intent: None }, cx) + { + task.detach_and_log_err(cx) + } }), ); @@ -1783,42 +1795,49 @@ impl Render for Pane { })) .on_action( cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| { - pane.close_active_item(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_active_item(action, cx) { + task.detach_and_log_err(cx) + } }), ) .on_action( cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| { - pane.close_inactive_items(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_inactive_items(action, cx) { + task.detach_and_log_err(cx) + } }), ) .on_action( cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| { - pane.close_clean_items(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_clean_items(action, cx) { + task.detach_and_log_err(cx) + } }), ) .on_action( cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| { - pane.close_items_to_the_left(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_items_to_the_left(action, cx) { + task.detach_and_log_err(cx) + } }), ) .on_action( cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| { - pane.close_items_to_the_right(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_items_to_the_right(action, cx) { + task.detach_and_log_err(cx) + } }), ) .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| { - pane.close_all_items(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_all_items(action, cx) { + task.detach_and_log_err(cx) + } })) .on_action( cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| { - pane.close_active_item(action, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = pane.close_active_item(action, cx) { + task.detach_and_log_err(cx) + } }), ) .on_action( @@ -2586,8 +2605,8 @@ mod tests { let mut index = 0; let items = labels.map(|mut label| { - if label.ends_with("*") { - label = label.trim_end_matches("*"); + if label.ends_with('*') { + label = label.trim_end_matches('*'); active_item_index = index; } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 6f55dc800e..0c3f9b9dc0 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -88,6 +88,7 @@ impl PaneGroup { }; } + #[allow(clippy::too_many_arguments)] pub(crate) fn render( &self, project: &Model, @@ -159,6 +160,7 @@ impl Member { } } + #[allow(clippy::too_many_arguments)] pub fn render( &self, project: &Model, @@ -266,7 +268,7 @@ impl Member { this.cursor_pointer().on_mouse_down( MouseButton::Left, cx.listener(move |this, _, cx| { - crate::join_remote_project( + crate::join_in_room_project( leader_project_id, leader_user_id, this.app_state().clone(), @@ -471,6 +473,7 @@ impl PaneAxis { None } + #[allow(clippy::too_many_arguments)] fn render( &self, project: &Model, @@ -640,6 +643,7 @@ mod element { self } + #[allow(clippy::too_many_arguments)] fn compute_resize( flexes: &Arc>>, e: &MouseMoveEvent, @@ -728,6 +732,7 @@ mod element { cx.refresh(); } + #[allow(clippy::too_many_arguments)] fn push_handle( flexes: Arc>>, dragged_handle: Rc>>, diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index d02154a6aa..9f99233c27 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -367,7 +367,7 @@ impl WorkspaceDb { conn.exec_bound(sql!( DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ? - ))?((&workspace.location, workspace.id.clone())) + ))?((&workspace.location, workspace.id)) .context("clearing out old locations")?; // Upsert @@ -622,11 +622,11 @@ impl WorkspaceDb { } fn get_items(&self, pane_id: PaneId) -> Result> { - Ok(self.select_bound(sql!( + self.select_bound(sql!( SELECT kind, item_id, active FROM items WHERE pane_id = ? ORDER BY position - ))?(pane_id)?) + ))?(pane_id) } fn save_items( diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 26d1ad3dd5..50631ba736 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result}; use call::{call_settings::CallSettings, ActiveCall}; use client::{ proto::{self, ErrorCode, PeerId}, - ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore, + ChannelId, Client, ErrorExt, HostedProjectId, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use derive_more::{Deref, DerefMut}; @@ -106,6 +106,7 @@ actions!( AddFolderToProject, Unfollow, SaveAs, + SaveWithoutFormat, ReloadActiveItem, ActivatePreviousPane, ActivateNextPane, @@ -679,8 +680,7 @@ impl Workspace { let mut active_call = None; if let Some(call) = ActiveCall::try_global(cx) { let call = call.clone(); - let mut subscriptions = Vec::new(); - subscriptions.push(cx.subscribe(&call, Self::on_active_call_event)); + let subscriptions = vec![cx.subscribe(&call, Self::on_active_call_event)]; active_call = Some((call, subscriptions)); } @@ -871,7 +871,6 @@ impl Workspace { cx.open_window(options, { let app_state = app_state.clone(); - let workspace_id = workspace_id.clone(); let project_handle = project_handle.clone(); move |cx| { cx.new_view(|cx| { @@ -1109,7 +1108,7 @@ impl Workspace { ) } - pub fn client(&self) -> &Client { + pub fn client(&self) -> &Arc { &self.app_state.client } @@ -1244,11 +1243,10 @@ impl Workspace { } } - Ok(this - .update(&mut cx, |this, cx| { - this.save_all_internal(SaveIntent::Close, cx) - })? - .await?) + this.update(&mut cx, |this, cx| { + this.save_all_internal(SaveIntent::Close, cx) + })? + .await }) } @@ -1260,7 +1258,7 @@ impl Workspace { fn send_keystrokes(&mut self, action: &SendKeystrokes, cx: &mut ViewContext) { let mut keystrokes: Vec = action .0 - .split(" ") + .split(' ') .flat_map(|k| Keystroke::parse(k).log_err()) .collect(); keystrokes.reverse(); @@ -1628,8 +1626,11 @@ impl Workspace { action: &CloseInactiveTabsAndPanes, cx: &mut ViewContext, ) { - self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = + self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx) + { + task.detach_and_log_err(cx) + } } pub fn close_all_items_and_panes( @@ -1637,8 +1638,11 @@ impl Workspace { action: &CloseAllItemsAndPanes, cx: &mut ViewContext, ) { - self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = + self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx) + { + task.detach_and_log_err(cx) + } } fn close_all_internal( @@ -2600,8 +2604,9 @@ impl Workspace { if Some(leader_id) == self.unfollow(&pane, cx) { return; } - self.start_following(leader_id, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = self.start_following(leader_id, cx) { + task.detach_and_log_err(cx) + } } pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext) { @@ -2630,7 +2635,7 @@ impl Workspace { // if they are active in another project, follow there. if let Some(project_id) = other_project_id { let app_state = self.app_state.clone(); - crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx) + crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx) .detach_and_log_err(cx); } @@ -2643,8 +2648,9 @@ impl Workspace { } // Otherwise, follow. - self.start_following(leader_id, cx) - .map(|task| task.detach_and_log_err(cx)); + if let Some(task) = self.start_following(leader_id, cx) { + task.detach_and_log_err(cx) + } } pub fn unfollow(&mut self, pane: &View, cx: &mut ViewContext) -> Option { @@ -2955,7 +2961,7 @@ impl Workspace { })?; let Some(id) = view.id.clone() else { - return Err(anyhow!("no id for view")).into(); + return Err(anyhow!("no id for view")); }; let id = ViewId::from_proto(id)?; @@ -3355,7 +3361,7 @@ impl Workspace { let left_visible = left_dock.is_open(); let left_active_panel = left_dock .visible_panel() - .and_then(|panel| Some(panel.persistent_name().to_string())); + .map(|panel| panel.persistent_name().to_string()); let left_dock_zoom = left_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) @@ -3365,7 +3371,7 @@ impl Workspace { let right_visible = right_dock.is_open(); let right_active_panel = right_dock .visible_panel() - .and_then(|panel| Some(panel.persistent_name().to_string())); + .map(|panel| panel.persistent_name().to_string()); let right_dock_zoom = right_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) @@ -3375,7 +3381,7 @@ impl Workspace { let bottom_visible = bottom_dock.is_open(); let bottom_active_panel = bottom_dock .visible_panel() - .and_then(|panel| Some(panel.persistent_name().to_string())); + .map(|panel| panel.persistent_name().to_string()); let bottom_dock_zoom = bottom_dock .visible_panel() .map(|panel| panel.is_zoomed(cx)) @@ -3527,6 +3533,11 @@ impl Workspace { .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx) .detach_and_log_err(cx); })) + .on_action(cx.listener(|workspace, _: &SaveWithoutFormat, cx| { + workspace + .save_active_item(SaveIntent::SaveWithoutFormat, cx) + .detach_and_log_err(cx); + })) .on_action(cx.listener(|workspace, _: &SaveAs, cx| { workspace .save_active_item(SaveIntent::SaveAs, cx) @@ -3713,7 +3724,7 @@ fn open_items( project_paths_to_open .into_iter() .enumerate() - .map(|(i, (abs_path, project_path))| { + .map(|(ix, (abs_path, project_path))| { let workspace = workspace.clone(); cx.spawn(|mut cx| { let fs = app_state.fs.clone(); @@ -3721,7 +3732,7 @@ fn open_items( let file_project_path = project_path?; if fs.is_file(&abs_path).await { Some(( - i, + ix, workspace .update(&mut cx, |workspace, cx| { workspace.open_path(file_project_path, None, true, cx) @@ -3738,11 +3749,9 @@ fn open_items( let tasks = tasks.collect::>(); - let tasks = futures::future::join_all(tasks.into_iter()); - for maybe_opened_path in tasks.await.into_iter() { - if let Some((i, path_open_result)) = maybe_opened_path { - opened_items[i] = Some(path_open_result); - } + let tasks = futures::future::join_all(tasks); + for (ix, path_open_result) in tasks.await.into_iter().flatten() { + opened_items[ix] = Some(path_open_result); } Ok(opened_items) @@ -3790,7 +3799,7 @@ impl Render for Workspace { let theme_settings = ThemeSettings::get_global(cx); ( theme_settings.ui_font.family.clone(), - theme_settings.ui_font_size.clone(), + theme_settings.ui_font_size, ) }; @@ -4149,7 +4158,7 @@ async fn join_channel_internal( if let Some(room) = open_room { let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project(cx) { - return Some(join_remote_project(project, host, app_state.clone(), cx)); + return Some(join_in_room_project(project, host, app_state.clone(), cx)); } None @@ -4220,7 +4229,7 @@ async fn join_channel_internal( let task = room.update(cx, |room, cx| { if let Some((project, host)) = room.most_active_project(cx) { - return Some(join_remote_project(project, host, app_state.clone(), cx)); + return Some(join_in_room_project(project, host, app_state.clone(), cx)); } // if you are the first to join a channel, share your project @@ -4393,7 +4402,7 @@ pub fn open_paths( cx.spawn(move |mut cx| async move { if let Some(existing) = existing { Ok(( - existing.clone(), + existing, existing .update(&mut cx, |workspace, cx| { workspace.open_paths(abs_paths, OpenVisible::All, None, cx) @@ -4455,7 +4464,56 @@ pub fn create_and_open_local_file( }) } -pub fn join_remote_project( +pub fn join_hosted_project( + hosted_project_id: HostedProjectId, + app_state: Arc, + cx: &mut AppContext, +) -> Task> { + cx.spawn(|mut cx| async move { + let existing_window = cx.update(|cx| { + cx.windows().into_iter().find_map(|window| { + let workspace = window.downcast::()?; + workspace + .read(cx) + .is_ok_and(|workspace| { + workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id) + }) + .then(|| workspace) + }) + })?; + + let workspace = if let Some(existing_window) = existing_window { + existing_window + } else { + let project = Project::hosted( + hosted_project_id, + app_state.user_store.clone(), + app_state.client.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + cx.clone(), + ) + .await?; + + let window_bounds_override = window_bounds_env_override(&cx); + cx.update(|cx| { + let options = (app_state.build_window_options)(window_bounds_override, None, cx); + cx.open_window(options, |cx| { + cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx)) + }) + })? + }; + + workspace.update(&mut cx, |_, cx| { + cx.activate(true); + cx.activate_window(); + })?; + + Ok(()) + }) +} + +pub fn join_in_room_project( project_id: u64, follow_user_id: u64, app_state: Arc, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 4ad8549e68..fe50da4884 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -6,6 +6,9 @@ version = "0.126.0" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [lib] name = "zed" path = "src/zed.rs" @@ -107,7 +110,7 @@ identifier = "dev.zed.Zed-Dev" name = "Zed Dev" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] -osx_url_schemes = ["zed-dev"] +osx_url_schemes = ["zed"] [package.metadata.bundle-nightly] icon = ["resources/app-icon-nightly@2x.png", "resources/app-icon-nightly.png"] @@ -115,7 +118,7 @@ identifier = "dev.zed.Zed-Nightly" name = "Zed Nightly" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] -osx_url_schemes = ["zed-nightly"] +osx_url_schemes = ["zed"] [package.metadata.bundle-preview] icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"] @@ -123,7 +126,7 @@ identifier = "dev.zed.Zed-Preview" name = "Zed Preview" osx_minimum_system_version = "10.15.7" osx_info_plist_exts = ["resources/info/*"] -osx_url_schemes = ["zed-preview"] +osx_url_schemes = ["zed"] [package.metadata.bundle-stable] icon = ["resources/app-icon@2x.png", "resources/app-icon.png"] diff --git a/crates/zed/build.rs b/crates/zed/build.rs index 6905d492e1..daea199e21 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -43,4 +43,9 @@ fn main() { } } } + + // todo!("windows"): This is to avoid stack overflow. Remove it when solved. + if std::env::var("CARGO_CFG_TARGET_ENV").ok() == Some("msvc".to_string()) { + println!("cargo:rustc-link-arg=/stack:{}", 8 * 1024 * 1024); + } } diff --git a/crates/zed/src/app_menus.rs b/crates/zed/src/app_menus.rs index 43b1d45c15..12a88ed216 100644 --- a/crates/zed/src/app_menus.rs +++ b/crates/zed/src/app_menus.rs @@ -40,7 +40,7 @@ pub fn app_menus() -> Vec> { MenuItem::action( "Open Recent...", recent_projects::OpenRecent { - create_new_window: false, + create_new_window: true, }, ), MenuItem::separator(), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 00af090d33..277d4dda03 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -5,7 +5,7 @@ use anyhow::{anyhow, Context as _, Result}; use backtrace::Backtrace; use chrono::Utc; use cli::FORCE_CLI_MODE_ENV_VAR_NAME; -use client::{Client, UserStore}; +use client::{parse_zed_link, Client, UserStore}; use collab_ui::channel_view::ChannelView; use db::kvp::KEY_VALUE_STORE; use editor::Editor; @@ -23,7 +23,7 @@ use assets::Assets; use mimalloc::MiMalloc; use node_runtime::RealNodeRuntime; use parking_lot::Mutex; -use release_channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL}; +use release_channel::{AppCommitSha, ReleaseChannel, RELEASE_CHANNEL}; use serde::{Deserialize, Serialize}; use settings::{ default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore, @@ -106,11 +106,9 @@ fn main() { let (listener, mut open_rx) = OpenListener::new(); let listener = Arc::new(listener); let open_listener = listener.clone(); - app.on_open_urls(move |urls, _| open_listener.open_urls(&urls)); + app.on_open_urls(move |urls, cx| open_listener.open_urls(&urls, cx)); app.on_reopen(move |cx| { - if let Some(app_state) = AppState::try_global(cx) - .map(|app_state| app_state.upgrade()) - .flatten() + if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade()) { workspace::open_new(&app_state, cx, |workspace, cx| { Editor::new_file(workspace, &Default::default(), cx) @@ -273,9 +271,9 @@ fn main() { #[cfg(not(target_os = "linux"))] upload_panics_and_crashes(http.clone(), cx); cx.activate(true); - let urls = collect_url_args(); + let urls = collect_url_args(cx); if !urls.is_empty() { - listener.open_urls(&urls) + listener.open_urls(&urls, cx) } } else { upload_panics_and_crashes(http.clone(), cx); @@ -284,7 +282,7 @@ fn main() { if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some() && !listener.triggered.load(Ordering::Acquire) { - listener.open_urls(&collect_url_args()) + listener.open_urls(&collect_url_args(cx), cx) } } @@ -298,9 +296,9 @@ fn main() { let task = workspace::open_paths(&paths, &app_state, None, cx); cx.spawn(|_| async move { if let Some((_window, results)) = task.await.log_err() { - for result in results { - if let Some(Err(e)) = result { - log::error!("Error opening path: {}", e); + for result in results.into_iter().flatten() { + if let Err(err) = result { + log::error!("Error opening path: {err}",); } } } @@ -664,7 +662,7 @@ fn init_panic_hook(app: &App, installation_id: Option, session_id: Strin let panic_data = Panic { thread: thread_name.into(), - payload: payload.into(), + payload, location_data: info.location().map(|location| LocationData { file: location.file().into(), line: location.line(), @@ -923,13 +921,13 @@ fn stdout_is_a_pty() -> bool { std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal() } -fn collect_url_args() -> Vec { +fn collect_url_args(cx: &AppContext) -> Vec { env::args() .skip(1) .filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) { Ok(path) => Some(format!("file://{}", path.to_string_lossy())), Err(error) => { - if let Some(_) = parse_zed_link(&arg) { + if let Some(_) = parse_zed_link(&arg, cx) { Some(arg) } else { log::error!("error parsing path argument: {}", error); diff --git a/crates/zed/src/open_listener.rs b/crates/zed/src/open_listener.rs index d6fec52df8..41dfe7e432 100644 --- a/crates/zed/src/open_listener.rs +++ b/crates/zed/src/open_listener.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Context, Result}; use cli::{ipc, IpcHandshake}; use cli::{ipc::IpcSender, CliRequest, CliResponse}; +use client::parse_zed_link; use collections::HashMap; use editor::scroll::Autoscroll; use editor::Editor; @@ -10,7 +11,6 @@ use futures::{FutureExt, SinkExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Global}; use itertools::Itertools; use language::{Bias, Point}; -use release_channel::parse_zed_link; use std::path::Path; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -66,13 +66,13 @@ impl OpenListener { ) } - pub fn open_urls(&self, urls: &[String]) { + pub fn open_urls(&self, urls: &[String], cx: &AppContext) { self.triggered.store(true, Ordering::Release); let request = if let Some(server_name) = urls.first().and_then(|url| url.strip_prefix("zed-cli://")) { self.handle_cli_connection(server_name) - } else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url)) { + } else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url, cx)) { self.handle_zed_url_scheme(request_path) } else { self.handle_file_urls(urls) @@ -95,10 +95,10 @@ impl OpenListener { } fn handle_zed_url_scheme(&self, request_path: &str) -> Option { - let mut parts = request_path.split("/"); + let mut parts = request_path.split('/'); if parts.next() == Some("channel") { if let Some(slug) = parts.next() { - if let Some(id_str) = slug.split("-").last() { + if let Some(id_str) = slug.split('-').last() { if let Ok(channel_id) = id_str.parse::() { let Some(next) = parts.next() else { return Some(OpenRequest::JoinChannel { channel_id }); @@ -184,7 +184,7 @@ pub async fn handle_cli_connection( } else { paths .into_iter() - .filter_map(|path_with_position_string| { + .map(|path_with_position_string| { let path_with_position = PathLikeWithPosition::parse_str( &path_with_position_string, |path_str| { @@ -203,7 +203,7 @@ pub async fn handle_cli_connection( caret_positions.insert(path.clone(), Point::new(row, col)); } } - Some(path) + path }) .collect() }; diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index fdbf173b2d..ca28267472 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -5,11 +5,12 @@ mod open_listener; pub use app_menus::*; use assistant::AssistantPanel; use breadcrumbs::Breadcrumbs; +use client::ZED_URL_SCHEME; use collections::VecDeque; use editor::{Editor, MultiBuffer}; use gpui::{ - actions, point, px, AppContext, Context, FocusableView, PromptLevel, TitlebarOptions, View, - ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions, + actions, point, px, AppContext, AsyncAppContext, Context, FocusableView, PromptLevel, + TitlebarOptions, View, ViewContext, VisualContext, WindowBounds, WindowKind, WindowOptions, }; pub use only_instance::*; pub use open_listener::*; @@ -38,11 +39,11 @@ use util::{ use uuid::Uuid; use vim::VimModeSetting; use welcome::BaseKeymap; -use workspace::Pane; use workspace::{ create_and_open_local_file, notifications::simple_message_notification::MessageNotification, - open_new, AppState, NewFile, NewWindow, Workspace, WorkspaceSettings, + open_new, AppState, NewFile, NewWindow, Toast, Workspace, WorkspaceSettings, }; +use workspace::{notifications::DetachAndPromptErr, Pane}; use zed_actions::{OpenBrowser, OpenSettings, OpenZedUrl, Quit}; actions!( @@ -232,7 +233,7 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { cx.toggle_full_screen(); }) .register_action(|_, action: &OpenZedUrl, cx| { - OpenListener::global(cx).open_urls(&[action.url.clone()]) + OpenListener::global(cx).open_urls(&[action.url.clone()], cx) }) .register_action(|_, action: &OpenBrowser, cx| cx.open_url(&action.url)) .register_action(move |_, _: &IncreaseBufferFontSize, cx| { @@ -243,12 +244,50 @@ pub fn initialize_workspace(app_state: Arc, cx: &mut AppContext) { }) .register_action(move |_, _: &ResetBufferFontSize, cx| theme::reset_font_size(cx)) .register_action(|_, _: &install_cli::Install, cx| { - cx.spawn(|_, cx| async move { - install_cli::install_cli(cx.deref()) + cx.spawn(|workspace, mut cx| async move { + let path = install_cli::install_cli(cx.deref()) .await - .context("error creating CLI symlink") + .context("error creating CLI symlink")?; + workspace.update(&mut cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + 0, + format!( + "Installed `zed` to {}. You can launch {} from your terminal.", + path.to_string_lossy(), + ReleaseChannel::global(cx).display_name() + ), + ), + cx, + ) + })?; + register_zed_scheme(&cx).await.log_err(); + Ok(()) }) - .detach_and_log_err(cx); + .detach_and_prompt_err("Error installing zed cli", cx, |_, _| None); + }) + .register_action(|_, _: &install_cli::RegisterZedScheme, cx| { + cx.spawn(|workspace, mut cx| async move { + register_zed_scheme(&cx).await?; + workspace.update(&mut cx, |workspace, cx| { + workspace.show_toast( + Toast::new( + 0, + format!( + "zed:// links will now open in {}.", + ReleaseChannel::global(cx).display_name() + ), + ), + cx, + ) + })?; + Ok(()) + }) + .detach_and_prompt_err( + "Error registering zed:// scheme", + cx, + |_, _| None, + ); }) .register_action(|workspace, _: &OpenLog, cx| { open_log_file(workspace, cx); @@ -561,7 +600,7 @@ pub fn handle_keymap_file_changes( let new_base_keymap = *BaseKeymap::get_global(cx); let new_vim_enabled = VimModeSetting::get_global(cx).0; if new_base_keymap != old_base_keymap || new_vim_enabled != old_vim_enabled { - old_base_keymap = new_base_keymap.clone(); + old_base_keymap = new_base_keymap; old_vim_enabled = new_vim_enabled; base_keymap_tx.unbounded_send(()).unwrap(); } @@ -1979,7 +2018,7 @@ mod tests { editor.newline(&Default::default(), cx); editor.move_down(&Default::default(), cx); editor.move_down(&Default::default(), cx); - editor.save(project.clone(), cx) + editor.save(true, project.clone(), cx) }) }) .unwrap() @@ -2845,10 +2884,10 @@ mod tests { )) } #[track_caller] - fn assert_key_bindings_for<'a>( + fn assert_key_bindings_for( window: AnyWindowHandle, cx: &TestAppContext, - actions: Vec<(&'static str, &'a dyn Action)>, + actions: Vec<(&'static str, &dyn Action)>, line: u32, ) { let available_actions = cx @@ -2881,3 +2920,8 @@ mod tests { } } } + +async fn register_zed_scheme(cx: &AsyncAppContext) -> anyhow::Result<()> { + cx.update(|cx| cx.register_url_scheme(ZED_URL_SCHEME))? + .await +} diff --git a/crates/zed_actions/Cargo.toml b/crates/zed_actions/Cargo.toml index 19c9415514..ee279cde65 100644 --- a/crates/zed_actions/Cargo.toml +++ b/crates/zed_actions/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" publish = false license = "GPL-3.0-or-later" +[lints] +workspace = true + [dependencies] gpui.workspace = true serde.workspace = true diff --git a/docs/src/configuring_zed.md b/docs/src/configuring_zed.md index 3323ea8c04..b2ae9ab86a 100644 --- a/docs/src/configuring_zed.md +++ b/docs/src/configuring_zed.md @@ -306,6 +306,70 @@ To override settings for a language, add an entry for that language server's nam } ``` +## Code Actions On Format + +- Description: The code actions to perform with the primary language server when formatting the buffer. +- Setting: `code_actions_on_format` +- Default: `{}`, except for Go it's `{ "source.organizeImports": true }` + +**Examples** + +1. Organize imports on format in TypeScript and TSX buffers: + +```json +{ + "languages": { + "TypeScript": { + "code_actions_on_format": { + "source.organizeImports": true + } + }, + "TSX": { + "code_actions_on_format": { + "source.organizeImports": true + } + } + } +} +``` + +2. Run ESLint `fixAll` code action when formatting (requires Zed `0.125.0`): + +```json +{ + "languages": { + "JavaScript": { + "code_actions_on_format": { + "source.fixAll.eslint": true + } + } + } +} +``` + +3. Run only a single ESLint rule when using `fixAll` (requires Zed `0.125.0`): + +```json +{ + "languages": { + "JavaScript": { + "code_actions_on_format": { + "source.fixAll.eslint": true + } + } + }, + "lsp": { + "eslint": { + "settings": { + "codeActionOnSave": { + "rules": ["import/order"] + } + } + } + } +} +``` + ## Auto close - Description: Whether or not to automatically type closing characters for you. diff --git a/docs/src/getting_started.md b/docs/src/getting_started.md index f1fbdd163d..cccc25d06b 100644 --- a/docs/src/getting_started.md +++ b/docs/src/getting_started.md @@ -8,7 +8,7 @@ You can obtain the release build via the [download page](https://zed.dev/downloa ## Configure Zed -Use `⌘` + `,` to open your custom settings to set things like fonts, formatting settings, per-language settings and more. You can access the default configuration using the `Zed > Settings > Open Default Settings` menu item. See Configuring Zed for all available settings. +Use `⌘` + `,` to open your custom settings to set things like fonts, formatting settings, per-language settings and more. You can access the default configuration using the `Zed > Settings > Open Default Settings` menu item. See [Configuring Zed](https://zed.dev/docs/configuring-zed) for all available settings. ## Set up your key bindings diff --git a/docs/src/languages/clojure.md b/docs/src/languages/clojure.md index 58ff9790e0..b89e122a56 100644 --- a/docs/src/languages/clojure.md +++ b/docs/src/languages/clojure.md @@ -1,4 +1,4 @@ # Clojure -- Tree Sitter: [tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure) +- Tree Sitter: [tree-sitter-clojure](https://github.com/prcastro/tree-sitter-clojure) - Language Server: [clojure-lsp](https://github.com/clojure-lsp/clojure-lsp) diff --git a/docs/src/languages/vue.md b/docs/src/languages/vue.md index 53b7207291..9cf0f58c35 100644 --- a/docs/src/languages/vue.md +++ b/docs/src/languages/vue.md @@ -1,4 +1,4 @@ # Vue -- Tree Sitter: [tree-sitter-vue](https://github.com/vuejs/language-tools/tree/master/packages/vue-language-server) +- Tree Sitter: [tree-sitter-vue](https://github.com/vuejs/language-tools/tree/master/packages/language-server) - Language Server: [vue](https://github.com/vuejs/language-tools) diff --git a/docs/src/tasks.md b/docs/src/tasks.md index bfe6f8f983..cebcf75cf2 100644 --- a/docs/src/tasks.md +++ b/docs/src/tasks.md @@ -4,7 +4,7 @@ Zed supports ways to spawn (and rerun) commands using its integrated terminal to Currently, two kinds of tasks are supported, but more will be added in the future. -All tasks are are sorted in LRU order and their names can be used (with `menu::UseSelectedQuery`, `shift-enter` by default) as an input text for quicker oneshot task edit-spawn cycle. +All tasks are sorted in LRU order and their names can be used (with `menu::UseSelectedQuery`, `shift-enter` by default) as an input text for quicker oneshot task edit-spawn cycle. ## Static tasks diff --git a/extensions/gleam/Cargo.toml b/extensions/gleam/Cargo.toml index 1e8ff5cdea..881978c90b 100644 --- a/extensions/gleam/Cargo.toml +++ b/extensions/gleam/Cargo.toml @@ -2,6 +2,11 @@ name = "zed_gleam" version = "0.0.1" edition = "2021" +publish = false +license = "Apache-2.0" + +[lints] +workspace = true [dependencies] zed_extension_api = { path = "../../crates/extension_api" } diff --git a/extensions/gleam/LICENSE-APACHE b/extensions/gleam/LICENSE-APACHE new file mode 120000 index 0000000000..1cd601d0a3 --- /dev/null +++ b/extensions/gleam/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/script/bundle b/script/bundle index a6414febcb..d90ed2a014 100755 --- a/script/bundle +++ b/script/bundle @@ -173,6 +173,7 @@ if [ "$local_arch" = false ]; then cp -R target/${local_target_triple}/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" else cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/" + cp -R target/${target_dir}/cli "${app_path}/Contents/MacOS/" fi # Note: The app identifier for our development builds is the same as the app identifier for nightly. diff --git a/script/clippy b/script/clippy index b0a1c86cc2..90a121be1c 100755 --- a/script/clippy +++ b/script/clippy @@ -2,8 +2,4 @@ set -euxo pipefail -# clippy.toml is not currently supporting specifying allowed lints -# so specify those here, and disable the rest until Zed's workspace -# will have more fixes & suppression for the standard lint set -cargo clippy --release --workspace --all-features --all-targets -- --allow clippy::all --deny clippy::dbg_macro --deny clippy::todo -cargo clippy --package gpui -- --deny warnings +cargo xtask clippy diff --git a/script/generate-licenses b/script/generate-licenses index 107da5a0da..10d85f60dc 100755 --- a/script/generate-licenses +++ b/script/generate-licenses @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e +set -euo pipefail OUTPUT_FILE=$(pwd)/assets/licenses.md diff --git a/script/licenses/zed-licenses.toml b/script/licenses/zed-licenses.toml index d338e7ab0b..c4557a8c20 100644 --- a/script/licenses/zed-licenses.toml +++ b/script/licenses/zed-licenses.toml @@ -12,6 +12,7 @@ accepted = [ "Unicode-DFS-2016", "OpenSSL", "Zlib", + "BSL-1.0" ] workarounds = [ "ring", @@ -35,3 +36,9 @@ license = "BSD-3-Clause" [[fuchsia-cprng.clarify.files]] path = 'LICENSE' checksum = '03b114f53e6587a398931762ee11e2395bfdba252a329940e2c8c9e81813845b' + +[tree-sitter-hcl.clarify] +license = "Apache-2.0" +[[tree-sitter-hcl.clarify.files]] +path = 'LICENSE' +checksum = 'c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4' diff --git a/script/linux b/script/linux index b5bd621b0d..9f4e0402c2 100755 --- a/script/linux +++ b/script/linux @@ -1,7 +1,7 @@ #!/usr/bin/bash -e # if sudo is not installed, define an empty alias -maysudo=$(command -v sudo || true) +maysudo=$(command -v sudo || command -v doas || true) # Ubuntu, Debian, etc. # https://packages.ubuntu.com/ @@ -13,6 +13,7 @@ if [[ -n $apt ]]; then libwayland-dev libxkbcommon-x11-dev libssl-dev + libzstd-dev ) $maysudo "$apt" install -y "${deps[@]}" exit 0 @@ -28,6 +29,7 @@ if [[ -n $dnf ]]; then wayland-devel libxkbcommon-x11-devel openssl-devel + libzstd-devel ) $maysudo "$dnf" install -y "${deps[@]}" exit 0 @@ -43,6 +45,7 @@ if [[ -n $zyp ]]; then wayland-devel libxkbcommon-x11-devel openssl-devel + libzstd-devel ) $maysudo "$zyp" install -y "${deps[@]}" exit 0 @@ -58,6 +61,7 @@ if [[ -n $pacman ]]; then wayland libxkbcommon-x11 openssl + zstd ) $maysudo "$pacman" -S --needed --noconfirm "${deps[@]}" exit 0 @@ -70,9 +74,11 @@ if [[ -n $xbps ]]; then deps=( alsa-lib-devel fontconfig-devel - wayland-devel + libxcb-devel libxkbcommon-devel + libzstd-devel openssl-devel + wayland-devel ) $maysudo "$xbps" -Syu "${deps[@]}" exit 0 diff --git a/script/run-local-minio b/script/run-local-minio index 8d2d4877ff..292e676c9d 100755 --- a/script/run-local-minio +++ b/script/run-local-minio @@ -6,4 +6,4 @@ mkdir -p .blob_store/zed-crash-reports export MINIO_ROOT_USER=the-blob-store-access-key export MINIO_ROOT_PASSWORD=the-blob-store-secret-key -minio server --quiet .blob_store +exec minio server --quiet .blob_store diff --git a/tooling/xtask/Cargo.toml b/tooling/xtask/Cargo.toml new file mode 100644 index 0000000000..11f0036c74 --- /dev/null +++ b/tooling/xtask/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" +publish = false +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[dependencies] +anyhow.workspace = true +clap = { workspace = true, features = ["derive"] } diff --git a/tooling/xtask/LICENSE-GPL b/tooling/xtask/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/tooling/xtask/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/tooling/xtask/src/main.rs b/tooling/xtask/src/main.rs new file mode 100644 index 0000000000..e4feb2bdb1 --- /dev/null +++ b/tooling/xtask/src/main.rs @@ -0,0 +1,86 @@ +use std::process::Command; + +use anyhow::{bail, Context, Result}; +use clap::{Parser, Subcommand}; + +#[derive(Parser)] +#[command(name = "cargo xtask")] +struct Args { + #[command(subcommand)] + command: CliCommand, +} + +#[derive(Subcommand)] +enum CliCommand { + /// Runs `cargo clippy`. + Clippy(ClippyArgs), +} + +fn main() -> Result<()> { + let args = Args::parse(); + + match args.command { + CliCommand::Clippy(args) => run_clippy(args), + } +} + +#[derive(Parser)] +struct ClippyArgs { + /// Automatically apply lint suggestions (`clippy --fix`). + #[arg(long)] + fix: bool, + + /// The package to run Clippy against (`cargo -p clippy`). + #[arg(long, short)] + package: Option, +} + +fn run_clippy(args: ClippyArgs) -> Result<()> { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + + let mut clippy_command = Command::new(&cargo); + clippy_command.arg("clippy"); + + if let Some(package) = args.package.as_ref() { + clippy_command.args(["--package", package]); + } else { + clippy_command.arg("--workspace"); + } + + clippy_command + .arg("--release") + .arg("--all-targets") + .arg("--all-features"); + + if args.fix { + clippy_command.arg("--fix"); + } + + clippy_command.arg("--"); + + // Deny all warnings. + // We don't do this yet on Windows, as it still has some warnings present. + #[cfg(not(target_os = "windows"))] + clippy_command.args(["--deny", "warnings"]); + + eprintln!( + "running: {cargo} {}", + clippy_command + .get_args() + .map(|arg| arg.to_str().unwrap()) + .collect::>() + .join(" ") + ); + + let exit_status = clippy_command + .spawn() + .context("failed to spawn child process")? + .wait() + .context("failed to wait for child process")?; + + if !exit_status.success() { + bail!("clippy failed: {}", exit_status); + } + + Ok(()) +} diff --git a/typos.toml b/typos.toml index af70fb77c7..91e95e35e9 100644 --- a/typos.toml +++ b/typos.toml @@ -26,6 +26,7 @@ extend-ignore-re = [ "COLUMN enviroment", # Typo in ClickHouse column name. # crates/collab/src/api/events.rs - "rename = \"sesssion_id\"" + "rename = \"sesssion_id\"", + "doas", ] check-filename = true