Compare commits
6 Commits
fix-git-ht
...
v0.176.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
121bd97cb1 | ||
|
|
2433e99d05 | ||
|
|
c75c2276b0 | ||
|
|
3659466f5b | ||
|
|
a82d7aa2a9 | ||
|
|
977e051983 |
17
.github/workflows/ci.yml
vendored
17
.github/workflows/ci.yml
vendored
@@ -110,13 +110,13 @@ jobs:
|
||||
run: ./script/clippy
|
||||
|
||||
- name: Install cargo-machete
|
||||
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
|
||||
uses: clechasseur/rs-cargo@v2
|
||||
with:
|
||||
command: install
|
||||
args: cargo-machete@0.7.0
|
||||
|
||||
- name: Check unused dependencies
|
||||
uses: clechasseur/rs-cargo@8435b10f6e71c2e3d4d3b7573003a8ce4bfc6386 # v2
|
||||
uses: clechasseur/rs-cargo@v2
|
||||
with:
|
||||
command: machete
|
||||
|
||||
@@ -240,7 +240,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
name: (Windows) Run Clippy and tests
|
||||
if: github.repository_owner == 'zed-industries'
|
||||
runs-on: hosted-windows-2
|
||||
runs-on: hosted-windows-1
|
||||
steps:
|
||||
# more info here:- https://github.com/rust-lang/cargo/issues/13020
|
||||
- name: Enable longer pathnames for git
|
||||
@@ -273,7 +273,8 @@ jobs:
|
||||
|
||||
- name: cargo clippy
|
||||
working-directory: ${{ env.ZED_WORKSPACE }}
|
||||
run: ./script/clippy.ps1
|
||||
# Windows can't run shell scripts, so we need to use `cargo xtask`.
|
||||
run: cargo xtask clippy
|
||||
|
||||
- name: Run tests
|
||||
uses: ./.github/actions/run_tests_windows
|
||||
@@ -358,14 +359,14 @@ jobs:
|
||||
mv target/x86_64-apple-darwin/release/Zed.dmg target/x86_64-apple-darwin/release/Zed-x86_64.dmg
|
||||
|
||||
- name: Upload app bundle (aarch64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-aarch64.dmg
|
||||
path: target/aarch64-apple-darwin/release/Zed-aarch64.dmg
|
||||
|
||||
- name: Upload app bundle (x86_64) to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: Zed_${{ github.event.pull_request.head.sha || github.sha }}-x86_64.dmg
|
||||
@@ -416,7 +417,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-x86_64-unknown-linux-gnu.tar.gz
|
||||
@@ -464,7 +465,7 @@ jobs:
|
||||
run: script/bundle-linux
|
||||
|
||||
- name: Upload Linux bundle to workflow run if main branch or specific label
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
if: ${{ github.ref == 'refs/heads/main' }} || contains(github.event.pull_request.labels.*.name, 'run-bundling') }}
|
||||
with:
|
||||
name: zed-${{ github.event.pull_request.head.sha || github.sha }}-aarch64-unknown-linux-gnu.tar.gz
|
||||
|
||||
2
.github/workflows/deploy_cloudflare.yml
vendored
2
.github/workflows/deploy_cloudflare.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
command: deploy .cloudflare/docs-proxy/src/worker.js
|
||||
|
||||
- name: Preserve Wrangler logs
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4
|
||||
if: always()
|
||||
with:
|
||||
name: wrangler_logs
|
||||
|
||||
191
Cargo.lock
generated
191
Cargo.lock
generated
@@ -490,7 +490,6 @@ dependencies = [
|
||||
"ui",
|
||||
"util",
|
||||
"uuid",
|
||||
"vim_mode_setting",
|
||||
"workspace",
|
||||
"zed_actions",
|
||||
]
|
||||
@@ -1176,9 +1175,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.5.17"
|
||||
version = "1.5.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490aa7465ee685b2ced076bb87ef654a47724a7844e2c7d3af4e749ce5b875dd"
|
||||
checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1269,9 +1268,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-bedrockruntime"
|
||||
version = "1.75.0"
|
||||
version = "1.74.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ddf7475b6f50a1a5be8edb1bcdf6e4ae00feed5b890d14a3f1f0e14d76f5a16"
|
||||
checksum = "6938541d1948a543bca23303fec4cff9c36bf0e63b8fa3ae1b337bcb9d5b81af"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1293,9 +1292,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-kinesis"
|
||||
version = "1.62.0"
|
||||
version = "1.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31622345afd0c35d33c1cbba73ccf9fb88e09857413d8963dea2c493e00704d"
|
||||
checksum = "89f2163d8704e8fdcd51ec6c2e0441c418471e422ee9690451b17a1c46344e1a"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1315,9 +1314,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.77.0"
|
||||
version = "1.76.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34e87342432a3de0e94e82c99a7cbd9042f99de029ae1f4e368160f9e9929264"
|
||||
checksum = "66e83401ad7287ad15244d557e35502c2a94105ca5b41d656c391f1a4fc04ca2"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1349,9 +1348,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.60.0"
|
||||
version = "1.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60186fab60b24376d3e33b9ff0a43485f99efd470e3b75a9160c849741d63d56"
|
||||
checksum = "16ff718c9ee45cc1ebd4774a0e086bb80a6ab752b4902edf1c9f56b86ee1f770"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1371,9 +1370,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.61.0"
|
||||
version = "1.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7033130ce1ee13e6018905b7b976c915963755aef299c1521897679d6cd4f8ef"
|
||||
checksum = "5183e088715cc135d8d396fdd3bc02f018f0da4c511f53cb8d795b6a31c55809"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1393,9 +1392,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.61.0"
|
||||
version = "1.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5c1cac7677179d622b4448b0d31bcb359185295dc6fca891920cfb17e2b5156"
|
||||
checksum = "c9f944ef032717596639cea4a2118a3a457268ef51bbb5fde9637e54c465da00"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
@@ -1456,9 +1455,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aws-smithy-checksums"
|
||||
version = "0.63.0"
|
||||
version = "0.62.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db2dc8d842d872529355c72632de49ef8c5a2949a4472f10e802f28cf925770c"
|
||||
checksum = "f2f45a1c384d7a393026bc5f5c177105aa9fa68e4749653b985707ac27d77295"
|
||||
dependencies = [
|
||||
"aws-smithy-http",
|
||||
"aws-smithy-types",
|
||||
@@ -1808,7 +1807,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
@@ -1831,7 +1830,7 @@ dependencies = [
|
||||
"bitflags 2.8.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
@@ -2401,6 +2400,25 @@ dependencies = [
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"heck 0.4.1",
|
||||
"indexmap",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 2.0.90",
|
||||
"tempfile",
|
||||
"toml 0.8.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbindgen"
|
||||
version = "0.28.0"
|
||||
@@ -2498,9 +2516,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@@ -2508,7 +2526,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2568,9 +2586,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.31"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
|
||||
checksum = "92b7b18d71fad5313a1e320fa9897994228ce274b60faa4d694fe0ea89cd9e6d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -2578,9 +2596,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.31"
|
||||
version = "4.5.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
|
||||
checksum = "a35db2071778a7344791a4fb4f95308b5673d219dee3ae348b86642574ecc90c"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -2635,7 +2653,6 @@ dependencies = [
|
||||
"serde",
|
||||
"tempfile",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3003,6 +3020,7 @@ dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
"linkme",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"theme",
|
||||
]
|
||||
@@ -3124,9 +3142,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.8.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||
checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -3505,10 +3523,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crc64fast-nvme"
|
||||
version = "1.2.0"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3"
|
||||
checksum = "d5e2ee08013e3f228d6d2394116c4549a6df77708442c62d887d83f68ef2ee37"
|
||||
dependencies = [
|
||||
"cbindgen 0.27.0",
|
||||
"crc",
|
||||
]
|
||||
|
||||
@@ -3645,19 +3664,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.4.0"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7747ac3a66a06f4ee6718686c8ea976d2d05fb30ada93ebd76b3f9aef97257c"
|
||||
checksum = "21d960ecacd0a1bf55e73144b72de745e7bf275c7952c50e36e8af0a0cb7ab1f"
|
||||
dependencies = [
|
||||
"ctor-proc-macro",
|
||||
"dtor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor-proc-macro"
|
||||
version = "0.0.5"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d"
|
||||
checksum = "c426d2ba3e525b39c1f0a9ba41b9fe61878dee11fa4e4a76b6ab440f46c5db5d"
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
@@ -4033,21 +4051,6 @@ dependencies = [
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bf39a0bfd1f94d62ffdb2802a7e6244c0f34f6ebacf5d4c26547d08cd1d67a5"
|
||||
dependencies = [
|
||||
"dtor-proc-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtor-proc-macro"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055"
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
@@ -4102,7 +4105,7 @@ dependencies = [
|
||||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"convert_case 0.8.0",
|
||||
"convert_case 0.7.1",
|
||||
"ctor",
|
||||
"db",
|
||||
"emojis",
|
||||
@@ -5350,7 +5353,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"smol",
|
||||
"sum_tree",
|
||||
"tempfile",
|
||||
"text",
|
||||
"time",
|
||||
"unindent",
|
||||
@@ -5420,7 +5422,6 @@ dependencies = [
|
||||
"theme",
|
||||
"time",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"windows 0.58.0",
|
||||
"workspace",
|
||||
@@ -5555,7 +5556,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"cbindgen",
|
||||
"cbindgen 0.28.0",
|
||||
"cocoa 0.26.0",
|
||||
"collections",
|
||||
"core-foundation 0.9.4",
|
||||
@@ -7228,9 +7229,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.170"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -7574,9 +7575,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.26"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"value-bag",
|
||||
@@ -7947,7 +7948,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"collections",
|
||||
"convert_case 0.8.0",
|
||||
"convert_case 0.7.1",
|
||||
"log",
|
||||
"pretty_assertions",
|
||||
"streaming-iterator",
|
||||
@@ -9757,15 +9758,6 @@ dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pgvector"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0e8871b6d7ca78348c6cd29b911b94851f3429f0cd403130ca17f26c1fb91a6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
@@ -10390,7 +10382,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
|
||||
dependencies = [
|
||||
"bytes 1.10.0",
|
||||
"heck 0.5.0",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"log",
|
||||
"multimap 0.10.0",
|
||||
"once_cell",
|
||||
@@ -10423,7 +10415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.12.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
@@ -11451,9 +11443,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "8.6.0"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f"
|
||||
checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
@@ -11462,9 +11454,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "8.6.0"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae"
|
||||
checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11475,9 +11467,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "8.6.0"
|
||||
version = "8.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a"
|
||||
checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d"
|
||||
dependencies = [
|
||||
"globset",
|
||||
"sha2",
|
||||
@@ -11752,9 +11744,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"indexmap",
|
||||
@@ -11765,9 +11757,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -11830,18 +11822,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm"
|
||||
version = "1.1.6"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13fba7b2c749b2d0a00303d5cb13e6761e39a4172554bdf930852cac4e7aeabd"
|
||||
checksum = "00733e5418e8ae3758cdb988c3654174e716230cc53ee2cb884207cf86a23029"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"futures 0.3.31",
|
||||
"log",
|
||||
"ouroboros",
|
||||
"pgvector",
|
||||
"rust_decimal",
|
||||
"sea-orm-macros",
|
||||
"sea-query",
|
||||
@@ -11859,9 +11850,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm-macros"
|
||||
version = "1.1.6"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2568cff8d35d5150b4276cc0dd766192a587f64b6ece60ae3706e0872c4eb209"
|
||||
checksum = "a98408f82fb4875d41ef469a79944a7da29767c7b3e4028e22188a3dd613b10f"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
@@ -12054,18 +12045,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -14578,7 +14569,7 @@ dependencies = [
|
||||
name = "ui_macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"convert_case 0.8.0",
|
||||
"convert_case 0.7.1",
|
||||
"linkme",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -14781,9 +14772,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.15.1"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587"
|
||||
checksum = "8c1f41ffb7cf259f1ecc2876861a17e7142e63ead296f671f81f6ae85903e0d6"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"serde",
|
||||
@@ -15906,12 +15897,6 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
@@ -16747,7 +16732,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.177.0"
|
||||
version = "0.176.1"
|
||||
dependencies = [
|
||||
"activity_indicator",
|
||||
"anyhow",
|
||||
@@ -16851,7 +16836,6 @@ dependencies = [
|
||||
"tasks_ui",
|
||||
"telemetry",
|
||||
"telemetry_events",
|
||||
"tempfile",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"theme_extension",
|
||||
@@ -16868,7 +16852,6 @@ dependencies = [
|
||||
"vim",
|
||||
"vim_mode_setting",
|
||||
"welcome",
|
||||
"which 6.0.3",
|
||||
"windows 0.58.0",
|
||||
"winresource",
|
||||
"workspace",
|
||||
@@ -16967,7 +16950,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed_html"
|
||||
version = "0.1.6"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"zed_extension_api 0.1.0",
|
||||
]
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -370,7 +370,7 @@ zeta = { path = "crates/zeta" }
|
||||
#
|
||||
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e" }
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", rev = "03c2907b44b4189aac5fdeaea331f5aab5c7072e"}
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
@@ -405,10 +405,10 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||
clap = { version = "4.4", features = ["derive"] }
|
||||
cocoa = "0.26"
|
||||
cocoa-foundation = "0.2.0"
|
||||
convert_case = "0.8.0"
|
||||
convert_case = "0.7.0"
|
||||
core-foundation = "0.9.3"
|
||||
core-foundation-sys = "0.8.6"
|
||||
ctor = "0.4.0"
|
||||
ctor = "0.3.0"
|
||||
dashmap = "6.0"
|
||||
derive_more = "0.99.17"
|
||||
dirs = "4.0"
|
||||
@@ -455,6 +455,7 @@ nanoid = "0.4"
|
||||
nbformat = { version = "0.10.0" }
|
||||
nix = "0.29"
|
||||
num-format = "0.4.4"
|
||||
once_cell = "1.20"
|
||||
ordered-float = "2.1.1"
|
||||
palette = { version = "0.7.5", default-features = false, features = ["std"] }
|
||||
parking_lot = "0.12.1"
|
||||
@@ -543,7 +544,7 @@ tree-sitter-cpp = "0.23"
|
||||
tree-sitter-css = "0.23"
|
||||
tree-sitter-elixir = "0.3"
|
||||
tree-sitter-embedded-template = "0.23.0"
|
||||
tree-sitter-gitcommit = { git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9" }
|
||||
tree-sitter-gitcommit = {git = "https://github.com/zed-industries/tree-sitter-git-commit", rev = "88309716a69dd13ab83443721ba6e0b491d37ee9"}
|
||||
tree-sitter-go = "0.23"
|
||||
tree-sitter-go-mod = { git = "https://github.com/camdencheek/tree-sitter-go-mod", rev = "6efb59652d30e0e9cd5f3b3a669afd6f1a926d3c", package = "tree-sitter-gomod" }
|
||||
tree-sitter-gowork = { git = "https://github.com/zed-industries/tree-sitter-go-work", rev = "acb0617bf7f4fda02c6217676cc64acb89536dc7" }
|
||||
@@ -618,7 +619,6 @@ features = [
|
||||
"Win32_Storage_FileSystem",
|
||||
"Win32_System_Com",
|
||||
"Win32_System_Com_StructuredStorage",
|
||||
"Win32_System_Console",
|
||||
"Win32_System_DataExchange",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
@@ -639,7 +639,7 @@ features = [
|
||||
# TODO livekit https://github.com/RustAudio/cpal/pull/891
|
||||
[patch.crates-io]
|
||||
cpal = { git = "https://github.com/zed-industries/cpal", rev = "fd8bc2fd39f1f5fdee5a0690656caff9a26d9d50" }
|
||||
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls" }
|
||||
real-async-tls = { git = "https://github.com/zed-industries/async-tls", rev = "1e759a4b5e370f87dc15e40756ac4f8815b61d9d", package = "async-tls"}
|
||||
|
||||
[profile.dev]
|
||||
split-debuginfo = "unpacked"
|
||||
@@ -709,9 +709,6 @@ debug = "full"
|
||||
lto = false
|
||||
codegen-units = 16
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "allow" }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
dbg_macro = "deny"
|
||||
todo = "deny"
|
||||
|
||||
@@ -606,7 +606,7 @@
|
||||
"ctrl-n": "assistant2::NewThread",
|
||||
"new": "assistant2::NewThread",
|
||||
"ctrl-shift-h": "assistant2::OpenHistory",
|
||||
"ctrl-alt-/": "assistant::ToggleModelSelector",
|
||||
"ctrl-alt-/": "assistant2::ToggleModelSelector",
|
||||
"ctrl-shift-a": "assistant2::ToggleContextPicker",
|
||||
"ctrl-e": "assistant2::ChatMode",
|
||||
"ctrl-alt-e": "assistant2::RemoveAllContext"
|
||||
|
||||
@@ -238,7 +238,7 @@
|
||||
"cmd-n": "assistant2::NewThread",
|
||||
"cmd-alt-p": "assistant2::NewPromptEditor",
|
||||
"cmd-shift-h": "assistant2::OpenHistory",
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-alt-/": "assistant2::ToggleModelSelector",
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-e": "assistant2::ChatMode",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext"
|
||||
@@ -658,7 +658,7 @@
|
||||
"use_key_equivalents": true,
|
||||
"bindings": {
|
||||
"cmd-shift-a": "assistant2::ToggleContextPicker",
|
||||
"cmd-alt-/": "assistant::ToggleModelSelector",
|
||||
"cmd-alt-/": "assistant2::ToggleModelSelector",
|
||||
"cmd-alt-e": "assistant2::RemoveAllContext",
|
||||
"ctrl-[": "assistant::CyclePreviousInlineAssist",
|
||||
"ctrl-]": "assistant::CycleNextInlineAssist"
|
||||
|
||||
@@ -176,8 +176,8 @@
|
||||
"show_completion_documentation": true,
|
||||
// Show method signatures in the editor, when inside parentheses.
|
||||
"auto_signature_help": false,
|
||||
// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
/// Whether to show the signature help after completion or a bracket pair inserted.
|
||||
/// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
|
||||
"show_signature_help_after_edits": false,
|
||||
// Whether to show wrap guides (vertical rulers) in the editor.
|
||||
// Setting this to true will show a guide at the 'preferred_line_length' value
|
||||
@@ -298,11 +298,11 @@
|
||||
// - "information": show only errors, warnings, and information
|
||||
// - "all" or true: show all diagnostics
|
||||
"diagnostics": "all",
|
||||
// Forcefully enable or disable the scrollbar for each axis
|
||||
/// Forcefully enable or disable the scrollbar for each axis
|
||||
"axes": {
|
||||
// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
|
||||
"horizontal": true,
|
||||
// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
|
||||
"vertical": true
|
||||
}
|
||||
},
|
||||
@@ -328,24 +328,24 @@
|
||||
"folds": true
|
||||
},
|
||||
"indent_guides": {
|
||||
// Whether to show indent guides in the editor.
|
||||
/// Whether to show indent guides in the editor.
|
||||
"enabled": true,
|
||||
// The width of the indent guides in pixels, between 1 and 10.
|
||||
/// The width of the indent guides in pixels, between 1 and 10.
|
||||
"line_width": 1,
|
||||
// The width of the active indent guide in pixels, between 1 and 10.
|
||||
/// The width of the active indent guide in pixels, between 1 and 10.
|
||||
"active_line_width": 1,
|
||||
// Determines how indent guides are colored.
|
||||
// This setting can take the following three values:
|
||||
/// Determines how indent guides are colored.
|
||||
/// This setting can take the following three values:
|
||||
///
|
||||
// 1. "disabled"
|
||||
// 2. "fixed"
|
||||
// 3. "indent_aware"
|
||||
/// 1. "disabled"
|
||||
/// 2. "fixed"
|
||||
/// 3. "indent_aware"
|
||||
"coloring": "fixed",
|
||||
// Determines how indent guide backgrounds are colored.
|
||||
// This setting can take the following two values:
|
||||
/// Determines how indent guide backgrounds are colored.
|
||||
/// This setting can take the following two values:
|
||||
///
|
||||
// 1. "disabled"
|
||||
// 2. "indent_aware"
|
||||
/// 1. "disabled"
|
||||
/// 2. "indent_aware"
|
||||
"background_coloring": "disabled"
|
||||
},
|
||||
// Whether the editor will scroll beyond the last line.
|
||||
@@ -379,9 +379,6 @@
|
||||
// 3. Never populate the search query
|
||||
// "never"
|
||||
"seed_search_query_from_cursor": "always",
|
||||
// When enabled, automatically adjusts search case sensitivity based on your query.
|
||||
// If your search query contains any uppercase letters, the search becomes case-sensitive;
|
||||
// if it contains only lowercase letters, the search becomes case-insensitive.
|
||||
"use_smartcase_search": false,
|
||||
// Inlay hint related settings
|
||||
"inlay_hints": {
|
||||
@@ -401,16 +398,7 @@
|
||||
"edit_debounce_ms": 700,
|
||||
// Time to wait after scrolling the buffer, before requesting the hints,
|
||||
// set to 0 to disable debouncing.
|
||||
"scroll_debounce_ms": 50,
|
||||
/// A set of modifiers which, when pressed, will toggle the visibility of inlay hints.
|
||||
/// If the set if empty or not all the modifiers specified are pressed, inlay hints will not be toggled.
|
||||
"toggle_on_modifiers_press": {
|
||||
"control": false,
|
||||
"shift": false,
|
||||
"alt": false,
|
||||
"platform": false,
|
||||
"function": false
|
||||
}
|
||||
"scroll_debounce_ms": 50
|
||||
},
|
||||
"project_panel": {
|
||||
// Whether to show the project panel button in the status bar
|
||||
@@ -436,32 +424,32 @@
|
||||
// Whether to fold directories automatically and show compact folders
|
||||
// (e.g. "a/b/c" ) when a directory has only one subdirectory inside.
|
||||
"auto_fold_dirs": true,
|
||||
// Scrollbar-related settings
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the project panel.
|
||||
// This setting can take five values:
|
||||
/// When to show the scrollbar in the project panel.
|
||||
/// This setting can take five values:
|
||||
///
|
||||
// 1. null (default): Inherit editor settings
|
||||
// 2. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 3. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 4. Always show the scrollbar:
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior (default):
|
||||
/// "auto"
|
||||
/// 3. Match the system's configured behavior:
|
||||
/// "system"
|
||||
/// 4. Always show the scrollbar:
|
||||
/// "always"
|
||||
/// 5. Never show the scrollbar:
|
||||
/// "never"
|
||||
"show": null
|
||||
},
|
||||
// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||
// This setting can take the following three values:
|
||||
/// Which files containing diagnostic errors/warnings to mark in the project panel.
|
||||
/// This setting can take the following three values:
|
||||
///
|
||||
// 1. Do not mark any files:
|
||||
// "off"
|
||||
// 2. Only mark files with errors:
|
||||
// "errors"
|
||||
// 3. Mark files with errors and warnings:
|
||||
// "all"
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
/// 2. Only mark files with errors:
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
"show_diagnostics": "all",
|
||||
// Settings related to indent guides in the project panel.
|
||||
"indent_guides": {
|
||||
@@ -494,8 +482,8 @@
|
||||
// when a corresponding outline entry becomes active.
|
||||
// Gitignored entries are never auto revealed.
|
||||
"auto_reveal_entries": true,
|
||||
// Whether to fold directories automatically
|
||||
// when a directory has only one directory inside.
|
||||
/// Whether to fold directories automatically
|
||||
/// when a directory has only one directory inside.
|
||||
"auto_fold_dirs": true,
|
||||
// Settings related to indent guides in the outline panel.
|
||||
"indent_guides": {
|
||||
@@ -508,21 +496,21 @@
|
||||
// "never"
|
||||
"show": "always"
|
||||
},
|
||||
// Scrollbar-related settings
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the project panel.
|
||||
// This setting can take five values:
|
||||
/// When to show the scrollbar in the project panel.
|
||||
/// This setting can take five values:
|
||||
///
|
||||
// 1. null (default): Inherit editor settings
|
||||
// 2. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 3. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 4. Always show the scrollbar:
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior (default):
|
||||
/// "auto"
|
||||
/// 3. Match the system's configured behavior:
|
||||
/// "system"
|
||||
/// 4. Always show the scrollbar:
|
||||
/// "always"
|
||||
/// 5. Never show the scrollbar:
|
||||
/// "never"
|
||||
"show": null
|
||||
}
|
||||
},
|
||||
@@ -640,7 +628,7 @@
|
||||
"show": true,
|
||||
// Whether or not to show the navigation history buttons.
|
||||
"show_nav_history_buttons": true,
|
||||
// Whether or not to show the tab bar buttons.
|
||||
/// Whether or not to show the tab bar buttons.
|
||||
"show_tab_bar_buttons": true
|
||||
},
|
||||
// Settings related to the editor's tabs
|
||||
@@ -662,16 +650,16 @@
|
||||
// 3. Activate the left neighbour tab if present
|
||||
// "left_neighbour"
|
||||
"activate_on_close": "history",
|
||||
// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||
// Diagnostics are only shown when file icons are also active.
|
||||
// This setting only works when can take the following three values:
|
||||
/// Which files containing diagnostic errors/warnings to mark in the tabs.
|
||||
/// Diagnostics are only shown when file icons are also active.
|
||||
/// This setting only works when can take the following three values:
|
||||
///
|
||||
// 1. Do not mark any files:
|
||||
// "off"
|
||||
// 2. Only mark files with errors:
|
||||
// "errors"
|
||||
// 3. Mark files with errors and warnings:
|
||||
// "all"
|
||||
/// 1. Do not mark any files:
|
||||
/// "off"
|
||||
/// 2. Only mark files with errors:
|
||||
/// "errors"
|
||||
/// 3. Mark files with errors and warnings:
|
||||
/// "all"
|
||||
"show_diagnostics": "off"
|
||||
},
|
||||
// Settings related to preview tabs.
|
||||
@@ -841,8 +829,6 @@
|
||||
// A list of globs representing files that edit predictions should be disabled for.
|
||||
// There's a sensible default list of globs already included.
|
||||
// Any addition to this list will be merged with the default list.
|
||||
// Globs are matched relative to the worktree root,
|
||||
// except when starting with a slash (/) or equivalent in Windows.
|
||||
"disabled_globs": [
|
||||
"**/.env*",
|
||||
"**/*.pem",
|
||||
@@ -854,14 +840,11 @@
|
||||
],
|
||||
// When to show edit predictions previews in buffer.
|
||||
// This setting takes two possible values:
|
||||
// 1. Display predictions inline when there are no language server completions available.
|
||||
// "mode": "eager"
|
||||
// 2. Display predictions inline only when holding a modifier key (alt by default).
|
||||
// "mode": "subtle"
|
||||
"mode": "eager",
|
||||
// Whether edit predictions are enabled in the assistant panel.
|
||||
// This setting has no effect if globally disabled.
|
||||
"enabled_in_assistant": true
|
||||
// 1. Display inline when there are no language server completions available.
|
||||
// "mode": "eager_preview"
|
||||
// 2. Display inline when holding modifier key (alt by default).
|
||||
// "mode": "auto"
|
||||
"mode": "eager_preview"
|
||||
},
|
||||
// Settings specific to journaling
|
||||
"journal": {
|
||||
@@ -997,21 +980,21 @@
|
||||
// Example: `echo -e "\e]2;New Title\007";`
|
||||
"breadcrumbs": true
|
||||
},
|
||||
// Scrollbar-related settings
|
||||
/// Scrollbar-related settings
|
||||
"scrollbar": {
|
||||
// When to show the scrollbar in the terminal.
|
||||
// This setting can take five values:
|
||||
/// When to show the scrollbar in the terminal.
|
||||
/// This setting can take five values:
|
||||
///
|
||||
// 1. null (default): Inherit editor settings
|
||||
// 2. Show the scrollbar if there's important information or
|
||||
// follow the system's configured behavior (default):
|
||||
// "auto"
|
||||
// 3. Match the system's configured behavior:
|
||||
// "system"
|
||||
// 4. Always show the scrollbar:
|
||||
// "always"
|
||||
// 5. Never show the scrollbar:
|
||||
// "never"
|
||||
/// 1. null (default): Inherit editor settings
|
||||
/// 2. Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior (default):
|
||||
/// "auto"
|
||||
/// 3. Match the system's configured behavior:
|
||||
/// "system"
|
||||
/// 4. Always show the scrollbar:
|
||||
/// "always"
|
||||
/// 5. Never show the scrollbar:
|
||||
/// "never"
|
||||
"show": null
|
||||
}
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
@@ -1030,7 +1013,7 @@
|
||||
// "max_scroll_history_lines": 10000,
|
||||
},
|
||||
"code_actions_on_format": {},
|
||||
// Settings related to running tasks.
|
||||
/// Settings related to running tasks.
|
||||
"tasks": {
|
||||
"variables": {}
|
||||
},
|
||||
@@ -1051,20 +1034,20 @@
|
||||
"JSONC": ["**/.zed/**/*.json", "**/zed/**/*.json", "**/Zed/**/*.json", "**/.vscode/**/*.json"],
|
||||
"Shell Script": [".env.*"]
|
||||
},
|
||||
// By default use a recent system version of node, or install our own.
|
||||
// You can override this to use a version of node that is not in $PATH with:
|
||||
// {
|
||||
// "node": {
|
||||
// "path": "/path/to/node"
|
||||
// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
|
||||
// }
|
||||
// }
|
||||
// or to ensure Zed always downloads and installs an isolated version of node:
|
||||
// {
|
||||
// "node": {
|
||||
// "ignore_system_version": true,
|
||||
// }
|
||||
// NOTE: changing this setting currently requires restarting Zed.
|
||||
/// By default use a recent system version of node, or install our own.
|
||||
/// You can override this to use a version of node that is not in $PATH with:
|
||||
/// {
|
||||
/// "node": {
|
||||
/// "path": "/path/to/node"
|
||||
/// "npm_path": "/path/to/npm" (defaults to node_path/../npm)
|
||||
/// }
|
||||
/// }
|
||||
/// or to ensure Zed always downloads and installs an isolated version of node:
|
||||
/// {
|
||||
/// "node": {
|
||||
/// "ignore_system_version": true,
|
||||
/// }
|
||||
/// NOTE: changing this setting currently requires restarting Zed.
|
||||
"node": {},
|
||||
// The extensions that Zed should automatically install on startup.
|
||||
//
|
||||
@@ -1313,7 +1296,6 @@
|
||||
},
|
||||
// Vim settings
|
||||
"vim": {
|
||||
"default_mode": "normal",
|
||||
"toggle_relative_line_numbers": false,
|
||||
"use_system_clipboard": "always",
|
||||
"use_multiline_find": false,
|
||||
|
||||
@@ -35,7 +35,7 @@ use language_model::{
|
||||
report_assistant_event, LanguageModel, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelTextStream, Role,
|
||||
};
|
||||
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use parking_lot::Mutex;
|
||||
use project::{CodeAction, ProjectTransaction};
|
||||
@@ -1589,10 +1589,29 @@ impl Render for PromptEditor {
|
||||
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
InlineLanguageModelSelector::new(self.language_model_selector.clone())
|
||||
.render(window, cx),
|
||||
)
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
))
|
||||
.map(|el| {
|
||||
let CodegenStatus::Error(error) = self.codegen.read(cx).status(cx) else {
|
||||
return el;
|
||||
|
||||
@@ -19,7 +19,7 @@ use language_model::{
|
||||
report_assistant_event, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, Role,
|
||||
};
|
||||
use language_model_selector::{InlineLanguageModelSelector, LanguageModelSelector};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use prompt_library::PromptBuilder;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::{
|
||||
@@ -506,7 +506,7 @@ struct PromptEditor {
|
||||
impl EventEmitter<PromptEditorEvent> for PromptEditor {}
|
||||
|
||||
impl Render for PromptEditor {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let status = &self.codegen.read(cx).status;
|
||||
let buttons = match status {
|
||||
CodegenStatus::Idle => {
|
||||
@@ -641,10 +641,29 @@ impl Render for PromptEditor {
|
||||
.w_12()
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(
|
||||
InlineLanguageModelSelector::new(self.language_model_selector.clone())
|
||||
.render(window, cx),
|
||||
)
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::TopRight,
|
||||
))
|
||||
.children(
|
||||
if let CodegenStatus::Error(error) = &self.codegen.read(cx).status {
|
||||
let error_message = SharedString::from(error.to_string());
|
||||
@@ -1054,10 +1073,7 @@ pub enum CodegenEvent {
|
||||
|
||||
impl EventEmitter<CodegenEvent> for Codegen {}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const CLEAR_INPUT: &str = "\x15";
|
||||
#[cfg(target_os = "windows")]
|
||||
const CLEAR_INPUT: &str = "\x03";
|
||||
const CARRIAGE_RETURN: &str = "\x0d";
|
||||
|
||||
struct TerminalTransaction {
|
||||
|
||||
@@ -73,7 +73,6 @@ time_format.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
||||
|
||||
@@ -8,16 +8,15 @@ use gpui::{
|
||||
UnderlineStyle, WeakEntity,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
|
||||
use language_model::Role;
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use settings::Settings as _;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Disclosure};
|
||||
use ui::prelude::*;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::thread::{MessageId, RequestKind, Thread, ThreadError, ThreadEvent};
|
||||
use crate::thread::{MessageId, Thread, ThreadError, ThreadEvent};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::tool_use::{ToolUse, ToolUseStatus};
|
||||
use crate::ui::ContextPill;
|
||||
|
||||
pub struct ActiveThread {
|
||||
@@ -29,7 +28,6 @@ pub struct ActiveThread {
|
||||
messages: Vec<MessageId>,
|
||||
list_state: ListState,
|
||||
rendered_messages_by_id: HashMap<MessageId, Entity<Markdown>>,
|
||||
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
|
||||
last_error: Option<ThreadError>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -57,7 +55,6 @@ impl ActiveThread {
|
||||
thread: thread.clone(),
|
||||
messages: Vec::new(),
|
||||
rendered_messages_by_id: HashMap::default(),
|
||||
expanded_tool_uses: HashMap::default(),
|
||||
list_state: ListState::new(0, ListAlignment::Bottom, px(1024.), {
|
||||
let this = cx.entity().downgrade();
|
||||
move |ix, _: &mut Window, cx: &mut App| {
|
||||
@@ -254,29 +251,17 @@ impl ActiveThread {
|
||||
let task = tool.run(tool_use.input, self.workspace.clone(), window, cx);
|
||||
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
thread.insert_tool_output(tool_use.id.clone(), task, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ThreadEvent::ToolFinished { .. } => {
|
||||
let all_tools_finished = self
|
||||
.thread
|
||||
.read(cx)
|
||||
.pending_tool_uses()
|
||||
.into_iter()
|
||||
.all(|tool_use| tool_use.status.is_error());
|
||||
if all_tools_finished {
|
||||
let model_registry = LanguageModelRegistry::read_global(cx);
|
||||
if let Some(model) = model_registry.active_model() {
|
||||
self.thread.update(cx, |thread, cx| {
|
||||
// Insert an empty user message to contain the tool results.
|
||||
thread.insert_user_message("", Vec::new(), cx);
|
||||
thread.send_to_model(model, RequestKind::Chat, true, cx);
|
||||
thread.insert_tool_output(
|
||||
tool_use.assistant_message_id,
|
||||
tool_use.id.clone(),
|
||||
task,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ThreadEvent::ToolFinished { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,17 +276,8 @@ impl ActiveThread {
|
||||
};
|
||||
|
||||
let context = self.thread.read(cx).context_for_message(message_id);
|
||||
let tool_uses = self.thread.read(cx).tool_uses_for_message(message_id);
|
||||
let colors = cx.theme().colors();
|
||||
|
||||
// Don't render user messages that are just there for returning tool results.
|
||||
if message.role == Role::User
|
||||
&& message.text.is_empty()
|
||||
&& self.thread.read(cx).message_has_tool_results(message_id)
|
||||
{
|
||||
return Empty.into_any();
|
||||
}
|
||||
|
||||
let message_content = v_flex()
|
||||
.child(div().p_2p5().text_ui(cx).child(markdown.clone()))
|
||||
.when_some(context, |parent, context| {
|
||||
@@ -356,22 +332,7 @@ impl ActiveThread {
|
||||
)
|
||||
.child(message_content),
|
||||
),
|
||||
Role::Assistant => div()
|
||||
.id(("message-container", ix))
|
||||
.child(message_content)
|
||||
.map(|parent| {
|
||||
if tool_uses.is_empty() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent.child(
|
||||
v_flex().children(
|
||||
tool_uses
|
||||
.into_iter()
|
||||
.map(|tool_use| self.render_tool_use(tool_use, cx)),
|
||||
),
|
||||
)
|
||||
}),
|
||||
Role::Assistant => div().id(("message-container", ix)).child(message_content),
|
||||
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
|
||||
v_flex()
|
||||
.bg(colors.editor_background)
|
||||
@@ -382,102 +343,6 @@ impl ActiveThread {
|
||||
|
||||
styled_message.into_any()
|
||||
}
|
||||
|
||||
fn render_tool_use(&self, tool_use: ToolUse, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let is_open = self
|
||||
.expanded_tool_uses
|
||||
.get(&tool_use.id)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
div().px_2p5().child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.rounded_lg()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.py_0p5()
|
||||
.pl_1()
|
||||
.pr_2()
|
||||
.bg(cx.theme().colors().editor_foreground.opacity(0.02))
|
||||
.when(is_open, |element| element.border_b_1().rounded_t(px(6.)))
|
||||
.when(!is_open, |element| element.rounded(px(6.)))
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(Disclosure::new("tool-use-disclosure", is_open).on_click(
|
||||
cx.listener({
|
||||
let tool_use_id = tool_use.id.clone();
|
||||
move |this, _event, _window, _cx| {
|
||||
let is_open = this
|
||||
.expanded_tool_uses
|
||||
.entry(tool_use_id.clone())
|
||||
.or_insert(false);
|
||||
|
||||
*is_open = !*is_open;
|
||||
}
|
||||
}),
|
||||
))
|
||||
.child(Label::new(tool_use.name)),
|
||||
)
|
||||
.child(
|
||||
Label::new(match tool_use.status {
|
||||
ToolUseStatus::Pending => "Pending",
|
||||
ToolUseStatus::Running => "Running",
|
||||
ToolUseStatus::Finished(_) => "Finished",
|
||||
ToolUseStatus::Error(_) => "Error",
|
||||
})
|
||||
.size(LabelSize::XSmall)
|
||||
.buffer_font(cx),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
if !is_open {
|
||||
return parent;
|
||||
}
|
||||
|
||||
parent.child(
|
||||
v_flex()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Label::new("Input:"))
|
||||
.child(Label::new(
|
||||
serde_json::to_string_pretty(&tool_use.input)
|
||||
.unwrap_or_default(),
|
||||
)),
|
||||
)
|
||||
.map(|parent| match tool_use.status {
|
||||
ToolUseStatus::Finished(output) => parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Label::new("Result:"))
|
||||
.child(Label::new(output)),
|
||||
),
|
||||
ToolUseStatus::Error(err) => parent.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.py_1()
|
||||
.px_2p5()
|
||||
.child(Label::new("Error:"))
|
||||
.child(Label::new(err)),
|
||||
),
|
||||
ToolUseStatus::Pending | ToolUseStatus::Running => parent,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ActiveThread {
|
||||
|
||||
@@ -16,7 +16,6 @@ mod terminal_inline_assistant;
|
||||
mod thread;
|
||||
mod thread_history;
|
||||
mod thread_store;
|
||||
mod tool_use;
|
||||
mod ui;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -39,6 +38,7 @@ actions!(
|
||||
NewThread,
|
||||
NewPromptEditor,
|
||||
ToggleContextPicker,
|
||||
ToggleModelSelector,
|
||||
RemoveAllContext,
|
||||
OpenHistory,
|
||||
OpenConfiguration,
|
||||
|
||||
@@ -158,16 +158,8 @@ impl Render for AssistantConfiguration {
|
||||
.child(
|
||||
v_flex()
|
||||
.p(DynamicSpacing::Base16.rems(cx))
|
||||
.gap_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_0p5()
|
||||
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
|
||||
.child(
|
||||
Label::new("Create reusable prompts and tag which ones you want sent in every LLM interaction.")
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.gap_1()
|
||||
.child(Headline::new("Prompt Library").size(HeadlineSize::Small))
|
||||
.child(
|
||||
Button::new("open-prompt-library", "Open Prompt Library")
|
||||
.style(ButtonStyle::Filled)
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
use assistant_settings::AssistantSettings;
|
||||
use fs::Fs;
|
||||
use gpui::{Entity, FocusHandle};
|
||||
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
|
||||
use gpui::{Entity, FocusHandle, SharedString};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::prelude::*;
|
||||
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
|
||||
|
||||
use crate::ToggleModelSelector;
|
||||
|
||||
pub struct AssistantModelSelector {
|
||||
pub selector: Entity<LanguageModelSelector>,
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
}
|
||||
|
||||
impl AssistantModelSelector {
|
||||
pub(crate) fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
focus_handle: FocusHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
@@ -33,14 +38,50 @@ impl AssistantModelSelector {
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
menu_handle,
|
||||
focus_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantModelSelector {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
AssistantLanguageModelSelector::new(self.focus_handle.clone(), self.selector.clone())
|
||||
.render(window, cx)
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
_ => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomRight,
|
||||
)
|
||||
.with_handle(self.menu_handle.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use client::zed_urls;
|
||||
use editor::Editor;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
prelude::*, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
|
||||
prelude::*, px, svg, Action, AnyElement, App, AsyncWindowContext, Corner, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, FontWeight, Pixels, Subscription, Task, UpdateGlobal, WeakEntity,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
@@ -458,12 +458,6 @@ impl AssistantPanel {
|
||||
pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
|
||||
self.context_editor.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn delete_context(&mut self, path: PathBuf, cx: &mut Context<Self>) {
|
||||
self.context_store
|
||||
.update(cx, |this, cx| this.delete_local_context(path, cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for AssistantPanel {
|
||||
@@ -596,6 +590,7 @@ impl AssistantPanel {
|
||||
|
||||
h_flex()
|
||||
.id("assistant-toolbar")
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.h(Tab::container_height(cx))
|
||||
.flex_none()
|
||||
.justify_between()
|
||||
@@ -603,86 +598,72 @@ impl AssistantPanel {
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
div()
|
||||
.id("title")
|
||||
.overflow_x_scroll()
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.child(Label::new(title).text_ellipsis()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.pl_2()
|
||||
.gap_2()
|
||||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.child(Label::new(title))
|
||||
.children(if matches!(self.active_view, ActiveView::PromptEditor) {
|
||||
self.context_editor
|
||||
.as_ref()
|
||||
.and_then(|editor| render_remaining_tokens(editor, cx))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.pl_1p5()
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.h_full()
|
||||
.px(DynamicSpacing::Base08.rems(cx))
|
||||
.border_l_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.gap(DynamicSpacing::Base02.rems(cx))
|
||||
.child(
|
||||
PopoverMenu::new("assistant-toolbar-new-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("New…"),
|
||||
PopoverMenu::new("assistant-toolbar-new-popover-menu")
|
||||
.trigger_with_tooltip(
|
||||
IconButton::new("new", IconName::Plus)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle),
|
||||
Tooltip::text("New…"),
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.new_item_context_menu_handle.clone())
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
||||
menu.action("New Thread", NewThread.boxed_clone())
|
||||
.action("New Prompt Editor", NewPromptEditor.boxed_clone())
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"History",
|
||||
&OpenHistory,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.anchor(Corner::TopRight)
|
||||
.with_handle(self.new_item_context_menu_handle.clone())
|
||||
.menu(move |window, cx| {
|
||||
Some(ContextMenu::build(
|
||||
window,
|
||||
cx,
|
||||
|menu, _window, _cx| {
|
||||
menu.action("New Thread", NewThread.boxed_clone())
|
||||
.action(
|
||||
"New Prompt Editor",
|
||||
NewPromptEditor.boxed_clone(),
|
||||
)
|
||||
},
|
||||
))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("open-history", IconName::HistoryRerun)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip({
|
||||
let focus_handle = self.focus_handle(cx);
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"History",
|
||||
&OpenHistory,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("Assistant Settings"))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
}
|
||||
})
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("configure-assistant", IconName::Settings)
|
||||
.icon_size(IconSize::Small)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.tooltip(Tooltip::text("Assistant Settings"))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenConfiguration.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -724,11 +705,12 @@ impl AssistantPanel {
|
||||
) -> impl IntoElement {
|
||||
let recent_history = self
|
||||
.history_store
|
||||
.update(cx, |this, cx| this.recent_entries(6, cx));
|
||||
.update(cx, |this, cx| this.recent_entries(3, cx));
|
||||
|
||||
let create_welcome_heading = || {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_center()
|
||||
.child(Headline::new("Welcome to the Assistant Panel").size(HeadlineSize::Small))
|
||||
};
|
||||
|
||||
@@ -736,27 +718,36 @@ impl AssistantPanel {
|
||||
let no_error = configuration_error.is_none();
|
||||
|
||||
v_flex()
|
||||
.p_1p5()
|
||||
.size_full()
|
||||
.justify_end()
|
||||
.gap_1()
|
||||
.gap_2()
|
||||
.child(
|
||||
v_flex().w_full().child(
|
||||
svg()
|
||||
.path("icons/logo_96.svg")
|
||||
.text_color(cx.theme().colors().text)
|
||||
.w(px(40.))
|
||||
.h(px(40.))
|
||||
.mx_auto()
|
||||
.mb_4(),
|
||||
),
|
||||
)
|
||||
.map(|parent| {
|
||||
match configuration_error {
|
||||
Some(ConfigurationError::ProviderNotAuthenticated)
|
||||
| Some(ConfigurationError::NoProvider) => {
|
||||
parent.child(
|
||||
v_flex()
|
||||
.px_1p5()
|
||||
.gap_0p5()
|
||||
.child(create_welcome_heading())
|
||||
.child(
|
||||
Label::new(
|
||||
"To start using the assistant, configure at least one LLM provider.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
h_flex().mb_2().w_full().justify_center().child(
|
||||
Label::new(
|
||||
"To start using the assistant, configure at least one LLM provider.",
|
||||
)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().mt_1().w_full().child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Button::new("open-configuration", "Configure a Provider")
|
||||
.size(ButtonSize::Compact)
|
||||
.icon(Some(IconName::Sliders))
|
||||
@@ -770,7 +761,7 @@ impl AssistantPanel {
|
||||
)
|
||||
}
|
||||
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
|
||||
.child(v_flex().px_1p5().gap_0p5().child(create_welcome_heading()).children(
|
||||
.child(v_flex().gap_0p5().child(create_welcome_heading()).children(
|
||||
provider.render_accept_terms(
|
||||
LanguageModelProviderTosView::ThreadEmptyState,
|
||||
cx,
|
||||
@@ -781,40 +772,21 @@ impl AssistantPanel {
|
||||
})
|
||||
.when(recent_history.is_empty() && no_error, |parent| {
|
||||
parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child(
|
||||
Label::new("Start typing to chat with your codebase").color(Color::Muted),
|
||||
h_flex().w_full().justify_center().child(
|
||||
Label::new("Start typing to chat with your codebase").color(Color::Muted),
|
||||
),
|
||||
))
|
||||
})
|
||||
.when(!recent_history.is_empty(), |parent| {
|
||||
parent
|
||||
.child(
|
||||
h_flex()
|
||||
.pl_1p5()
|
||||
.pb_1()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(
|
||||
Label::new("Past Interactions")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Button::new("view-history", "View All")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
h_flex().w_full().justify_center().child(
|
||||
Label::new("Recent Threads:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
)
|
||||
.child(v_flex().gap_1().children(
|
||||
.child(v_flex().mx_auto().w_4_5().gap_2().children(
|
||||
recent_history.into_iter().map(|entry| {
|
||||
// TODO: Add keyboard navigation.
|
||||
match entry {
|
||||
@@ -829,6 +801,22 @@ impl AssistantPanel {
|
||||
}
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Button::new("view-all-past-threads", "View All Past Threads")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.on_click(move |_event, window, cx| {
|
||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
||||
use crate::{RemoveAllContext, ToggleContextPicker};
|
||||
use crate::{RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
use client::ErrorExt;
|
||||
use collections::VecDeque;
|
||||
use editor::{
|
||||
@@ -20,6 +20,7 @@ use gpui::{
|
||||
EventEmitter, FocusHandle, Focusable, FontWeight, Subscription, TextStyle, WeakEntity, Window,
|
||||
};
|
||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use parking_lot::Mutex;
|
||||
use settings::Settings;
|
||||
use std::cmp;
|
||||
@@ -39,6 +40,7 @@ pub struct PromptEditor<T> {
|
||||
context_strip: Entity<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: Entity<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
edited_since_done: bool,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_history_ix: Option<usize>,
|
||||
@@ -102,12 +104,7 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
.items_start()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(|this, action, window, cx| {
|
||||
let selector = this.model_selector.read(cx).selector.clone();
|
||||
selector.update(cx, |selector, cx| {
|
||||
selector.toggle_model_selector(action, window, cx);
|
||||
})
|
||||
}))
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
@@ -350,6 +347,15 @@ impl<T: 'static> PromptEditor<T> {
|
||||
self.context_picker_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.model_selector_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
pub fn remove_all_context(
|
||||
&mut self,
|
||||
_: &RemoveAllContext,
|
||||
@@ -858,6 +864,7 @@ impl PromptEditor<BufferCodegen> {
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new(|cx| {
|
||||
ContextStrip::new(
|
||||
@@ -881,8 +888,15 @@ impl PromptEditor<BufferCodegen> {
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
prompt_history,
|
||||
prompt_history_ix: None,
|
||||
@@ -1006,6 +1020,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let context_strip = cx.new(|cx| {
|
||||
ContextStrip::new(
|
||||
@@ -1029,8 +1044,15 @@ impl PromptEditor<TerminalCodegen> {
|
||||
context_strip,
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(fs, prompt_editor.focus_handle(cx), window, cx)
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
prompt_editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
prompt_history,
|
||||
prompt_history_ix: None,
|
||||
|
||||
@@ -7,17 +7,16 @@ use gpui::{
|
||||
pulsating_between, Animation, AnimationExt, App, DismissEvent, Entity, Focusable, Subscription,
|
||||
TextStyle, WeakEntity,
|
||||
};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use rope::Point;
|
||||
use settings::Settings;
|
||||
use std::time::Duration;
|
||||
use text::Bias;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, KeyBinding, PlatformStyle, PopoverMenu, PopoverMenuHandle, Switch,
|
||||
TintColor, Tooltip,
|
||||
prelude::*, ButtonLike, KeyBinding, PopoverMenu, PopoverMenuHandle, Switch, TintColor, Tooltip,
|
||||
};
|
||||
use vim_mode_setting::VimModeSetting;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::assistant_model_selector::AssistantModelSelector;
|
||||
@@ -26,7 +25,7 @@ use crate::context_store::{refresh_context_store_text, ContextStore};
|
||||
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
|
||||
use crate::thread::{RequestKind, Thread};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker};
|
||||
use crate::{Chat, ChatMode, RemoveAllContext, ToggleContextPicker, ToggleModelSelector};
|
||||
|
||||
pub struct MessageEditor {
|
||||
thread: Entity<Thread>,
|
||||
@@ -37,6 +36,7 @@ pub struct MessageEditor {
|
||||
inline_context_picker: Entity<ContextPicker>,
|
||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: Entity<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
use_tools: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -53,6 +53,7 @@ impl MessageEditor {
|
||||
let context_store = cx.new(|_cx| ContextStore::new(workspace.clone()));
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::auto_height(10, window, cx);
|
||||
@@ -105,13 +106,30 @@ impl MessageEditor {
|
||||
context_picker_menu_handle,
|
||||
inline_context_picker,
|
||||
inline_context_picker_menu_handle,
|
||||
model_selector: cx
|
||||
.new(|cx| AssistantModelSelector::new(fs, editor.focus_handle(cx), window, cx)),
|
||||
model_selector: cx.new(|cx| {
|
||||
AssistantModelSelector::new(
|
||||
fs,
|
||||
model_selector_menu_handle.clone(),
|
||||
editor.focus_handle(cx),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
use_tools: false,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.model_selector_menu_handle.toggle(window, cx)
|
||||
}
|
||||
|
||||
fn toggle_chat_mode(&mut self, _: &ChatMode, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.use_tools = !self.use_tools;
|
||||
cx.notify();
|
||||
@@ -187,7 +205,22 @@ impl MessageEditor {
|
||||
.update(&mut cx, |thread, cx| {
|
||||
let context = context_store.read(cx).snapshot(cx).collect::<Vec<_>>();
|
||||
thread.insert_user_message(user_message, context, cx);
|
||||
thread.send_to_model(model, request_kind, use_tools, cx);
|
||||
let mut request = thread.to_completion_request(request_kind, cx);
|
||||
|
||||
if use_tools {
|
||||
request.tools = thread
|
||||
.tools()
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
thread.stream_completion(request, model, cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
@@ -276,6 +309,7 @@ impl Render for MessageEditor {
|
||||
let inline_context_picker = self.inline_context_picker.clone();
|
||||
let bg_color = cx.theme().colors().editor_background;
|
||||
let is_streaming_completion = self.thread.read(cx).is_streaming();
|
||||
let button_width = px(64.);
|
||||
let is_model_selected = self.is_model_selected(cx);
|
||||
let is_editor_empty = self.is_editor_empty(cx);
|
||||
let submit_label_color = if is_editor_empty {
|
||||
@@ -284,25 +318,10 @@ impl Render for MessageEditor {
|
||||
Color::Default
|
||||
};
|
||||
|
||||
let vim_mode_enabled = VimModeSetting::get_global(cx).0;
|
||||
let platform = PlatformStyle::platform();
|
||||
let linux = platform == PlatformStyle::Linux;
|
||||
let windows = platform == PlatformStyle::Windows;
|
||||
let button_width = if linux || windows || vim_mode_enabled {
|
||||
px(92.)
|
||||
} else {
|
||||
px(64.)
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.key_context("MessageEditor")
|
||||
.on_action(cx.listener(Self::chat))
|
||||
.on_action(cx.listener(|this, action, window, cx| {
|
||||
let selector = this.model_selector.read(cx).selector.clone();
|
||||
selector.update(cx, |this, cx| {
|
||||
this.toggle_model_selector(action, window, cx);
|
||||
})
|
||||
}))
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::remove_all_context))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
@@ -314,7 +333,7 @@ impl Render for MessageEditor {
|
||||
.child(self.context_strip.clone())
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_5()
|
||||
.gap_4()
|
||||
.child({
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
|
||||
@@ -155,10 +155,7 @@ pub enum CodegenEvent {
|
||||
Finished,
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub const CLEAR_INPUT: &str = "\x15";
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const CLEAR_INPUT: &str = "\x03";
|
||||
const CARRIAGE_RETURN: &str = "\x0d";
|
||||
|
||||
struct TerminalTransaction {
|
||||
|
||||
@@ -4,12 +4,14 @@ use anyhow::Result;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
use chrono::{DateTime, Utc};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use futures::StreamExt as _;
|
||||
use futures::future::Shared;
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use gpui::{App, Context, EventEmitter, SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
|
||||
LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolUseId,
|
||||
MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason,
|
||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError,
|
||||
Role, StopReason,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use util::{post_inc, TryFutureExt as _};
|
||||
@@ -17,13 +19,10 @@ use uuid::Uuid;
|
||||
|
||||
use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
|
||||
use crate::thread_store::SavedThread;
|
||||
use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum RequestKind {
|
||||
Chat,
|
||||
/// Used when summarizing a thread.
|
||||
Summarize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
|
||||
@@ -42,7 +41,7 @@ impl std::fmt::Display for ThreadId {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct MessageId(pub(crate) usize);
|
||||
pub struct MessageId(usize);
|
||||
|
||||
impl MessageId {
|
||||
fn post_inc(&mut self) -> Self {
|
||||
@@ -71,7 +70,9 @@ pub struct Thread {
|
||||
completion_count: usize,
|
||||
pending_completions: Vec<PendingCompletion>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
tool_use: ToolUseState,
|
||||
tool_uses_by_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
||||
tool_results_by_message: HashMap<MessageId, Vec<LanguageModelToolResult>>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
}
|
||||
|
||||
impl Thread {
|
||||
@@ -88,7 +89,9 @@ impl Thread {
|
||||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
tools,
|
||||
tool_use: ToolUseState::default(),
|
||||
tool_uses_by_message: HashMap::default(),
|
||||
tool_results_by_message: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +123,9 @@ impl Thread {
|
||||
completion_count: 0,
|
||||
pending_completions: Vec::new(),
|
||||
tools,
|
||||
tool_use: ToolUseState::default(),
|
||||
tool_uses_by_message: HashMap::default(),
|
||||
tool_results_by_message: HashMap::default(),
|
||||
pending_tool_uses_by_id: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,15 +187,7 @@ impl Thread {
|
||||
}
|
||||
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
self.tool_use.pending_tool_uses()
|
||||
}
|
||||
|
||||
pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
|
||||
self.tool_use.tool_uses_for_message(id)
|
||||
}
|
||||
|
||||
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
|
||||
self.tool_use.message_has_tool_results(message_id)
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn insert_user_message(
|
||||
@@ -244,34 +241,9 @@ impl Thread {
|
||||
text
|
||||
}
|
||||
|
||||
pub fn send_to_model(
|
||||
&mut self,
|
||||
model: Arc<dyn LanguageModel>,
|
||||
request_kind: RequestKind,
|
||||
use_tools: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut request = self.to_completion_request(request_kind, cx);
|
||||
|
||||
if use_tools {
|
||||
request.tools = self
|
||||
.tools()
|
||||
.tools(cx)
|
||||
.into_iter()
|
||||
.map(|tool| LanguageModelRequestTool {
|
||||
name: tool.name(),
|
||||
description: tool.description(),
|
||||
input_schema: tool.input_schema(),
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
self.stream_completion(request, model, cx);
|
||||
}
|
||||
|
||||
pub fn to_completion_request(
|
||||
&self,
|
||||
request_kind: RequestKind,
|
||||
_request_kind: RequestKind,
|
||||
_cx: &App,
|
||||
) -> LanguageModelRequest {
|
||||
let mut request = LanguageModelRequest {
|
||||
@@ -293,13 +265,12 @@ impl Thread {
|
||||
content: Vec::new(),
|
||||
cache: false,
|
||||
};
|
||||
match request_kind {
|
||||
RequestKind::Chat => {
|
||||
self.tool_use
|
||||
.attach_tool_results(message.id, &mut request_message);
|
||||
}
|
||||
RequestKind::Summarize => {
|
||||
// We don't care about tool use during summarization.
|
||||
|
||||
if let Some(tool_results) = self.tool_results_by_message.get(&message.id) {
|
||||
for tool_result in tool_results {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolResult(tool_result.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,13 +280,11 @@ impl Thread {
|
||||
.push(MessageContent::Text(message.text.clone()));
|
||||
}
|
||||
|
||||
match request_kind {
|
||||
RequestKind::Chat => {
|
||||
self.tool_use
|
||||
.attach_tool_uses(message.id, &mut request_message);
|
||||
}
|
||||
RequestKind::Summarize => {
|
||||
// We don't care about tool use during summarization.
|
||||
if let Some(tool_uses) = self.tool_uses_by_message.get(&message.id) {
|
||||
for tool_use in tool_uses {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolUse(tool_use.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,8 +360,21 @@ impl Thread {
|
||||
.rfind(|message| message.role == Role::Assistant)
|
||||
{
|
||||
thread
|
||||
.tool_use
|
||||
.request_tool_use(last_assistant_message.id, tool_use);
|
||||
.tool_uses_by_message
|
||||
.entry(last_assistant_message.id)
|
||||
.or_default()
|
||||
.push(tool_use.clone());
|
||||
|
||||
thread.pending_tool_uses_by_id.insert(
|
||||
tool_use.id.clone(),
|
||||
PendingToolUse {
|
||||
assistant_message_id: last_assistant_message.id,
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
status: PendingToolUseStatus::Idle,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,7 +451,7 @@ impl Thread {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut request = self.to_completion_request(RequestKind::Summarize, cx);
|
||||
let mut request = self.to_completion_request(RequestKind::Chat, cx);
|
||||
request.messages.push(LanguageModelRequestMessage {
|
||||
role: Role::User,
|
||||
content: vec![
|
||||
@@ -512,6 +494,7 @@ impl Thread {
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
assistant_message_id: MessageId,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Task<Result<String>>,
|
||||
cx: &mut Context<Self>,
|
||||
@@ -522,18 +505,50 @@ impl Thread {
|
||||
let output = output.await;
|
||||
thread
|
||||
.update(&mut cx, |thread, cx| {
|
||||
thread
|
||||
.tool_use
|
||||
.insert_tool_output(tool_use_id.clone(), output);
|
||||
// The tool use was requested by an Assistant message,
|
||||
// so we want to attach the tool results to the next
|
||||
// user message.
|
||||
let next_user_message = MessageId(assistant_message_id.0 + 1);
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
|
||||
let tool_results = thread
|
||||
.tool_results_by_message
|
||||
.entry(next_user_message)
|
||||
.or_default();
|
||||
|
||||
match output {
|
||||
Ok(output) => {
|
||||
tool_results.push(LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
content: output,
|
||||
is_error: false,
|
||||
});
|
||||
|
||||
cx.emit(ThreadEvent::ToolFinished { tool_use_id });
|
||||
}
|
||||
Err(err) => {
|
||||
tool_results.push(LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
content: err.to_string(),
|
||||
is_error: true,
|
||||
});
|
||||
|
||||
if let Some(tool_use) =
|
||||
thread.pending_tool_uses_by_id.get_mut(&tool_use_id)
|
||||
{
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
self.tool_use
|
||||
.run_pending_tool(tool_use_id, insert_output_task);
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: insert_output_task.shared(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Cancels the last pending completion, if there are any pending.
|
||||
@@ -575,3 +590,26 @@ struct PendingCompletion {
|
||||
id: usize,
|
||||
_task: Task<()>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
/// The ID of the Assistant message in which the tool use was requested.
|
||||
pub assistant_message_id: MessageId,
|
||||
pub name: String,
|
||||
pub input: serde_json::Value,
|
||||
pub status: PendingToolUseStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PendingToolUseStatus {
|
||||
Idle,
|
||||
Running { _task: Shared<Task<()>> },
|
||||
Error(#[allow(unused)] String),
|
||||
}
|
||||
|
||||
impl PendingToolUseStatus {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Idle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,13 +134,7 @@ impl ThreadHistory {
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
HistoryEntry::Context(context) => {
|
||||
self.assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.delete_context(context.path.clone(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
HistoryEntry::Context(_context) => {}
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -254,28 +248,18 @@ impl RenderOnce for PastThread {
|
||||
);
|
||||
|
||||
ListItem::new(SharedString::from(self.thread.id.to_string()))
|
||||
.rounded()
|
||||
.outlined()
|
||||
.toggle_state(self.selected)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(
|
||||
div()
|
||||
.max_w_4_5()
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
|
||||
Icon::new(IconName::MessageCircle)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("Thread")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.size(px(3.))
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().text_disabled),
|
||||
)
|
||||
.child(
|
||||
Label::new(thread_timestamp)
|
||||
.color(Color::Muted)
|
||||
@@ -350,50 +334,21 @@ impl RenderOnce for PastContext {
|
||||
ListItem::new(SharedString::from(
|
||||
self.context.path.to_string_lossy().to_string(),
|
||||
))
|
||||
.rounded()
|
||||
.outlined()
|
||||
.toggle_state(self.selected)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.start_slot(
|
||||
div()
|
||||
.max_w_4_5()
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis()),
|
||||
Icon::new(IconName::Code)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Label::new("Prompt Editor")
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.size(px(3.))
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().text_disabled),
|
||||
)
|
||||
.child(
|
||||
Label::new(context_timestamp)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("delete", IconName::TrashAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.tooltip(Tooltip::text("Delete Prompt Editor"))
|
||||
.on_click({
|
||||
let assistant_panel = self.assistant_panel.clone();
|
||||
let path = self.context.path.clone();
|
||||
move |_event, _window, cx| {
|
||||
assistant_panel
|
||||
.update(cx, |this, cx| {
|
||||
this.delete_context(path.clone(), cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
),
|
||||
h_flex().gap_1p5().child(
|
||||
Label::new(context_timestamp)
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::XSmall),
|
||||
),
|
||||
)
|
||||
.on_click({
|
||||
let assistant_panel = self.assistant_panel.clone();
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use futures::future::Shared;
|
||||
use futures::FutureExt as _;
|
||||
use gpui::{SharedString, Task};
|
||||
use language_model::{
|
||||
LanguageModelRequestMessage, LanguageModelToolResult, LanguageModelToolUse,
|
||||
LanguageModelToolUseId, MessageContent,
|
||||
};
|
||||
|
||||
use crate::thread::MessageId;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
pub name: SharedString,
|
||||
pub status: ToolUseStatus,
|
||||
pub input: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ToolUseStatus {
|
||||
Pending,
|
||||
Running,
|
||||
Finished(SharedString),
|
||||
Error(SharedString),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ToolUseState {
|
||||
tool_uses_by_assistant_message: HashMap<MessageId, Vec<LanguageModelToolUse>>,
|
||||
tool_uses_by_user_message: HashMap<MessageId, Vec<LanguageModelToolUseId>>,
|
||||
tool_results: HashMap<LanguageModelToolUseId, LanguageModelToolResult>,
|
||||
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
|
||||
}
|
||||
|
||||
impl ToolUseState {
|
||||
pub fn pending_tool_uses(&self) -> Vec<&PendingToolUse> {
|
||||
self.pending_tool_uses_by_id.values().collect()
|
||||
}
|
||||
|
||||
pub fn tool_uses_for_message(&self, id: MessageId) -> Vec<ToolUse> {
|
||||
let Some(tool_uses_for_message) = &self.tool_uses_by_assistant_message.get(&id) else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut tool_uses = Vec::new();
|
||||
|
||||
for tool_use in tool_uses_for_message.iter() {
|
||||
let tool_result = self.tool_results.get(&tool_use.id);
|
||||
|
||||
let status = (|| {
|
||||
if let Some(tool_result) = tool_result {
|
||||
return if tool_result.is_error {
|
||||
ToolUseStatus::Error(tool_result.content.clone().into())
|
||||
} else {
|
||||
ToolUseStatus::Finished(tool_result.content.clone().into())
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(pending_tool_use) = self.pending_tool_uses_by_id.get(&tool_use.id) {
|
||||
return match pending_tool_use.status {
|
||||
PendingToolUseStatus::Idle => ToolUseStatus::Pending,
|
||||
PendingToolUseStatus::Running { .. } => ToolUseStatus::Running,
|
||||
PendingToolUseStatus::Error(ref err) => {
|
||||
ToolUseStatus::Error(err.clone().into())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ToolUseStatus::Pending
|
||||
})();
|
||||
|
||||
tool_uses.push(ToolUse {
|
||||
id: tool_use.id.clone(),
|
||||
name: tool_use.name.clone().into(),
|
||||
input: tool_use.input.clone(),
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
tool_uses
|
||||
}
|
||||
|
||||
pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
|
||||
self.tool_uses_by_user_message
|
||||
.get(&message_id)
|
||||
.map_or(false, |results| !results.is_empty())
|
||||
}
|
||||
|
||||
pub fn request_tool_use(
|
||||
&mut self,
|
||||
assistant_message_id: MessageId,
|
||||
tool_use: LanguageModelToolUse,
|
||||
) {
|
||||
self.tool_uses_by_assistant_message
|
||||
.entry(assistant_message_id)
|
||||
.or_default()
|
||||
.push(tool_use.clone());
|
||||
|
||||
// The tool use is being requested by the Assistant, so we want to
|
||||
// attach the tool results to the next user message.
|
||||
let next_user_message_id = MessageId(assistant_message_id.0 + 1);
|
||||
self.tool_uses_by_user_message
|
||||
.entry(next_user_message_id)
|
||||
.or_default()
|
||||
.push(tool_use.id.clone());
|
||||
|
||||
self.pending_tool_uses_by_id.insert(
|
||||
tool_use.id.clone(),
|
||||
PendingToolUse {
|
||||
assistant_message_id,
|
||||
id: tool_use.id,
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
status: PendingToolUseStatus::Idle,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn run_pending_tool(&mut self, tool_use_id: LanguageModelToolUseId, task: Task<()>) {
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Running {
|
||||
_task: task.shared(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_tool_output(
|
||||
&mut self,
|
||||
tool_use_id: LanguageModelToolUseId,
|
||||
output: Result<String>,
|
||||
) {
|
||||
match output {
|
||||
Ok(output) => {
|
||||
self.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
content: output.into(),
|
||||
is_error: false,
|
||||
},
|
||||
);
|
||||
self.pending_tool_uses_by_id.remove(&tool_use_id);
|
||||
}
|
||||
Err(err) => {
|
||||
self.tool_results.insert(
|
||||
tool_use_id.clone(),
|
||||
LanguageModelToolResult {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
content: err.to_string().into(),
|
||||
is_error: true,
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(tool_use) = self.pending_tool_uses_by_id.get_mut(&tool_use_id) {
|
||||
tool_use.status = PendingToolUseStatus::Error(err.to_string().into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach_tool_uses(
|
||||
&self,
|
||||
message_id: MessageId,
|
||||
request_message: &mut LanguageModelRequestMessage,
|
||||
) {
|
||||
if let Some(tool_uses) = self.tool_uses_by_assistant_message.get(&message_id) {
|
||||
for tool_use in tool_uses {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolUse(tool_use.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attach_tool_results(
|
||||
&self,
|
||||
message_id: MessageId,
|
||||
request_message: &mut LanguageModelRequestMessage,
|
||||
) {
|
||||
if let Some(tool_uses) = self.tool_uses_by_user_message.get(&message_id) {
|
||||
for tool_use_id in tool_uses {
|
||||
if let Some(tool_result) = self.tool_results.get(tool_use_id) {
|
||||
request_message
|
||||
.content
|
||||
.push(MessageContent::ToolResult(tool_result.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PendingToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
/// The ID of the Assistant message in which the tool use was requested.
|
||||
pub assistant_message_id: MessageId,
|
||||
pub name: Arc<str>,
|
||||
pub input: serde_json::Value,
|
||||
pub status: PendingToolUseStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PendingToolUseStatus {
|
||||
Idle,
|
||||
Running { _task: Shared<Task<()>> },
|
||||
Error(#[allow(unused)] Arc<str>),
|
||||
}
|
||||
|
||||
impl PendingToolUseStatus {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Idle)
|
||||
}
|
||||
|
||||
pub fn is_error(&self) -> bool {
|
||||
matches!(self, PendingToolUseStatus::Error(_))
|
||||
}
|
||||
}
|
||||
@@ -29,22 +29,19 @@ use gpui::{
|
||||
WeakEntity,
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{
|
||||
language_settings::{all_language_settings, SoftWrap},
|
||||
BufferSnapshot, LspAdapterDelegate, ToOffset,
|
||||
};
|
||||
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
|
||||
use language_model::{
|
||||
LanguageModelImage, LanguageModelProvider, LanguageModelProviderTosView, LanguageModelRegistry,
|
||||
Role,
|
||||
};
|
||||
use language_model_selector::{AssistantLanguageModelSelector, LanguageModelSelector};
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use picker::Picker;
|
||||
use project::lsp_store::LocalLspAdapterDelegate;
|
||||
use project::{Project, Worktree};
|
||||
use rope::Point;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::{any::TypeId, borrow::Cow, cmp, ops::Range, path::PathBuf, sync::Arc, time::Duration};
|
||||
use text::SelectionGoal;
|
||||
use ui::{
|
||||
@@ -80,6 +77,7 @@ actions!(
|
||||
InsertIntoEditor,
|
||||
QuoteSelection,
|
||||
Split,
|
||||
ToggleModelSelector,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -196,6 +194,7 @@ pub struct ContextEditor {
|
||||
// context editor, we keep a reference here.
|
||||
dragged_file_worktrees: Vec<Entity<Worktree>>,
|
||||
language_model_selector: Entity<LanguageModelSelector>,
|
||||
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TAB_TITLE: &str = "New Chat";
|
||||
@@ -231,13 +230,6 @@ impl ContextEditor {
|
||||
editor.set_completion_provider(Some(Box::new(completion_provider)));
|
||||
editor.set_menu_inline_completions_policy(MenuInlineCompletionsPolicy::Never);
|
||||
editor.set_collaboration_hub(Box::new(project.clone()));
|
||||
|
||||
let show_edit_predictions = all_language_settings(None, cx)
|
||||
.edit_predictions
|
||||
.enabled_in_assistant;
|
||||
|
||||
editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
|
||||
|
||||
editor
|
||||
});
|
||||
|
||||
@@ -246,7 +238,6 @@ impl ContextEditor {
|
||||
cx.subscribe_in(&context, window, Self::handle_context_event),
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_event),
|
||||
cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
|
||||
cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
|
||||
];
|
||||
|
||||
let fs_clone = fs.clone();
|
||||
@@ -264,6 +255,7 @@ impl ContextEditor {
|
||||
)
|
||||
});
|
||||
|
||||
let language_model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
let sections = context.read(cx).slash_command_output_sections().to_vec();
|
||||
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
|
||||
let slash_commands = context.read(cx).slash_commands().clone();
|
||||
@@ -289,6 +281,7 @@ impl ContextEditor {
|
||||
slash_menu_handle: Default::default(),
|
||||
dragged_file_worktrees: Vec::new(),
|
||||
language_model_selector,
|
||||
language_model_selector_menu_handle,
|
||||
};
|
||||
this.update_message_headers(cx);
|
||||
this.update_image_blocks(cx);
|
||||
@@ -297,16 +290,6 @@ impl ContextEditor {
|
||||
this
|
||||
}
|
||||
|
||||
fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let show_edit_predictions = all_language_settings(None, cx)
|
||||
.edit_predictions
|
||||
.enabled_in_assistant;
|
||||
|
||||
editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &Entity<AssistantContext> {
|
||||
&self.context
|
||||
}
|
||||
@@ -2041,6 +2024,15 @@ impl ContextEditor {
|
||||
});
|
||||
}
|
||||
|
||||
fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.language_model_selector_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.context.update(cx, |context, cx| {
|
||||
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||
@@ -2388,6 +2380,46 @@ impl ContextEditor {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_language_model_selector(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
None => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
gpui::Corner::BottomLeft,
|
||||
)
|
||||
.with_handle(self.language_model_selector_menu_handle.clone())
|
||||
}
|
||||
|
||||
fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
||||
let last_error = self.last_error.as_ref()?;
|
||||
|
||||
@@ -2832,7 +2864,6 @@ impl Render for ContextEditor {
|
||||
None
|
||||
};
|
||||
|
||||
let language_model_selector = self.language_model_selector.clone();
|
||||
v_flex()
|
||||
.key_context("ContextEditor")
|
||||
.capture_action(cx.listener(ContextEditor::cancel))
|
||||
@@ -2845,11 +2876,7 @@ impl Render for ContextEditor {
|
||||
.on_action(cx.listener(ContextEditor::edit))
|
||||
.on_action(cx.listener(ContextEditor::assist))
|
||||
.on_action(cx.listener(ContextEditor::split))
|
||||
.on_action(move |action, window, cx| {
|
||||
language_model_selector.update(cx, |this, cx| {
|
||||
this.toggle_model_selector(action, window, cx);
|
||||
})
|
||||
})
|
||||
.on_action(cx.listener(ContextEditor::toggle_model_selector))
|
||||
.size_full()
|
||||
.children(self.render_notice(cx))
|
||||
.child(
|
||||
@@ -2887,14 +2914,11 @@ impl Render for ContextEditor {
|
||||
.gap_1()
|
||||
.child(self.render_inject_context_menu(cx))
|
||||
.child(ui::Divider::vertical())
|
||||
.child(div().pl_0p5().child({
|
||||
let focus_handle = self.editor().focus_handle(cx).clone();
|
||||
AssistantLanguageModelSelector::new(
|
||||
focus_handle,
|
||||
self.language_model_selector.clone(),
|
||||
)
|
||||
.render(window, cx)
|
||||
})),
|
||||
.child(
|
||||
div()
|
||||
.pl_0p5()
|
||||
.child(self.render_language_model_selector(cx)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
|
||||
@@ -9,7 +9,7 @@ use clock::ReplicaId;
|
||||
use collections::HashMap;
|
||||
use context_server::manager::ContextServerManager;
|
||||
use context_server::ContextServerFactoryRegistry;
|
||||
use fs::{Fs, RemoveOptions};
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
|
||||
@@ -475,38 +475,6 @@ impl ContextStore {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_local_context(
|
||||
&mut self,
|
||||
path: PathBuf,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let fs = self.fs.clone();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
fs.remove_file(
|
||||
&path,
|
||||
RemoveOptions {
|
||||
recursive: false,
|
||||
ignore_if_not_exists: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.contexts.retain(|context| {
|
||||
context
|
||||
.upgrade()
|
||||
.and_then(|context| context.read(cx).path())
|
||||
!= Some(&path)
|
||||
});
|
||||
this.contexts_metadata
|
||||
.retain(|context| context.path != path);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn loaded_context_for_path(&self, path: &Path, cx: &App) -> Option<Entity<AssistantContext>> {
|
||||
self.contexts.iter().find_map(|context| {
|
||||
let context = context.upgrade()?;
|
||||
|
||||
@@ -21,11 +21,11 @@ impl SlashCommand for DefaultSlashCommand {
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"Insert default prompt".into()
|
||||
"insert default prompt".into()
|
||||
}
|
||||
|
||||
fn menu_text(&self) -> String {
|
||||
self.description()
|
||||
"Insert Default Prompt".into()
|
||||
}
|
||||
|
||||
fn requires_argument(&self) -> bool {
|
||||
|
||||
@@ -109,7 +109,7 @@ impl sum_tree::Summary for DiffHunkSummary {
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor {
|
||||
impl<'a> sum_tree::SeekTarget<'a, DiffHunkSummary, DiffHunkSummary> for Anchor {
|
||||
fn cmp(&self, cursor_location: &DiffHunkSummary, buffer: &text::BufferSnapshot) -> Ordering {
|
||||
if self
|
||||
.cmp(&cursor_location.buffer_range.start, buffer)
|
||||
|
||||
@@ -48,7 +48,7 @@ pub struct ChannelPathsInsertGuard<'a> {
|
||||
channels_by_id: &'a mut BTreeMap<ChannelId, Arc<Channel>>,
|
||||
}
|
||||
|
||||
impl ChannelPathsInsertGuard<'_> {
|
||||
impl<'a> ChannelPathsInsertGuard<'a> {
|
||||
pub fn insert(&mut self, channel_proto: proto::Channel) -> bool {
|
||||
let mut ret = false;
|
||||
let parent_path = channel_proto
|
||||
@@ -86,7 +86,7 @@ impl ChannelPathsInsertGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ChannelPathsInsertGuard<'_> {
|
||||
impl<'a> Drop for ChannelPathsInsertGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.channels_ordered.sort_by(|a, b| {
|
||||
let a = channel_path_sorting_key(*a, self.channels_by_id);
|
||||
|
||||
@@ -33,13 +33,10 @@ util.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "freebsd"))'.dependencies]
|
||||
exec.workspace = true
|
||||
exec.workspace = true
|
||||
fork.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
core-foundation.workspace = true
|
||||
core-services = "0.2"
|
||||
plist = "1.3"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
@@ -521,108 +521,30 @@ mod flatpak {
|
||||
}
|
||||
}
|
||||
|
||||
// todo("windows")
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows {
|
||||
use anyhow::Context;
|
||||
use release_channel::app_identifier;
|
||||
use windows::{
|
||||
core::HSTRING,
|
||||
Win32::{
|
||||
Foundation::{CloseHandle, GetLastError, ERROR_ALREADY_EXISTS, GENERIC_WRITE},
|
||||
Storage::FileSystem::{
|
||||
CreateFileW, WriteFile, FILE_FLAGS_AND_ATTRIBUTES, FILE_SHARE_MODE, OPEN_EXISTING,
|
||||
},
|
||||
System::Threading::CreateMutexW,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{Detect, InstalledApp};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::process::ExitStatus;
|
||||
|
||||
fn check_single_instance() -> bool {
|
||||
let mutex = unsafe {
|
||||
CreateMutexW(
|
||||
None,
|
||||
false,
|
||||
&HSTRING::from(format!("{}-Instance-Mutex", app_identifier())),
|
||||
)
|
||||
.expect("Unable to create instance sync event")
|
||||
};
|
||||
let last_err = unsafe { GetLastError() };
|
||||
let _ = unsafe { CloseHandle(mutex) };
|
||||
last_err != ERROR_ALREADY_EXISTS
|
||||
}
|
||||
|
||||
struct App(PathBuf);
|
||||
|
||||
struct App;
|
||||
impl InstalledApp for App {
|
||||
fn zed_version_string(&self) -> String {
|
||||
format!(
|
||||
"Zed {}{}{} – {}",
|
||||
if *release_channel::RELEASE_CHANNEL_NAME == "stable" {
|
||||
"".to_string()
|
||||
} else {
|
||||
format!("{} ", *release_channel::RELEASE_CHANNEL_NAME)
|
||||
},
|
||||
option_env!("RELEASE_VERSION").unwrap_or_default(),
|
||||
match option_env!("ZED_COMMIT_SHA") {
|
||||
Some(commit_sha) => format!(" {commit_sha} "),
|
||||
None => "".to_string(),
|
||||
},
|
||||
self.0.display(),
|
||||
)
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn launch(&self, ipc_url: String) -> anyhow::Result<()> {
|
||||
if check_single_instance() {
|
||||
std::process::Command::new(self.0.clone())
|
||||
.arg(ipc_url)
|
||||
.spawn()?;
|
||||
} else {
|
||||
unsafe {
|
||||
let pipe = CreateFileW(
|
||||
&HSTRING::from(format!("\\\\.\\pipe\\{}-Named-Pipe", app_identifier())),
|
||||
GENERIC_WRITE.0,
|
||||
FILE_SHARE_MODE::default(),
|
||||
None,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAGS_AND_ATTRIBUTES::default(),
|
||||
None,
|
||||
)?;
|
||||
let message = ipc_url.as_bytes();
|
||||
let mut bytes_written = 0;
|
||||
WriteFile(pipe, Some(message), Some(&mut bytes_written), None)?;
|
||||
CloseHandle(pipe)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
fn launch(&self, _ipc_url: String) -> anyhow::Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn run_foreground(&self, ipc_url: String) -> io::Result<ExitStatus> {
|
||||
std::process::Command::new(self.0.clone())
|
||||
.arg(ipc_url)
|
||||
.arg("--foreground")
|
||||
.spawn()?
|
||||
.wait()
|
||||
fn run_foreground(&self, _ipc_url: String) -> io::Result<ExitStatus> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Detect {
|
||||
pub fn detect(path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
|
||||
let path = if let Some(path) = path {
|
||||
path.to_path_buf().canonicalize()?
|
||||
} else {
|
||||
std::env::current_exe()?
|
||||
.parent()
|
||||
.context("no parent path for cli")?
|
||||
.parent()
|
||||
.context("no parent path for cli folder")?
|
||||
.join("Zed.exe")
|
||||
};
|
||||
|
||||
Ok(App(path))
|
||||
pub fn detect(_path: Option<&Path>) -> anyhow::Result<impl InstalledApp> {
|
||||
Ok(App)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_known_extension_versions(&self) -> Result<HashMap<String, Vec<String>>> {
|
||||
pub async fn get_known_extension_versions<'a>(&self) -> Result<HashMap<String, Vec<String>>> {
|
||||
self.transaction(|tx| async move {
|
||||
let mut extension_external_ids_by_id = HashMap::default();
|
||||
|
||||
|
||||
@@ -975,7 +975,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ConnectionPoolGuard<'_> {
|
||||
impl<'a> Deref for ConnectionPoolGuard<'a> {
|
||||
type Target = ConnectionPool;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -983,13 +983,13 @@ impl Deref for ConnectionPoolGuard<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ConnectionPoolGuard<'_> {
|
||||
impl<'a> DerefMut for ConnectionPoolGuard<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ConnectionPoolGuard<'_> {
|
||||
impl<'a> Drop for ConnectionPoolGuard<'a> {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(test)]
|
||||
self.check_invariants();
|
||||
|
||||
@@ -1537,7 +1537,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1553,7 +1552,6 @@ async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1772,7 +1770,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
@@ -1788,7 +1785,6 @@ async fn test_inlay_hint_refresh_is_forwarded(
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6354,7 +6354,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 1 as preview
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_1.clone(), None, true, true, true, window, cx)
|
||||
workspace.open_path_preview(path_1.clone(), None, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6375,7 +6375,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 2 as preview
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6507,7 +6507,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 2 as preview in right pane
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -6545,7 +6545,7 @@ async fn test_preview_tabs(cx: &mut TestAppContext) {
|
||||
// Open item 2 as preview in left pane
|
||||
workspace
|
||||
.update_in(cx, |workspace, window, cx| {
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, true, window, cx)
|
||||
workspace.open_path_preview(path_2.clone(), None, true, true, window, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -27,7 +27,7 @@ impl<'de> Deserialize<'de> for ChatPanelButton {
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl serde::de::Visitor<'_> for Visitor {
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = ChatPanelButton;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
|
||||
@@ -15,6 +15,7 @@ path = "src/component.rs"
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
linkme.workspace = true
|
||||
once_cell.workspace = true
|
||||
parking_lot.workspace = true
|
||||
theme.workspace = true
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use collections::HashMap;
|
||||
use gpui::{div, prelude::*, px, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
|
||||
use linkme::distributed_slice;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use theme::ActiveTheme;
|
||||
|
||||
@@ -27,8 +27,8 @@ pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||
#[distributed_slice]
|
||||
pub static __ALL_PREVIEWS: [fn()] = [..];
|
||||
|
||||
pub static COMPONENT_DATA: LazyLock<RwLock<ComponentRegistry>> =
|
||||
LazyLock::new(|| RwLock::new(ComponentRegistry::new()));
|
||||
pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> =
|
||||
Lazy::new(|| RwLock::new(ComponentRegistry::new()));
|
||||
|
||||
pub struct ComponentRegistry {
|
||||
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
|
||||
|
||||
@@ -411,7 +411,7 @@ async fn stream_completion(
|
||||
|
||||
match serde_json::from_str::<ResponseEvent>(line) {
|
||||
Ok(response) => {
|
||||
if response.choices.is_empty()
|
||||
if response.choices.first().is_none()
|
||||
|| response.choices.first().unwrap().finish_reason.is_some()
|
||||
{
|
||||
None
|
||||
|
||||
@@ -192,7 +192,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
|
||||
fn discard(&mut self, cx: &mut Context<Self>) {
|
||||
let settings = AllLanguageSettings::get_global(cx);
|
||||
|
||||
let copilot_enabled = settings.show_edit_predictions(None, cx);
|
||||
let copilot_enabled = settings.show_inline_completions(None, cx);
|
||||
|
||||
if !copilot_enabled {
|
||||
return;
|
||||
|
||||
@@ -562,7 +562,9 @@ impl ProjectDiagnosticsEditor {
|
||||
)),
|
||||
height: diagnostic.message.matches('\n').count() as u32 + 1,
|
||||
style: BlockStyle::Fixed,
|
||||
render: diagnostic_block_renderer(diagnostic, None, true),
|
||||
render: diagnostic_block_renderer(
|
||||
diagnostic, None, true, true,
|
||||
),
|
||||
priority: 0,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -330,11 +330,7 @@ impl DisplayMap {
|
||||
block_map.remove_intersecting_replace_blocks(offset_ranges, inclusive);
|
||||
}
|
||||
|
||||
pub fn fold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = language::BufferId>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn fold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
@@ -345,14 +341,10 @@ impl DisplayMap {
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let mut block_map = self.block_map.write(snapshot, edits);
|
||||
block_map.fold_buffers(buffer_ids, self.buffer.read(cx), cx)
|
||||
block_map.fold_buffer(buffer_id, self.buffer.read(cx), cx)
|
||||
}
|
||||
|
||||
pub fn unfold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = language::BufferId>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
pub fn unfold_buffer(&mut self, buffer_id: language::BufferId, cx: &mut Context<Self>) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let edits = self.buffer_subscription.consume().into_inner();
|
||||
let tab_size = Self::tab_size(&self.buffer, cx);
|
||||
@@ -363,7 +355,7 @@ impl DisplayMap {
|
||||
.wrap_map
|
||||
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
|
||||
let mut block_map = self.block_map.write(snapshot, edits);
|
||||
block_map.unfold_buffers(buffer_ids, self.buffer.read(cx), cx)
|
||||
block_map.unfold_buffer(buffer_id, self.buffer.read(cx), cx)
|
||||
}
|
||||
|
||||
pub(crate) fn is_buffer_folded(&self, buffer_id: language::BufferId) -> bool {
|
||||
|
||||
@@ -999,7 +999,7 @@ impl std::ops::DerefMut for BlockPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BlockMapReader<'_> {
|
||||
impl<'a> Deref for BlockMapReader<'a> {
|
||||
type Target = BlockSnapshot;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -1007,13 +1007,13 @@ impl Deref for BlockMapReader<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for BlockMapReader<'_> {
|
||||
impl<'a> DerefMut for BlockMapReader<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.snapshot
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockMapReader<'_> {
|
||||
impl<'a> BlockMapReader<'a> {
|
||||
pub fn row_for_block(&self, block_id: CustomBlockId) -> Option<BlockRow> {
|
||||
let block = self.blocks.iter().find(|block| block.id == block_id)?;
|
||||
let buffer_row = block
|
||||
@@ -1053,7 +1053,7 @@ impl BlockMapReader<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockMapWriter<'_> {
|
||||
impl<'a> BlockMapWriter<'a> {
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
|
||||
@@ -1239,45 +1239,26 @@ impl BlockMapWriter<'_> {
|
||||
self.remove(blocks_to_remove);
|
||||
}
|
||||
|
||||
pub fn fold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = BufferId>,
|
||||
multi_buffer: &MultiBuffer,
|
||||
cx: &App,
|
||||
) {
|
||||
self.fold_or_unfold_buffers(true, buffer_ids, multi_buffer, cx);
|
||||
pub fn fold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
|
||||
self.0.folded_buffers.insert(buffer_id);
|
||||
self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
|
||||
}
|
||||
|
||||
pub fn unfold_buffers(
|
||||
&mut self,
|
||||
buffer_ids: impl IntoIterator<Item = BufferId>,
|
||||
multi_buffer: &MultiBuffer,
|
||||
cx: &App,
|
||||
) {
|
||||
self.fold_or_unfold_buffers(false, buffer_ids, multi_buffer, cx);
|
||||
pub fn unfold_buffer(&mut self, buffer_id: BufferId, multi_buffer: &MultiBuffer, cx: &App) {
|
||||
self.0.folded_buffers.remove(&buffer_id);
|
||||
self.recompute_blocks_for_buffer(buffer_id, multi_buffer, cx);
|
||||
}
|
||||
|
||||
fn fold_or_unfold_buffers(
|
||||
fn recompute_blocks_for_buffer(
|
||||
&mut self,
|
||||
fold: bool,
|
||||
buffer_ids: impl IntoIterator<Item = BufferId>,
|
||||
buffer_id: BufferId,
|
||||
multi_buffer: &MultiBuffer,
|
||||
cx: &App,
|
||||
) {
|
||||
let mut ranges = Vec::new();
|
||||
for buffer_id in buffer_ids {
|
||||
if fold {
|
||||
self.0.folded_buffers.insert(buffer_id);
|
||||
} else {
|
||||
self.0.folded_buffers.remove(&buffer_id);
|
||||
}
|
||||
ranges.extend(multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx));
|
||||
}
|
||||
ranges.sort_unstable_by_key(|range| range.start);
|
||||
let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
|
||||
|
||||
let mut edits = Patch::default();
|
||||
let wrap_snapshot = self.0.wrap_snapshot.borrow().clone();
|
||||
for range in ranges {
|
||||
for range in multi_buffer.excerpt_ranges_for_buffer(buffer_id, cx) {
|
||||
let last_edit_row = cmp::min(
|
||||
wrap_snapshot.make_wrap_point(range.end, Bias::Right).row() + 1,
|
||||
wrap_snapshot.max_point().row(),
|
||||
@@ -1740,7 +1721,7 @@ impl BlockSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockChunks<'_> {
|
||||
impl<'a> BlockChunks<'a> {
|
||||
/// Go to the next transform
|
||||
fn advance(&mut self) {
|
||||
self.input_chunk = Chunk::default();
|
||||
@@ -1856,7 +1837,7 @@ impl<'a> Iterator for BlockChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for BlockRows<'_> {
|
||||
impl<'a> Iterator for BlockRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1952,7 +1933,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BlockContext<'_, '_> {
|
||||
impl<'a> Deref for BlockContext<'a, '_> {
|
||||
type Target = App;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -2749,7 +2730,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffers([buffer_id_1], buffer, cx);
|
||||
writer.fold_buffer(buffer_id_1, buffer, cx);
|
||||
});
|
||||
let excerpt_blocks_1 = writer.insert(vec![BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
@@ -2824,7 +2805,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffers([buffer_id_2], buffer, cx);
|
||||
writer.fold_buffer(buffer_id_2, buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -2880,7 +2861,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.unfold_buffers([buffer_id_1], buffer, cx);
|
||||
writer.unfold_buffer(buffer_id_1, buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -2941,7 +2922,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffers([buffer_id_3], buffer, cx);
|
||||
writer.fold_buffer(buffer_id_3, buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -3019,7 +3000,7 @@ mod tests {
|
||||
|
||||
let mut writer = block_map.write(wrap_snapshot.clone(), Patch::default());
|
||||
buffer.read_with(cx, |buffer, cx| {
|
||||
writer.fold_buffers([buffer_id], buffer, cx);
|
||||
writer.fold_buffer(buffer_id, buffer, cx);
|
||||
});
|
||||
let blocks_snapshot = block_map.read(wrap_snapshot.clone(), Patch::default());
|
||||
let blocks = blocks_snapshot
|
||||
@@ -3269,7 +3250,7 @@ mod tests {
|
||||
);
|
||||
folded_count += 1;
|
||||
unfolded_count -= 1;
|
||||
block_map.fold_buffers([buffer_to_fold], buffer, cx);
|
||||
block_map.fold_buffer(buffer_to_fold, buffer, cx);
|
||||
}
|
||||
if unfold {
|
||||
let buffer_to_unfold =
|
||||
@@ -3277,7 +3258,7 @@ mod tests {
|
||||
log::info!("Unfolding {buffer_to_unfold:?}");
|
||||
unfolded_count += 1;
|
||||
folded_count -= 1;
|
||||
block_map.unfold_buffers([buffer_to_unfold], buffer, cx);
|
||||
block_map.unfold_buffer(buffer_to_unfold, buffer, cx);
|
||||
}
|
||||
log::info!(
|
||||
"Unfolded buffers: {unfolded_count}, folded buffers: {folded_count}"
|
||||
|
||||
@@ -132,7 +132,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldPoint {
|
||||
|
||||
pub(crate) struct FoldMapWriter<'a>(&'a mut FoldMap);
|
||||
|
||||
impl FoldMapWriter<'_> {
|
||||
impl<'a> FoldMapWriter<'a> {
|
||||
pub(crate) fn fold<T: ToOffset>(
|
||||
&mut self,
|
||||
ranges: impl IntoIterator<Item = (Range<T>, FoldPlaceholder)>,
|
||||
@@ -1121,7 +1121,7 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for FoldRange {
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::SeekTarget<'_, FoldSummary, FoldRange> for FoldRange {
|
||||
impl<'a> sum_tree::SeekTarget<'a, FoldSummary, FoldRange> for FoldRange {
|
||||
fn cmp(&self, other: &Self, buffer: &MultiBufferSnapshot) -> Ordering {
|
||||
AnchorRangeExt::cmp(&self.0, &other.0, buffer)
|
||||
}
|
||||
@@ -1144,7 +1144,7 @@ pub struct FoldRows<'a> {
|
||||
fold_point: FoldPoint,
|
||||
}
|
||||
|
||||
impl FoldRows<'_> {
|
||||
impl<'a> FoldRows<'a> {
|
||||
pub(crate) fn seek(&mut self, row: u32) {
|
||||
let fold_point = FoldPoint::new(row, 0);
|
||||
self.cursor.seek(&fold_point, Bias::Left, &());
|
||||
@@ -1155,7 +1155,7 @@ impl FoldRows<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for FoldRows<'_> {
|
||||
impl<'a> Iterator for FoldRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1190,7 +1190,7 @@ pub struct FoldChunks<'a> {
|
||||
max_output_offset: FoldOffset,
|
||||
}
|
||||
|
||||
impl FoldChunks<'_> {
|
||||
impl<'a> FoldChunks<'a> {
|
||||
pub(crate) fn seek(&mut self, range: Range<FoldOffset>) {
|
||||
self.transform_cursor.seek(&range.start, Bias::Right, &());
|
||||
|
||||
|
||||
@@ -215,7 +215,7 @@ pub struct InlayChunks<'a> {
|
||||
snapshot: &'a InlaySnapshot,
|
||||
}
|
||||
|
||||
impl InlayChunks<'_> {
|
||||
impl<'a> InlayChunks<'a> {
|
||||
pub fn seek(&mut self, new_range: Range<InlayOffset>) {
|
||||
self.transforms.seek(&new_range.start, Bias::Right, &());
|
||||
|
||||
@@ -341,7 +341,7 @@ impl<'a> Iterator for InlayChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl InlayBufferRows<'_> {
|
||||
impl<'a> InlayBufferRows<'a> {
|
||||
pub fn seek(&mut self, row: u32) {
|
||||
let inlay_point = InlayPoint::new(row, 0);
|
||||
self.transforms.seek(&inlay_point, Bias::Left, &());
|
||||
@@ -363,7 +363,7 @@ impl InlayBufferRows<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for InlayBufferRows<'_> {
|
||||
impl<'a> Iterator for InlayBufferRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
|
||||
@@ -498,7 +498,7 @@ pub struct TabChunks<'a> {
|
||||
inside_leading_tab: bool,
|
||||
}
|
||||
|
||||
impl TabChunks<'_> {
|
||||
impl<'a> TabChunks<'a> {
|
||||
pub(crate) fn seek(&mut self, range: Range<TabPoint>) {
|
||||
let (input_start, expanded_char_column, to_next_stop) =
|
||||
self.snapshot.to_fold_point(range.start, Bias::Left);
|
||||
|
||||
@@ -69,7 +69,7 @@ pub struct WrapRows<'a> {
|
||||
transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>,
|
||||
}
|
||||
|
||||
impl WrapRows<'_> {
|
||||
impl<'a> WrapRows<'a> {
|
||||
pub(crate) fn seek(&mut self, start_row: u32) {
|
||||
self.transforms
|
||||
.seek(&WrapPoint::new(start_row, 0), Bias::Left, &());
|
||||
@@ -872,7 +872,7 @@ impl WrapSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
impl WrapChunks<'_> {
|
||||
impl<'a> WrapChunks<'a> {
|
||||
pub(crate) fn seek(&mut self, rows: Range<u32>) {
|
||||
let output_start = WrapPoint::new(rows.start, 0);
|
||||
let output_end = WrapPoint::new(rows.end, 0);
|
||||
@@ -955,7 +955,7 @@ impl<'a> Iterator for WrapChunks<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for WrapRows<'_> {
|
||||
impl<'a> Iterator for WrapRows<'a> {
|
||||
type Item = RowInfo;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@@ -1120,7 +1120,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::SeekTarget<'_, TransformSummary, TransformSummary> for TabPoint {
|
||||
impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint {
|
||||
fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
|
||||
Ord::cmp(&self.0, &cursor_location.input.lines)
|
||||
}
|
||||
|
||||
@@ -994,7 +994,7 @@ struct RegisteredInlineCompletionProvider {
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug)]
|
||||
struct ActiveDiagnosticGroup {
|
||||
primary_range: Range<Anchor>,
|
||||
primary_message: String,
|
||||
@@ -1031,7 +1031,6 @@ pub enum GotoDefinitionKind {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum InlayHintRefreshReason {
|
||||
ModifiersChanged(bool),
|
||||
Toggle(bool),
|
||||
SettingsChange(InlayHintSettings),
|
||||
NewLinesShown,
|
||||
@@ -1043,7 +1042,6 @@ enum InlayHintRefreshReason {
|
||||
impl InlayHintRefreshReason {
|
||||
fn description(&self) -> &'static str {
|
||||
match self {
|
||||
Self::ModifiersChanged(_) => "modifiers changed",
|
||||
Self::Toggle(_) => "toggle",
|
||||
Self::SettingsChange(_) => "settings change",
|
||||
Self::NewLinesShown => "new lines shown",
|
||||
@@ -3670,7 +3668,7 @@ impl Editor {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.refresh_inlay_hints(
|
||||
InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
|
||||
InlayHintRefreshReason::Toggle(!self.inlay_hint_cache.enabled),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@@ -3692,44 +3690,21 @@ impl Editor {
|
||||
| InlayHintRefreshReason::ExcerptsRemoved(_)
|
||||
);
|
||||
let (invalidate_cache, required_languages) = match reason {
|
||||
InlayHintRefreshReason::ModifiersChanged(enabled) => {
|
||||
match self.inlay_hint_cache.modifiers_override(enabled) {
|
||||
Some(enabled) => {
|
||||
if enabled {
|
||||
(InvalidationStrategy::RefreshRequested, None)
|
||||
} else {
|
||||
self.splice_inlays(
|
||||
&self
|
||||
.visible_inlay_hints(cx)
|
||||
.iter()
|
||||
.map(|inlay| inlay.id)
|
||||
.collect::<Vec<InlayId>>(),
|
||||
Vec::new(),
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => return,
|
||||
}
|
||||
}
|
||||
InlayHintRefreshReason::Toggle(enabled) => {
|
||||
if self.inlay_hint_cache.toggle(enabled) {
|
||||
if enabled {
|
||||
(InvalidationStrategy::RefreshRequested, None)
|
||||
} else {
|
||||
self.splice_inlays(
|
||||
&self
|
||||
.visible_inlay_hints(cx)
|
||||
.iter()
|
||||
.map(|inlay| inlay.id)
|
||||
.collect::<Vec<InlayId>>(),
|
||||
Vec::new(),
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
self.inlay_hint_cache.enabled = enabled;
|
||||
if enabled {
|
||||
(InvalidationStrategy::RefreshRequested, None)
|
||||
} else {
|
||||
self.inlay_hint_cache.clear();
|
||||
self.splice_inlays(
|
||||
&self
|
||||
.visible_inlay_hints(cx)
|
||||
.iter()
|
||||
.map(|inlay| inlay.id)
|
||||
.collect::<Vec<InlayId>>(),
|
||||
Vec::new(),
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4984,7 +4959,7 @@ impl Editor {
|
||||
});
|
||||
|
||||
let preview_requires_modifier =
|
||||
all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
|
||||
all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Auto;
|
||||
|
||||
EditPredictionSettings::Enabled {
|
||||
show_in_menu,
|
||||
@@ -5030,7 +5005,7 @@ impl Editor {
|
||||
return Some(true);
|
||||
};
|
||||
let settings = all_language_settings(Some(file), cx);
|
||||
Some(settings.edit_predictions_enabled_for_file(file, cx))
|
||||
Some(settings.inline_completions_enabled_for_path(file.path()))
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@@ -7734,20 +7709,12 @@ impl Editor {
|
||||
for hunk in &hunks {
|
||||
self.prepare_restore_change(&mut revert_changes, hunk, cx);
|
||||
}
|
||||
Self::do_stage_or_unstage(
|
||||
project,
|
||||
false,
|
||||
buffer_id,
|
||||
hunks.into_iter(),
|
||||
&snapshot,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
Self::do_stage_or_unstage(project, false, buffer_id, hunks.into_iter(), &snapshot, cx);
|
||||
}
|
||||
drop(chunk_by);
|
||||
if !revert_changes.is_empty() {
|
||||
self.transact(window, cx, |editor, window, cx| {
|
||||
editor.restore(revert_changes, window, cx);
|
||||
editor.revert(revert_changes, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -8803,13 +8770,6 @@ impl Editor {
|
||||
self.change_selections(None, window, cx, |s| {
|
||||
s.select_anchors(selections.to_vec());
|
||||
});
|
||||
} else {
|
||||
log::error!(
|
||||
"No entry in selection_history found for undo. \
|
||||
This may correspond to a bug where undo does not update the selection. \
|
||||
If this is occurring, please add details to \
|
||||
https://github.com/zed-industries/zed/issues/22692"
|
||||
);
|
||||
}
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
self.unmark_text(window, cx);
|
||||
@@ -8831,13 +8791,6 @@ impl Editor {
|
||||
self.change_selections(None, window, cx, |s| {
|
||||
s.select_anchors(selections.to_vec());
|
||||
});
|
||||
} else {
|
||||
log::error!(
|
||||
"No entry in selection_history found for redo. \
|
||||
This may correspond to a bug where undo does not update the selection. \
|
||||
If this is occurring, please add details to \
|
||||
https://github.com/zed-industries/zed/issues/22692"
|
||||
);
|
||||
}
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
self.unmark_text(window, cx);
|
||||
@@ -12564,20 +12517,16 @@ impl Editor {
|
||||
|
||||
if is_valid != active_diagnostics.is_valid {
|
||||
active_diagnostics.is_valid = is_valid;
|
||||
if is_valid {
|
||||
let mut new_styles = HashMap::default();
|
||||
for (block_id, diagnostic) in &active_diagnostics.blocks {
|
||||
new_styles.insert(
|
||||
*block_id,
|
||||
diagnostic_block_renderer(diagnostic.clone(), None, true),
|
||||
);
|
||||
}
|
||||
self.display_map.update(cx, |display_map, _cx| {
|
||||
display_map.replace_blocks(new_styles);
|
||||
});
|
||||
} else {
|
||||
self.dismiss_diagnostics(cx);
|
||||
let mut new_styles = HashMap::default();
|
||||
for (block_id, diagnostic) in &active_diagnostics.blocks {
|
||||
new_styles.insert(
|
||||
*block_id,
|
||||
diagnostic_block_renderer(diagnostic.clone(), None, true, is_valid),
|
||||
);
|
||||
}
|
||||
self.display_map.update(cx, |display_map, _cx| {
|
||||
display_map.replace_blocks(new_styles)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12628,7 +12577,7 @@ impl Editor {
|
||||
buffer.anchor_after(entry.range.start),
|
||||
),
|
||||
height: message_height,
|
||||
render: diagnostic_block_renderer(diagnostic, None, true),
|
||||
render: diagnostic_block_renderer(diagnostic, None, true, true),
|
||||
priority: 0,
|
||||
}
|
||||
}),
|
||||
@@ -13339,9 +13288,8 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.fold_buffers([buffer_id], cx)
|
||||
});
|
||||
self.display_map
|
||||
.update(cx, |display_map, cx| display_map.fold_buffer(buffer_id, cx));
|
||||
cx.emit(EditorEvent::BufferFoldToggled {
|
||||
ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
|
||||
folded: true,
|
||||
@@ -13355,7 +13303,7 @@ impl Editor {
|
||||
}
|
||||
let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
|
||||
self.display_map.update(cx, |display_map, cx| {
|
||||
display_map.unfold_buffers([buffer_id], cx);
|
||||
display_map.unfold_buffer(buffer_id, cx);
|
||||
});
|
||||
cx.emit(EditorEvent::BufferFoldToggled {
|
||||
ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
|
||||
@@ -13476,13 +13424,13 @@ impl Editor {
|
||||
pub fn toggle_staged_selected_diff_hunks(
|
||||
&mut self,
|
||||
_: &::git::ToggleStaged,
|
||||
window: &mut Window,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
|
||||
let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges, window, cx);
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges, cx);
|
||||
}
|
||||
|
||||
pub fn stage_and_next(
|
||||
@@ -13507,7 +13455,6 @@ impl Editor {
|
||||
&mut self,
|
||||
stage: bool,
|
||||
ranges: &[Range<Anchor>],
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
@@ -13519,7 +13466,7 @@ impl Editor {
|
||||
.diff_hunks_in_ranges(&ranges, &snapshot)
|
||||
.chunk_by(|hunk| hunk.buffer_id);
|
||||
for (buffer_id, hunks) in &chunk_by {
|
||||
Self::do_stage_or_unstage(project, stage, buffer_id, hunks, &snapshot, window, cx);
|
||||
Self::do_stage_or_unstage(project, stage, buffer_id, hunks, &snapshot, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13531,7 +13478,7 @@ impl Editor {
|
||||
) {
|
||||
let mut ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
|
||||
if ranges.iter().any(|range| range.start != range.end) {
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], cx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13574,7 +13521,7 @@ impl Editor {
|
||||
buffer.read(cx).remote_id(),
|
||||
range,
|
||||
)];
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], cx);
|
||||
let snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let mut point = ranges.last().unwrap().end.to_point(&snapshot);
|
||||
if point.row < snapshot.max_row().0 {
|
||||
@@ -13588,7 +13535,7 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], window, cx);
|
||||
self.stage_or_unstage_diff_hunks(stage, &ranges[..], cx);
|
||||
self.go_to_next_hunk(&Default::default(), window, cx);
|
||||
}
|
||||
|
||||
@@ -13598,7 +13545,6 @@ impl Editor {
|
||||
buffer_id: BufferId,
|
||||
hunks: impl Iterator<Item = MultiBufferDiffHunk>,
|
||||
snapshot: &MultiBufferSnapshot,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let Some(buffer) = project.read(cx).buffer_for_id(buffer_id, cx) else {
|
||||
@@ -13641,18 +13587,20 @@ impl Editor {
|
||||
cx,
|
||||
)
|
||||
};
|
||||
|
||||
if file_exists {
|
||||
let buffer_store = project.read(cx).buffer_store().clone();
|
||||
buffer_store
|
||||
.update(cx, |buffer_store, cx| buffer_store.save_buffer(buffer, cx))
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
let recv = repo
|
||||
.read(cx)
|
||||
.set_index_text(&path, new_index_text.map(|rope| rope.to_string()));
|
||||
|
||||
cx.background_spawn(async move { recv.await? })
|
||||
.detach_and_notify_err(window, cx);
|
||||
cx.background_spawn(
|
||||
repo.read(cx)
|
||||
.set_index_text(&path, new_index_text.map(|rope| rope.to_string()))
|
||||
.log_err(),
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
|
||||
@@ -15238,16 +15186,8 @@ impl Editor {
|
||||
.retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
|
||||
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
|
||||
}
|
||||
multi_buffer::Event::ExcerptsEdited {
|
||||
excerpt_ids,
|
||||
buffer_ids,
|
||||
} => {
|
||||
self.display_map.update(cx, |map, cx| {
|
||||
map.unfold_buffers(buffer_ids.iter().copied(), cx)
|
||||
});
|
||||
cx.emit(EditorEvent::ExcerptsEdited {
|
||||
ids: excerpt_ids.clone(),
|
||||
})
|
||||
multi_buffer::Event::ExcerptsEdited { ids } => {
|
||||
cx.emit(EditorEvent::ExcerptsEdited { ids: ids.clone() })
|
||||
}
|
||||
multi_buffer::Event::ExcerptsExpanded { ids } => {
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
@@ -15864,12 +15804,11 @@ impl Editor {
|
||||
&mut self,
|
||||
event: FocusOutEvent,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
_cx: &mut Context<Self>,
|
||||
) {
|
||||
if event.blurred != self.focus_handle {
|
||||
self.last_focused_descendant = Some(event.blurred);
|
||||
}
|
||||
self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
|
||||
}
|
||||
|
||||
pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -15925,16 +15864,13 @@ impl Editor {
|
||||
FILE_HEADER_HEIGHT
|
||||
}
|
||||
|
||||
pub fn restore(
|
||||
pub fn revert(
|
||||
&mut self,
|
||||
revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let workspace = self.workspace();
|
||||
let project = self.project.as_ref();
|
||||
let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
|
||||
let mut tasks = Vec::new();
|
||||
self.buffer().update(cx, |multi_buffer, cx| {
|
||||
for (buffer_id, changes) in revert_changes {
|
||||
if let Some(buffer) = multi_buffer.buffer(buffer_id) {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
@@ -15946,44 +15882,9 @@ impl Editor {
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
if let Some(project) =
|
||||
project.filter(|_| multi_buffer.all_diff_hunks_expanded())
|
||||
{
|
||||
project.update(cx, |project, cx| {
|
||||
tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks
|
||||
});
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
for (buffer, task) in save_tasks {
|
||||
let result = task.await;
|
||||
if result.is_err() {
|
||||
let Some(path) = buffer
|
||||
.read_with(&cx, |buffer, cx| buffer.project_path(cx))
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if let Some((workspace, path)) = workspace.as_ref().zip(path) {
|
||||
let Some(task) = cx
|
||||
.update_window_entity(&workspace, |workspace, window, cx| {
|
||||
workspace
|
||||
.open_path_preview(path, None, false, false, false, window, cx)
|
||||
})
|
||||
.ok()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
task.await.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
self.change_selections(None, window, cx, |selections| selections.refresh());
|
||||
}
|
||||
|
||||
@@ -17393,7 +17294,7 @@ impl Focusable for Editor {
|
||||
}
|
||||
|
||||
impl Render for Editor {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||
fn render<'a>(&mut self, _: &mut Window, cx: &mut Context<'a, Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
|
||||
let mut text_style = match self.mode {
|
||||
@@ -17834,6 +17735,7 @@ pub fn diagnostic_block_renderer(
|
||||
diagnostic: Diagnostic,
|
||||
max_message_rows: Option<u8>,
|
||||
allow_closing: bool,
|
||||
_is_valid: bool,
|
||||
) -> RenderBlock {
|
||||
let (text_without_backticks, code_ranges) =
|
||||
highlight_diagnostic_message(&diagnostic, max_message_rows);
|
||||
|
||||
@@ -177,7 +177,7 @@ impl<'de> Deserialize<'de> for ScrollbarDiagnostics {
|
||||
{
|
||||
struct Visitor;
|
||||
|
||||
impl serde::de::Visitor<'_> for Visitor {
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = ScrollbarDiagnostics;
|
||||
|
||||
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
|
||||
@@ -10970,106 +10970,6 @@ async fn cycle_through_same_place_diagnostics(
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn active_diagnostics_dismiss_after_invalidation(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let lsp_store =
|
||||
cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
|
||||
|
||||
cx.set_state(indoc! {"
|
||||
ˇfn func(abc def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
let message = "Something's wrong!";
|
||||
cx.update(|_, cx| {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: vec![lsp::Diagnostic {
|
||||
range: lsp::Range::new(
|
||||
lsp::Position::new(0, 11),
|
||||
lsp::Position::new(0, 12),
|
||||
),
|
||||
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||
message: message.to_string(),
|
||||
..Default::default()
|
||||
}],
|
||||
},
|
||||
&[],
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
assert_eq!(
|
||||
editor
|
||||
.active_diagnostics
|
||||
.as_ref()
|
||||
.map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
|
||||
Some(message),
|
||||
"Should have a diagnostics group activated"
|
||||
);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update(|_, cx| {
|
||||
lsp_store.update(cx, |lsp_store, cx| {
|
||||
lsp_store
|
||||
.update_diagnostics(
|
||||
LanguageServerId(0),
|
||||
lsp::PublishDiagnosticsParams {
|
||||
uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
|
||||
version: None,
|
||||
diagnostics: Vec::new(),
|
||||
},
|
||||
&[],
|
||||
cx,
|
||||
)
|
||||
.unwrap()
|
||||
});
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, _, _| {
|
||||
assert_eq!(
|
||||
editor.active_diagnostics, None,
|
||||
"After no diagnostics set to the editor, no diagnostics should be active"
|
||||
);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
|
||||
assert_eq!(
|
||||
editor.active_diagnostics, None,
|
||||
"Should be no diagnostics to go to and activate"
|
||||
);
|
||||
});
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn func(abcˇ def: i32) -> u32 {
|
||||
}
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -15084,78 +14984,6 @@ async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestApp
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_toggle_deletion_hunk_at_start_of_file(
|
||||
executor: BackgroundExecutor,
|
||||
cx: &mut TestAppContext,
|
||||
) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let diff_base = r#"
|
||||
a
|
||||
b
|
||||
c
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.set_state(
|
||||
&r#"
|
||||
ˇb
|
||||
c
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.set_head_text(&diff_base);
|
||||
cx.update_editor(|editor, window, cx| {
|
||||
editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
let hunk_expanded = r#"
|
||||
- a
|
||||
ˇb
|
||||
c
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.assert_state_with_diff(hunk_expanded.clone());
|
||||
|
||||
let hunk_ranges = cx.update_editor(|editor, window, cx| {
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
let hunks = editor
|
||||
.diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
|
||||
let buffer_id = hunks[0].buffer_id;
|
||||
hunks
|
||||
.into_iter()
|
||||
.map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunk_ranges.len(), 1);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
let hunk_collapsed = r#"
|
||||
ˇb
|
||||
c
|
||||
"#
|
||||
.unindent();
|
||||
|
||||
cx.assert_state_with_diff(hunk_collapsed);
|
||||
|
||||
cx.update_editor(|editor, _, cx| {
|
||||
editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_state_with_diff(hunk_expanded.clone());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_display_diff_hunks(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
@@ -15716,7 +15544,7 @@ async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_folding_buffers(cx: &mut TestAppContext) {
|
||||
async fn test_multi_buffer_folding(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
|
||||
@@ -15823,7 +15651,7 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
|
||||
let multi_buffer_editor = cx.new_window_entity(|window, cx| {
|
||||
Editor::new(
|
||||
EditorMode::Full,
|
||||
multi_buffer.clone(),
|
||||
multi_buffer,
|
||||
Some(project.clone()),
|
||||
true,
|
||||
window,
|
||||
@@ -15831,9 +15659,10 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
|
||||
)
|
||||
});
|
||||
|
||||
let full_text = "\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n";
|
||||
assert_eq!(
|
||||
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
|
||||
full_text,
|
||||
);
|
||||
|
||||
multi_buffer_editor.update(cx, |editor, cx| {
|
||||
@@ -15879,25 +15708,12 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
|
||||
"After unfolding the second buffer, its text should be displayed"
|
||||
);
|
||||
|
||||
// Typing inside of buffer 1 causes that buffer to be unfolded.
|
||||
multi_buffer_editor.update_in(cx, |editor, window, cx| {
|
||||
assert_eq!(
|
||||
multi_buffer
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.text_for_range(Point::new(1, 0)..Point::new(1, 4))
|
||||
.collect::<String>(),
|
||||
"bbbb"
|
||||
);
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
|
||||
});
|
||||
editor.handle_input("B", window, cx);
|
||||
multi_buffer_editor.update(cx, |editor, cx| {
|
||||
editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
|
||||
"\n\n\naaaa\nbbbb\ncccc\n\n\n\nffff\ngggg\n\n\n\njjjj\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n",
|
||||
"After unfolding the first buffer, its and 2nd buffer's text should be displayed"
|
||||
);
|
||||
|
||||
@@ -15906,13 +15722,13 @@ async fn test_folding_buffers(cx: &mut TestAppContext) {
|
||||
});
|
||||
assert_eq!(
|
||||
multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
|
||||
"\n\n\nB\n\n\n\n\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\n\nqqqq\nrrrr\n\n\n\nuuuu\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n\n1111\n2222\n\n\n\n5555\n",
|
||||
full_text,
|
||||
"After unfolding the all buffers, all original text should be displayed"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
|
||||
async fn test_multi_buffer_single_excerpts_folding(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let sample_text_1 = "1111\n2222\n3333".to_string();
|
||||
@@ -16061,7 +15877,7 @@ async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
|
||||
async fn test_multi_buffer_with_single_excerpt_folding(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::{
|
||||
hover_popover::{
|
||||
self, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT,
|
||||
},
|
||||
inlay_hint_settings,
|
||||
items::BufferSearchHighlights,
|
||||
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
||||
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
|
||||
@@ -20,17 +19,16 @@ use crate::{
|
||||
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
|
||||
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
|
||||
GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
|
||||
InlayHintRefreshReason, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown,
|
||||
PageUp, Point, RowExt, RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap,
|
||||
StickyHeaderExcerpt, ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
|
||||
FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
|
||||
MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt,
|
||||
RowRangeExt, SelectPhase, SelectedTextHighlight, Selection, SoftWrap, StickyHeaderExcerpt,
|
||||
ToPoint, ToggleFold, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
|
||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
|
||||
};
|
||||
use buffer_diff::{DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
|
||||
use client::ParticipantIndex;
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use file_icons::FileIcons;
|
||||
use git::{blame::BlameEntry, status::FileStatus, Oid};
|
||||
use git::{blame::BlameEntry, Oid};
|
||||
use gpui::{
|
||||
anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad,
|
||||
relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds,
|
||||
@@ -75,7 +73,7 @@ use ui::{
|
||||
POPOVER_Y_PADDING,
|
||||
};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use util::{debug_panic, maybe, RangeExt, ResultExt};
|
||||
use util::{debug_panic, RangeExt, ResultExt};
|
||||
use workspace::{item::Item, notifications::NotifyTaskExt};
|
||||
|
||||
const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 7.;
|
||||
@@ -507,25 +505,6 @@ impl EditorElement {
|
||||
return;
|
||||
}
|
||||
editor.update(cx, |editor, cx| {
|
||||
let inlay_hint_settings = inlay_hint_settings(
|
||||
editor.selections.newest_anchor().head(),
|
||||
&editor.buffer.read(cx).snapshot(cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
if let Some(inlay_modifiers) = inlay_hint_settings
|
||||
.toggle_on_modifiers_press
|
||||
.as_ref()
|
||||
.filter(|modifiers| modifiers.modified())
|
||||
{
|
||||
editor.refresh_inlay_hints(
|
||||
InlayHintRefreshReason::ModifiersChanged(
|
||||
inlay_modifiers == &event.modifiers,
|
||||
),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
if editor.hover_state.focused(window, cx) {
|
||||
return;
|
||||
}
|
||||
@@ -2667,21 +2646,6 @@ impl EditorElement {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Div {
|
||||
let file_status = maybe!({
|
||||
let project = self.editor.read(cx).project.as_ref()?.read(cx);
|
||||
let (repo, path) =
|
||||
project.repository_and_path_for_buffer_id(for_excerpt.buffer_id, cx)?;
|
||||
let status = repo.read(cx).repository_entry.status_for_path(&path)?;
|
||||
Some(status.status)
|
||||
})
|
||||
.filter(|_| {
|
||||
self.editor
|
||||
.read(cx)
|
||||
.buffer
|
||||
.read(cx)
|
||||
.all_diff_hunks_expanded()
|
||||
});
|
||||
|
||||
let include_root = self
|
||||
.editor
|
||||
.read(cx)
|
||||
@@ -2785,36 +2749,12 @@ impl EditorElement {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Label::new(
|
||||
filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into()),
|
||||
)
|
||||
.single_line()
|
||||
.when_some(
|
||||
file_status,
|
||||
|el, status| {
|
||||
el.color(if status.is_conflicted() {
|
||||
Color::Conflict
|
||||
} else if status.is_modified() {
|
||||
Color::Modified
|
||||
} else if status.is_deleted() {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Created
|
||||
})
|
||||
.when(status.is_deleted(), |el| el.strikethrough())
|
||||
},
|
||||
),
|
||||
filename
|
||||
.map(SharedString::from)
|
||||
.unwrap_or_else(|| "untitled".into()),
|
||||
)
|
||||
.when_some(parent_path, |then, path| {
|
||||
then.child(div().child(path).text_color(
|
||||
if file_status.is_some_and(FileStatus::is_deleted) {
|
||||
colors.text_disabled
|
||||
} else {
|
||||
colors.text_muted
|
||||
},
|
||||
))
|
||||
then.child(div().child(path).text_color(colors.text_muted))
|
||||
}),
|
||||
)
|
||||
.when(is_selected, |el| {
|
||||
@@ -8780,12 +8720,11 @@ fn diff_hunk_controls(
|
||||
})
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
move |_event, window, cx| {
|
||||
move |_event, _, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.stage_or_unstage_diff_hunks(
|
||||
false,
|
||||
&[hunk_range.start..hunk_range.start],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
@@ -8810,12 +8749,11 @@ fn diff_hunk_controls(
|
||||
})
|
||||
.on_click({
|
||||
let editor = editor.clone();
|
||||
move |_event, window, cx| {
|
||||
move |_event, _, cx| {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.stage_or_unstage_diff_hunks(
|
||||
true,
|
||||
&[hunk_range.start..hunk_range.start],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1271,7 +1271,6 @@ mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1536,7 +1536,6 @@ mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ pub struct InlayHintCache {
|
||||
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
|
||||
version: usize,
|
||||
pub(super) enabled: bool,
|
||||
modifiers_override: bool,
|
||||
enabled_in_settings: bool,
|
||||
update_tasks: HashMap<ExcerptId, TasksForRanges>,
|
||||
refresh_task: Task<()>,
|
||||
@@ -266,7 +265,6 @@ impl InlayHintCache {
|
||||
Self {
|
||||
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
|
||||
enabled: inlay_hint_settings.enabled,
|
||||
modifiers_override: false,
|
||||
enabled_in_settings: inlay_hint_settings.enabled,
|
||||
hints: HashMap::default(),
|
||||
update_tasks: HashMap::default(),
|
||||
@@ -297,9 +295,8 @@ impl InlayHintCache {
|
||||
// visibility would not change when updating the setting if they were ever toggled.
|
||||
if new_hint_settings.enabled != self.enabled_in_settings {
|
||||
self.enabled = new_hint_settings.enabled;
|
||||
self.enabled_in_settings = new_hint_settings.enabled;
|
||||
self.modifiers_override = false;
|
||||
};
|
||||
self.enabled_in_settings = new_hint_settings.enabled;
|
||||
self.invalidate_debounce = debounce_value(new_hint_settings.edit_debounce_ms);
|
||||
self.append_debounce = debounce_value(new_hint_settings.scroll_debounce_ms);
|
||||
let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds();
|
||||
@@ -326,7 +323,6 @@ impl InlayHintCache {
|
||||
}
|
||||
}
|
||||
(true, false) => {
|
||||
self.modifiers_override = false;
|
||||
self.allowed_hint_kinds = new_allowed_hint_kinds;
|
||||
if self.hints.is_empty() {
|
||||
ControlFlow::Break(None)
|
||||
@@ -339,39 +335,12 @@ impl InlayHintCache {
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
self.modifiers_override = false;
|
||||
self.allowed_hint_kinds = new_allowed_hint_kinds;
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn modifiers_override(&mut self, new_override: bool) -> Option<bool> {
|
||||
if self.modifiers_override == new_override {
|
||||
return None;
|
||||
}
|
||||
self.modifiers_override = new_override;
|
||||
if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
|
||||
{
|
||||
self.clear();
|
||||
Some(false)
|
||||
} else {
|
||||
Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn toggle(&mut self, enabled: bool) -> bool {
|
||||
if self.enabled == enabled {
|
||||
return false;
|
||||
}
|
||||
self.enabled = enabled;
|
||||
self.modifiers_override = false;
|
||||
if !enabled {
|
||||
self.clear();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// If needed, queries LSP for new inlay hints, using the invalidation strategy given.
|
||||
/// To reduce inlay hint jumping, attempts to query a visible range of the editor(s) first,
|
||||
/// followed by the delayed queries of the same range above and below the visible one.
|
||||
@@ -384,8 +353,7 @@ impl InlayHintCache {
|
||||
ignore_debounce: bool,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Option<InlaySplice> {
|
||||
if (self.enabled && self.modifiers_override) || (!self.enabled && !self.modifiers_override)
|
||||
{
|
||||
if !self.enabled {
|
||||
return None;
|
||||
}
|
||||
let mut invalidated_hints = Vec::new();
|
||||
@@ -1320,7 +1288,6 @@ pub mod tests {
|
||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
let (_, editor, fake_server) = prepare_test_objects(cx, |fake_server, file_with_hints| {
|
||||
@@ -1424,7 +1391,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1527,7 +1493,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1747,7 +1712,6 @@ pub mod tests {
|
||||
show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1907,7 +1871,6 @@ pub mod tests {
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: new_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -1950,7 +1913,6 @@ pub mod tests {
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: another_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -2005,7 +1967,6 @@ pub mod tests {
|
||||
.contains(&Some(InlayHintKind::Parameter)),
|
||||
show_other_hints: final_allowed_hint_kinds.contains(&None),
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -2077,7 +2038,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2209,7 +2169,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2508,7 +2467,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2853,7 +2811,6 @@ pub mod tests {
|
||||
show_parameter_hints: false,
|
||||
show_other_hints: false,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3035,7 +2992,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -3067,7 +3023,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3159,7 +3114,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3233,7 +3187,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
@@ -3293,7 +3246,6 @@ pub mod tests {
|
||||
show_parameter_hints: true,
|
||||
show_other_hints: true,
|
||||
show_background: false,
|
||||
toggle_on_modifiers_press: None,
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1739,7 +1739,6 @@ mod tests {
|
||||
let file = TestFile {
|
||||
path: Path::new("").into(),
|
||||
root_name: String::new(),
|
||||
local_root: None,
|
||||
};
|
||||
assert_eq!(path_for_file(&file, 0, false, cx), None);
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ pub fn indented_line_beginning(
|
||||
}
|
||||
|
||||
/// Returns a position of the end of line.
|
||||
///
|
||||
|
||||
/// If `stop_at_soft_boundaries` is true, the returned position is that of the
|
||||
/// displayed line (e.g. it could actually be in the middle of a text line if that line is soft-wrapped).
|
||||
/// Otherwise it's always going to be the end of a logical line.
|
||||
|
||||
@@ -843,14 +843,14 @@ impl<'a> MutableSelectionsCollection<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for MutableSelectionsCollection<'_> {
|
||||
impl<'a> Deref for MutableSelectionsCollection<'a> {
|
||||
type Target = SelectionsCollection;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.collection
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for MutableSelectionsCollection<'_> {
|
||||
impl<'a> DerefMut for MutableSelectionsCollection<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.collection
|
||||
}
|
||||
|
||||
@@ -1,73 +1,93 @@
|
||||
use crate::Editor;
|
||||
|
||||
use gpui::{App, Task, Window};
|
||||
use gpui::{App, AppContext as _, Task as AsyncTask, Window};
|
||||
use project::Location;
|
||||
use task::{TaskContext, TaskVariables, VariableName};
|
||||
use text::{ToOffset, ToPoint};
|
||||
use workspace::Workspace;
|
||||
|
||||
impl Editor {
|
||||
pub fn task_context(&self, window: &mut Window, cx: &mut App) -> Task<Option<TaskContext>> {
|
||||
let Some(project) = self.project.clone() else {
|
||||
return Task::ready(None);
|
||||
fn task_context_with_editor(
|
||||
editor: &mut Editor,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AsyncTask<Option<TaskContext>> {
|
||||
let Some(project) = editor.project.clone() else {
|
||||
return AsyncTask::ready(None);
|
||||
};
|
||||
let (selection, buffer, editor_snapshot) = {
|
||||
let selection = editor.selections.newest_adjusted(cx);
|
||||
let Some((buffer, _)) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)
|
||||
else {
|
||||
return AsyncTask::ready(None);
|
||||
};
|
||||
let (selection, buffer, editor_snapshot) = {
|
||||
let selection = self.selections.newest_adjusted(cx);
|
||||
let Some((buffer, _)) = self
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)
|
||||
else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
(selection, buffer, snapshot)
|
||||
};
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
let captured_variables = {
|
||||
let mut variables = TaskVariables::default();
|
||||
let buffer = location.buffer.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let snapshot = buffer.snapshot();
|
||||
let starting_point = location.range.start.to_point(&snapshot);
|
||||
let starting_offset = starting_point.to_offset(&snapshot);
|
||||
for (_, tasks) in self
|
||||
.tasks
|
||||
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
|
||||
let snapshot = editor.snapshot(window, cx);
|
||||
(selection, buffer, snapshot)
|
||||
};
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
let captured_variables = {
|
||||
let mut variables = TaskVariables::default();
|
||||
let buffer = location.buffer.read(cx);
|
||||
let buffer_id = buffer.remote_id();
|
||||
let snapshot = buffer.snapshot();
|
||||
let starting_point = location.range.start.to_point(&snapshot);
|
||||
let starting_offset = starting_point.to_offset(&snapshot);
|
||||
for (_, tasks) in editor
|
||||
.tasks
|
||||
.range((buffer_id, 0)..(buffer_id, starting_point.row + 1))
|
||||
{
|
||||
if !tasks
|
||||
.context_range
|
||||
.contains(&crate::BufferOffset(starting_offset))
|
||||
{
|
||||
if !tasks
|
||||
.context_range
|
||||
.contains(&crate::BufferOffset(starting_offset))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (capture_name, value) in tasks.extra_variables.iter() {
|
||||
variables.insert(
|
||||
VariableName::Custom(capture_name.to_owned().into()),
|
||||
value.clone(),
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
variables
|
||||
};
|
||||
for (capture_name, value) in tasks.extra_variables.iter() {
|
||||
variables.insert(
|
||||
VariableName::Custom(capture_name.to_owned().into()),
|
||||
value.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
variables
|
||||
};
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.task_store().update(cx, |task_store, cx| {
|
||||
task_store.task_context_for_location(captured_variables, location, cx)
|
||||
})
|
||||
project.update(cx, |project, cx| {
|
||||
project.task_store().update(cx, |task_store, cx| {
|
||||
task_store.task_context_for_location(captured_variables, location, cx)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn task_context(
|
||||
workspace: &Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AsyncTask<TaskContext> {
|
||||
let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
else {
|
||||
return AsyncTask::ready(TaskContext::default());
|
||||
};
|
||||
editor.update(cx, |editor, cx| {
|
||||
let context_task = task_context_with_editor(editor, window, cx);
|
||||
cx.background_spawn(async move { context_task.await.unwrap_or_default() })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -275,11 +275,7 @@ async fn run_evaluation(
|
||||
let db_path = Path::new(EVAL_DB_PATH);
|
||||
let api_key = std::env::var("OPENAI_API_KEY").unwrap();
|
||||
let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
|
||||
let fs = Arc::new(RealFs::new(
|
||||
git_hosting_provider_registry,
|
||||
None,
|
||||
PathBuf::from("/non/existent/askpass"),
|
||||
)) as Arc<dyn Fs>;
|
||||
let fs = Arc::new(RealFs::new(git_hosting_provider_registry, None)) as Arc<dyn Fs>;
|
||||
let clock = Arc::new(RealSystemClock);
|
||||
let client = cx
|
||||
.update(|cx| {
|
||||
|
||||
@@ -181,10 +181,7 @@ pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
|
||||
}
|
||||
|
||||
fn extension() -> &'static mut dyn Extension {
|
||||
#[expect(static_mut_refs)]
|
||||
unsafe {
|
||||
EXTENSION.as_deref_mut().unwrap()
|
||||
}
|
||||
unsafe { EXTENSION.as_deref_mut().unwrap() }
|
||||
}
|
||||
|
||||
static mut EXTENSION: Option<Box<dyn Extension>> = None;
|
||||
|
||||
@@ -78,9 +78,10 @@ impl HeadlessExtensionStore {
|
||||
if e.dev {
|
||||
return true;
|
||||
}
|
||||
self.loaded_extensions
|
||||
!self
|
||||
.loaded_extensions
|
||||
.get(e.id.as_str())
|
||||
.is_none_or(|loaded| loaded.as_ref() != e.version.as_str())
|
||||
.is_some_and(|loaded| loaded.as_ref() == e.version.as_str())
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ impl Extension {
|
||||
.call_labels_for_completions(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&completions.into_iter().collect::<Vec<_>>(),
|
||||
&completions.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
@@ -402,7 +402,7 @@ impl Extension {
|
||||
.call_labels_for_symbols(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&symbols.into_iter().collect::<Vec<_>>(),
|
||||
&symbols.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
)
|
||||
.await?
|
||||
.map(|labels| {
|
||||
|
||||
@@ -1195,7 +1195,6 @@ impl PickerDelegate for FileFinderDelegate {
|
||||
None,
|
||||
true,
|
||||
allow_preview,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
@@ -1451,9 +1450,9 @@ impl<'a> PathComponentSlice<'a> {
|
||||
matches.next();
|
||||
}
|
||||
if is_first_normal || is_last || !is_normal || contains_match {
|
||||
if longest
|
||||
if !longest
|
||||
.as_ref()
|
||||
.is_none_or(|old| old.end - old.start <= cur.end - cur.start)
|
||||
.is_some_and(|old| old.end - old.start > cur.end - cur.start)
|
||||
{
|
||||
longest = Some(cur);
|
||||
}
|
||||
@@ -1462,9 +1461,9 @@ impl<'a> PathComponentSlice<'a> {
|
||||
cur.end = i + 1;
|
||||
}
|
||||
}
|
||||
if longest
|
||||
if !longest
|
||||
.as_ref()
|
||||
.is_none_or(|old| old.end - old.start <= cur.end - cur.start)
|
||||
.is_some_and(|old| old.end - old.start > cur.end - cur.start)
|
||||
{
|
||||
longest = Some(cur);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ impl Match {
|
||||
fn styled_text(&self, project: &Project, window: &Window, cx: &App) -> StyledText {
|
||||
let mut text = "./".to_string();
|
||||
let mut highlights = Vec::new();
|
||||
let mut offset = text.len();
|
||||
let mut offset = text.as_bytes().len();
|
||||
|
||||
let separator = '/';
|
||||
let dir_indicator = "[…]";
|
||||
@@ -125,7 +125,7 @@ impl Match {
|
||||
highlights.push((range.start + offset..range.end + offset, style))
|
||||
}
|
||||
text.push(separator);
|
||||
offset = text.len();
|
||||
offset = text.as_bytes().len();
|
||||
|
||||
if let Some(suffix) = &self.suffix {
|
||||
text.push_str(suffix);
|
||||
@@ -140,10 +140,10 @@ impl Match {
|
||||
Color::Created
|
||||
};
|
||||
highlights.push((
|
||||
offset..offset + suffix.len(),
|
||||
offset..offset + suffix.as_bytes().len(),
|
||||
HighlightStyle::color(color.color(cx)),
|
||||
));
|
||||
offset += suffix.len();
|
||||
offset += suffix.as_bytes().len();
|
||||
if entry.is_some_and(|e| e.is_dir()) {
|
||||
text.push(separator);
|
||||
offset += separator.len_utf8();
|
||||
@@ -165,7 +165,7 @@ impl Match {
|
||||
text.push_str(suffix);
|
||||
let existing_prefix_len = self
|
||||
.existing_prefix(project, cx)
|
||||
.map(|prefix| prefix.to_string_lossy().len())
|
||||
.map(|prefix| prefix.to_string_lossy().as_bytes().len())
|
||||
.unwrap_or(0);
|
||||
|
||||
if existing_prefix_len > 0 {
|
||||
@@ -175,14 +175,14 @@ impl Match {
|
||||
));
|
||||
}
|
||||
highlights.push((
|
||||
offset + existing_prefix_len..offset + suffix.len(),
|
||||
offset + existing_prefix_len..offset + suffix.as_bytes().len(),
|
||||
HighlightStyle::color(if self.entry(project, cx).is_some() {
|
||||
Color::Conflict.color(cx)
|
||||
} else {
|
||||
Color::Created.color(cx)
|
||||
}),
|
||||
));
|
||||
offset += suffix.len();
|
||||
offset += suffix.as_bytes().len();
|
||||
if suffix.ends_with('/') {
|
||||
text.push_str(dir_indicator);
|
||||
highlights.push((
|
||||
|
||||
@@ -248,7 +248,6 @@ impl From<MTime> for proto::Timestamp {
|
||||
pub struct RealFs {
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
askpass_path: PathBuf,
|
||||
}
|
||||
|
||||
pub trait FileHandle: Send + Sync + std::fmt::Debug {
|
||||
@@ -303,12 +302,10 @@ impl RealFs {
|
||||
pub fn new(
|
||||
git_hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
askpass_path: PathBuf,
|
||||
) -> Self {
|
||||
Self {
|
||||
git_hosting_provider_registry,
|
||||
git_binary_path,
|
||||
askpass_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,7 +769,6 @@ impl Fs for RealFs {
|
||||
Some(Arc::new(RealGitRepository::new(
|
||||
repo,
|
||||
self.git_binary_path.clone(),
|
||||
self.askpass_path.to_owned(),
|
||||
self.git_hosting_provider_registry.clone(),
|
||||
)))
|
||||
}
|
||||
@@ -1265,7 +1261,7 @@ impl FakeFs {
|
||||
self.with_git_state(dot_git, true, |state| {
|
||||
let branch = branch.map(Into::into);
|
||||
state.branches.extend(branch.clone());
|
||||
state.current_branch_name = branch
|
||||
state.current_branch_name = branch.map(Into::into)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
|
||||
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||
|
||||
let num_cpus = executor.num_cpus().min(path_count);
|
||||
let segment_size = path_count.div_ceil(num_cpus);
|
||||
let segment_size = (path_count + num_cpus - 1) / num_cpus;
|
||||
let mut segment_results = (0..num_cpus)
|
||||
.map(|_| Vec::with_capacity(max_results))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -145,7 +145,7 @@ pub async fn match_strings(
|
||||
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||
|
||||
let num_cpus = executor.num_cpus().min(candidates.len());
|
||||
let segment_size = candidates.len().div_ceil(num_cpus);
|
||||
let segment_size = (candidates.len() + num_cpus - 1) / num_cpus;
|
||||
let mut segment_results = (0..num_cpus)
|
||||
.map(|_| Vec::with_capacity(max_results.min(candidates.len())))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -30,7 +30,6 @@ schemars.workspace = true
|
||||
serde.workspace = true
|
||||
smol.workspace = true
|
||||
sum_tree.workspace = true
|
||||
tempfile.workspace = true
|
||||
text.workspace = true
|
||||
time.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
@@ -344,7 +344,7 @@ mod tests {
|
||||
have_json.push('\n');
|
||||
|
||||
let update = std::env::var("UPDATE_GOLDEN")
|
||||
.map(|val| val.eq_ignore_ascii_case("true"))
|
||||
.map(|val| val.to_ascii_lowercase() == "true")
|
||||
.unwrap_or(false);
|
||||
|
||||
if update {
|
||||
|
||||
@@ -10,11 +10,8 @@ use rope::Rope;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
use std::borrow::Borrow;
|
||||
use std::env::temp_dir;
|
||||
use std::io::Write as _;
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::process::Stdio;
|
||||
use std::sync::LazyLock;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
@@ -203,7 +200,6 @@ impl std::fmt::Debug for dyn GitRepository {
|
||||
pub struct RealGitRepository {
|
||||
pub repository: Mutex<git2::Repository>,
|
||||
pub git_binary_path: PathBuf,
|
||||
pub askpass_path: PathBuf,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
}
|
||||
|
||||
@@ -211,13 +207,11 @@ impl RealGitRepository {
|
||||
pub fn new(
|
||||
repository: git2::Repository,
|
||||
git_binary_path: Option<PathBuf>,
|
||||
askpass_path: PathBuf,
|
||||
hosting_provider_registry: Arc<GitHostingProviderRegistry>,
|
||||
) -> Self {
|
||||
Self {
|
||||
repository: Mutex::new(repository),
|
||||
git_binary_path: git_binary_path.unwrap_or_else(|| PathBuf::from("git")),
|
||||
askpass_path,
|
||||
hosting_provider_registry,
|
||||
}
|
||||
}
|
||||
@@ -364,30 +358,24 @@ impl GitRepository for RealGitRepository {
|
||||
|
||||
log::debug!("indexing SHA: {sha}, path {path:?}");
|
||||
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
let status = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["update-index", "--add", "--cacheinfo", "100644", &sha])
|
||||
.arg(path.as_ref())
|
||||
.output()?;
|
||||
.status()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to stage:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
if !status.success() {
|
||||
return Err(anyhow!("Failed to add to index: {status:?}"));
|
||||
}
|
||||
} else {
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
let status = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["update-index", "--force-remove"])
|
||||
.arg(path.as_ref())
|
||||
.output()?;
|
||||
.status()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!(
|
||||
"Failed to unstage:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
if !status.success() {
|
||||
return Err(anyhow!("Failed to remove from index: {status:?}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,10 +602,7 @@ impl GitRepository for RealGitRepository {
|
||||
) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
// We don't use the bundled git, so we can ensure that system
|
||||
// credential management and transfer mechanisms are respected
|
||||
let output = new_std_command("git")
|
||||
.env("GIT_ASKPASS", &self.askpass_path)
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["push", "--quiet"])
|
||||
.args(options.map(|option| match option {
|
||||
@@ -633,20 +618,18 @@ impl GitRepository for RealGitRepository {
|
||||
"Failed to push:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: Get remote response out of this and show it to the user
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pull(&self, branch_name: &str, remote_name: &str) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
// We don't use the bundled git, so we can ensure that system
|
||||
// credential management and transfer mechanisms are respected
|
||||
let output = new_std_command("git")
|
||||
.env("GIT_ASKPASS", &self.askpass_path)
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["pull"])
|
||||
.args(["pull", "--quiet"])
|
||||
.arg(remote_name)
|
||||
.arg(branch_name)
|
||||
.output()?;
|
||||
@@ -656,18 +639,16 @@ impl GitRepository for RealGitRepository {
|
||||
"Failed to pull:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO: Get remote response out of this and show it to the user
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fetch(&self) -> Result<()> {
|
||||
let working_directory = self.working_directory()?;
|
||||
|
||||
// We don't use the bundled git, so we can ensure that system
|
||||
// credential management and transfer mechanisms are respected
|
||||
let output = new_std_command("git")
|
||||
.env("GIT_ASKPASS", &self.askpass_path)
|
||||
let output = new_std_command(&self.git_binary_path)
|
||||
.current_dir(&working_directory)
|
||||
.args(["fetch", "--quiet", "--all"])
|
||||
.output()?;
|
||||
@@ -677,9 +658,10 @@ impl GitRepository for RealGitRepository {
|
||||
"Failed to fetch:\n{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO: Get remote response out of this and show it to the user
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_remotes(&self, branch_name: Option<&str>) -> Result<Vec<Remote>> {
|
||||
@@ -1034,7 +1016,7 @@ impl Borrow<Path> for RepoPath {
|
||||
#[derive(Debug)]
|
||||
pub struct RepoPathDescendants<'a>(pub &'a Path);
|
||||
|
||||
impl MapSeekTarget<RepoPath> for RepoPathDescendants<'_> {
|
||||
impl<'a> MapSeekTarget<RepoPath> for RepoPathDescendants<'a> {
|
||||
fn cmp_cursor(&self, key: &RepoPath) -> Ordering {
|
||||
if key.starts_with(self.0) {
|
||||
Ordering::Greater
|
||||
|
||||
@@ -12,10 +12,6 @@ workspace = true
|
||||
name = "git_ui"
|
||||
path = "src/git_ui.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
test-support = ["multi_buffer/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
@@ -51,10 +47,6 @@ zed_actions.workspace = true
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
project = { workspace = true, features = ["test-support"] }
|
||||
settings = { workspace = true, features = ["test-support"] }
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
[features]
|
||||
default = []
|
||||
test-support = ["multi_buffer/test-support"]
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
|
||||
use git::repository::Branch;
|
||||
use gpui::{
|
||||
rems, App, AsyncApp, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Subscription,
|
||||
Task, Window,
|
||||
Task, WeakEntity, Window,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use project::{Project, ProjectPath};
|
||||
use project::ProjectPath;
|
||||
use std::sync::Arc;
|
||||
use ui::{
|
||||
prelude::*, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenuHandle, TriggerablePopover,
|
||||
};
|
||||
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::notifications::DetachAndPromptErr;
|
||||
use workspace::{ModalView, Workspace};
|
||||
@@ -25,29 +23,19 @@ pub fn init(cx: &mut App) {
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
workspace: &mut Workspace,
|
||||
_: &mut Workspace,
|
||||
_: &zed_actions::git::Branch,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
let project = workspace.project().clone();
|
||||
let this = cx.entity();
|
||||
let style = BranchListStyle::Modal;
|
||||
let this = cx.entity().clone();
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
// Modal branch picker has a longer trailoff than a popover one.
|
||||
let delegate = BranchListDelegate::new(project.clone(), style, 70, &cx).await?;
|
||||
let delegate = BranchListDelegate::new(this.clone(), 70, &cx).await?;
|
||||
|
||||
this.update_in(&mut cx, move |workspace, window, cx| {
|
||||
this.update_in(&mut cx, |workspace, window, cx| {
|
||||
workspace.toggle_modal(window, cx, |window, cx| {
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
});
|
||||
|
||||
let mut list = BranchList::new(project, style, 34., cx);
|
||||
list._subscription = Some(_subscription);
|
||||
list.picker = Some(picker);
|
||||
list
|
||||
BranchList::new(delegate, 34., window, cx)
|
||||
})
|
||||
})?;
|
||||
|
||||
@@ -56,86 +44,34 @@ pub fn open(
|
||||
.detach_and_prompt_err("Failed to read branches", window, cx, |_, _, _| None)
|
||||
}
|
||||
|
||||
pub fn popover(project: Entity<Project>, window: &mut Window, cx: &mut App) -> Entity<BranchList> {
|
||||
cx.new(|cx| {
|
||||
let mut list = BranchList::new(project, BranchListStyle::Popover, 15., cx);
|
||||
list.reload_branches(window, cx);
|
||||
list
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
enum BranchListStyle {
|
||||
Modal,
|
||||
Popover,
|
||||
}
|
||||
|
||||
pub struct BranchList {
|
||||
pub picker: Entity<Picker<BranchListDelegate>>,
|
||||
rem_width: f32,
|
||||
popover_handle: PopoverMenuHandle<Self>,
|
||||
default_focus_handle: FocusHandle,
|
||||
project: Entity<Project>,
|
||||
style: BranchListStyle,
|
||||
pub picker: Option<Entity<Picker<BranchListDelegate>>>,
|
||||
_subscription: Option<Subscription>,
|
||||
}
|
||||
|
||||
impl TriggerablePopover for BranchList {
|
||||
fn menu_handle(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
) -> PopoverMenuHandle<Self> {
|
||||
self.popover_handle.clone()
|
||||
}
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl BranchList {
|
||||
fn new(project: Entity<Project>, style: BranchListStyle, rem_width: f32, cx: &mut App) -> Self {
|
||||
let popover_handle = PopoverMenuHandle::default();
|
||||
pub fn new(
|
||||
delegate: BranchListDelegate,
|
||||
rem_width: f32,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
|
||||
let _subscription = cx.subscribe(&picker, |_, _, _, cx| cx.emit(DismissEvent));
|
||||
Self {
|
||||
project,
|
||||
picker: None,
|
||||
picker,
|
||||
rem_width,
|
||||
popover_handle,
|
||||
default_focus_handle: cx.focus_handle(),
|
||||
style,
|
||||
_subscription: None,
|
||||
_subscription,
|
||||
}
|
||||
}
|
||||
|
||||
fn reload_branches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let project = self.project.clone();
|
||||
let style = self.style;
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
let delegate = BranchListDelegate::new(project, style, 20, &cx).await?;
|
||||
let picker =
|
||||
cx.new_window_entity(|window, cx| Picker::uniform_list(delegate, window, cx))?;
|
||||
|
||||
this.update(&mut cx, |branch_list, cx| {
|
||||
let subscription =
|
||||
cx.subscribe(&picker, |_, _, _: &DismissEvent, cx| cx.emit(DismissEvent));
|
||||
|
||||
branch_list.picker = Some(picker);
|
||||
branch_list._subscription = Some(subscription);
|
||||
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
impl ModalView for BranchList {}
|
||||
impl EventEmitter<DismissEvent> for BranchList {}
|
||||
|
||||
impl Focusable for BranchList {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.picker
|
||||
.as_ref()
|
||||
.map(|picker| picker.focus_handle(cx))
|
||||
.unwrap_or_else(|| self.default_focus_handle.clone())
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,25 +79,12 @@ impl Render for BranchList {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.w(rems(self.rem_width))
|
||||
.map(|parent| match self.picker.as_ref() {
|
||||
Some(picker) => parent.child(picker.clone()).on_mouse_down_out({
|
||||
let picker = picker.clone();
|
||||
cx.listener(move |_, _, window, cx| {
|
||||
picker.update(cx, |this, cx| {
|
||||
this.cancel(&Default::default(), window, cx);
|
||||
})
|
||||
})
|
||||
}),
|
||||
None => parent.child(
|
||||
h_flex()
|
||||
.id("branch-picker-error")
|
||||
.on_click(
|
||||
cx.listener(|this, _, window, cx| this.reload_branches(window, cx)),
|
||||
)
|
||||
.child("Could not load branches.")
|
||||
.child("Click to retry"),
|
||||
),
|
||||
})
|
||||
.child(self.picker.clone())
|
||||
.on_mouse_down_out(cx.listener(|this, _, window, cx| {
|
||||
this.picker.update(cx, |this, cx| {
|
||||
this.cancel(&Default::default(), window, cx);
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,8 +108,7 @@ impl BranchEntry {
|
||||
pub struct BranchListDelegate {
|
||||
matches: Vec<BranchEntry>,
|
||||
all_branches: Vec<Branch>,
|
||||
project: Entity<Project>,
|
||||
style: BranchListStyle,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
selected_index: usize,
|
||||
last_query: String,
|
||||
/// Max length of branch name before we truncate it and add a trailing `...`.
|
||||
@@ -194,14 +116,13 @@ pub struct BranchListDelegate {
|
||||
}
|
||||
|
||||
impl BranchListDelegate {
|
||||
async fn new(
|
||||
project: Entity<Project>,
|
||||
style: BranchListStyle,
|
||||
pub async fn new(
|
||||
workspace: Entity<Workspace>,
|
||||
branch_name_trailoff_after: usize,
|
||||
cx: &AsyncApp,
|
||||
) -> Result<Self> {
|
||||
let all_branches_request = cx.update(|cx| {
|
||||
let project = project.read(cx);
|
||||
let project = workspace.read(cx).project().read(cx);
|
||||
let first_worktree = project
|
||||
.visible_worktrees(cx)
|
||||
.next()
|
||||
@@ -214,8 +135,7 @@ impl BranchListDelegate {
|
||||
|
||||
Ok(Self {
|
||||
matches: vec![],
|
||||
project,
|
||||
style,
|
||||
workspace: workspace.downgrade(),
|
||||
all_branches,
|
||||
selected_index: 0,
|
||||
last_query: Default::default(),
|
||||
@@ -334,12 +254,18 @@ impl PickerDelegate for BranchListDelegate {
|
||||
return;
|
||||
};
|
||||
|
||||
let current_branch = self.project.update(cx, |project, cx| {
|
||||
project
|
||||
.active_repository(cx)
|
||||
.and_then(|repo| repo.read(cx).current_branch())
|
||||
.map(|branch| branch.name.to_string())
|
||||
});
|
||||
let current_branch = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.active_repository(cx)
|
||||
.and_then(|repo| repo.read(cx).current_branch())
|
||||
.map(|branch| branch.name.to_string())
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
if current_branch == Some(branch.name().to_string()) {
|
||||
cx.emit(DismissEvent);
|
||||
@@ -350,7 +276,13 @@ impl PickerDelegate for BranchListDelegate {
|
||||
let branch = branch.clone();
|
||||
|picker, mut cx| async move {
|
||||
let branch_change_task = picker.update(&mut cx, |this, cx| {
|
||||
let project = this.delegate.project.read(cx);
|
||||
let workspace = this
|
||||
.delegate
|
||||
.workspace
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("workspace was dropped"))?;
|
||||
|
||||
let project = workspace.read(cx).project().read(cx);
|
||||
let branch_to_checkout = match branch {
|
||||
BranchEntry::Branch(branch) => branch.string,
|
||||
BranchEntry::History(string) => string,
|
||||
@@ -395,10 +327,6 @@ impl PickerDelegate for BranchListDelegate {
|
||||
Some(
|
||||
ListItem::new(SharedString::from(format!("vcs-menu-{ix}")))
|
||||
.inset(true)
|
||||
.spacing(match self.style {
|
||||
BranchListStyle::Modal => ListItemSpacing::default(),
|
||||
BranchListStyle::Popover => ListItemSpacing::ExtraDense,
|
||||
})
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.toggle_state(selected)
|
||||
.when(matches!(hit, BranchEntry::History(_)), |el| {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// #![allow(unused, dead_code)]
|
||||
|
||||
use crate::branch_picker::{self, BranchList};
|
||||
use crate::git_panel::{commit_message_editor, GitPanel};
|
||||
use git::Commit;
|
||||
use panel::{panel_button, panel_editor_style, panel_filled_button};
|
||||
use project::Project;
|
||||
use ui::{prelude::*, KeybindingHint, PopoverButton, Tooltip, TriggerablePopover};
|
||||
use ui::{prelude::*, KeybindingHint, Tooltip};
|
||||
|
||||
use editor::{Editor, EditorElement};
|
||||
use gpui::*;
|
||||
@@ -66,7 +64,6 @@ pub fn init(cx: &mut App) {
|
||||
}
|
||||
|
||||
pub struct CommitModal {
|
||||
branch_list: Entity<BranchList>,
|
||||
git_panel: Entity<GitPanel>,
|
||||
commit_editor: Entity<Editor>,
|
||||
restore_dock: RestoreDock,
|
||||
@@ -142,11 +139,9 @@ impl CommitModal {
|
||||
is_open,
|
||||
active_index,
|
||||
};
|
||||
|
||||
let project = workspace.project().clone();
|
||||
workspace.open_panel::<GitPanel>(window, cx);
|
||||
workspace.toggle_modal(window, cx, move |window, cx| {
|
||||
CommitModal::new(git_panel, restore_dock_position, project, window, cx)
|
||||
CommitModal::new(git_panel, restore_dock_position, window, cx)
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -154,7 +149,6 @@ impl CommitModal {
|
||||
fn new(
|
||||
git_panel: Entity<GitPanel>,
|
||||
restore_dock: RestoreDock,
|
||||
project: Entity<Project>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
@@ -165,7 +159,7 @@ impl CommitModal {
|
||||
git_panel.set_modal_open(true, cx);
|
||||
let buffer = git_panel.commit_message_buffer(cx).clone();
|
||||
let project = git_panel.project.clone();
|
||||
cx.new(|cx| commit_message_editor(buffer, None, project.clone(), false, window, cx))
|
||||
cx.new(|cx| commit_message_editor(buffer, project.clone(), false, window, cx))
|
||||
});
|
||||
|
||||
let commit_message = commit_editor.read(cx).text(cx);
|
||||
@@ -188,21 +182,14 @@ impl CommitModal {
|
||||
|
||||
let focus_handle = commit_editor.focus_handle(cx);
|
||||
|
||||
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
|
||||
if !this
|
||||
.branch_list
|
||||
.focus_handle(cx)
|
||||
.contains_focused(window, cx)
|
||||
{
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
cx.on_focus_out(&focus_handle, window, |_, _, _, cx| {
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
.detach();
|
||||
|
||||
let properties = ModalContainerProperties::new(window, 50);
|
||||
|
||||
Self {
|
||||
branch_list: branch_picker::popover(project.clone(), window, cx),
|
||||
git_panel,
|
||||
commit_editor,
|
||||
restore_dock,
|
||||
@@ -243,7 +230,7 @@ impl CommitModal {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let git_panel = self.git_panel.clone();
|
||||
|
||||
let (branch, tooltip, commit_label, co_authors) =
|
||||
@@ -251,12 +238,7 @@ impl CommitModal {
|
||||
let branch = git_panel
|
||||
.active_repository
|
||||
.as_ref()
|
||||
.and_then(|repo| {
|
||||
repo.read(cx)
|
||||
.repository_entry
|
||||
.branch()
|
||||
.map(|b| b.name.clone())
|
||||
})
|
||||
.and_then(|repo| repo.read(cx).current_branch().map(|b| b.name.clone()))
|
||||
.unwrap_or_else(|| "<no branch>".into());
|
||||
let tooltip = if git_panel.has_staged_changes() {
|
||||
"Commit staged changes"
|
||||
@@ -266,13 +248,13 @@ impl CommitModal {
|
||||
let title = if git_panel.has_staged_changes() {
|
||||
"Commit"
|
||||
} else {
|
||||
"Commit All"
|
||||
"Commit Tracked"
|
||||
};
|
||||
let co_authors = git_panel.render_co_authors(cx);
|
||||
(branch, tooltip, title, co_authors)
|
||||
});
|
||||
|
||||
let branch_picker_button = panel_button(branch)
|
||||
let branch_selector = panel_button(branch)
|
||||
.icon(IconName::GitBranch)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Placeholder)
|
||||
@@ -287,13 +269,6 @@ impl CommitModal {
|
||||
}))
|
||||
.style(ButtonStyle::Transparent);
|
||||
|
||||
let branch_picker = PopoverButton::new(
|
||||
self.branch_list.clone(),
|
||||
Corner::BottomLeft,
|
||||
branch_picker_button,
|
||||
Tooltip::for_action_title("Switch Branch", &zed_actions::git::Branch),
|
||||
);
|
||||
|
||||
let close_kb_hint =
|
||||
if let Some(close_kb) = ui::KeyBinding::for_action(&menu::Cancel, window, cx) {
|
||||
Some(
|
||||
@@ -328,12 +303,7 @@ impl CommitModal {
|
||||
.w_full()
|
||||
.h(px(self.properties.footer_height))
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(branch_picker.render(window, cx))
|
||||
.children(co_authors),
|
||||
)
|
||||
.child(h_flex().gap_1().child(branch_selector).children(co_authors))
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
h_flex()
|
||||
@@ -370,13 +340,6 @@ impl Render for CommitModal {
|
||||
.key_context("GitCommit")
|
||||
.on_action(cx.listener(Self::dismiss))
|
||||
.on_action(cx.listener(Self::commit))
|
||||
.on_action(
|
||||
cx.listener(|this, _: &zed_actions::git::Branch, window, cx| {
|
||||
this.branch_list.update(cx, |branch_list, cx| {
|
||||
branch_list.menu_handle(window, cx).toggle(window, cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
.elevation_3(cx)
|
||||
.overflow_hidden()
|
||||
.flex_none()
|
||||
|
||||
@@ -170,7 +170,7 @@ pub struct GitPanel {
|
||||
pending_remote_operations: RemoteOperations,
|
||||
pub(crate) active_repository: Option<Entity<Repository>>,
|
||||
commit_editor: Entity<Editor>,
|
||||
pub(crate) suggested_commit_message: Option<String>,
|
||||
suggested_commit_message: Option<String>,
|
||||
conflicted_count: usize,
|
||||
conflicted_staged_count: usize,
|
||||
current_modifiers: Modifiers,
|
||||
@@ -212,7 +212,6 @@ impl Drop for RemoteOperationGuard {
|
||||
|
||||
pub(crate) fn commit_message_editor(
|
||||
commit_message_buffer: Entity<Buffer>,
|
||||
placeholder: Option<&str>,
|
||||
project: Entity<Project>,
|
||||
in_panel: bool,
|
||||
window: &mut Window,
|
||||
@@ -233,8 +232,7 @@ pub(crate) fn commit_message_editor(
|
||||
commit_editor.set_show_gutter(false, cx);
|
||||
commit_editor.set_show_wrap_guides(false, cx);
|
||||
commit_editor.set_show_indent_guides(false, cx);
|
||||
let placeholder = placeholder.unwrap_or("Enter commit message");
|
||||
commit_editor.set_placeholder_text(placeholder, cx);
|
||||
commit_editor.set_placeholder_text("Enter commit message", cx);
|
||||
commit_editor
|
||||
}
|
||||
|
||||
@@ -262,7 +260,7 @@ impl GitPanel {
|
||||
// Once the active git repo is set, this buffer will be replaced.
|
||||
let temporary_buffer = cx.new(|cx| Buffer::local("", cx));
|
||||
let commit_editor = cx.new(|cx| {
|
||||
commit_message_editor(temporary_buffer, None, project.clone(), true, window, cx)
|
||||
commit_message_editor(temporary_buffer, project.clone(), true, window, cx)
|
||||
});
|
||||
commit_editor.update(cx, |editor, cx| {
|
||||
editor.clear(window, cx);
|
||||
@@ -553,7 +551,7 @@ impl GitPanel {
|
||||
}
|
||||
|
||||
fn select_first(&mut self, _: &SelectFirst, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if !self.entries.is_empty() {
|
||||
if self.entries.first().is_some() {
|
||||
self.selected_entry = Some(1);
|
||||
self.scroll_to_selected_entry(cx);
|
||||
}
|
||||
@@ -695,7 +693,7 @@ impl GitPanel {
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.open_path_preview(path, None, false, false, true, window, cx)
|
||||
.open_path_preview(path, None, false, false, window, cx)
|
||||
.detach_and_prompt_err("Failed to open file", window, cx, |e, _, _| {
|
||||
Some(format!("{e}"))
|
||||
});
|
||||
@@ -842,7 +840,7 @@ impl GitPanel {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn restore_tracked_files(
|
||||
fn discard_tracked_changes(
|
||||
&mut self,
|
||||
_: &RestoreTrackedFiles,
|
||||
window: &mut Window,
|
||||
@@ -872,8 +870,8 @@ impl GitPanel {
|
||||
|
||||
#[derive(strum::EnumIter, strum::VariantNames)]
|
||||
#[strum(serialize_all = "title_case")]
|
||||
enum RestoreCancel {
|
||||
RestoreTrackedFiles,
|
||||
enum DiscardCancel {
|
||||
DiscardTrackedChanges,
|
||||
Cancel,
|
||||
}
|
||||
let prompt = prompt(
|
||||
@@ -884,7 +882,7 @@ impl GitPanel {
|
||||
);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
match prompt.await {
|
||||
Ok(RestoreCancel::RestoreTrackedFiles) => {
|
||||
Ok(DiscardCancel::DiscardTrackedChanges) => {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let repo_paths = entries.into_iter().map(|entry| entry.repo_path).collect();
|
||||
this.perform_checkout(repo_paths, cx);
|
||||
@@ -1257,50 +1255,51 @@ impl GitPanel {
|
||||
|
||||
/// Suggests a commit message based on the changed files and their statuses
|
||||
pub fn suggest_commit_message(&self) -> Option<String> {
|
||||
if self.total_staged_count() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let entry = self
|
||||
let entries = self
|
||||
.entries
|
||||
.iter()
|
||||
.find(|entry| match entry.status_entry() {
|
||||
Some(entry) => entry.is_staged.unwrap_or(false),
|
||||
_ => false,
|
||||
})?;
|
||||
.filter_map(|entry| {
|
||||
if let GitListEntry::GitStatusEntry(status_entry) = entry {
|
||||
Some(status_entry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<&GitStatusEntry>>();
|
||||
|
||||
let GitListEntry::GitStatusEntry(git_status_entry) = entry.clone() else {
|
||||
return None;
|
||||
};
|
||||
if entries.is_empty() {
|
||||
None
|
||||
} else if entries.len() == 1 {
|
||||
let entry = &entries[0];
|
||||
let file_name = entry
|
||||
.repo_path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy();
|
||||
|
||||
let action_text = if git_status_entry.status.is_deleted() {
|
||||
Some("Delete")
|
||||
} else if git_status_entry.status.is_created() {
|
||||
Some("Create")
|
||||
} else if git_status_entry.status.is_modified() {
|
||||
Some("Update")
|
||||
if entry.status.is_deleted() {
|
||||
Some(format!("Delete {}", file_name))
|
||||
} else if entry.status.is_created() {
|
||||
Some(format!("Create {}", file_name))
|
||||
} else if entry.status.is_modified() {
|
||||
Some(format!("Update {}", file_name))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let file_name = git_status_entry
|
||||
.repo_path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy();
|
||||
|
||||
Some(format!("{} {}", action_text?, file_name))
|
||||
}
|
||||
}
|
||||
|
||||
fn update_editor_placeholder(&mut self, cx: &mut Context<Self>) {
|
||||
let suggested_commit_message = self.suggest_commit_message();
|
||||
let suggested_commit_message = suggested_commit_message
|
||||
.as_deref()
|
||||
.unwrap_or("Enter commit message");
|
||||
self.suggested_commit_message = suggested_commit_message.clone();
|
||||
|
||||
self.commit_editor.update(cx, |editor, cx| {
|
||||
editor.set_placeholder_text(Arc::from(suggested_commit_message), cx)
|
||||
});
|
||||
if let Some(suggested_commit_message) = suggested_commit_message {
|
||||
self.commit_editor.update(cx, |editor, cx| {
|
||||
editor.set_placeholder_text(Arc::from(suggested_commit_message), cx)
|
||||
});
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
@@ -1387,14 +1386,14 @@ impl GitPanel {
|
||||
};
|
||||
|
||||
let mut current_remotes: Vec<Remote> = repo
|
||||
.update(&mut cx, |repo, _| {
|
||||
.update(&mut cx, |repo, cx| {
|
||||
let Some(current_branch) = repo.current_branch() else {
|
||||
return Err(anyhow::anyhow!("No active branch"));
|
||||
};
|
||||
|
||||
Ok(repo.get_remotes(Some(current_branch.name.to_string())))
|
||||
Ok(repo.get_remotes(Some(current_branch.name.to_string()), cx))
|
||||
})??
|
||||
.await??;
|
||||
.await?;
|
||||
|
||||
if current_remotes.len() == 0 {
|
||||
return Err(anyhow::anyhow!("No active remote"));
|
||||
@@ -1582,14 +1581,7 @@ impl GitPanel {
|
||||
!= Some(&buffer)
|
||||
{
|
||||
git_panel.commit_editor = cx.new(|cx| {
|
||||
commit_message_editor(
|
||||
buffer,
|
||||
git_panel.suggested_commit_message.as_deref(),
|
||||
git_panel.project.clone(),
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
commit_message_editor(buffer, git_panel.project.clone(), true, window, cx)
|
||||
});
|
||||
}
|
||||
})
|
||||
@@ -2365,9 +2357,7 @@ impl GitPanel {
|
||||
let Some(repo) = self.active_repository.clone() else {
|
||||
return Task::ready(Err(anyhow::anyhow!("no active repo")));
|
||||
};
|
||||
|
||||
let show = repo.read(cx).show(sha);
|
||||
cx.spawn(|_, _| async move { show.await? })
|
||||
repo.update(cx, |repo, cx| repo.show(sha, cx))
|
||||
}
|
||||
|
||||
fn deploy_entry_context_menu(
|
||||
@@ -2385,15 +2375,17 @@ impl GitPanel {
|
||||
} else {
|
||||
"Stage File"
|
||||
};
|
||||
let restore_title = if entry.status.is_created() {
|
||||
"Trash File"
|
||||
let revert_title = if entry.status.is_deleted() {
|
||||
"Restore file"
|
||||
} else if entry.status.is_created() {
|
||||
"Trash file"
|
||||
} else {
|
||||
"Restore File"
|
||||
"Discard changes"
|
||||
};
|
||||
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
|
||||
context_menu
|
||||
.action(stage_title, ToggleStaged.boxed_clone())
|
||||
.action(restore_title, git::RestoreFile.boxed_clone())
|
||||
.action(revert_title, git::RestoreFile.boxed_clone())
|
||||
.separator()
|
||||
.action("Open Diff", Confirm.boxed_clone())
|
||||
.action("Open File", SecondaryConfirm.boxed_clone())
|
||||
@@ -2410,7 +2402,7 @@ impl GitPanel {
|
||||
.separator()
|
||||
.action("Open Diff", project_diff::Diff.boxed_clone())
|
||||
.separator()
|
||||
.action("Restore Tracked Files", RestoreTrackedFiles.boxed_clone())
|
||||
.action("Discard Tracked Changes", RestoreTrackedFiles.boxed_clone())
|
||||
.action("Trash Untracked Files", TrashUntrackedFiles.boxed_clone())
|
||||
})
|
||||
}
|
||||
@@ -2494,7 +2486,6 @@ impl GitPanel {
|
||||
|
||||
let id: ElementId = ElementId::Name(format!("entry_{}", display_name).into());
|
||||
|
||||
let is_entry_staged = self.entry_is_staged(entry);
|
||||
let mut is_staged: ToggleState = self.entry_is_staged(entry).into();
|
||||
|
||||
if !self.has_staged_changes() && !self.has_conflicts() && !entry.status.is_created() {
|
||||
@@ -2518,23 +2509,18 @@ impl GitPanel {
|
||||
})
|
||||
});
|
||||
|
||||
let start_slot = h_flex()
|
||||
.id(("start-slot", ix))
|
||||
.gap(DynamicSpacing::Base04.rems(cx))
|
||||
.child(checkbox.tooltip(move |window, cx| {
|
||||
let tooltip_name = if is_entry_staged.unwrap_or(false) {
|
||||
"Unstage"
|
||||
} else {
|
||||
"Stage"
|
||||
};
|
||||
|
||||
Tooltip::for_action(tooltip_name, &ToggleStaged, window, cx)
|
||||
}))
|
||||
.child(git_status_icon(status, cx))
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
||||
// prevent the list item active state triggering when toggling checkbox
|
||||
cx.stop_propagation();
|
||||
});
|
||||
let start_slot =
|
||||
h_flex()
|
||||
.id(("start-slot", ix))
|
||||
.gap(DynamicSpacing::Base04.rems(cx))
|
||||
.child(checkbox.tooltip(|window, cx| {
|
||||
Tooltip::for_action("Stage File", &ToggleStaged, window, cx)
|
||||
}))
|
||||
.child(git_status_icon(status, cx))
|
||||
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
||||
// prevent the list item active state triggering when toggling checkbox
|
||||
cx.stop_propagation();
|
||||
});
|
||||
|
||||
div()
|
||||
.w_full()
|
||||
@@ -2694,7 +2680,7 @@ impl Render for GitPanel {
|
||||
.on_action(cx.listener(Self::toggle_staged_for_selected))
|
||||
.on_action(cx.listener(Self::stage_all))
|
||||
.on_action(cx.listener(Self::unstage_all))
|
||||
.on_action(cx.listener(Self::restore_tracked_files))
|
||||
.on_action(cx.listener(Self::discard_tracked_changes))
|
||||
.on_action(cx.listener(Self::clean_all))
|
||||
.on_action(cx.listener(Self::fetch))
|
||||
.on_action(cx.listener(Self::pull))
|
||||
|
||||
@@ -47,7 +47,6 @@ pub struct ProjectDiff {
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DiffBuffer {
|
||||
path_key: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
@@ -106,7 +105,7 @@ impl ProjectDiff {
|
||||
};
|
||||
if let Some(entry) = entry {
|
||||
project_diff.update(cx, |project_diff, cx| {
|
||||
project_diff.move_to_entry(entry, window, cx);
|
||||
project_diff.scroll_to(entry, window, cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -168,7 +167,7 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_to_entry(
|
||||
pub fn scroll_to(
|
||||
&mut self,
|
||||
entry: GitStatusEntry,
|
||||
window: &mut Window,
|
||||
@@ -189,16 +188,16 @@ impl ProjectDiff {
|
||||
|
||||
let path_key = PathKey::namespaced(namespace, entry.repo_path.0.clone());
|
||||
|
||||
self.move_to_path(path_key, window, cx)
|
||||
self.scroll_to_path(path_key, window, cx)
|
||||
}
|
||||
|
||||
fn move_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn scroll_to_path(&mut self, path_key: PathKey, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(position) = self.multibuffer.read(cx).location_for_path(&path_key, cx) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
|
||||
s.select_ranges([position..position]);
|
||||
})
|
||||
});
|
||||
})
|
||||
} else {
|
||||
self.pending_scroll = Some(path_key);
|
||||
}
|
||||
@@ -281,7 +280,6 @@ impl ProjectDiff {
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
let mut point = anchor.to_point(&snapshot);
|
||||
point.row = (point.row + 1).min(snapshot.max_row().0);
|
||||
point.column = 0;
|
||||
|
||||
let Some((_, buffer, _)) = self.multibuffer.read(cx).excerpt_containing(point, cx)
|
||||
else {
|
||||
@@ -386,28 +384,21 @@ impl ProjectDiff {
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
let was_empty = multibuffer.is_empty();
|
||||
let is_newly_added = multibuffer.set_excerpts_for_path(
|
||||
let is_excerpt_newly_added = self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.set_excerpts_for_path(
|
||||
path_key.clone(),
|
||||
buffer,
|
||||
diff_hunk_ranges,
|
||||
editor::DEFAULT_MULTIBUFFER_CONTEXT,
|
||||
cx,
|
||||
);
|
||||
(was_empty, is_newly_added)
|
||||
)
|
||||
});
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
if was_empty {
|
||||
editor.change_selections(None, window, cx, |selections| {
|
||||
selections.select_ranges([0..0])
|
||||
});
|
||||
}
|
||||
if is_excerpt_newly_added && diff_buffer.file_status.is_deleted() {
|
||||
if is_excerpt_newly_added && diff_buffer.file_status.is_deleted() {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.fold_buffer(snapshot.text.remote_id(), cx)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if self.multibuffer.read(cx).is_empty()
|
||||
&& self
|
||||
@@ -423,7 +414,7 @@ impl ProjectDiff {
|
||||
});
|
||||
}
|
||||
if self.pending_scroll.as_ref() == Some(&path_key) {
|
||||
self.move_to_path(path_key, window, cx);
|
||||
self.scroll_to_path(path_key, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -932,189 +923,3 @@ impl Render for ProjectDiffToolbar {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use collections::HashMap;
|
||||
use editor::test::editor_test_context::assert_state_with_diff;
|
||||
use git::status::{StatusCode, TrackedStatus};
|
||||
use gpui::TestAppContext;
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use unindent::Unindent as _;
|
||||
use util::path;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn init_test(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
editor::init(cx);
|
||||
crate::init(cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_save_after_restore(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/project"),
|
||||
json!({
|
||||
".git": {},
|
||||
"foo": "FOO\n",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("foo".into(), "foo\n".into())],
|
||||
);
|
||||
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
|
||||
state.statuses = HashMap::from_iter([(
|
||||
"foo".into(),
|
||||
TrackedStatus {
|
||||
index_status: StatusCode::Unmodified,
|
||||
worktree_status: StatusCode::Modified,
|
||||
}
|
||||
.into(),
|
||||
)]);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let editor = diff.update(cx, |diff, _| diff.editor.clone());
|
||||
assert_state_with_diff(
|
||||
&editor,
|
||||
cx,
|
||||
&"
|
||||
- foo
|
||||
+ ˇFOO
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
editor.git_restore(&Default::default(), window, cx);
|
||||
});
|
||||
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
|
||||
state.statuses = HashMap::default();
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
assert_state_with_diff(&editor, cx, &"ˇ".unindent());
|
||||
|
||||
let text = String::from_utf8(fs.read_file_sync("/project/foo").unwrap()).unwrap();
|
||||
assert_eq!(text, "foo\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_scroll_to_beginning_with_deletion(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/project"),
|
||||
json!({
|
||||
".git": {},
|
||||
"bar": "BAR\n",
|
||||
"foo": "FOO\n",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[
|
||||
("bar".into(), "bar\n".into()),
|
||||
("foo".into(), "foo\n".into()),
|
||||
],
|
||||
);
|
||||
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
|
||||
state.statuses = HashMap::from_iter([
|
||||
(
|
||||
"bar".into(),
|
||||
TrackedStatus {
|
||||
index_status: StatusCode::Unmodified,
|
||||
worktree_status: StatusCode::Modified,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
(
|
||||
"foo".into(),
|
||||
TrackedStatus {
|
||||
index_status: StatusCode::Unmodified,
|
||||
worktree_status: StatusCode::Modified,
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
]);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
|
||||
diff.move_to_path(
|
||||
PathKey::namespaced(TRACKED_NAMESPACE, Path::new("foo").into()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
diff.editor.clone()
|
||||
});
|
||||
assert_state_with_diff(
|
||||
&editor,
|
||||
cx,
|
||||
&"
|
||||
- bar
|
||||
+ BAR
|
||||
|
||||
- ˇfoo
|
||||
+ FOO
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
let editor = cx.update_window_entity(&diff, |diff, window, cx| {
|
||||
diff.move_to_path(
|
||||
PathKey::namespaced(TRACKED_NAMESPACE, Path::new("bar").into()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
diff.editor.clone()
|
||||
});
|
||||
assert_state_with_diff(
|
||||
&editor,
|
||||
cx,
|
||||
&"
|
||||
- ˇbar
|
||||
+ BAR
|
||||
|
||||
- foo
|
||||
+ FOO
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ impl AppCell {
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct AppRef<'a>(Ref<'a, App>);
|
||||
|
||||
impl Drop for AppRef<'_> {
|
||||
impl<'a> Drop for AppRef<'a> {
|
||||
fn drop(&mut self) {
|
||||
if option_env!("TRACK_THREAD_BORROWS").is_some() {
|
||||
let thread_id = std::thread::current().id();
|
||||
@@ -95,7 +95,7 @@ impl Drop for AppRef<'_> {
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct AppRefMut<'a>(RefMut<'a, App>);
|
||||
|
||||
impl Drop for AppRefMut<'_> {
|
||||
impl<'a> Drop for AppRefMut<'a> {
|
||||
fn drop(&mut self) {
|
||||
if option_env!("TRACK_THREAD_BORROWS").is_some() {
|
||||
let thread_id = std::thread::current().id();
|
||||
|
||||
@@ -649,7 +649,7 @@ impl<'a, T: 'static> Context<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Context<'_, T> {
|
||||
impl<'a, T> Context<'a, T> {
|
||||
/// Emit an event of the specified type, which can be handled by other entities that have subscribed via `subscribe` methods on their respective contexts.
|
||||
pub fn emit<Evt>(&mut self, event: Evt)
|
||||
where
|
||||
@@ -664,7 +664,7 @@ impl<T> Context<'_, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AppContext for Context<'_, T> {
|
||||
impl<'a, T> AppContext for Context<'a, T> {
|
||||
type Result<U> = U;
|
||||
|
||||
fn new<U: 'static>(
|
||||
|
||||
@@ -191,7 +191,7 @@ pub(crate) struct Lease<'a, T> {
|
||||
entity_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> core::ops::Deref for Lease<'_, T> {
|
||||
impl<'a, T: 'static> core::ops::Deref for Lease<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -199,13 +199,13 @@ impl<T: 'static> core::ops::Deref for Lease<'_, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> core::ops::DerefMut for Lease<'_, T> {
|
||||
impl<'a, T: 'static> core::ops::DerefMut for Lease<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.entity.as_mut().unwrap().downcast_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Lease<'_, T> {
|
||||
impl<'a, T> Drop for Lease<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
if self.entity.is_some() && !panicking() {
|
||||
panic!("Leases must be ended with EntityMap::end_lease")
|
||||
|
||||
@@ -82,7 +82,7 @@ impl From<Rgba> for u32 {
|
||||
|
||||
struct RgbaVisitor;
|
||||
|
||||
impl Visitor<'_> for RgbaVisitor {
|
||||
impl<'de> Visitor<'de> for RgbaVisitor {
|
||||
type Value = Rgba;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
@@ -180,7 +180,7 @@ impl TryFrom<&'_ str> for Rgba {
|
||||
/// Duplicates a given hex digit.
|
||||
/// E.g., `0xf` -> `0xff`.
|
||||
const fn duplicate(value: u8) -> u8 {
|
||||
(value << 4) | value
|
||||
value << 4 | value
|
||||
}
|
||||
|
||||
(duplicate(r), duplicate(g), duplicate(b), duplicate(a))
|
||||
|
||||
@@ -946,13 +946,13 @@ impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height {
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::SeekTarget<'_, ListItemSummary, ListItemSummary> for Count {
|
||||
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Count {
|
||||
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||
self.0.partial_cmp(&other.count).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl sum_tree::SeekTarget<'_, ListItemSummary, ListItemSummary> for Height {
|
||||
impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
|
||||
fn cmp(&self, other: &ListItemSummary, _: &()) -> std::cmp::Ordering {
|
||||
self.0.partial_cmp(&other.height).unwrap()
|
||||
}
|
||||
|
||||
@@ -587,7 +587,7 @@ impl<'a> Scope<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Scope<'_> {
|
||||
impl<'a> Drop for Scope<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.tx.take().unwrap();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{Display, Write},
|
||||
@@ -307,29 +306,24 @@ impl std::fmt::Display for Keystroke {
|
||||
}
|
||||
|
||||
/// The state of the modifier keys at some point in time
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
|
||||
pub struct Modifiers {
|
||||
/// The control key
|
||||
#[serde(default)]
|
||||
pub control: bool,
|
||||
|
||||
/// The alt key
|
||||
/// Sometimes also known as the 'meta' key
|
||||
#[serde(default)]
|
||||
pub alt: bool,
|
||||
|
||||
/// The shift key
|
||||
#[serde(default)]
|
||||
pub shift: bool,
|
||||
|
||||
/// The command key, on macos
|
||||
/// the windows key, on windows
|
||||
/// the super key, on linux
|
||||
#[serde(default)]
|
||||
pub platform: bool,
|
||||
|
||||
/// The function key
|
||||
#[serde(default)]
|
||||
pub function: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||
/// The `cocoa` crate does not define NSAttributedString (and related Cocoa classes),
|
||||
/// which are needed for copying rich text (that is, text intermingled with images)
|
||||
/// to the clipboard. This adds access to those APIs.
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub trait NSAttributedString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
|
||||
@@ -26,7 +26,7 @@ pub(crate) mod dispatch_sys {
|
||||
|
||||
use dispatch_sys::*;
|
||||
pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t {
|
||||
addr_of!(_dispatch_main_q) as *const _ as dispatch_queue_t
|
||||
unsafe { addr_of!(_dispatch_main_q) as *const _ as dispatch_queue_t }
|
||||
}
|
||||
|
||||
pub(crate) struct MacDispatcher {
|
||||
|
||||
@@ -1221,10 +1221,10 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
|
||||
unsafe {
|
||||
type SetWindowCompositionAttributeType =
|
||||
unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
|
||||
let module_name = PCSTR::from_raw(c"user32.dll".as_ptr() as *const u8);
|
||||
let module_name = PCSTR::from_raw("user32.dll\0".as_ptr());
|
||||
let user32 = GetModuleHandleA(module_name);
|
||||
if user32.is_ok() {
|
||||
let func_name = PCSTR::from_raw(c"SetWindowCompositionAttribute".as_ptr() as *const u8);
|
||||
let func_name = PCSTR::from_raw("SetWindowCompositionAttribute\0".as_ptr());
|
||||
let set_window_composition_attribute: SetWindowCompositionAttributeType =
|
||||
std::mem::transmute(GetProcAddress(user32.unwrap(), func_name));
|
||||
let mut color = color.unwrap_or_default();
|
||||
@@ -1238,7 +1238,7 @@ fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32
|
||||
gradient_color: (color.0 as u32)
|
||||
| ((color.1 as u32) << 8)
|
||||
| ((color.2 as u32) << 16)
|
||||
| ((color.3 as u32) << 24),
|
||||
| (color.3 as u32) << 24,
|
||||
animation_id: 0,
|
||||
};
|
||||
let mut data = WINDOWCOMPOSITIONATTRIBDATA {
|
||||
|
||||
@@ -670,15 +670,6 @@ pub struct TextRun {
|
||||
pub strikethrough: Option<StrikethroughStyle>,
|
||||
}
|
||||
|
||||
#[cfg(all(target_os = "macos", test))]
|
||||
impl TextRun {
|
||||
fn with_len(&self, len: usize) -> Self {
|
||||
let mut this = self.clone();
|
||||
this.len = len;
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/// An identifier for a specific glyph, as returned by [`TextSystem::layout_line`].
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
#[repr(C)]
|
||||
|
||||
@@ -601,15 +601,15 @@ struct CacheKeyRef<'a> {
|
||||
wrap_width: Option<Pixels>,
|
||||
}
|
||||
|
||||
impl PartialEq for (dyn AsCacheKeyRef + '_) {
|
||||
impl<'a> PartialEq for (dyn AsCacheKeyRef + 'a) {
|
||||
fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
|
||||
self.as_cache_key_ref() == other.as_cache_key_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for (dyn AsCacheKeyRef + '_) {}
|
||||
impl<'a> Eq for (dyn AsCacheKeyRef + 'a) {}
|
||||
|
||||
impl Hash for (dyn AsCacheKeyRef + '_) {
|
||||
impl<'a> Hash for (dyn AsCacheKeyRef + 'a) {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.as_cache_key_ref().hash(state)
|
||||
}
|
||||
@@ -644,7 +644,7 @@ impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsCacheKeyRef for CacheKeyRef<'_> {
|
||||
impl<'a> AsCacheKeyRef for CacheKeyRef<'a> {
|
||||
fn as_cache_key_ref(&self) -> CacheKeyRef {
|
||||
*self
|
||||
}
|
||||
|
||||
@@ -543,6 +543,14 @@ mod tests {
|
||||
background_color: None,
|
||||
};
|
||||
|
||||
impl TextRun {
|
||||
fn with_len(&self, len: usize) -> Self {
|
||||
let mut this = self.clone();
|
||||
this.len = len;
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
let text = "aa bbb cccc ddddd eeee".into();
|
||||
let lines = text_system
|
||||
.shape_text(
|
||||
|
||||
@@ -40,19 +40,6 @@ pub trait FluentBuilder {
|
||||
}
|
||||
})
|
||||
}
|
||||
/// Conditionally unwrap and modify self with the given closure, if the given option is Some.
|
||||
fn when_none<T>(self, option: &Option<T>, then: impl FnOnce(Self) -> Self) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.map(|this| {
|
||||
if let Some(_) = option {
|
||||
this
|
||||
} else {
|
||||
then(this)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -72,7 +59,7 @@ where
|
||||
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl std::fmt::Debug for CwdBacktrace<'_> {
|
||||
impl<'a> std::fmt::Debug for CwdBacktrace<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use backtrace::{BacktraceFmt, BytesOrWideString};
|
||||
|
||||
|
||||
@@ -3689,6 +3689,8 @@ impl Window {
|
||||
dispatch_tree.bindings_for_action(action, &[context])
|
||||
}
|
||||
|
||||
/// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
|
||||
|
||||
/// Returns a generic event listener that invokes the given listener with the view and context associated with the given view handle.
|
||||
pub fn listener_for<V: Render, E>(
|
||||
&self,
|
||||
|
||||
@@ -79,7 +79,7 @@ pub trait HttpClient: 'static + Send + Sync {
|
||||
.body(body);
|
||||
|
||||
match request {
|
||||
Ok(request) => Box::pin(async move { self.send(request).await }),
|
||||
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }),
|
||||
Err(e) => Box::pin(async move { Err(e.into()) }),
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ pub trait HttpClient: 'static + Send + Sync {
|
||||
.body(body);
|
||||
|
||||
match request {
|
||||
Ok(request) => Box::pin(async move { self.send(request).await }),
|
||||
Ok(request) => Box::pin(async move { self.send(request).await.map_err(Into::into) }),
|
||||
Err(e) => Box::pin(async move { Err(e.into()) }),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ impl InlineCompletionButton {
|
||||
}
|
||||
|
||||
let settings = AllLanguageSettings::get_global(cx);
|
||||
let globally_enabled = settings.show_edit_predictions(None, cx);
|
||||
let globally_enabled = settings.show_inline_completions(None, cx);
|
||||
menu = menu.toggleable_entry("All Files", globally_enabled, IconPosition::Start, None, {
|
||||
let fs = fs.clone();
|
||||
move |_, cx| toggle_inline_completions_globally(fs.clone(), cx)
|
||||
@@ -592,8 +592,8 @@ impl InlineCompletionButton {
|
||||
|
||||
if cx.has_flag::<feature_flags::PredictEditsNonEagerModeFeatureFlag>() {
|
||||
let is_eager_preview_enabled = match settings.edit_predictions_mode() {
|
||||
language::EditPredictionsMode::Subtle => false,
|
||||
language::EditPredictionsMode::Eager => true,
|
||||
language::EditPredictionsMode::Auto => false,
|
||||
language::EditPredictionsMode::EagerPreview => true,
|
||||
};
|
||||
menu = menu.separator().toggleable_entry(
|
||||
"Eager Preview Mode",
|
||||
@@ -608,8 +608,8 @@ impl InlineCompletionButton {
|
||||
cx,
|
||||
move |settings, _cx| {
|
||||
let new_mode = match is_eager_preview_enabled {
|
||||
true => language::EditPredictionsMode::Subtle,
|
||||
false => language::EditPredictionsMode::Eager,
|
||||
true => language::EditPredictionsMode::Auto,
|
||||
false => language::EditPredictionsMode::EagerPreview,
|
||||
};
|
||||
|
||||
if let Some(edit_predictions) = settings.edit_predictions.as_mut() {
|
||||
@@ -702,7 +702,7 @@ impl InlineCompletionButton {
|
||||
Some(
|
||||
file.map(|file| {
|
||||
all_language_settings(Some(file), cx)
|
||||
.edit_predictions_enabled_for_file(file, cx)
|
||||
.inline_completions_enabled_for_path(file.path())
|
||||
})
|
||||
.unwrap_or(true),
|
||||
)
|
||||
@@ -825,7 +825,7 @@ async fn open_disabled_globs_setting_in_editor(
|
||||
}
|
||||
|
||||
fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
|
||||
let show_edit_predictions = all_language_settings(None, cx).show_edit_predictions(None, cx);
|
||||
let show_edit_predictions = all_language_settings(None, cx).show_inline_completions(None, cx);
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.defaults.show_edit_predictions = Some(!show_edit_predictions)
|
||||
});
|
||||
@@ -845,7 +845,7 @@ fn toggle_show_inline_completions_for_language(
|
||||
cx: &mut App,
|
||||
) {
|
||||
let show_edit_predictions =
|
||||
all_language_settings(None, cx).show_edit_predictions(Some(&language), cx);
|
||||
all_language_settings(None, cx).show_inline_completions(Some(&language), cx);
|
||||
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
|
||||
file.languages
|
||||
.entry(language.name())
|
||||
|
||||
@@ -509,7 +509,7 @@ impl fmt::Debug for ChunkRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for ChunkRendererContext<'_, '_> {
|
||||
impl<'a, 'b> Deref for ChunkRendererContext<'a, 'b> {
|
||||
type Target = App;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
@@ -517,7 +517,7 @@ impl Deref for ChunkRendererContext<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for ChunkRendererContext<'_, '_> {
|
||||
impl<'a, 'b> DerefMut for ChunkRendererContext<'a, 'b> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.context
|
||||
}
|
||||
@@ -3507,13 +3507,24 @@ impl BufferSnapshot {
|
||||
true,
|
||||
);
|
||||
let mut last_buffer_range_end = 0;
|
||||
|
||||
for (buffer_range, is_name) in buffer_ranges {
|
||||
let space_added = !text.is_empty() && buffer_range.start > last_buffer_range_end;
|
||||
if space_added {
|
||||
if !text.is_empty() && buffer_range.start > last_buffer_range_end {
|
||||
text.push(' ');
|
||||
}
|
||||
let before_append_len = text.len();
|
||||
last_buffer_range_end = buffer_range.end;
|
||||
if is_name {
|
||||
let mut start = text.len();
|
||||
let end = start + buffer_range.len();
|
||||
|
||||
// When multiple names are captured, then the matchable text
|
||||
// includes the whitespace in between the names.
|
||||
if !name_ranges.is_empty() {
|
||||
start -= 1;
|
||||
}
|
||||
|
||||
name_ranges.push(start..end);
|
||||
}
|
||||
|
||||
let mut offset = buffer_range.start;
|
||||
chunks.seek(buffer_range.clone());
|
||||
for mut chunk in chunks.by_ref() {
|
||||
@@ -3537,16 +3548,6 @@ impl BufferSnapshot {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if is_name {
|
||||
let after_append_len = text.len();
|
||||
let start = if space_added && !name_ranges.is_empty() {
|
||||
before_append_len - 1
|
||||
} else {
|
||||
before_append_len
|
||||
};
|
||||
name_ranges.push(start..after_append_len);
|
||||
}
|
||||
last_buffer_range_end = buffer_range.end;
|
||||
}
|
||||
|
||||
Some(OutlineItem {
|
||||
@@ -4137,7 +4138,7 @@ impl Deref for BufferSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for BufferChunks<'_> {}
|
||||
unsafe impl<'a> Send for BufferChunks<'a> {}
|
||||
|
||||
impl<'a> BufferChunks<'a> {
|
||||
pub(crate) fn new(
|
||||
@@ -4477,7 +4478,6 @@ impl IndentSize {
|
||||
pub struct TestFile {
|
||||
pub path: Arc<Path>,
|
||||
pub root_name: String,
|
||||
pub local_root: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@@ -4491,11 +4491,7 @@ impl File for TestFile {
|
||||
}
|
||||
|
||||
fn as_local(&self) -> Option<&dyn LocalFile> {
|
||||
if self.local_root.is_some() {
|
||||
Some(self)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn disk_state(&self) -> DiskState {
|
||||
@@ -4523,23 +4519,6 @@ impl File for TestFile {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl LocalFile for TestFile {
|
||||
fn abs_path(&self, _cx: &App) -> PathBuf {
|
||||
PathBuf::from(self.local_root.as_ref().unwrap())
|
||||
.join(&self.root_name)
|
||||
.join(self.path.as_ref())
|
||||
}
|
||||
|
||||
fn load(&self, _cx: &App) -> Task<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn load_bytes(&self, _cx: &App) -> Task<Result<Vec<u8>>> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn contiguous_ranges(
|
||||
values: impl Iterator<Item = u32>,
|
||||
max_len: usize,
|
||||
|
||||
@@ -254,7 +254,6 @@ fn file(path: &str) -> Arc<dyn File> {
|
||||
Arc::new(TestFile {
|
||||
path: Path::new(path).into(),
|
||||
root_name: "zed".into(),
|
||||
local_root: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use ec4rs::{
|
||||
Properties as EditorconfigProperties,
|
||||
};
|
||||
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
|
||||
use gpui::{App, Modifiers};
|
||||
use gpui::App;
|
||||
use itertools::{Either, Itertools};
|
||||
use schemars::{
|
||||
schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
|
||||
@@ -231,34 +231,11 @@ pub struct EditPredictionSettings {
|
||||
/// A list of globs representing files that edit predictions should be disabled for.
|
||||
/// This list adds to a pre-existing, sensible default set of globs.
|
||||
/// Any additional ones you add are combined with them.
|
||||
pub disabled_globs: Vec<DisabledGlob>,
|
||||
pub disabled_globs: Vec<GlobMatcher>,
|
||||
/// Configures how edit predictions are displayed in the buffer.
|
||||
pub mode: EditPredictionsMode,
|
||||
/// Settings specific to GitHub Copilot.
|
||||
pub copilot: CopilotSettings,
|
||||
/// Whether edit predictions are enabled in the assistant panel.
|
||||
/// This setting has no effect if globally disabled.
|
||||
pub enabled_in_assistant: bool,
|
||||
}
|
||||
|
||||
impl EditPredictionSettings {
|
||||
/// Returns whether edit predictions are enabled for the given path.
|
||||
pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
|
||||
!self.disabled_globs.iter().any(|glob| {
|
||||
if glob.is_absolute {
|
||||
file.as_local()
|
||||
.map_or(false, |local| glob.matcher.is_match(local.abs_path(cx)))
|
||||
} else {
|
||||
glob.matcher.is_match(file.path())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DisabledGlob {
|
||||
matcher: GlobMatcher,
|
||||
is_absolute: bool,
|
||||
}
|
||||
|
||||
/// The mode in which edit predictions should be displayed.
|
||||
@@ -267,12 +244,10 @@ pub struct DisabledGlob {
|
||||
pub enum EditPredictionsMode {
|
||||
/// If provider supports it, display inline when holding modifier key (e.g., alt).
|
||||
/// Otherwise, eager preview is used.
|
||||
#[serde(alias = "auto")]
|
||||
Subtle,
|
||||
Auto,
|
||||
/// Display inline when there are no language server completions available.
|
||||
#[default]
|
||||
#[serde(alias = "eager_preview")]
|
||||
Eager,
|
||||
EagerPreview,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -503,10 +478,6 @@ pub struct EditPredictionSettingsContent {
|
||||
/// Settings specific to GitHub Copilot.
|
||||
#[serde(default)]
|
||||
pub copilot: CopilotSettingsContent,
|
||||
/// Whether edit predictions are enabled in the assistant prompt editor.
|
||||
/// This has no effect if globally disabled.
|
||||
#[serde(default = "default_true")]
|
||||
pub enabled_in_assistant: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
|
||||
@@ -932,13 +903,6 @@ pub struct InlayHintSettings {
|
||||
/// Default: 50
|
||||
#[serde(default = "scroll_debounce_ms")]
|
||||
pub scroll_debounce_ms: u64,
|
||||
/// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
|
||||
/// If only a subset of the modifiers specified is pressed, hints are not toggled.
|
||||
/// If no modifiers are specified, this is equivalent to `None`.
|
||||
///
|
||||
/// Default: None
|
||||
#[serde(default)]
|
||||
pub toggle_on_modifiers_press: Option<Modifiers>,
|
||||
}
|
||||
|
||||
fn edit_debounce_ms() -> u64 {
|
||||
@@ -999,12 +963,16 @@ impl AllLanguageSettings {
|
||||
}
|
||||
|
||||
/// Returns whether edit predictions are enabled for the given path.
|
||||
pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
|
||||
self.edit_predictions.enabled_for_file(file, cx)
|
||||
pub fn inline_completions_enabled_for_path(&self, path: &Path) -> bool {
|
||||
!self
|
||||
.edit_predictions
|
||||
.disabled_globs
|
||||
.iter()
|
||||
.any(|glob| glob.is_match(path))
|
||||
}
|
||||
|
||||
/// Returns whether edit predictions are enabled for the given language and path.
|
||||
pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
|
||||
pub fn show_inline_completions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
|
||||
self.language(None, language.map(|l| l.name()).as_ref(), cx)
|
||||
.show_edit_predictions
|
||||
}
|
||||
@@ -1133,12 +1101,6 @@ impl settings::Settings for AllLanguageSettings {
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut edit_predictions_enabled_in_assistant = default_value
|
||||
.edit_predictions
|
||||
.as_ref()
|
||||
.map(|settings| settings.enabled_in_assistant)
|
||||
.unwrap_or(true);
|
||||
|
||||
let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
|
||||
|
||||
for (language, suffixes) in &default_value.file_types {
|
||||
@@ -1165,7 +1127,6 @@ impl settings::Settings for AllLanguageSettings {
|
||||
|
||||
if let Some(edit_predictions) = user_settings.edit_predictions.as_ref() {
|
||||
edit_predictions_mode = edit_predictions.mode;
|
||||
edit_predictions_enabled_in_assistant = edit_predictions.enabled_in_assistant;
|
||||
|
||||
if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() {
|
||||
completion_globs.extend(disabled_globs.iter());
|
||||
@@ -1236,16 +1197,10 @@ impl settings::Settings for AllLanguageSettings {
|
||||
},
|
||||
disabled_globs: completion_globs
|
||||
.iter()
|
||||
.filter_map(|g| {
|
||||
Some(DisabledGlob {
|
||||
matcher: globset::Glob::new(g).ok()?.compile_matcher(),
|
||||
is_absolute: Path::new(g).is_absolute(),
|
||||
})
|
||||
})
|
||||
.filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
|
||||
.collect(),
|
||||
mode: edit_predictions_mode,
|
||||
copilot: copilot_settings,
|
||||
enabled_in_assistant: edit_predictions_enabled_in_assistant,
|
||||
},
|
||||
defaults,
|
||||
languages,
|
||||
@@ -1400,8 +1355,6 @@ pub struct PrettierSettings {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::TestAppContext;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -1446,132 +1399,6 @@ mod tests {
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
|
||||
use crate::TestFile;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let cx = cx.app.borrow_mut();
|
||||
|
||||
let build_settings = |globs: &[&str]| -> EditPredictionSettings {
|
||||
EditPredictionSettings {
|
||||
disabled_globs: globs
|
||||
.iter()
|
||||
.map(|glob_str| {
|
||||
#[cfg(windows)]
|
||||
let glob_str = {
|
||||
let mut g = String::new();
|
||||
|
||||
if glob_str.starts_with('/') {
|
||||
g.push_str("C:");
|
||||
}
|
||||
|
||||
g.push_str(&glob_str.replace('/', "\\"));
|
||||
g
|
||||
};
|
||||
#[cfg(windows)]
|
||||
let glob_str = glob_str.as_str();
|
||||
|
||||
DisabledGlob {
|
||||
matcher: globset::Glob::new(glob_str).unwrap().compile_matcher(),
|
||||
is_absolute: Path::new(glob_str).is_absolute(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
const WORKTREE_NAME: &str = "project";
|
||||
let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
|
||||
let mut path_buf = PathBuf::new();
|
||||
path_buf.extend(segments);
|
||||
|
||||
Arc::new(TestFile {
|
||||
path: path_buf.as_path().into(),
|
||||
root_name: WORKTREE_NAME.to_string(),
|
||||
local_root: Some(PathBuf::from(if cfg!(windows) {
|
||||
"C:\\absolute\\"
|
||||
} else {
|
||||
"/absolute/"
|
||||
})),
|
||||
})
|
||||
};
|
||||
|
||||
let test_file = make_test_file(&["src", "test", "file.rs"]);
|
||||
|
||||
// Test relative globs
|
||||
let settings = build_settings(&["*.rs"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["*.txt"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test absolute globs
|
||||
let settings = build_settings(&["/absolute/**/*.rs"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["/other/**/*.rs"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test exact path match relative
|
||||
let settings = build_settings(&["src/test/file.rs"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["src/test/otherfile.rs"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test exact path match absolute
|
||||
let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["/other/test/otherfile.rs"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test * glob
|
||||
let settings = build_settings(&["*"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["*.txt"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test **/* glob
|
||||
let settings = build_settings(&["**/*"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["other/**/*"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test directory/** glob
|
||||
let settings = build_settings(&["src/**"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
let test_file_root: Arc<dyn File> = Arc::new(TestFile {
|
||||
path: PathBuf::from("file.rs").as_path().into(),
|
||||
root_name: WORKTREE_NAME.to_string(),
|
||||
local_root: Some(PathBuf::from("/absolute/")),
|
||||
});
|
||||
assert!(settings.enabled_for_file(&test_file_root, &cx));
|
||||
|
||||
let settings = build_settings(&["other/**"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test **/directory/* glob
|
||||
let settings = build_settings(&["**/test/*"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["**/other/*"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test multiple globs
|
||||
let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
|
||||
assert!(!settings.enabled_for_file(&test_file, &cx));
|
||||
let settings = build_settings(&["*.txt", "*.md", "other/**"]);
|
||||
assert!(settings.enabled_for_file(&test_file, &cx));
|
||||
|
||||
// Test dot files
|
||||
let dot_file = make_test_file(&[".config", "settings.json"]);
|
||||
let settings = build_settings(&[".*/**"]);
|
||||
assert!(!settings.enabled_for_file(&dot_file, &cx));
|
||||
|
||||
let dot_env_file = make_test_file(&[".env"]);
|
||||
let settings = build_settings(&[".env"]);
|
||||
assert!(!settings.enabled_for_file(&dot_env_file, &cx));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_resolve_language_servers() {
|
||||
fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
|
||||
|
||||
@@ -836,7 +836,7 @@ impl SyntaxSnapshot {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayer<'a>> {
|
||||
pub fn layers<'a>(&'a self, buffer: &'a BufferSnapshot) -> Vec<SyntaxLayer> {
|
||||
self.layers_for_range(0..buffer.len(), buffer, true)
|
||||
.collect()
|
||||
}
|
||||
@@ -1142,7 +1142,7 @@ impl<'a> SyntaxMapMatches<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxMapCapturesLayer<'_> {
|
||||
impl<'a> SyntaxMapCapturesLayer<'a> {
|
||||
fn advance(&mut self) {
|
||||
self.next_capture = self.captures.next().map(|(mat, ix)| mat.captures[*ix]);
|
||||
}
|
||||
@@ -1157,7 +1157,7 @@ impl SyntaxMapCapturesLayer<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl SyntaxMapMatchesLayer<'_> {
|
||||
impl<'a> SyntaxMapMatchesLayer<'a> {
|
||||
fn advance(&mut self) {
|
||||
if let Some(mat) = self.matches.next() {
|
||||
self.next_captures.clear();
|
||||
@@ -1740,7 +1740,7 @@ impl sum_tree::Summary for SyntaxLayerSummary {
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPosition {
|
||||
impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPosition {
|
||||
fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering {
|
||||
Ord::cmp(&self.depth, &cursor_location.max_depth)
|
||||
.then_with(|| {
|
||||
@@ -1758,14 +1758,16 @@ impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPosit
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for ChangeStartPosition {
|
||||
impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary> for ChangeStartPosition {
|
||||
fn cmp(&self, cursor_location: &SyntaxLayerSummary, text: &BufferSnapshot) -> Ordering {
|
||||
Ord::cmp(&self.depth, &cursor_location.max_depth)
|
||||
.then_with(|| self.position.cmp(&cursor_location.range.end, text))
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekTarget<'_, SyntaxLayerSummary, SyntaxLayerSummary> for SyntaxLayerPositionBeforeChange {
|
||||
impl<'a> SeekTarget<'a, SyntaxLayerSummary, SyntaxLayerSummary>
|
||||
for SyntaxLayerPositionBeforeChange
|
||||
{
|
||||
fn cmp(&self, cursor_location: &SyntaxLayerSummary, buffer: &BufferSnapshot) -> Ordering {
|
||||
if self.change.cmp(cursor_location, buffer).is_le() {
|
||||
Ordering::Less
|
||||
@@ -1867,7 +1869,7 @@ struct LogPoint(Point);
|
||||
struct LogAnchorRange<'a>(&'a Range<Anchor>, &'a text::BufferSnapshot);
|
||||
struct LogChangedRegions<'a>(&'a ChangeRegionSet, &'a text::BufferSnapshot);
|
||||
|
||||
impl fmt::Debug for LogIncludedRanges<'_> {
|
||||
impl<'a> fmt::Debug for LogIncludedRanges<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(self.0.iter().map(|range| {
|
||||
@@ -1879,14 +1881,14 @@ impl fmt::Debug for LogIncludedRanges<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for LogAnchorRange<'_> {
|
||||
impl<'a> fmt::Debug for LogAnchorRange<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let range = self.0.to_point(self.1);
|
||||
(LogPoint(range.start)..LogPoint(range.end)).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for LogChangedRegions<'_> {
|
||||
impl<'a> fmt::Debug for LogChangedRegions<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(
|
||||
|
||||
@@ -305,7 +305,13 @@ impl LspAdapter for ExtensionLspAdapter {
|
||||
.labels_for_symbols(self.language_server_id.clone(), symbols)
|
||||
.await?;
|
||||
|
||||
Ok(labels_from_extension(labels, language))
|
||||
Ok(labels_from_extension(
|
||||
labels
|
||||
.into_iter()
|
||||
.map(|label| label.map(Into::into))
|
||||
.collect(),
|
||||
language,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ where
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
|
||||
pub struct LanguageModelToolUse {
|
||||
pub id: LanguageModelToolUseId,
|
||||
pub name: Arc<str>,
|
||||
pub name: String,
|
||||
pub input: serde_json::Value,
|
||||
}
|
||||
|
||||
|
||||
@@ -153,8 +153,8 @@ impl LlmApiToken {
|
||||
Self::fetch(self.0.write().await, client).await
|
||||
}
|
||||
|
||||
async fn fetch(
|
||||
mut lock: RwLockWriteGuard<'_, Option<String>>,
|
||||
async fn fetch<'a>(
|
||||
mut lock: RwLockWriteGuard<'a, Option<String>>,
|
||||
client: &Arc<Client>,
|
||||
) -> Result<String> {
|
||||
let response = client.request(proto::GetLlmToken {}).await?;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::io::{Cursor, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::role::Role;
|
||||
use crate::{LanguageModelToolUse, LanguageModelToolUseId};
|
||||
@@ -168,7 +167,7 @@ impl LanguageModelImage {
|
||||
pub struct LanguageModelToolResult {
|
||||
pub tool_use_id: LanguageModelToolUseId,
|
||||
pub is_error: bool,
|
||||
pub content: Arc<str>,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
|
||||
@@ -200,16 +199,15 @@ pub struct LanguageModelRequestMessage {
|
||||
|
||||
impl LanguageModelRequestMessage {
|
||||
pub fn string_contents(&self) -> String {
|
||||
let mut buffer = String::new();
|
||||
let mut string_buffer = String::new();
|
||||
for string in self.content.iter().filter_map(|content| match content {
|
||||
MessageContent::Text(text) => Some(text.as_str()),
|
||||
MessageContent::ToolResult(tool_result) => Some(tool_result.content.as_ref()),
|
||||
MessageContent::Text(text) => Some(text),
|
||||
MessageContent::ToolResult(tool_result) => Some(&tool_result.content),
|
||||
MessageContent::ToolUse(_) | MessageContent::Image(_) => None,
|
||||
}) {
|
||||
buffer.push_str(string);
|
||||
string_buffer.push_str(string.as_str())
|
||||
}
|
||||
|
||||
buffer
|
||||
string_buffer
|
||||
}
|
||||
|
||||
pub fn contents_empty(&self) -> bool {
|
||||
|
||||
@@ -2,26 +2,17 @@ use std::sync::Arc;
|
||||
|
||||
use feature_flags::ZedPro;
|
||||
use gpui::{
|
||||
action_with_deprecated_aliases, Action, AnyElement, App, Corner, DismissEvent, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
|
||||
Action, AnyElement, AnyView, App, Corner, DismissEvent, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, Subscription, Task, WeakEntity,
|
||||
};
|
||||
use language_model::{
|
||||
AuthenticateError, LanguageModel, LanguageModelAvailability, LanguageModelRegistry,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use proto::Plan;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, IconButtonShape, ListItem, ListItemSpacing, PopoverButton,
|
||||
PopoverMenuHandle, Tooltip, TriggerablePopover,
|
||||
};
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
|
||||
use workspace::ShowConfiguration;
|
||||
|
||||
action_with_deprecated_aliases!(
|
||||
assistant,
|
||||
ToggleModelSelector,
|
||||
["assistant2::ToggleModelSelector"]
|
||||
);
|
||||
|
||||
const TRY_ZED_PRO_URL: &str = "https://zed.dev/pro";
|
||||
|
||||
type OnModelChanged = Arc<dyn Fn(Arc<dyn LanguageModel>, &App) + 'static>;
|
||||
@@ -31,7 +22,6 @@ pub struct LanguageModelSelector {
|
||||
/// The task used to update the picker's matches when there is a change to
|
||||
/// the language model registry.
|
||||
update_matches_task: Option<Task<()>>,
|
||||
popover_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
_authenticate_all_providers_task: Task<()>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -63,7 +53,6 @@ impl LanguageModelSelector {
|
||||
LanguageModelSelector {
|
||||
picker,
|
||||
update_matches_task: None,
|
||||
popover_menu_handle: PopoverMenuHandle::default(),
|
||||
_authenticate_all_providers_task: Self::authenticate_all_providers(cx),
|
||||
_subscriptions: vec![cx.subscribe_in(
|
||||
&LanguageModelRegistry::global(cx),
|
||||
@@ -73,15 +62,6 @@ impl LanguageModelSelector {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_model_selector(
|
||||
&mut self,
|
||||
_: &ToggleModelSelector,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.popover_menu_handle.toggle(window, cx);
|
||||
}
|
||||
|
||||
fn handle_language_model_registry_event(
|
||||
&mut self,
|
||||
_registry: &Entity<LanguageModelRegistry>,
|
||||
@@ -201,13 +181,62 @@ impl Render for LanguageModelSelector {
|
||||
}
|
||||
}
|
||||
|
||||
impl TriggerablePopover for LanguageModelSelector {
|
||||
fn menu_handle(
|
||||
&mut self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
) -> PopoverMenuHandle<Self> {
|
||||
self.popover_menu_handle.clone()
|
||||
#[derive(IntoElement)]
|
||||
pub struct LanguageModelSelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
language_model_selector: Entity<LanguageModelSelector>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
handle: Option<PopoverMenuHandle<LanguageModelSelector>>,
|
||||
anchor: Corner,
|
||||
}
|
||||
|
||||
impl<T, TT> LanguageModelSelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
pub fn new(
|
||||
language_model_selector: Entity<LanguageModelSelector>,
|
||||
trigger: T,
|
||||
tooltip: TT,
|
||||
anchor: Corner,
|
||||
) -> Self {
|
||||
Self {
|
||||
language_model_selector,
|
||||
trigger,
|
||||
tooltip,
|
||||
handle: None,
|
||||
anchor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_handle(mut self, handle: PopoverMenuHandle<LanguageModelSelector>) -> Self {
|
||||
self.handle = Some(handle);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, TT> RenderOnce for LanguageModelSelectorPopoverMenu<T, TT>
|
||||
where
|
||||
T: PopoverTrigger + ButtonCommon,
|
||||
TT: Fn(&mut Window, &mut App) -> AnyView + 'static,
|
||||
{
|
||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||
let language_model_selector = self.language_model_selector.clone();
|
||||
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_window, _cx| Some(language_model_selector.clone()))
|
||||
.trigger_with_tooltip(self.trigger, self.tooltip)
|
||||
.anchor(self.anchor)
|
||||
.when_some(self.handle.clone(), |menu, handle| menu.with_handle(handle))
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-2.0),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,98 +521,3 @@ impl PickerDelegate for LanguageModelPickerDelegate {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InlineLanguageModelSelector {
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
}
|
||||
|
||||
impl InlineLanguageModelSelector {
|
||||
pub fn new(selector: Entity<LanguageModelSelector>) -> Self {
|
||||
Self { selector }
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for InlineLanguageModelSelector {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
PopoverButton::new(
|
||||
self.selector,
|
||||
gpui::Corner::TopRight,
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted),
|
||||
move |window, cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
.render(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AssistantLanguageModelSelector {
|
||||
focus_handle: FocusHandle,
|
||||
selector: Entity<LanguageModelSelector>,
|
||||
}
|
||||
|
||||
impl AssistantLanguageModelSelector {
|
||||
pub fn new(focus_handle: FocusHandle, selector: Entity<LanguageModelSelector>) -> Self {
|
||||
Self {
|
||||
focus_handle,
|
||||
selector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for AssistantLanguageModelSelector {
|
||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
let model_name = match active_model {
|
||||
Some(model) => model.name().0,
|
||||
_ => SharedString::from("No model selected"),
|
||||
};
|
||||
|
||||
PopoverButton::new(
|
||||
self.selector.clone(),
|
||||
Corner::BottomRight,
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
Label::new(model_name)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
),
|
||||
move |window, cx| {
|
||||
Tooltip::for_action_in(
|
||||
"Change Model",
|
||||
&ToggleModelSelector,
|
||||
&focus_handle,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
.render(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use provider::deepseek::DeepSeekLanguageModelProvider;
|
||||
|
||||
pub mod provider;
|
||||
mod settings;
|
||||
pub mod ui;
|
||||
|
||||
use crate::provider::anthropic::AnthropicLanguageModelProvider;
|
||||
use crate::provider::bedrock::BedrockLanguageModelProvider;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use crate::ui::InstructionListItem;
|
||||
use crate::AllLanguageModelSettings;
|
||||
use anthropic::{AnthropicError, ContentDelta, Event, ResponseContent};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
@@ -25,7 +24,7 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use strum::IntoEnumIterator;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{prelude::*, Icon, IconName, List, Tooltip};
|
||||
use ui::{prelude::*, Icon, IconName, Tooltip};
|
||||
use util::{maybe, ResultExt};
|
||||
|
||||
const PROVIDER_ID: &str = language_model::ANTHROPIC_PROVIDER_ID;
|
||||
@@ -507,7 +506,7 @@ pub fn into_anthropic(
|
||||
MessageContent::ToolUse(tool_use) => {
|
||||
Some(anthropic::RequestContent::ToolUse {
|
||||
id: tool_use.id.to_string(),
|
||||
name: tool_use.name.to_string(),
|
||||
name: tool_use.name,
|
||||
input: tool_use.input,
|
||||
cache_control,
|
||||
})
|
||||
@@ -516,7 +515,7 @@ pub fn into_anthropic(
|
||||
Some(anthropic::RequestContent::ToolResult {
|
||||
tool_use_id: tool_result.tool_use_id.to_string(),
|
||||
is_error: tool_result.is_error,
|
||||
content: tool_result.content.to_string(),
|
||||
content: tool_result.content,
|
||||
cache_control,
|
||||
})
|
||||
}
|
||||
@@ -637,7 +636,7 @@ pub fn map_to_language_model_completion_events(
|
||||
Ok(LanguageModelCompletionEvent::ToolUse(
|
||||
LanguageModelToolUse {
|
||||
id: tool_use.id.into(),
|
||||
name: tool_use.name.into(),
|
||||
name: tool_use.name,
|
||||
input: if tool_use.input_json.is_empty() {
|
||||
serde_json::Value::Null
|
||||
} else {
|
||||
@@ -804,6 +803,12 @@ impl ConfigurationView {
|
||||
|
||||
impl Render for ConfigurationView {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
const ANTHROPIC_CONSOLE_URL: &str = "https://console.anthropic.com/settings/keys";
|
||||
const INSTRUCTIONS: [&str; 3] = [
|
||||
"To use Zed's assistant with Anthropic, you need to add an API key. Follow these steps:",
|
||||
"- Create one at:",
|
||||
"- Paste your API key below and hit enter to use the assistant:",
|
||||
];
|
||||
let env_var_set = self.state.read(cx).api_key_from_env;
|
||||
|
||||
if self.load_credentials_task.is_some() {
|
||||
@@ -812,20 +817,17 @@ impl Render for ConfigurationView {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::save_api_key))
|
||||
.child(Label::new("To use Zed's assistant with Anthropic, you need to add an API key. Follow these steps:"))
|
||||
.child(
|
||||
List::new()
|
||||
.child(
|
||||
InstructionListItem::new(
|
||||
"Create one by visiting",
|
||||
Some("Anthropic's settings"),
|
||||
Some("https://console.anthropic.com/settings/keys")
|
||||
)
|
||||
)
|
||||
.child(
|
||||
InstructionListItem::text_only("Paste your API key below and hit enter to start using the assistant")
|
||||
)
|
||||
.child(Label::new(INSTRUCTIONS[0]))
|
||||
.child(h_flex().child(Label::new(INSTRUCTIONS[1])).child(
|
||||
Button::new("anthropic_console", ANTHROPIC_CONSOLE_URL)
|
||||
.style(ButtonStyle::Subtle)
|
||||
.icon(IconName::ArrowUpRight)
|
||||
.icon_size(IconSize::XSmall)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, _, cx| cx.open_url(ANTHROPIC_CONSOLE_URL))
|
||||
)
|
||||
)
|
||||
.child(Label::new(INSTRUCTIONS[2]))
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
@@ -842,8 +844,7 @@ impl Render for ConfigurationView {
|
||||
Label::new(
|
||||
format!("You can also assign the {ANTHROPIC_API_KEY_VAR} environment variable and restart Zed."),
|
||||
)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.into_any()
|
||||
} else {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user